Merge branch 'main' into Xe/reset-logid-on-logout-login

This commit is contained in:
Christine Dodrill 2021-03-05 11:29:31 -05:00 committed by GitHub
commit e951666778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
76 changed files with 4963 additions and 685 deletions

View File

@ -15,7 +15,10 @@ depaware:
buildwindows:
GOOS=windows GOARCH=amd64 go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
check: staticcheck vet depaware buildwindows
build386:
GOOS=linux GOARCH=386 go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled
check: staticcheck vet depaware buildwindows build386
staticcheck:
go run honnef.co/go/tools/cmd/staticcheck -- $$(go list ./... | grep -v tempfork)

View File

@ -0,0 +1,90 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package tailscale contains Tailscale client code.
package tailscale
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
)
// tsClient does HTTP requests to the local Tailscale daemon.
var tsClient = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
if addr != "local-tailscaled.sock:80" {
return nil, fmt.Errorf("unexpected URL address %q", addr)
}
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
// a TCP server on a random port, find the random port. For HTTP connections,
// we don't send the token. It gets added in an HTTP Basic-Auth header.
if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil {
var d net.Dialer
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
}
return safesocket.ConnectDefault()
},
},
}
// DoLocalRequest makes an HTTP request to the local machine's Tailscale daemon.
//
// URLs are of the form http://local-tailscaled.sock/localapi/v0/whois?ip=1.2.3.4.
//
// The hostname must be "local-tailscaled.sock", even though it
// doesn't actually do any DNS lookup. The actual means of connecting to and
// authenticating to the local Tailscale daemon vary by platform.
//
// DoLocalRequest may mutate the request to add Authorization headers.
func DoLocalRequest(req *http.Request) (*http.Response, error) {
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
req.SetBasicAuth("", token)
}
return tsClient.Do(req)
}
// WhoIs returns the owner of the remoteAddr, which must be an IP or IP:port.
func WhoIs(ctx context.Context, remoteAddr string) (*tailcfg.WhoIsResponse, error) {
var ip string
if net.ParseIP(remoteAddr) != nil {
ip = remoteAddr
} else {
var err error
ip, _, err = net.SplitHostPort(remoteAddr)
if err != nil {
return nil, fmt.Errorf("invalid remoteAddr %q", remoteAddr)
}
}
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/whois?ip="+url.QueryEscape(ip), nil)
if err != nil {
return nil, err
}
res, err := DoLocalRequest(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
slurp, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %s: %s", res.Status, slurp)
}
r := new(tailcfg.WhoIsResponse)
if err := json.Unmarshal(slurp, r); err != nil {
if max := 200; len(slurp) > max {
slurp = slurp[:max]
}
return nil, fmt.Errorf("failed to parse JSON WhoIsResponse from %q", slurp)
}
return r, nil
}

View File

@ -7,21 +7,18 @@ package main // import "tailscale.com/cmd/hello"
import (
"context"
_ "embed"
"encoding/json"
"flag"
"fmt"
"html/template"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
"tailscale.com/client/tailscale"
)
var (
@ -30,10 +27,13 @@ var (
testIP = flag.String("test-ip", "", "if non-empty, look up IP and exit before running a server")
)
//go:embed hello.tmpl.html
var embeddedTemplate string
func main() {
flag.Parse()
if *testIP != "" {
res, err := whoIs(*testIP)
res, err := tailscale.WhoIs(context.Background(), *testIP)
if err != nil {
log.Fatal(err)
}
@ -42,8 +42,18 @@ func main() {
e.Encode(res)
return
}
if !devMode() {
tmpl = template.Must(template.New("home").Parse(slurpHTML()))
if devMode() {
// Parse it optimistically
var err error
tmpl, err = template.New("home").Parse(embeddedTemplate)
if err != nil {
log.Printf("ignoring template error in dev mode: %v", err)
}
} else {
if embeddedTemplate == "" {
log.Fatalf("embeddedTemplate is empty; must be build with Go 1.16+")
}
tmpl = template.Must(template.New("home").Parse(embeddedTemplate))
}
http.HandleFunc("/", root)
@ -69,24 +79,24 @@ func main() {
log.Fatal(<-errc)
}
func slurpHTML() string {
slurp, err := ioutil.ReadFile("hello.tmpl.html")
if err != nil {
log.Fatal(err)
}
return string(slurp)
}
func devMode() bool { return *httpsAddr == "" && *httpAddr != "" }
func getTmpl() (*template.Template, error) {
if devMode() {
return template.New("home").Parse(slurpHTML())
tmplData, err := ioutil.ReadFile("hello.tmpl.html")
if os.IsNotExist(err) {
log.Printf("using baked-in template in dev mode; can't find hello.tmpl.html in current directory")
return tmpl, nil
}
return template.New("home").Parse(string(tmplData))
}
return tmpl, nil
}
var tmpl *template.Template // not used in dev mode, initialized by main after flag parse
// tmpl is the template used in prod mode.
// In dev mode it's only used if the template file doesn't exist on disk.
// It's initialized by main after flag parsing.
var tmpl *template.Template
type tmplData struct {
DisplayName string // "Foo Barberson"
@ -110,11 +120,6 @@ func root(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusFound)
return
}
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
http.Error(w, "no remote addr", 500)
return
}
tmpl, err := getTmpl()
if err != nil {
w.Header().Set("Content-Type", "text/plain")
@ -122,7 +127,7 @@ func root(w http.ResponseWriter, r *http.Request) {
return
}
who, err := whoIs(ip)
who, err := tailscale.WhoIs(r.Context(), r.RemoteAddr)
var data tmplData
if err != nil {
if devMode() {
@ -136,11 +141,12 @@ func root(w http.ResponseWriter, r *http.Request) {
IP: "100.1.2.3",
}
} else {
log.Printf("whois(%q) error: %v", ip, err)
log.Printf("whois(%q) error: %v", r.RemoteAddr, err)
http.Error(w, "Your Tailscale works, but we failed to look you up.", 500)
return
}
} else {
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
data = tmplData{
DisplayName: who.UserProfile.DisplayName,
LoginName: who.UserProfile.LoginName,
@ -161,48 +167,3 @@ func firstLabel(s string) string {
}
return s
}
// tsSockClient does HTTP requests to the local Tailscale daemon.
// The hostname in the HTTP request is ignored.
var tsSockClient = &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
// On macOS, when dialing from non-sandboxed program to sandboxed GUI running
// a TCP server on a random port, find the random port. For HTTP connections,
// we don't send the token. It gets added in an HTTP Basic-Auth header.
if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil {
var d net.Dialer
return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port))
}
return safesocket.ConnectDefault()
},
},
}
func whoIs(ip string) (*tailcfg.WhoIsResponse, error) {
ctx := context.Background()
req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/whois?ip="+url.QueryEscape(ip), nil)
if err != nil {
return nil, err
}
if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil {
req.SetBasicAuth("", token)
}
res, err := tsSockClient.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
slurp, _ := ioutil.ReadAll(res.Body)
if res.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %s: %s", res.Status, slurp)
}
r := new(tailcfg.WhoIsResponse)
if err := json.Unmarshal(slurp, r); err != nil {
if max := 200; len(slurp) > max {
slurp = slurp[:max]
}
return nil, fmt.Errorf("failed to parse JSON WhoIsResponse from %q", slurp)
}
return r, nil
}

View File

@ -2,8 +2,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscale
W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
@ -80,7 +78,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
golang.org/x/sync/singleflight from tailscale.com/net/dnscache
golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+
LD golang.org/x/sys/unix from tailscale.com/net/netns+
W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+
W golang.org/x/sys/windows from golang.org/x/sys/windows/registry+
W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg
golang.org/x/text/secure/bidirule from golang.org/x/net/idna
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+

View File

@ -8,19 +8,12 @@ package main // import "tailscale.com/cmd/tailscale"
import (
"fmt"
"log"
"os"
"github.com/apenwarr/fixconsole"
"tailscale.com/cmd/tailscale/cli"
)
func main() {
err := fixconsole.FixConsoleIfNeeded()
if err != nil {
log.Printf("fixConsoleOutput: %v\n", err)
}
if err := cli.Run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)

View File

@ -60,12 +60,7 @@ func debugMode(args []string) error {
}
func runMonitor(ctx context.Context) error {
dump := func() {
st, err := interfaces.GetState()
if err != nil {
log.Printf("error getting state: %v", err)
return
}
dump := func(st *interfaces.State) {
j, _ := json.MarshalIndent(st, "", " ")
os.Stderr.Write(j)
}
@ -73,12 +68,16 @@ func runMonitor(ctx context.Context) error {
if err != nil {
return err
}
mon.RegisterChangeCallback(func() {
log.Printf("Link monitor fired. State:")
dump()
mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
if changed {
log.Printf("Link monitor fired; no change")
return
}
log.Printf("Link monitor fired. New state:")
dump(st)
})
log.Printf("Starting link change monitor; initial state:")
dump()
dump(mon.InterfaceState())
mon.Start()
log.Printf("Started link change monitor; waiting...")
select {}

View File

@ -89,14 +89,15 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/logtail/filch from tailscale.com/logpolicy
tailscale.com/metrics from tailscale.com/derp
tailscale.com/net/dnscache from tailscale.com/control/controlclient+
tailscale.com/net/flowtrack from tailscale.com/net/packet+
tailscale.com/net/dnsfallback from tailscale.com/control/controlclient
tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+
💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled+
tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock
tailscale.com/net/netns from tailscale.com/control/controlclient+
💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver
tailscale.com/net/packet from tailscale.com/wgengine+
tailscale.com/net/portmapper from tailscale.com/net/netcheck+
tailscale.com/net/socks5 from tailscale.com/wgengine/netstack
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
tailscale.com/net/stun from tailscale.com/net/netcheck+
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
@ -107,6 +108,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+
tailscale.com/syncs from tailscale.com/net/interfaces+
tailscale.com/tailcfg from tailscale.com/cmd/tailscaled+
W 💣 tailscale.com/tempfork/wireguard-windows/firewall from tailscale.com/cmd/tailscaled
W tailscale.com/tsconst from tailscale.com/net/interfaces
tailscale.com/tstime from tailscale.com/wgengine/magicsock
tailscale.com/types/empty from tailscale.com/control/controlclient+

View File

@ -14,6 +14,7 @@ import (
"flag"
"fmt"
"log"
"net"
"net/http"
"net/http/pprof"
"os"
@ -21,19 +22,24 @@ import (
"runtime"
"runtime/debug"
"strconv"
"sync"
"syscall"
"time"
"tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy"
"tailscale.com/net/socks5"
"tailscale.com/paths"
"tailscale.com/types/flagtype"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/version"
"tailscale.com/wgengine"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/netstack"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/tstun"
)
// globalStateKey is the ipn.StateKey that tailscaled loads on
@ -62,13 +68,13 @@ func defaultTunName() string {
var args struct {
cleanup bool
fake bool
debug string
tunname string
port uint16
statepath string
socketpath string
verbose int
socksAddr string // listen address for SOCKS5 server
}
var (
@ -94,9 +100,9 @@ func main() {
printVersion := false
flag.IntVar(&args.verbose, "verbose", 0, "log verbosity level; 0 is default, 1 or higher are increasingly verbose")
flag.BoolVar(&args.cleanup, "cleanup", false, "clean up system state and exit")
flag.BoolVar(&args.fake, "fake", false, "use userspace fake tunnel+routing instead of kernel TUN interface")
flag.StringVar(&args.debug, "debug", "", "listen address ([ip]:port) of optional debug server")
flag.StringVar(&args.tunname, "tun", defaultTunName(), "tunnel interface name")
flag.StringVar(&args.socksAddr, "socks5-server", "", `optional [ip]:port to run a SOCK5 server (e.g. "localhost:1080")`)
flag.StringVar(&args.tunname, "tun", defaultTunName(), `tunnel interface name; use "userspace-networking" (beta) to not use TUN`)
flag.Var(flagtype.PortValue(&args.port, magicsock.DefaultPort), "port", "UDP port to listen on for WireGuard and peer-to-peer traffic; 0 means automatically select")
flag.StringVar(&args.statepath, "state", paths.DefaultTailscaledStateFile(), "path of state file")
flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket")
@ -131,9 +137,9 @@ func main() {
os.Exit(0)
}
if runtime.GOOS == "darwin" && os.Getuid() != 0 {
if runtime.GOOS == "darwin" && os.Getuid() != 0 && useTUN() {
log.SetFlags(0)
log.Fatalf("tailscaled requires root; use sudo tailscaled")
log.Fatalf("tailscaled requires root; use sudo tailscaled (or use --tun=userspace-networking)")
}
if args.socketpath == "" && runtime.GOOS != "windows" {
@ -190,23 +196,80 @@ func run() error {
go runDebugServer(debugMux, args.debug)
}
var e wgengine.Engine
if args.fake {
var impl wgengine.FakeImplFactory
if args.tunname == "userspace-networking" {
impl = netstack.Create
}
e, err = wgengine.NewFakeUserspaceEngine(logf, 0, impl)
} else {
e, err = wgengine.NewUserspaceEngine(logf, wgengine.Config{
TUNName: args.tunname,
ListenPort: args.port,
})
linkMon, err := monitor.New(logf)
if err != nil {
log.Fatalf("creating link monitor: %v", err)
}
pol.Logtail.SetLinkMonitor(linkMon)
var socksListener net.Listener
if args.socksAddr != "" {
var err error
socksListener, err = net.Listen("tcp", args.socksAddr)
if err != nil {
log.Fatalf("SOCKS5 listener: %v", err)
}
}
conf := wgengine.Config{
ListenPort: args.port,
LinkMonitor: linkMon,
}
if useTUN() {
conf.TUNName = args.tunname
} else {
conf.TUN = tstun.NewFakeTUN()
conf.RouterGen = router.NewFake
}
e, err := wgengine.NewUserspaceEngine(logf, conf)
if err != nil {
logf("wgengine.New: %v", err)
return err
}
var ns *netstack.Impl
if useNetstack() {
tunDev, magicConn := e.(wgengine.InternalsGetter).GetInternals()
ns, err = netstack.Create(logf, tunDev, e, magicConn)
if err != nil {
log.Fatalf("netstack.Create: %v", err)
}
if err := ns.Start(); err != nil {
log.Fatalf("failed to start netstack: %v", err)
}
}
if socksListener != nil {
srv := &socks5.Server{
Logf: logger.WithPrefix(logf, "socks5: "),
}
if useNetstack() {
srv.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
return ns.DialContextTCP(ctx, addr)
}
} else {
var mu sync.Mutex
var dns netstack.DNSMap
e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) {
mu.Lock()
defer mu.Unlock()
dns = netstack.DNSMapFromNetworkMap(nm)
})
srv.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
ipp, err := dns.Resolve(ctx, addr)
if err != nil {
return nil, err
}
var d net.Dialer
return d.DialContext(ctx, network, ipp.String())
}
}
go func() {
log.Fatalf("SOCKS5 server exited: %v", srv.Serve(socksListener))
}()
}
e = wgengine.NewWatchdog(e)
ctx, cancel := context.WithCancel(context.Background())
@ -266,3 +329,6 @@ func runDebugServer(mux *http.ServeMux, addr string) {
log.Fatal(err)
}
}
func useTUN() bool { return args.tunname != "userspace-networking" }
func useNetstack() bool { return !useTUN() }

View File

@ -21,13 +21,16 @@ import (
"context"
"fmt"
"log"
"net"
"os"
"time"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/svc"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/ipn/ipnserver"
"tailscale.com/logpolicy"
"tailscale.com/tempfork/wireguard-windows/firewall"
"tailscale.com/types/logger"
"tailscale.com/version"
"tailscale.com/wgengine"
@ -83,6 +86,10 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch
}
func beWindowsSubprocess() bool {
if beFirewallKillswitch() {
return true
}
if len(os.Args) != 3 || os.Args[1] != "/subproc" {
return false
}
@ -108,6 +115,44 @@ func beWindowsSubprocess() bool {
return true
}
func beFirewallKillswitch() bool {
if len(os.Args) != 3 || os.Args[1] != "/firewall" {
return false
}
log.SetFlags(0)
log.Printf("killswitch subprocess starting, tailscale GUID is %s", os.Args[2])
go func() {
b := make([]byte, 16)
for {
_, err := os.Stdin.Read(b)
if err != nil {
log.Fatalf("parent process died or requested exit, exiting (%v)", err)
}
}
}()
guid, err := windows.GUIDFromString(os.Args[2])
if err != nil {
log.Fatalf("invalid GUID %q: %v", os.Args[2], err)
}
luid, err := winipcfg.LUIDFromGUID(&guid)
if err != nil {
log.Fatalf("no interface with GUID %q", guid)
}
noProtection := false
var dnsIPs []net.IP // unused in called code.
start := time.Now()
firewall.EnableFirewall(uint64(luid), noProtection, dnsIPs)
log.Printf("killswitch enabled, took %s", time.Since(start))
// Block until the monitor goroutine shuts us down.
select {}
}
func startIPNServer(ctx context.Context, logid string) error {
var logf logger.Logf = log.Printf
var eng wgengine.Engine

View File

@ -0,0 +1,69 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package controlclient
import (
"bytes"
"compress/gzip"
"context"
"fmt"
"log"
"net/http"
"regexp"
"runtime"
"strconv"
"time"
)
func dumpGoroutinesToURL(c *http.Client, targetURL string) {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
zbuf := new(bytes.Buffer)
zw := gzip.NewWriter(zbuf)
zw.Write(scrubbedGoroutineDump())
zw.Close()
req, err := http.NewRequestWithContext(ctx, "PUT", targetURL, zbuf)
if err != nil {
log.Printf("dumpGoroutinesToURL: %v", err)
return
}
req.Header.Set("Content-Encoding", "gzip")
t0 := time.Now()
_, err = c.Do(req)
d := time.Since(t0).Round(time.Millisecond)
if err != nil {
log.Printf("dumpGoroutinesToURL error: %v to %v (after %v)", err, targetURL, d)
} else {
log.Printf("dumpGoroutinesToURL complete to %v (after %v)", targetURL, d)
}
}
var reHexArgs = regexp.MustCompile(`\b0x[0-9a-f]+\b`)
// scrubbedGoroutineDump returns the list of all current goroutines, but with the actual
// values of arguments scrubbed out, lest it contain some private key material.
func scrubbedGoroutineDump() []byte {
buf := make([]byte, 1<<20)
buf = buf[:runtime.Stack(buf, true)]
saw := map[string][]byte{} // "0x123" => "v1%3" (unique value 1 and its value mod 8)
return reHexArgs.ReplaceAllFunc(buf, func(in []byte) []byte {
if string(in) == "0x0" {
return in
}
if v, ok := saw[string(in)]; ok {
return v
}
u64, err := strconv.ParseUint(string(in[2:]), 16, 64)
if err != nil {
return []byte("??")
}
v := []byte(fmt.Sprintf("v%d%%%d", len(saw)+1, u64%8))
saw[string(in)] = v
return v
})
}

View File

@ -0,0 +1,11 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package controlclient
import "testing"
func TestScrubbedGoroutineDump(t *testing.T) {
t.Logf("Got:\n%s\n", scrubbedGoroutineDump())
}

View File

@ -38,6 +38,7 @@ import (
"tailscale.com/logpolicy"
"tailscale.com/logtail"
"tailscale.com/net/dnscache"
"tailscale.com/net/dnsfallback"
"tailscale.com/net/netns"
"tailscale.com/net/tlsdial"
"tailscale.com/net/tshttpproxy"
@ -50,6 +51,7 @@ import (
"tailscale.com/util/systemd"
"tailscale.com/version"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/monitor"
)
// Direct is the client that connects to a tailcontrol server for a node.
@ -61,6 +63,7 @@ type Direct struct {
newDecompressor func() (Decompressor, error)
keepAlive bool
logf logger.Logf
linkMon *monitor.Mon // or nil
discoPubKey tailcfg.DiscoKey
machinePrivKey wgkey.Private
debugFlags []string
@ -92,6 +95,7 @@ type Options struct {
Logf logger.Logf
HTTPTestClient *http.Client // optional HTTP client to use (for tests only)
DebugFlags []string // debug settings to send to control
LinkMonitor *monitor.Mon // optional link monitor
// KeepSharerAndUserSplit controls whether the client
// understands Node.Sharer. If false, the Sharer is mapped to the User.
@ -128,16 +132,18 @@ func NewDirect(opts Options) (*Direct, error) {
httpc := opts.HTTPTestClient
if httpc == nil {
dnsCache := &dnscache.Resolver{
Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true,
Forward: dnscache.Get().Forward, // use default cache's forwarder
UseLastGood: true,
LookupIPFallback: dnsfallback.Lookup,
}
dialer := netns.NewDialer()
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
tr.DialContext = dnscache.Dialer(dialer.DialContext, dnsCache)
tr.ForceAttemptHTTP2 = true
tr.TLSClientConfig = tlsdial.Config(serverURL.Host, tr.TLSClientConfig)
tr.DialContext = dnscache.Dialer(dialer.DialContext, dnsCache)
tr.DialTLSContext = dnscache.TLSDialer(dialer.DialContext, dnsCache, tr.TLSClientConfig)
tr.ForceAttemptHTTP2 = true
httpc = &http.Client{Transport: tr}
}
@ -154,6 +160,7 @@ func NewDirect(opts Options) (*Direct, error) {
discoPubKey: opts.DiscoPublicKey,
debugFlags: opts.DebugFlags,
keepSharerAndUserSplit: opts.KeepSharerAndUserSplit,
linkMon: opts.LinkMonitor,
}
if opts.Hostinfo == nil {
c.SetHostinfo(NewHostinfo())
@ -731,6 +738,9 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netm
if resp.Debug.LogHeapPprof {
go logheap.LogHeap(resp.Debug.LogHeapURL)
}
if resp.Debug.GoroutineDumpURL != "" {
go dumpGoroutinesToURL(c.httpc, resp.Debug.GoroutineDumpURL)
}
setControlAtomic(&controlUseDERPRoute, resp.Debug.DERPRoute)
setControlAtomic(&controlTrimWGConfig, resp.Debug.TrimWGConfig)
}

4
go.mod
View File

@ -32,7 +32,7 @@ require (
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58
@ -40,6 +40,6 @@ require (
gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6
honnef.co/go/tools v0.1.0
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44
inet.af/peercred v0.0.0-20210216231719-993aa01eacaa
inet.af/peercred v0.0.0-20210302202138-56e694897155
rsc.io/goversion v1.2.0
)

4
go.sum
View File

@ -449,6 +449,8 @@ golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b h1:kHlr0tATeLRMEiZJu5CknOw/E8V6h69sXXQFGoPtjcc=
golang.org/x/sys v0.0.0-20210301091718-77cc2087c03b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q=
@ -565,6 +567,8 @@ inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44 h1:p7fX77zWzZMuNdJUhniBsmN1Ov
inet.af/netaddr v0.0.0-20210222205655-a1ec2b7b8c44/go.mod h1:I2i9ONCXRZDnG1+7O8fSuYzjcPxHQXrIfzD/IkR87x4=
inet.af/peercred v0.0.0-20210216231719-993aa01eacaa h1:6qseJO2iNDHl+MLL2BkO5oURJR4A9pLmRz11Yf7KdGM=
inet.af/peercred v0.0.0-20210216231719-993aa01eacaa/go.mod h1:VZeNdG7cRIUqKl9DWoFX86AHyfYwdb4RextAw1CAEO4=
inet.af/peercred v0.0.0-20210302202138-56e694897155 h1:KojYNEYqDkZ2O3LdyTstR1l13L3ePKTIEM2h7ONkfkE=
inet.af/peercred v0.0.0-20210302202138-56e694897155/go.mod h1:FjawnflS/udxX+SvpsMgZfdqx2aykOlkISeAsADi5IU=
k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs=
k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=
k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ=

View File

@ -64,19 +64,20 @@ func getControlDebugFlags() []string {
// state machine generates events back out to zero or more components.
type LocalBackend struct {
// Elements that are thread-safe or constant after construction.
ctx context.Context // canceled by Close
ctxCancel context.CancelFunc // cancels ctx
logf logger.Logf // general logging
keyLogf logger.Logf // for printing list of peers on change
statsLogf logger.Logf // for printing peers stats on change
e wgengine.Engine
store ipn.StateStore
backendLogID string
portpoll *portlist.Poller // may be nil
portpollOnce sync.Once // guards starting readPoller
gotPortPollRes chan struct{} // closed upon first readPoller result
serverURL string // tailcontrol URL
newDecompressor func() (controlclient.Decompressor, error)
ctx context.Context // canceled by Close
ctxCancel context.CancelFunc // cancels ctx
logf logger.Logf // general logging
keyLogf logger.Logf // for printing list of peers on change
statsLogf logger.Logf // for printing peers stats on change
e wgengine.Engine
store ipn.StateStore
backendLogID string
unregisterLinkMon func()
portpoll *portlist.Poller // may be nil
portpollOnce sync.Once // guards starting readPoller
gotPortPollRes chan struct{} // closed upon first readPoller result
serverURL string // tailcontrol URL
newDecompressor func() (controlclient.Decompressor, error)
filterHash string
@ -138,14 +139,19 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge
portpoll: portpoll,
gotPortPollRes: make(chan struct{}),
}
e.SetLinkChangeCallback(b.linkChange)
b.statusChanged = sync.NewCond(&b.statusLock)
linkMon := e.GetLinkMonitor()
// Call our linkChange code once with the current state, and
// then also whenever it changes:
b.linkChange(false, linkMon.InterfaceState())
b.unregisterLinkMon = linkMon.RegisterChangeCallback(b.linkChange)
return b, nil
}
// linkChange is called (in a new goroutine) by wgengine when its link monitor
// detects a network change.
// linkChange is our link monitor callback, called whenever the network changes.
// major is whether ifst is different than earlier.
func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
b.mu.Lock()
defer b.mu.Unlock()
@ -169,6 +175,10 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) {
go b.authReconfig()
}
}
// If the local network configuration has changed, our filter may
// need updating to tweak default routes.
b.updateFilter(b.netMap, b.prefs)
}
// Shutdown halts the backend and all its sub-components. The backend
@ -178,6 +188,7 @@ func (b *LocalBackend) Shutdown() {
cli := b.c
b.mu.Unlock()
b.unregisterLinkMon()
if cli != nil {
cli.Shutdown()
}
@ -543,6 +554,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
HTTPTestClient: opts.HTTPTestClient,
DiscoPublicKey: discoPublic,
DebugFlags: controlDebugFlags,
LinkMonitor: b.e.GetLinkMonitor(),
})
if err != nil {
return err
@ -604,9 +616,22 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
}
if prefs != nil {
for _, r := range prefs.AdvertiseRoutes {
// TODO: when advertising default routes, trim out local
// nets.
localNetsB.AddPrefix(r)
if r.Bits == 0 {
// When offering a default route to the world, we
// filter out locally reachable LANs, so that the
// default route effectively appears to be a "guest
// wifi": you get internet access, but to additionally
// get LAN access the LAN(s) need to be offered
// explicitly as well.
s, err := shrinkDefaultRoute(r)
if err != nil {
b.logf("computing default route filter: %v", err)
continue
}
localNetsB.AddSet(s)
} else {
localNetsB.AddPrefix(r)
}
}
}
localNets := localNetsB.IPSet()
@ -632,6 +657,42 @@ func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs)
}
}
var removeFromDefaultRoute = []netaddr.IPPrefix{
// RFC1918 LAN ranges
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("172.16.0.0/12"),
netaddr.MustParseIPPrefix("10.0.0.0/8"),
// Tailscale IPv4 range
tsaddr.CGNATRange(),
// IPv6 Link-local addresses
netaddr.MustParseIPPrefix("fe80::/10"),
// Tailscale IPv6 range
tsaddr.TailscaleULARange(),
}
// shrinkDefaultRoute returns an IPSet representing the IPs in route,
// minus those in removeFromDefaultRoute and local interface subnets.
func shrinkDefaultRoute(route netaddr.IPPrefix) (*netaddr.IPSet, error) {
var b netaddr.IPSetBuilder
b.AddPrefix(route)
err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP) {
return
}
if pfx.IsSingleIP() {
return
}
b.RemovePrefix(pfx)
})
if err != nil {
return nil, err
}
for _, pfx := range removeFromDefaultRoute {
b.RemovePrefix(pfx)
}
return b.IPSet(), nil
}
// dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool {
@ -1302,6 +1363,41 @@ var (
ipv6Default = netaddr.MustParseIPPrefix("::/0")
)
// peerRoutes returns the routerConfig.Routes to access peers.
// If there are over cgnatThreshold CGNAT routes, one big CGNAT route
// is used instead.
func peerRoutes(peers []wgcfg.Peer, cgnatThreshold int) (routes []netaddr.IPPrefix) {
tsULA := tsaddr.TailscaleULARange()
cgNAT := tsaddr.CGNATRange()
var didULA bool
var cgNATIPs []netaddr.IPPrefix
for _, peer := range peers {
for _, aip := range peer.AllowedIPs {
aip = unmapIPPrefix(aip)
// Only add the Tailscale IPv6 ULA once, if we see anybody using part of it.
if aip.IP.Is6() && aip.IsSingleIP() && tsULA.Contains(aip.IP) {
if !didULA {
didULA = true
routes = append(routes, tsULA)
}
continue
}
if aip.IsSingleIP() && cgNAT.Contains(aip.IP) {
cgNATIPs = append(cgNATIPs, aip)
} else {
routes = append(routes, aip)
}
}
}
if len(cgNATIPs) > cgnatThreshold {
// Probably the hello server. Just append one big route.
routes = append(routes, cgNAT)
} else {
routes = append(routes, cgNATIPs...)
}
return routes
}
// routerConfig produces a router.Config from a wireguard config and IPN prefs.
func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
rs := &router.Config{
@ -1309,10 +1405,7 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes),
SNATSubnetRoutes: !prefs.NoSNAT,
NetfilterMode: prefs.NetfilterMode,
}
for _, peer := range cfg.Peers {
rs.Routes = append(rs.Routes, unmapIPPrefixes(peer.AllowedIPs)...)
Routes: peerRoutes(cfg.Peers, 10_000),
}
// Sanity check: we expect the control server to program both a v4
@ -1349,10 +1442,14 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config {
return rs
}
func unmapIPPrefix(ipp netaddr.IPPrefix) netaddr.IPPrefix {
return netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits}
}
func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) {
for _, ipps := range ippsList {
for _, ipp := range ipps {
ret = append(ret, netaddr.IPPrefix{IP: ipp.IP.Unmap(), Bits: ipp.Bits})
ret = append(ret, unmapIPPrefix(ipp))
}
}
return ret

View File

@ -5,11 +5,14 @@
package ipnlocal
import (
"reflect"
"testing"
"inet.af/netaddr"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/wgcfg"
)
func TestNetworkMapCompare(t *testing.T) {
@ -118,3 +121,147 @@ func TestNetworkMapCompare(t *testing.T) {
}
}
}
func TestShrinkDefaultRoute(t *testing.T) {
tests := []struct {
route string
in []string
out []string
}{
{
route: "0.0.0.0/0",
in: []string{"1.2.3.4", "25.0.0.1"},
out: []string{
"10.0.0.1",
"10.255.255.255",
"192.168.0.1",
"192.168.255.255",
"172.16.0.1",
"172.31.255.255",
"100.101.102.103",
// Some random IPv6 stuff that shouldn't be in a v4
// default route.
"fe80::",
"2601::1",
},
},
{
route: "::/0",
in: []string{"::1", "2601::1"},
out: []string{
"fe80::1",
tsaddr.TailscaleULARange().IP.String(),
},
},
}
for _, test := range tests {
def := netaddr.MustParseIPPrefix(test.route)
got, err := shrinkDefaultRoute(def)
if err != nil {
t.Fatalf("shrinkDefaultRoute(%q): %v", test.route, err)
}
for _, ip := range test.in {
if !got.Contains(netaddr.MustParseIP(ip)) {
t.Errorf("shrink(%q).Contains(%v) = false, want true", test.route, ip)
}
}
for _, ip := range test.out {
if got.Contains(netaddr.MustParseIP(ip)) {
t.Errorf("shrink(%q).Contains(%v) = true, want false", test.route, ip)
}
}
}
}
func TestPeerRoutes(t *testing.T) {
pp := netaddr.MustParseIPPrefix
tests := []struct {
name string
peers []wgcfg.Peer
want []netaddr.IPPrefix
}{
{
name: "small_v4",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
pp("100.101.102.103/32"),
},
},
},
want: []netaddr.IPPrefix{
pp("100.101.102.103/32"),
},
},
{
name: "big_v4",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
pp("100.101.102.103/32"),
pp("100.101.102.104/32"),
pp("100.101.102.105/32"),
},
},
},
want: []netaddr.IPPrefix{
pp("100.64.0.0/10"),
},
},
{
name: "has_1_v6",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"),
},
},
},
want: []netaddr.IPPrefix{
pp("fd7a:115c:a1e0::/48"),
},
},
{
name: "has_2_v6",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"),
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b241/128"),
},
},
},
want: []netaddr.IPPrefix{
pp("fd7a:115c:a1e0::/48"),
},
},
{
name: "big_v4_big_v6",
peers: []wgcfg.Peer{
{
AllowedIPs: []netaddr.IPPrefix{
pp("100.101.102.103/32"),
pp("100.101.102.104/32"),
pp("100.101.102.105/32"),
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b240/128"),
pp("fd7a:115c:a1e0:ab12:4843:cd96:6258:b241/128"),
},
},
},
want: []netaddr.IPPrefix{
pp("fd7a:115c:a1e0::/48"),
pp("100.64.0.0/10"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := peerRoutes(tt.peers, 2)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got = %v; want %v", got, tt.want)
}
})
}
}

View File

@ -41,7 +41,7 @@ func TestLocalLogLines(t *testing.T) {
// set up a LocalBackend, super bare bones. No functional data.
store := &ipn.MemoryStore{}
e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0, nil)
e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0)
if err != nil {
t.Fatal(err)
}

View File

@ -19,6 +19,7 @@ import (
"os/signal"
"os/user"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
@ -319,6 +320,9 @@ func isReadonlyConn(c net.Conn, logf logger.Logf) bool {
}
const ro = true
const rw = false
if !safesocket.PlatformUsesPeerCreds() {
return rw
}
creds, err := peercred.Get(c)
if err != nil {
logf("connection from unknown peer; read-only")
@ -333,6 +337,10 @@ func isReadonlyConn(c net.Conn, logf logger.Logf) bool {
logf("connection from userid %v; root has access", uid)
return rw
}
if selfUID := os.Getuid(); selfUID != 0 && uid == strconv.Itoa(selfUID) {
logf("connection from userid %v; connection from non-root user matching daemon has access", uid)
return rw
}
var adminGroupID string
switch runtime.GOOS {
case "darwin":

View File

@ -56,7 +56,7 @@ func TestRunMultipleAccepts(t *testing.T) {
}
}
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0, nil)
eng, err := wgengine.NewFakeUserspaceEngine(logf, 0)
if err != nil {
t.Fatal(err)
}

View File

@ -18,7 +18,9 @@ import (
"time"
"tailscale.com/logtail/backoff"
"tailscale.com/net/interfaces"
tslogger "tailscale.com/types/logger"
"tailscale.com/wgengine/monitor"
)
// DefaultHost is the default host name to upload logs to when
@ -106,6 +108,7 @@ type Logger struct {
url string
lowMem bool
skipClientTime bool
linkMonitor *monitor.Mon
buffer Buffer
sent chan struct{} // signal to speed up drain
drainLogs <-chan struct{} // if non-nil, external signal to attempt a drain
@ -128,6 +131,14 @@ func (l *Logger) SetVerbosityLevel(level int) {
l.stderrLevel = level
}
// SetLinkMonitor sets the optional the link monitor.
//
// It should not be changed concurrently with log writes and should
// only be set once.
func (l *Logger) SetLinkMonitor(lm *monitor.Mon) {
l.linkMonitor = lm
}
// Shutdown gracefully shuts down the logger while completing any
// remaining uploads.
//
@ -277,6 +288,11 @@ func (l *Logger) uploading(ctx context.Context) {
}
uploaded, err := l.upload(ctx, body, origlen)
if err != nil {
if !l.internetUp() {
fmt.Fprintf(l.stderr, "logtail: internet down; waiting\n")
l.awaitInternetUp(ctx)
continue
}
fmt.Fprintf(l.stderr, "logtail: upload: %v\n", err)
}
l.bo.BackOff(ctx, err)
@ -293,6 +309,34 @@ func (l *Logger) uploading(ctx context.Context) {
}
}
func (l *Logger) internetUp() bool {
if l.linkMonitor == nil {
// No way to tell, so assume it is.
return true
}
return l.linkMonitor.InterfaceState().AnyInterfaceUp()
}
func (l *Logger) awaitInternetUp(ctx context.Context) {
upc := make(chan bool, 1)
defer l.linkMonitor.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
if st.AnyInterfaceUp() {
select {
case upc <- true:
default:
}
}
})()
if l.internetUp() {
return
}
select {
case <-upc:
fmt.Fprintf(l.stderr, "logtail: internet back up\n")
case <-ctx.Done():
}
}
// upload uploads body to the log server.
// origlen indicates the pre-compression body length.
// origlen of -1 indicates that the body is not compressed.

View File

@ -8,6 +8,8 @@ package dnscache
import (
"context"
"crypto/tls"
"errors"
"fmt"
"log"
"net"
@ -18,6 +20,7 @@ import (
"time"
"golang.org/x/sync/singleflight"
"inet.af/netaddr"
)
var single = &Resolver{
@ -55,6 +58,10 @@ type Resolver struct {
// If nil, net.DefaultResolver is used.
Forward *net.Resolver
// LookupIPFallback optionally provides a backup DNS mechanism
// to use if Forward returns an error or no results.
LookupIPFallback func(ctx context.Context, host string) ([]netaddr.IP, error)
// TTL is how long to keep entries cached
//
// If zero, a default (currently 10 minutes) is used.
@ -198,6 +205,18 @@ func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, err error) {
ctx, cancel := context.WithTimeout(context.Background(), r.lookupTimeoutForHost(host))
defer cancel()
ips, err := r.fwd().LookupIPAddr(ctx, host)
if (err != nil || len(ips) == 0) && r.LookupIPFallback != nil {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
var fips []netaddr.IP
fips, err = r.LookupIPFallback(ctx, host)
if err == nil {
ips = nil
for _, fip := range fips {
ips = append(ips, *fip.IPAddr())
}
}
}
if err != nil {
return nil, nil, err
}
@ -269,13 +288,33 @@ type DialContextFunc func(ctx context.Context, network, address string) (net.Con
// Dialer returns a wrapped DialContext func that uses the provided dnsCache.
func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
return func(ctx context.Context, network, address string) (net.Conn, error) {
return func(ctx context.Context, network, address string) (retConn net.Conn, ret error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
// Bogus. But just let the real dialer return an error rather than
// inventing a similar one.
return fwd(ctx, network, address)
}
defer func() {
// On any failure, assume our DNS is wrong and try our fallback, if any.
if ret == nil || dnsCache.LookupIPFallback == nil {
return
}
ips, err := dnsCache.LookupIPFallback(ctx, host)
if err != nil {
// Return with original error
return
}
for _, ip := range ips {
dst := net.JoinHostPort(ip.String(), port)
if c, err := fwd(ctx, network, dst); err == nil {
retConn = c
ret = nil
return
}
}
}()
ip, ip6, err := dnsCache.LookupIP(ctx, host)
if err != nil {
return nil, fmt.Errorf("failed to resolve %q: %w", host, err)
@ -300,3 +339,62 @@ func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
return fwd(ctx, network, dst)
}
}
var errTLSHandshakeTimeout = errors.New("timeout doing TLS handshake")
// TLSDialer is like Dialer but returns a func suitable for using with net/http.Transport.DialTLSContext.
// It returns a *tls.Conn type on success.
// On TLS cert validation failure, it can invoke a backup DNS resolution strategy.
func TLSDialer(fwd DialContextFunc, dnsCache *Resolver, tlsConfigBase *tls.Config) DialContextFunc {
tcpDialer := Dialer(fwd, dnsCache)
return func(ctx context.Context, network, address string) (net.Conn, error) {
host, _, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
tcpConn, err := tcpDialer(ctx, network, address)
if err != nil {
return nil, err
}
cfg := cloneTLSConfig(tlsConfigBase)
if cfg.ServerName == "" {
cfg.ServerName = host
}
tlsConn := tls.Client(tcpConn, cfg)
errc := make(chan error, 2)
handshakeCtx, handshakeTimeoutCancel := context.WithTimeout(ctx, 5*time.Second)
defer handshakeTimeoutCancel()
done := make(chan bool)
defer close(done)
go func() {
select {
case <-done:
case <-handshakeCtx.Done():
errc <- errTLSHandshakeTimeout
}
}()
go func() {
err := tlsConn.Handshake()
handshakeTimeoutCancel()
errc <- err
}()
if err := <-errc; err != nil {
tcpConn.Close()
// TODO: if err != errTLSHandshakeTimeout,
// assume it might be some captive portal or
// otherwise incorrect DNS and try the backup
// DNS mechanism.
return nil, err
}
return tlsConn, nil
}
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}

View File

@ -0,0 +1,117 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package dnsfallback contains a DNS fallback mechanism
// for starting up Tailscale when the system DNS is broken or otherwise unavailable.
package dnsfallback
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"math/rand"
"net"
"net/http"
"net/url"
"time"
"inet.af/netaddr"
"tailscale.com/derp/derpmap"
"tailscale.com/net/netns"
"tailscale.com/net/tshttpproxy"
)
func Lookup(ctx context.Context, host string) ([]netaddr.IP, error) {
type nameIP struct {
dnsName string
ip netaddr.IP
}
dm := derpmap.Prod()
var cands4, cands6 []nameIP
for _, dr := range dm.Regions {
for _, n := range dr.Nodes {
if ip, err := netaddr.ParseIP(n.IPv4); err == nil {
cands4 = append(cands4, nameIP{n.HostName, ip})
}
if ip, err := netaddr.ParseIP(n.IPv6); err == nil {
cands6 = append(cands6, nameIP{n.HostName, ip})
}
}
}
rand.Shuffle(len(cands4), func(i, j int) { cands4[i], cands4[j] = cands4[j], cands4[i] })
rand.Shuffle(len(cands6), func(i, j int) { cands6[i], cands6[j] = cands6[j], cands6[i] })
const maxCands = 6
var cands []nameIP // up to maxCands alternating v4/v6 as long as we have both
for (len(cands4) > 0 || len(cands6) > 0) && len(cands) < maxCands {
if len(cands4) > 0 {
cands = append(cands, cands4[0])
cands4 = cands4[1:]
}
if len(cands6) > 0 {
cands = append(cands, cands6[0])
cands6 = cands6[1:]
}
}
if len(cands) == 0 {
return nil, fmt.Errorf("no DNS fallback options for %q", host)
}
for _, cand := range cands {
if err := ctx.Err(); err != nil {
return nil, err
}
log.Printf("trying bootstrapDNS(%q, %q) for %q ...", cand.dnsName, cand.ip, host)
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel()
dm, err := bootstrapDNSMap(ctx, cand.dnsName, cand.ip, host)
if err != nil {
log.Printf("bootstrapDNS(%q, %q) for %q error: %v", cand.dnsName, cand.ip, host, err)
continue
}
if ips := dm[host]; len(ips) > 0 {
log.Printf("bootstrapDNS(%q, %q) for %q = %v", cand.dnsName, cand.ip, host, ips)
return ips, nil
}
}
if err := ctx.Err(); err != nil {
return nil, err
}
return nil, fmt.Errorf("no DNS fallback candidates remain for %q", host)
}
// serverName and serverIP of are, say, "derpN.tailscale.com".
// queryName is the name being sought (e.g. "login.tailscale.com"), passed as hint.
func bootstrapDNSMap(ctx context.Context, serverName string, serverIP netaddr.IP, queryName string) (dnsMap, error) {
dialer := netns.NewDialer()
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.Proxy = tshttpproxy.ProxyFromEnvironment
tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(serverIP.String(), "443"))
}
c := &http.Client{Transport: tr}
req, err := http.NewRequestWithContext(ctx, "GET", "https://"+serverName+"/bootstrap-dns?q="+url.QueryEscape(queryName), nil)
if err != nil {
return nil, err
}
dm := make(dnsMap)
res, err := c.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != 200 {
return nil, errors.New(res.Status)
}
if err := json.NewDecoder(res.Body).Decode(&dm); err != nil {
return nil, err
}
return dm, nil
}
// dnsMap is the JSON type returned by the DERP /bootstrap-dns handler:
// https://derp10.tailscale.com/bootstrap-dns
type dnsMap map[string][]netaddr.IP

View File

@ -78,7 +78,7 @@ func isProblematicInterface(nif *net.Interface) bool {
// LocalAddresses returns the machine's IP addresses, separated by
// whether they're loopback addresses.
func LocalAddresses() (regular, loopback []string, err error) {
func LocalAddresses() (regular, loopback []netaddr.IP, err error) {
// TODO(crawshaw): don't serve interface addresses that we are routing
ifaces, err := net.Interfaces()
if err != nil {
@ -117,16 +117,22 @@ func LocalAddresses() (regular, loopback []string, err error) {
continue
}
if ip.IsLoopback() || ifcIsLoopback {
loopback = append(loopback, ip.String())
loopback = append(loopback, ip)
} else {
regular = append(regular, ip.String())
regular = append(regular, ip)
}
}
}
}
sortIPs(regular)
sortIPs(loopback)
return regular, loopback, nil
}
func sortIPs(s []netaddr.IP) {
sort.Slice(s, func(i, j int) bool { return s[i].Less(s[j]) })
}
// Interface is a wrapper around Go's net.Interface with some extra methods.
type Interface struct {
*net.Interface
@ -135,8 +141,10 @@ type Interface struct {
func (i Interface) IsLoopback() bool { return isLoopback(i.Interface) }
func (i Interface) IsUp() bool { return isUp(i.Interface) }
// ForeachInterfaceAddress calls fn for each interface's address on the machine.
func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error {
// ForeachInterfaceAddress calls fn for each interface's address on
// the machine. The IPPrefix's IP is the IP address assigned to the
// interface, and Bits are the subnet mask.
func ForeachInterfaceAddress(fn func(Interface, netaddr.IPPrefix)) error {
ifaces, err := net.Interfaces()
if err != nil {
return err
@ -150,8 +158,8 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error {
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
if ip, ok := netaddr.FromStdIP(v.IP); ok {
fn(Interface{iface}, ip)
if pfx, ok := netaddr.FromStdIPNet(v); ok {
fn(Interface{iface}, pfx)
}
}
}
@ -159,8 +167,10 @@ func ForeachInterfaceAddress(fn func(Interface, netaddr.IP)) error {
return nil
}
// ForeachInterface calls fn for each interface on the machine, with all its addresses.
func ForeachInterface(fn func(Interface, []netaddr.IP)) error {
// ForeachInterface calls fn for each interface on the machine, with
// all its addresses. The IPPrefix's IP is the IP address assigned to
// the interface, and Bits are the subnet mask.
func ForeachInterface(fn func(Interface, []netaddr.IPPrefix)) error {
ifaces, err := net.Interfaces()
if err != nil {
return err
@ -171,16 +181,16 @@ func ForeachInterface(fn func(Interface, []netaddr.IP)) error {
if err != nil {
return err
}
var ips []netaddr.IP
var pfxs []netaddr.IPPrefix
for _, a := range addrs {
switch v := a.(type) {
case *net.IPNet:
if ip, ok := netaddr.FromStdIP(v.IP); ok {
ips = append(ips, ip)
if pfx, ok := netaddr.FromStdIPNet(v); ok {
pfxs = append(pfxs, pfx)
}
}
}
fn(Interface{iface}, ips)
fn(Interface{iface}, pfxs)
}
return nil
}
@ -189,7 +199,11 @@ func ForeachInterface(fn func(Interface, []netaddr.IP)) error {
// routing table, and other network configuration.
// For now it's pretty basic.
type State struct {
InterfaceIPs map[string][]netaddr.IP
// InterfaceIPs maps from an interface name to the IP addresses
// configured on that interface. Each address is represented as an
// IPPrefix, where the IP is the interface IP address and Bits is
// the subnet mask.
InterfaceIPs map[string][]netaddr.IPPrefix
InterfaceUp map[string]bool
// HaveV6Global is whether this machine has an IPv6 global address
@ -242,14 +256,14 @@ func (s *State) String() string {
if s.InterfaceUp[ifName] {
fmt.Fprintf(&sb, "%s:[", ifName)
needSpace := false
for _, ip := range s.InterfaceIPs[ifName] {
if !isInterestingIP(ip) {
for _, pfx := range s.InterfaceIPs[ifName] {
if !isInterestingIP(pfx.IP) {
continue
}
if needSpace {
sb.WriteString(" ")
}
fmt.Fprintf(&sb, "%s", ip)
fmt.Fprintf(&sb, "%s", pfx)
needSpace = true
}
sb.WriteString("]")
@ -287,24 +301,24 @@ func (s *State) AnyInterfaceUp() bool {
// are owned by this process. (TODO: make this true; currently it
// uses some heuristics)
func (s *State) RemoveTailscaleInterfaces() {
for name, ips := range s.InterfaceIPs {
if isTailscaleInterface(name, ips) {
for name, pfxs := range s.InterfaceIPs {
if isTailscaleInterface(name, pfxs) {
delete(s.InterfaceIPs, name)
delete(s.InterfaceUp, name)
}
}
}
func hasTailscaleIP(ips []netaddr.IP) bool {
for _, ip := range ips {
if tsaddr.IsTailscaleIP(ip) {
func hasTailscaleIP(pfxs []netaddr.IPPrefix) bool {
for _, pfx := range pfxs {
if tsaddr.IsTailscaleIP(pfx.IP) {
return true
}
}
return false
}
func isTailscaleInterface(name string, ips []netaddr.IP) bool {
func isTailscaleInterface(name string, ips []netaddr.IPPrefix) bool {
if runtime.GOOS == "darwin" && strings.HasPrefix(name, "utun") && hasTailscaleIP(ips) {
// On macOS in the sandboxed app (at least as of
// 2021-02-25), we often see two utun devices
@ -326,22 +340,22 @@ var getPAC func() string
// It does not set the returned State.IsExpensive. The caller can populate that.
func GetState() (*State, error) {
s := &State{
InterfaceIPs: make(map[string][]netaddr.IP),
InterfaceIPs: make(map[string][]netaddr.IPPrefix),
InterfaceUp: make(map[string]bool),
}
if err := ForeachInterface(func(ni Interface, ips []netaddr.IP) {
if err := ForeachInterface(func(ni Interface, pfxs []netaddr.IPPrefix) {
ifUp := ni.IsUp()
s.InterfaceUp[ni.Name] = ifUp
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], ips...)
if !ifUp || isTailscaleInterface(ni.Name, ips) {
s.InterfaceIPs[ni.Name] = append(s.InterfaceIPs[ni.Name], pfxs...)
if !ifUp || isTailscaleInterface(ni.Name, pfxs) {
return
}
for _, ip := range ips {
if ip.IsLoopback() || ip.IsLinkLocalUnicast() {
for _, pfx := range pfxs {
if pfx.IP.IsLoopback() || pfx.IP.IsLinkLocalUnicast() {
continue
}
s.HaveV6Global = s.HaveV6Global || isGlobalV6(ip)
s.HaveV4 = s.HaveV4 || ip.Is4()
s.HaveV6Global = s.HaveV6Global || isGlobalV6(pfx.IP)
s.HaveV4 = s.HaveV4 || pfx.IP.Is4()
}
}); err != nil {
return nil, err
@ -375,7 +389,8 @@ func HTTPOfListener(ln net.Listener) string {
var goodIP string
var privateIP string
ForeachInterfaceAddress(func(i Interface, ip netaddr.IP) {
ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
ip := pfx.IP
if isPrivateIP(ip) {
if privateIP == "" {
privateIP = ip.String()
@ -411,7 +426,8 @@ func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) {
if !ok {
return
}
ForeachInterfaceAddress(func(i Interface, ip netaddr.IP) {
ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
ip := pfx.IP
if !i.IsUp() || ip.IsZero() || !myIP.IsZero() {
return
}
@ -451,11 +467,11 @@ var (
v6Global1 = mustCIDR("2000::/3")
)
// anyInterestingIP reports ips contains any IP that matches
// anyInterestingIP reports whether pfxs contains any IP that matches
// isInterestingIP.
func anyInterestingIP(ips []netaddr.IP) bool {
for _, ip := range ips {
if isInterestingIP(ip) {
func anyInterestingIP(pfxs []netaddr.IPPrefix) bool {
for _, pfx := range pfxs {
if isInterestingIP(pfx.IP) {
return true
}
}

View File

@ -6,9 +6,13 @@ package interfaces
import (
"errors"
"fmt"
"net"
"os/exec"
"syscall"
"go4.org/mem"
"golang.org/x/net/route"
"inet.af/netaddr"
"tailscale.com/util/lineread"
"tailscale.com/version"
@ -72,3 +76,65 @@ func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) {
}
var errStopReadingNetstatTable = errors.New("found private gateway")
func DefaultRouteInterface() (string, error) {
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return "", err
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return "", err
}
return iface.Name, nil
}
func DefaultRouteInterfaceIndex() (int, error) {
// $ netstat -nr
// Routing tables
// Internet:
// Destination Gateway Flags Netif Expire
// default 10.0.0.1 UGSc en0 <-- want this one
// default 10.0.0.1 UGScI en1
// From man netstat:
// U RTF_UP Route usable
// G RTF_GATEWAY Destination requires forwarding by intermediary
// S RTF_STATIC Manually added
// c RTF_PRCLONING Protocol-specified generate new routes on use
// I RTF_IFSCOPE Route is associated with an interface scope
rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
if err != nil {
return 0, fmt.Errorf("route.FetchRIB: %w", err)
}
msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err)
}
indexSeen := map[int]int{} // index => count
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
const RTF_GATEWAY = 0x2
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_GATEWAY == 0 {
continue
}
if rm.Flags&RTF_IFSCOPE != 0 {
continue
}
indexSeen[rm.Index]++
}
if len(indexSeen) == 0 {
return 0, errors.New("no gateway index found")
}
if len(indexSeen) == 1 {
for idx := range indexSeen {
return idx, nil
}
}
return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen)
}

View File

@ -1,81 +0,0 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin,!redo,!ios
// (Exclude redo, because we don't want this code in the App Store
// version's sandbox, where it won't work, and also don't want it on
// iOS. This is just for utun-using non-sandboxed cmd/tailscaled on macOS.
package interfaces
import (
"errors"
"fmt"
"net"
"syscall"
"golang.org/x/net/route"
)
func DefaultRouteInterface() (string, error) {
idx, err := DefaultRouteInterfaceIndex()
if err != nil {
return "", err
}
iface, err := net.InterfaceByIndex(idx)
if err != nil {
return "", err
}
return iface.Name, nil
}
func DefaultRouteInterfaceIndex() (int, error) {
// $ netstat -nr
// Routing tables
// Internet:
// Destination Gateway Flags Netif Expire
// default 10.0.0.1 UGSc en0 <-- want this one
// default 10.0.0.1 UGScI en1
// From man netstat:
// U RTF_UP Route usable
// G RTF_GATEWAY Destination requires forwarding by intermediary
// S RTF_STATIC Manually added
// c RTF_PRCLONING Protocol-specified generate new routes on use
// I RTF_IFSCOPE Route is associated with an interface scope
rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0)
if err != nil {
return 0, fmt.Errorf("route.FetchRIB: %w", err)
}
msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib)
if err != nil {
return 0, fmt.Errorf("route.ParseRIB: %w", err)
}
indexSeen := map[int]int{} // index => count
for _, m := range msgs {
rm, ok := m.(*route.RouteMessage)
if !ok {
continue
}
const RTF_GATEWAY = 0x2
const RTF_IFSCOPE = 0x1000000
if rm.Flags&RTF_GATEWAY == 0 {
continue
}
if rm.Flags&RTF_IFSCOPE != 0 {
continue
}
indexSeen[rm.Index]++
}
if len(indexSeen) == 0 {
return 0, errors.New("no gateway index found")
}
if len(indexSeen) == 1 {
for idx := range indexSeen {
return idx, nil
}
}
return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen)
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux,!windows,!darwin darwin,redo
// +build !linux,!windows,!darwin
package interfaces

View File

@ -222,7 +222,7 @@ func IsNoMappingError(err error) bool {
var (
ErrNoPortMappingServices = errors.New("no port mapping services were found")
ErrGatewayNotFound = errors.New("failed to look gateway address")
ErrGatewayNotFound = errors.New("failed to look up gateway address")
)
// CreateOrGetMapping either creates a new mapping or returns a cached

View File

@ -108,7 +108,7 @@ func (s *Server) Serve(l net.Listener) error {
conn := &Conn{clientConn: c, srv: s}
err := conn.Run()
if err != nil {
s.logf("socks5: client connection failed: %v", err)
s.logf("client connection failed: %v", err)
conn.clientConn.Close()
}
}()
@ -123,7 +123,6 @@ type Conn struct {
srv *Server
clientConn net.Conn
serverConn net.Conn
request *request
}
@ -153,11 +152,7 @@ func (c *Conn) handleRequest() error {
return fmt.Errorf("unsupported command %v", req.command)
}
c.request = req
return c.createReply()
}
func (c *Conn) createReply() error {
var err error
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv, err := c.srv.dial(
@ -171,14 +166,12 @@ func (c *Conn) createReply() error {
c.clientConn.Write(buf)
return err
}
c.serverConn = srv
serverAddr, serverPortStr, err := net.SplitHostPort(c.serverConn.LocalAddr().String())
defer srv.Close()
serverAddr, serverPortStr, err := net.SplitHostPort(srv.LocalAddr().String())
if err != nil {
return err
}
serverPort, _ := strconv.Atoi(serverPortStr)
go io.Copy(c.clientConn, c.serverConn)
go io.Copy(c.serverConn, c.clientConn)
var bindAddrType addrType
if ip := net.ParseIP(serverAddr); ip != nil {
@ -190,7 +183,6 @@ func (c *Conn) createReply() error {
} else {
bindAddrType = domainName
}
res := &response{
reply: success,
bindAddrType: bindAddrType,
@ -203,7 +195,23 @@ func (c *Conn) createReply() error {
buf, _ = res.marshal()
}
c.clientConn.Write(buf)
return err
errc := make(chan error, 2)
go func() {
_, err := io.Copy(c.clientConn, srv)
if err != nil {
err = fmt.Errorf("from backend to client: %w", err)
}
errc <- err
}()
go func() {
_, err := io.Copy(srv, c.clientConn)
if err != nil {
err = fmt.Errorf("from client to backend: %w", err)
}
errc <- err
}()
return <-errc
}
// parseClientGreeting parses a request initiation packet

View File

@ -71,6 +71,17 @@ func Tailscale4To6Range() netaddr.IPPrefix {
return ula4To6Range.v
}
// Tailscale4To6Placeholder returns an IP address that can be used as
// a source IP when one is required, but a netmap didn't provide
// any. This address never gets allocated by the 4-to-6 algorithm in
// control.
//
// Currently used to work around a Windows limitation when programming
// IPv6 routes in corner cases.
func Tailscale4To6Placeholder() netaddr.IP {
return Tailscale4To6Range().IP
}
// Tailscale4To6 returns a Tailscale IPv6 address that maps 1:1 to the
// given Tailscale IPv4 address. Returns a zero IP if ipv4 isn't a
// Tailscale IPv4 address.

View File

@ -11,6 +11,7 @@ import (
"log"
"net/http"
"net/url"
"runtime"
"strings"
"sync"
"syscall"
@ -109,6 +110,9 @@ func proxyFromWinHTTPOrCache(req *http.Request) (*url.URL, error) {
}
func proxyFromWinHTTP(ctx context.Context, urlStr string) (proxy *url.URL, err error) {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
whi, err := winHTTPOpen()
if err != nil {
proxyErrorf("winhttp: Open: %v", err)

34
portlist/clean.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package portlist
import (
"path/filepath"
"strings"
)
// argvSubject takes a command and its flags, and returns the
// short/pretty name for the process. This is usually the basename of
// the binary being executed, but can sometimes vary (e.g. so that we
// don't report all Java programs as "java").
func argvSubject(argv ...string) string {
if len(argv) == 0 {
return ""
}
ret := filepath.Base(argv[0])
// Handle special cases.
switch {
case ret == "mono" && len(argv) >= 2:
// .Net programs execute as `mono actualProgram.exe`.
ret = filepath.Base(argv[1])
}
// Remove common noise.
ret = strings.TrimSpace(ret)
ret = strings.TrimSuffix(ret, ".exe")
return ret
}

42
portlist/clean_test.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package portlist
import "testing"
func TestArgvSubject(t *testing.T) {
tests := []struct {
in []string
want string
}{
{
in: nil,
want: "",
},
{
in: []string{"/usr/bin/sshd"},
want: "sshd",
},
{
in: []string{"/bin/mono"},
want: "mono",
},
{
in: []string{"/nix/store/x2cw2xjw98zdysf56bdlfzsr7cyxv0jf-mono-5.20.1.27/bin/mono", "/bin/exampleProgram.exe"},
want: "exampleProgram",
},
{
in: []string{"/bin/mono", "/sbin/exampleProgram.bin"},
want: "exampleProgram.bin",
},
}
for _, test := range tests {
got := argvSubject(test.in...)
if got != test.want {
t.Errorf("argvSubject(%v) = %q, want %q", test.in, got, test.want)
}
}
}

View File

@ -101,13 +101,9 @@ func parsePortsNetstat(output string) List {
delete(m, lastport)
proc := trimline[1 : len(trimline)-1]
if proc == "svchost.exe" && lastline != "" {
p.Process = lastline
p.Process = argvSubject(lastline)
} else {
if strings.HasSuffix(proc, ".exe") {
p.Process = proc[:len(proc)-4]
} else {
p.Process = proc
}
p.Process = argvSubject(proc)
}
m[p] = nothing{}
} else {

View File

@ -10,6 +10,7 @@ import (
"io"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
@ -26,19 +27,25 @@ const pollInterval = 1 * time.Second
// TODO(apenwarr): Include IPv6 ports eventually.
// Right now we don't route IPv6 anyway so it's better to exclude them.
var sockfiles = []string{"/proc/net/tcp", "/proc/net/udp"}
var protos = []string{"tcp", "udp"}
var sockfiles = []string{"/proc/net/tcp", "/proc/net/tcp6", "/proc/net/udp", "/proc/net/udp6"}
var sawProcNetPermissionErr syncs.AtomicBool
const (
v6Localhost = "00000000000000000000000001000000:"
v6Any = "00000000000000000000000000000000:0000"
v4Localhost = "0100007F:"
v4Any = "00000000:0000"
)
func listPorts() (List, error) {
if sawProcNetPermissionErr.Get() {
return nil, nil
}
l := []Port{}
for pi, fname := range sockfiles {
proto := protos[pi]
for _, fname := range sockfiles {
proto := strings.TrimSuffix(filepath.Base(fname), "6")
// Android 10+ doesn't allow access to this anymore.
// https://developer.android.com/about/versions/10/privacy/changes#proc-net-filesystem
@ -59,47 +66,12 @@ func listPorts() (List, error) {
defer f.Close()
r := bufio.NewReader(f)
// skip header row
_, err = r.ReadString('\n')
ports, err := parsePorts(r, proto)
if err != nil {
return nil, err
return nil, fmt.Errorf("parsing %q: %w", fname, err)
}
for err == nil {
line, err := r.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
// sl local rem ... inode
words := strings.Fields(line)
local := words[1]
rem := words[2]
inode := words[9]
// If a port is bound to 127.0.0.1, ignore it.
if strings.HasPrefix(local, "0100007F:") {
continue
}
if rem != "00000000:0000" {
// not a "listener" port
continue
}
portv, err := strconv.ParseUint(local[9:], 16, 16)
if err != nil {
return nil, fmt.Errorf("%#v: %s", local[9:], err)
}
inodev := fmt.Sprintf("socket:[%s]", inode)
l = append(l, Port{
Proto: proto,
Port: uint16(portv),
inode: inodev,
})
}
l = append(l, ports...)
}
sort.Slice(l, func(i, j int) bool {
@ -109,6 +81,62 @@ func listPorts() (List, error) {
return l, nil
}
func parsePorts(r *bufio.Reader, proto string) ([]Port, error) {
var ret []Port
// skip header row
_, err := r.ReadString('\n')
if err != nil {
return nil, err
}
for err == nil {
line, err := r.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
// sl local rem ... inode
words := strings.Fields(line)
local := words[1]
rem := words[2]
inode := words[9]
// If a port is bound to localhost, ignore it.
// TODO: localhost is bigger than 1 IP, we need to ignore
// more things.
if strings.HasPrefix(local, v4Localhost) || strings.HasPrefix(local, v6Localhost) {
continue
}
if rem != v4Any && rem != v6Any {
// not a "listener" port
continue
}
// Don't use strings.Split here, because it causes
// allocations significant enough to show up in profiles.
i := strings.IndexByte(local, ':')
if i == -1 {
return nil, fmt.Errorf("%q unexpectedly didn't have a colon", local)
}
portv, err := strconv.ParseUint(local[i+1:], 16, 16)
if err != nil {
return nil, fmt.Errorf("%#v: %s", local[9:], err)
}
inodev := fmt.Sprintf("socket:[%s]", inode)
ret = append(ret, Port{
Proto: proto,
Port: uint16(portv),
inode: inodev,
})
}
return ret, nil
}
func addProcesses(pl []Port) ([]Port, error) {
pm := map[string]*Port{} // by Port.inode
for i := range pl {
@ -158,18 +186,17 @@ func addProcesses(pl []Port) ([]Port, error) {
continue
}
// TODO(apenwarr): use /proc/*/cmdline instead of /comm?
// Unsure right now whether users will want the extra detail
// or not.
pe := pm[string(targetBuf[:n])] // m[string([]byte)] avoids alloc
if pe != nil {
comm, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/comm", pid))
bs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%s/cmdline", pid))
if err != nil {
// Usually shouldn't happen. One possibility is
// the process has gone away, so let's skip it.
continue
}
pe.Process = strings.TrimSpace(string(comm))
argv := strings.Split(strings.TrimSuffix(string(bs), "\x00"), "\x00")
pe.Process = argvSubject(argv...)
}
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package portlist
import (
"bufio"
"bytes"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestParsePorts(t *testing.T) {
tests := []struct {
name string
in string
want []Port
}{
{
name: "empty",
in: "header line (ignored)\n",
want: nil,
},
{
name: "ipv4",
in: `header line
0: 0100007F:0277 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 22303 1 0000000000000000 100 0 0 10 0
1: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 34062 1 0000000000000000 100 0 0 10 0
2: 5501A8C0:ADD4 B25E9536:01BB 01 00000000:00000000 02:00000B2B 00000000 1000 0 155276677 2 0000000000000000 22 4 30 10 -1
`,
want: []Port{
{Proto: "tcp", Port: 22, inode: "socket:[34062]"},
},
},
{
name: "ipv6",
in: ` sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000000000000000000001000000:0277 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 35720 1 0000000000000000 100 0 0 10 0
1: 00000000000000000000000000000000:1F91 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 142240557 1 0000000000000000 100 0 0 10 0
2: 00000000000000000000000000000000:0016 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 34064 1 0000000000000000 100 0 0 10 0
3: 69050120005716BC64906EBE009ECD4D:D506 0047062600000000000000006E171268:01BB 01 00000000:00000000 02:0000009E 00000000 1000 0 151042856 2 0000000000000000 21 4 28 10 -1
`,
want: []Port{
{Proto: "tcp", Port: 8081, inode: "socket:[142240557]"},
{Proto: "tcp", Port: 22, inode: "socket:[34064]"},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
buf := bytes.NewBufferString(test.in)
r := bufio.NewReader(buf)
got, err := parsePorts(r, "tcp")
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(got, test.want, cmp.AllowUnexported(Port{})); diff != "" {
t.Errorf("unexpected parsed ports (-got+want):\n%s", diff)
}
})
}
}

View File

@ -65,3 +65,13 @@ func LocalTCPPortAndToken() (port int, token string, err error) {
}
return localTCPPortAndToken()
}
// PlatformUsesPeerCreds reports whether the current platform uses peer credentials
// to authenticate connections.
func PlatformUsesPeerCreds() bool {
switch runtime.GOOS {
case "linux", "darwin", "freebsd":
return true
}
return false
}

View File

@ -103,21 +103,7 @@ func tailscaledRunningUnderLaunchd() bool {
// socketPermissionsForOS returns the permissions to use for the
// tailscaled.sock.
func socketPermissionsForOS() os.FileMode {
switch runtime.GOOS {
case "linux", "darwin":
// On Linux and Darwin, the ipn/ipnserver package looks at the Unix peer creds
// and only permits read-only actions from non-root users, so we want
// this opened up wider.
//
// TODO(bradfitz): unify this all one in place probably, moving some
// of ipnserver (which does much of the "safe" bits) here. Maybe
// instead of net.Listener, we should return a type that returns
// an identity in addition to a net.Conn? (returning a wrapped net.Conn
// would surprise downstream callers probably)
//
// TODO(bradfitz): if OpenBSD and FreeBSD do the equivalent peercreds
// stuff that's in ipn/ipnserver/conn_ucred.go, they should also
// return 0666 here.
if PlatformUsesPeerCreds() {
return 0666
}
// Otherwise, root only.

View File

@ -31,10 +31,11 @@ import (
// 5: 2020-10-19, implies IncludeIPv6, delta Peers/UserProfiles, supports MagicDNS
// 6: 2020-12-07: means MapResponse.PacketFilter nil means unchanged
// 7: 2020-12-15: FilterRule.SrcIPs accepts CIDRs+ranges, doesn't warn about 0.0.0.0/::
// 8: 2020-12-19: client can receive IPv6 addresses and routes if beta enabled server-side
// 8: 2020-12-19: client can buggily receive IPv6 addresses and routes if beta enabled server-side
// 9: 2020-12-30: client doesn't auto-add implicit search domains from peers; only DNSConfig.Domains
// 10: 2021-01-17: client understands MapResponse.PeerSeenChange
const CurrentMapRequestVersion = 10
// 11: 2021-03-03: client understands IPv6, multiple default routes, and goroutine dumping
const CurrentMapRequestVersion = 11
type StableID string
@ -824,6 +825,10 @@ type Debug struct {
// DisableSubnetsIfPAC controls whether subnet routers should be
// disabled if WPAD is present on the network.
DisableSubnetsIfPAC opt.Bool `json:",omitempty"`
// GoroutineDumpURL, if non-empty, requests that the client do
// a one-time dump of its active goroutines to the given URL.
GoroutineDumpURL string `json:",omitempty"`
}
func (k MachineKey) String() string { return fmt.Sprintf("mkey:%x", k[:]) }

View File

@ -0,0 +1,9 @@
This is a copy of the `tunnel/firewall` package of
https://git.zx2c4.com/wireguard-windows, with some hardcoded filter
rules adjusted to function with Tailscale, rather than
wireguard-windows's process structure.
You should not use this package. It exists as a band-aid while we
figure out how to upstream a more flexible firewall package that does
the fancier things we need, while also supporting wireguard-windows's
goals.

View File

@ -0,0 +1,190 @@
// +build windows
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
import (
"errors"
"net"
"unsafe"
"golang.org/x/sys/windows"
)
type wfpObjectInstaller func(uintptr) error
//
// Fundamental WireGuard specific WFP objects.
//
type baseObjects struct {
provider windows.GUID
filters windows.GUID
}
var wfpSession uintptr
func createWfpSession() (uintptr, error) {
sessionDisplayData, err := createWtFwpmDisplayData0("WireGuard", "WireGuard dynamic session")
if err != nil {
return 0, wrapErr(err)
}
session := wtFwpmSession0{
displayData: *sessionDisplayData,
flags: cFWPM_SESSION_FLAG_DYNAMIC,
txnWaitTimeoutInMSec: windows.INFINITE,
}
sessionHandle := uintptr(0)
err = fwpmEngineOpen0(nil, cRPC_C_AUTHN_WINNT, nil, &session, unsafe.Pointer(&sessionHandle))
if err != nil {
return 0, wrapErr(err)
}
return sessionHandle, nil
}
func registerBaseObjects(session uintptr) (*baseObjects, error) {
bo := &baseObjects{}
var err error
bo.provider, err = windows.GenerateGUID()
if err != nil {
return nil, wrapErr(err)
}
bo.filters, err = windows.GenerateGUID()
if err != nil {
return nil, wrapErr(err)
}
//
// Register provider.
//
{
displayData, err := createWtFwpmDisplayData0("WireGuard", "WireGuard provider")
if err != nil {
return nil, wrapErr(err)
}
provider := wtFwpmProvider0{
providerKey: bo.provider,
displayData: *displayData,
}
err = fwpmProviderAdd0(session, &provider, 0)
if err != nil {
// TODO: cleanup entire call chain of these if failure?
return nil, wrapErr(err)
}
}
//
// Register filters sublayer.
//
{
displayData, err := createWtFwpmDisplayData0("WireGuard filters", "Permissive and blocking filters")
if err != nil {
return nil, wrapErr(err)
}
sublayer := wtFwpmSublayer0{
subLayerKey: bo.filters,
displayData: *displayData,
providerKey: &bo.provider,
weight: ^uint16(0),
}
err = fwpmSubLayerAdd0(session, &sublayer, 0)
if err != nil {
return nil, wrapErr(err)
}
}
return bo, nil
}
func EnableFirewall(luid uint64, doNotRestrict bool, restrictToDNSServers []net.IP) error {
if wfpSession != 0 {
return errors.New("The firewall has already been enabled")
}
session, err := createWfpSession()
if err != nil {
return wrapErr(err)
}
objectInstaller := func(session uintptr) error {
baseObjects, err := registerBaseObjects(session)
if err != nil {
return wrapErr(err)
}
err = permitWireGuardService(session, baseObjects, 15)
if err != nil {
return wrapErr(err)
}
if !doNotRestrict {
err = allowDNS(session, baseObjects)
if err != nil {
return wrapErr(err)
}
err = permitLoopback(session, baseObjects, 13)
if err != nil {
return wrapErr(err)
}
err = permitTunInterface(session, baseObjects, 12, luid)
if err != nil {
return wrapErr(err)
}
err = permitDHCPIPv4(session, baseObjects, 12)
if err != nil {
return wrapErr(err)
}
err = permitDHCPIPv6(session, baseObjects, 12)
if err != nil {
return wrapErr(err)
}
err = permitNdp(session, baseObjects, 12)
if err != nil {
return wrapErr(err)
}
/* TODO: actually evaluate if this does anything and if we need this. It's layer 2; our other rules are layer 3.
* In other words, if somebody complains, try enabling it. For now, keep it off.
err = permitHyperV(session, baseObjects, 12)
if err != nil {
return wrapErr(err)
}
*/
err = blockAll(session, baseObjects, 0)
if err != nil {
return wrapErr(err)
}
}
return nil
}
err = runTransaction(session, objectInstaller)
if err != nil {
fwpmEngineClose0(session)
return wrapErr(err)
}
wfpSession = session
return nil
}
func DisableFirewall() {
if wfpSession != 0 {
fwpmEngineClose0(wfpSession)
wfpSession = 0
}
}

View File

@ -0,0 +1,150 @@
// +build windows
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
import (
"fmt"
"os"
"runtime"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
func runTransaction(session uintptr, operation wfpObjectInstaller) error {
err := fwpmTransactionBegin0(session, 0)
if err != nil {
return wrapErr(err)
}
err = operation(session)
if err != nil {
fwpmTransactionAbort0(session)
return wrapErr(err)
}
err = fwpmTransactionCommit0(session)
if err != nil {
fwpmTransactionAbort0(session)
return wrapErr(err)
}
return nil
}
func createWtFwpmDisplayData0(name, description string) (*wtFwpmDisplayData0, error) {
namePtr, err := windows.UTF16PtrFromString(name)
if err != nil {
return nil, wrapErr(err)
}
descriptionPtr, err := windows.UTF16PtrFromString(description)
if err != nil {
return nil, wrapErr(err)
}
return &wtFwpmDisplayData0{
name: namePtr,
description: descriptionPtr,
}, nil
}
func filterWeight(weight uint8) wtFwpValue0 {
return wtFwpValue0{
_type: cFWP_UINT8,
value: uintptr(weight),
}
}
func wrapErr(err error) error {
if _, ok := err.(syscall.Errno); !ok {
return err
}
_, file, line, ok := runtime.Caller(1)
if !ok {
return fmt.Errorf("Firewall error at unknown location: %w", err)
}
return fmt.Errorf("Firewall error at %s:%d: %w", file, line, err)
}
func getCurrentProcessSecurityDescriptor() (*windows.SECURITY_DESCRIPTOR, error) {
var processToken windows.Token
err := windows.OpenProcessToken(windows.CurrentProcess(), windows.TOKEN_QUERY, &processToken)
if err != nil {
return nil, wrapErr(err)
}
defer processToken.Close()
gs, err := processToken.GetTokenGroups()
if err != nil {
return nil, wrapErr(err)
}
var sid *windows.SID
for _, g := range gs.AllGroups() {
if g.Attributes != windows.SE_GROUP_ENABLED|windows.SE_GROUP_ENABLED_BY_DEFAULT|windows.SE_GROUP_OWNER {
continue
}
// We could be checking != 6, but hopefully Microsoft will update
// RtlCreateServiceSid to use SHA2, which will then likely bump
// this up. So instead just roll with a minimum.
if !g.Sid.IsValid() || g.Sid.IdentifierAuthority() != windows.SECURITY_NT_AUTHORITY || g.Sid.SubAuthorityCount() < 6 || g.Sid.SubAuthority(0) != 80 {
continue
}
sid = g.Sid
break
}
if sid == nil {
return nil, wrapErr(windows.ERROR_NO_SUCH_GROUP)
}
access := []windows.EXPLICIT_ACCESS{{
AccessPermissions: cFWP_ACTRL_MATCH_FILTER,
AccessMode: windows.GRANT_ACCESS,
Trustee: windows.TRUSTEE{
TrusteeForm: windows.TRUSTEE_IS_SID,
TrusteeType: windows.TRUSTEE_IS_GROUP,
TrusteeValue: windows.TrusteeValueFromSID(sid),
},
}}
dacl, err := windows.ACLFromEntries(access, nil)
if err != nil {
return nil, wrapErr(err)
}
sd, err := windows.NewSecurityDescriptor()
if err != nil {
return nil, wrapErr(err)
}
err = sd.SetDACL(dacl, true, false)
if err != nil {
return nil, wrapErr(err)
}
sd, err = sd.ToSelfRelative()
if err != nil {
return nil, wrapErr(err)
}
return sd, nil
}
func getCurrentProcessAppID() (*wtFwpByteBlob, error) {
currentFile, err := os.Executable()
if err != nil {
return nil, wrapErr(err)
}
curFilePtr, err := windows.UTF16PtrFromString(currentFile)
if err != nil {
return nil, wrapErr(err)
}
var appID *wtFwpByteBlob
err = fwpmGetAppIdFromFileName0(curFilePtr, unsafe.Pointer(&appID))
if err != nil {
return nil, wrapErr(err)
}
return appID, nil
}

View File

@ -0,0 +1,8 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmengineopen0
//sys fwpmEngineOpen0(serverName *uint16, authnService wtRpcCAuthN, authIdentity *uintptr, session *wtFwpmSession0, engineHandle unsafe.Pointer) (err error) [failretval!=0] = fwpuclnt.FwpmEngineOpen0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmengineclose0
//sys fwpmEngineClose0(engineHandle uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmEngineClose0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmsublayeradd0
//sys fwpmSubLayerAdd0(engineHandle uintptr, subLayer *wtFwpmSublayer0, sd uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmSubLayerAdd0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmgetappidfromfilename0
//sys fwpmGetAppIdFromFileName0(fileName *uint16, appID unsafe.Pointer) (err error) [failretval!=0] = fwpuclnt.FwpmGetAppIdFromFileName0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmfreememory0
//sys fwpmFreeMemory0(p unsafe.Pointer) = fwpuclnt.FwpmFreeMemory0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmfilteradd0
//sys fwpmFilterAdd0(engineHandle uintptr, filter *wtFwpmFilter0, sd uintptr, id *uint64) (err error) [failretval!=0] = fwpuclnt.FwpmFilterAdd0
// https://docs.microsoft.com/en-us/windows/desktop/api/Fwpmu/nf-fwpmu-fwpmtransactionbegin0
//sys fwpmTransactionBegin0(engineHandle uintptr, flags uint32) (err error) [failretval!=0] = fwpuclnt.FwpmTransactionBegin0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmtransactioncommit0
//sys fwpmTransactionCommit0(engineHandle uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmTransactionCommit0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmtransactionabort0
//sys fwpmTransactionAbort0(engineHandle uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmTransactionAbort0
// https://docs.microsoft.com/en-us/windows/desktop/api/fwpmu/nf-fwpmu-fwpmprovideradd0
//sys fwpmProviderAdd0(engineHandle uintptr, provider *wtFwpmProvider0, sd uintptr) (err error) [failretval!=0] = fwpuclnt.FwpmProviderAdd0

View File

@ -0,0 +1,412 @@
// +build windows
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
import "golang.org/x/sys/windows"
const (
anysizeArray = 1 // ANYSIZE_ARRAY defined in winnt.h
wtFwpBitmapArray64_Size = 8
wtFwpByteArray16_Size = 16
wtFwpByteArray6_Size = 6
wtFwpmAction0_Size = 20
wtFwpmAction0_filterType_Offset = 4
wtFwpV4AddrAndMask_Size = 8
wtFwpV4AddrAndMask_mask_Offset = 4
wtFwpV6AddrAndMask_Size = 17
wtFwpV6AddrAndMask_prefixLength_Offset = 16
)
type wtFwpActionFlag uint32
const (
cFWP_ACTION_FLAG_TERMINATING wtFwpActionFlag = 0x00001000
cFWP_ACTION_FLAG_NON_TERMINATING wtFwpActionFlag = 0x00002000
cFWP_ACTION_FLAG_CALLOUT wtFwpActionFlag = 0x00004000
)
// FWP_ACTION_TYPE defined in fwptypes.h
type wtFwpActionType uint32
const (
cFWP_ACTION_BLOCK wtFwpActionType = wtFwpActionType(0x00000001 | cFWP_ACTION_FLAG_TERMINATING)
cFWP_ACTION_PERMIT wtFwpActionType = wtFwpActionType(0x00000002 | cFWP_ACTION_FLAG_TERMINATING)
cFWP_ACTION_CALLOUT_TERMINATING wtFwpActionType = wtFwpActionType(0x00000003 | cFWP_ACTION_FLAG_CALLOUT | cFWP_ACTION_FLAG_TERMINATING)
cFWP_ACTION_CALLOUT_INSPECTION wtFwpActionType = wtFwpActionType(0x00000004 | cFWP_ACTION_FLAG_CALLOUT | cFWP_ACTION_FLAG_NON_TERMINATING)
cFWP_ACTION_CALLOUT_UNKNOWN wtFwpActionType = wtFwpActionType(0x00000005 | cFWP_ACTION_FLAG_CALLOUT)
cFWP_ACTION_CONTINUE wtFwpActionType = wtFwpActionType(0x00000006 | cFWP_ACTION_FLAG_NON_TERMINATING)
cFWP_ACTION_NONE wtFwpActionType = 0x00000007
cFWP_ACTION_NONE_NO_MATCH wtFwpActionType = 0x00000008
cFWP_ACTION_BITMAP_INDEX_SET wtFwpActionType = 0x00000009
)
// FWP_BYTE_BLOB defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_byte_blob_)
type wtFwpByteBlob struct {
size uint32
data *uint8
}
// FWP_MATCH_TYPE defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ne-fwptypes-fwp_match_type_)
type wtFwpMatchType uint32
const (
cFWP_MATCH_EQUAL wtFwpMatchType = 0
cFWP_MATCH_GREATER wtFwpMatchType = cFWP_MATCH_EQUAL + 1
cFWP_MATCH_LESS wtFwpMatchType = cFWP_MATCH_GREATER + 1
cFWP_MATCH_GREATER_OR_EQUAL wtFwpMatchType = cFWP_MATCH_LESS + 1
cFWP_MATCH_LESS_OR_EQUAL wtFwpMatchType = cFWP_MATCH_GREATER_OR_EQUAL + 1
cFWP_MATCH_RANGE wtFwpMatchType = cFWP_MATCH_LESS_OR_EQUAL + 1
cFWP_MATCH_FLAGS_ALL_SET wtFwpMatchType = cFWP_MATCH_RANGE + 1
cFWP_MATCH_FLAGS_ANY_SET wtFwpMatchType = cFWP_MATCH_FLAGS_ALL_SET + 1
cFWP_MATCH_FLAGS_NONE_SET wtFwpMatchType = cFWP_MATCH_FLAGS_ANY_SET + 1
cFWP_MATCH_EQUAL_CASE_INSENSITIVE wtFwpMatchType = cFWP_MATCH_FLAGS_NONE_SET + 1
cFWP_MATCH_NOT_EQUAL wtFwpMatchType = cFWP_MATCH_EQUAL_CASE_INSENSITIVE + 1
cFWP_MATCH_PREFIX wtFwpMatchType = cFWP_MATCH_NOT_EQUAL + 1
cFWP_MATCH_NOT_PREFIX wtFwpMatchType = cFWP_MATCH_PREFIX + 1
cFWP_MATCH_TYPE_MAX wtFwpMatchType = cFWP_MATCH_NOT_PREFIX + 1
)
// FWPM_ACTION0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_action0_)
type wtFwpmAction0 struct {
_type wtFwpActionType
filterType windows.GUID // Windows type: GUID
}
// Defined in fwpmu.h. 4cd62a49-59c3-4969-b7f3-bda5d32890a4
var cFWPM_CONDITION_IP_LOCAL_INTERFACE = windows.GUID{
Data1: 0x4cd62a49,
Data2: 0x59c3,
Data3: 0x4969,
Data4: [8]byte{0xb7, 0xf3, 0xbd, 0xa5, 0xd3, 0x28, 0x90, 0xa4},
}
// Defined in fwpmu.h. b235ae9a-1d64-49b8-a44c-5ff3d9095045
var cFWPM_CONDITION_IP_REMOTE_ADDRESS = windows.GUID{
Data1: 0xb235ae9a,
Data2: 0x1d64,
Data3: 0x49b8,
Data4: [8]byte{0xa4, 0x4c, 0x5f, 0xf3, 0xd9, 0x09, 0x50, 0x45},
}
// Defined in fwpmu.h. 3971ef2b-623e-4f9a-8cb1-6e79b806b9a7
var cFWPM_CONDITION_IP_PROTOCOL = windows.GUID{
Data1: 0x3971ef2b,
Data2: 0x623e,
Data3: 0x4f9a,
Data4: [8]byte{0x8c, 0xb1, 0x6e, 0x79, 0xb8, 0x06, 0xb9, 0xa7},
}
// Defined in fwpmu.h. 0c1ba1af-5765-453f-af22-a8f791ac775b
var cFWPM_CONDITION_IP_LOCAL_PORT = windows.GUID{
Data1: 0x0c1ba1af,
Data2: 0x5765,
Data3: 0x453f,
Data4: [8]byte{0xaf, 0x22, 0xa8, 0xf7, 0x91, 0xac, 0x77, 0x5b},
}
// Defined in fwpmu.h. c35a604d-d22b-4e1a-91b4-68f674ee674b
var cFWPM_CONDITION_IP_REMOTE_PORT = windows.GUID{
Data1: 0xc35a604d,
Data2: 0xd22b,
Data3: 0x4e1a,
Data4: [8]byte{0x91, 0xb4, 0x68, 0xf6, 0x74, 0xee, 0x67, 0x4b},
}
// Defined in fwpmu.h. d78e1e87-8644-4ea5-9437-d809ecefc971
var cFWPM_CONDITION_ALE_APP_ID = windows.GUID{
Data1: 0xd78e1e87,
Data2: 0x8644,
Data3: 0x4ea5,
Data4: [8]byte{0x94, 0x37, 0xd8, 0x09, 0xec, 0xef, 0xc9, 0x71},
}
// af043a0a-b34d-4f86-979c-c90371af6e66
var cFWPM_CONDITION_ALE_USER_ID = windows.GUID{
Data1: 0xaf043a0a,
Data2: 0xb34d,
Data3: 0x4f86,
Data4: [8]byte{0x97, 0x9c, 0xc9, 0x03, 0x71, 0xaf, 0x6e, 0x66},
}
// d9ee00de-c1ef-4617-bfe3-ffd8f5a08957
var cFWPM_CONDITION_IP_LOCAL_ADDRESS = windows.GUID{
Data1: 0xd9ee00de,
Data2: 0xc1ef,
Data3: 0x4617,
Data4: [8]byte{0xbf, 0xe3, 0xff, 0xd8, 0xf5, 0xa0, 0x89, 0x57},
}
var cFWPM_CONDITION_ICMP_TYPE = cFWPM_CONDITION_IP_LOCAL_PORT
var cFWPM_CONDITION_ICMP_CODE = cFWPM_CONDITION_IP_REMOTE_PORT
// 7bc43cbf-37ba-45f1-b74a-82ff518eeb10
var cFWPM_CONDITION_L2_FLAGS = windows.GUID{
Data1: 0x7bc43cbf,
Data2: 0x37ba,
Data3: 0x45f1,
Data4: [8]byte{0xb7, 0x4a, 0x82, 0xff, 0x51, 0x8e, 0xeb, 0x10},
}
type wtFwpmL2Flags uint32
const cFWP_CONDITION_L2_IS_VM2VM wtFwpmL2Flags = 0x00000010
var cFWPM_CONDITION_FLAGS = windows.GUID{
Data1: 0x632ce23b,
Data2: 0x5167,
Data3: 0x435c,
Data4: [8]byte{0x86, 0xd7, 0xe9, 0x03, 0x68, 0x4a, 0xa8, 0x0c},
}
type wtFwpmFlags uint32
const cFWP_CONDITION_FLAG_IS_LOOPBACK wtFwpmFlags = 0x00000001
// Defined in fwpmtypes.h
type wtFwpmFilterFlags uint32
const (
cFWPM_FILTER_FLAG_NONE wtFwpmFilterFlags = 0x00000000
cFWPM_FILTER_FLAG_PERSISTENT wtFwpmFilterFlags = 0x00000001
cFWPM_FILTER_FLAG_BOOTTIME wtFwpmFilterFlags = 0x00000002
cFWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT wtFwpmFilterFlags = 0x00000004
cFWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT wtFwpmFilterFlags = 0x00000008
cFWPM_FILTER_FLAG_PERMIT_IF_CALLOUT_UNREGISTERED wtFwpmFilterFlags = 0x00000010
cFWPM_FILTER_FLAG_DISABLED wtFwpmFilterFlags = 0x00000020
cFWPM_FILTER_FLAG_INDEXED wtFwpmFilterFlags = 0x00000040
cFWPM_FILTER_FLAG_HAS_SECURITY_REALM_PROVIDER_CONTEXT wtFwpmFilterFlags = 0x00000080
cFWPM_FILTER_FLAG_SYSTEMOS_ONLY wtFwpmFilterFlags = 0x00000100
cFWPM_FILTER_FLAG_GAMEOS_ONLY wtFwpmFilterFlags = 0x00000200
cFWPM_FILTER_FLAG_SILENT_MODE wtFwpmFilterFlags = 0x00000400
cFWPM_FILTER_FLAG_IPSEC_NO_ACQUIRE_INITIATE wtFwpmFilterFlags = 0x00000800
)
// FWPM_LAYER_ALE_AUTH_CONNECT_V4 (c38d57d1-05a7-4c33-904f-7fbceee60e82) defined in fwpmu.h
var cFWPM_LAYER_ALE_AUTH_CONNECT_V4 = windows.GUID{
Data1: 0xc38d57d1,
Data2: 0x05a7,
Data3: 0x4c33,
Data4: [8]byte{0x90, 0x4f, 0x7f, 0xbc, 0xee, 0xe6, 0x0e, 0x82},
}
// e1cd9fe7-f4b5-4273-96c0-592e487b8650
var cFWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 = windows.GUID{
Data1: 0xe1cd9fe7,
Data2: 0xf4b5,
Data3: 0x4273,
Data4: [8]byte{0x96, 0xc0, 0x59, 0x2e, 0x48, 0x7b, 0x86, 0x50},
}
// FWPM_LAYER_ALE_AUTH_CONNECT_V6 (4a72393b-319f-44bc-84c3-ba54dcb3b6b4) defined in fwpmu.h
var cFWPM_LAYER_ALE_AUTH_CONNECT_V6 = windows.GUID{
Data1: 0x4a72393b,
Data2: 0x319f,
Data3: 0x44bc,
Data4: [8]byte{0x84, 0xc3, 0xba, 0x54, 0xdc, 0xb3, 0xb6, 0xb4},
}
// a3b42c97-9f04-4672-b87e-cee9c483257f
var cFWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6 = windows.GUID{
Data1: 0xa3b42c97,
Data2: 0x9f04,
Data3: 0x4672,
Data4: [8]byte{0xb8, 0x7e, 0xce, 0xe9, 0xc4, 0x83, 0x25, 0x7f},
}
// 94c44912-9d6f-4ebf-b995-05ab8a088d1b
var cFWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE = windows.GUID{
Data1: 0x94c44912,
Data2: 0x9d6f,
Data3: 0x4ebf,
Data4: [8]byte{0xb9, 0x95, 0x05, 0xab, 0x8a, 0x08, 0x8d, 0x1b},
}
// d4220bd3-62ce-4f08-ae88-b56e8526df50
var cFWPM_LAYER_INBOUND_MAC_FRAME_NATIVE = windows.GUID{
Data1: 0xd4220bd3,
Data2: 0x62ce,
Data3: 0x4f08,
Data4: [8]byte{0xae, 0x88, 0xb5, 0x6e, 0x85, 0x26, 0xdf, 0x50},
}
// FWP_BITMAP_ARRAY64 defined in fwtypes.h
type wtFwpBitmapArray64 struct {
bitmapArray64 [8]uint8 // Windows type: [8]UINT8
}
// FWP_BYTE_ARRAY6 defined in fwtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_byte_array6_)
type wtFwpByteArray6 struct {
byteArray6 [6]uint8 // Windows type: [6]UINT8
}
// FWP_BYTE_ARRAY16 defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_byte_array16_)
type wtFwpByteArray16 struct {
byteArray16 [16]uint8 // Windows type [16]UINT8
}
// FWP_CONDITION_VALUE0 defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_condition_value0).
type wtFwpConditionValue0 wtFwpValue0
// FWP_DATA_TYPE defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ne-fwptypes-fwp_data_type_)
type wtFwpDataType uint
const (
cFWP_EMPTY wtFwpDataType = 0
cFWP_UINT8 wtFwpDataType = cFWP_EMPTY + 1
cFWP_UINT16 wtFwpDataType = cFWP_UINT8 + 1
cFWP_UINT32 wtFwpDataType = cFWP_UINT16 + 1
cFWP_UINT64 wtFwpDataType = cFWP_UINT32 + 1
cFWP_INT8 wtFwpDataType = cFWP_UINT64 + 1
cFWP_INT16 wtFwpDataType = cFWP_INT8 + 1
cFWP_INT32 wtFwpDataType = cFWP_INT16 + 1
cFWP_INT64 wtFwpDataType = cFWP_INT32 + 1
cFWP_FLOAT wtFwpDataType = cFWP_INT64 + 1
cFWP_DOUBLE wtFwpDataType = cFWP_FLOAT + 1
cFWP_BYTE_ARRAY16_TYPE wtFwpDataType = cFWP_DOUBLE + 1
cFWP_BYTE_BLOB_TYPE wtFwpDataType = cFWP_BYTE_ARRAY16_TYPE + 1
cFWP_SID wtFwpDataType = cFWP_BYTE_BLOB_TYPE + 1
cFWP_SECURITY_DESCRIPTOR_TYPE wtFwpDataType = cFWP_SID + 1
cFWP_TOKEN_INFORMATION_TYPE wtFwpDataType = cFWP_SECURITY_DESCRIPTOR_TYPE + 1
cFWP_TOKEN_ACCESS_INFORMATION_TYPE wtFwpDataType = cFWP_TOKEN_INFORMATION_TYPE + 1
cFWP_UNICODE_STRING_TYPE wtFwpDataType = cFWP_TOKEN_ACCESS_INFORMATION_TYPE + 1
cFWP_BYTE_ARRAY6_TYPE wtFwpDataType = cFWP_UNICODE_STRING_TYPE + 1
cFWP_BITMAP_INDEX_TYPE wtFwpDataType = cFWP_BYTE_ARRAY6_TYPE + 1
cFWP_BITMAP_ARRAY64_TYPE wtFwpDataType = cFWP_BITMAP_INDEX_TYPE + 1
cFWP_SINGLE_DATA_TYPE_MAX wtFwpDataType = 0xff
cFWP_V4_ADDR_MASK wtFwpDataType = cFWP_SINGLE_DATA_TYPE_MAX + 1
cFWP_V6_ADDR_MASK wtFwpDataType = cFWP_V4_ADDR_MASK + 1
cFWP_RANGE_TYPE wtFwpDataType = cFWP_V6_ADDR_MASK + 1
cFWP_DATA_TYPE_MAX wtFwpDataType = cFWP_RANGE_TYPE + 1
)
// FWP_V4_ADDR_AND_MASK defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_v4_addr_and_mask).
type wtFwpV4AddrAndMask struct {
addr uint32
mask uint32
}
// FWP_V6_ADDR_AND_MASK defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_v6_addr_and_mask).
type wtFwpV6AddrAndMask struct {
addr [16]uint8
prefixLength uint8
}
// FWP_VALUE0 defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwp_value0_)
type wtFwpValue0 struct {
_type wtFwpDataType
value uintptr
}
// FWPM_DISPLAY_DATA0 defined in fwptypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwptypes/ns-fwptypes-fwpm_display_data0).
type wtFwpmDisplayData0 struct {
name *uint16 // Windows type: *wchar_t
description *uint16 // Windows type: *wchar_t
}
// FWPM_FILTER_CONDITION0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_filter_condition0).
type wtFwpmFilterCondition0 struct {
fieldKey windows.GUID // Windows type: GUID
matchType wtFwpMatchType
conditionValue wtFwpConditionValue0
}
// FWPM_PROVIDER0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_provider0_)
type wtFwpProvider0 struct {
providerKey windows.GUID // Windows type: GUID
displayData wtFwpmDisplayData0
flags uint32
providerData wtFwpByteBlob
serviceName *uint16 // Windows type: *wchar_t
}
type wtFwpmSessionFlagsValue uint32
const (
cFWPM_SESSION_FLAG_DYNAMIC wtFwpmSessionFlagsValue = 0x00000001 // FWPM_SESSION_FLAG_DYNAMIC defined in fwpmtypes.h
)
// FWPM_SESSION0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_session0).
type wtFwpmSession0 struct {
sessionKey windows.GUID // Windows type: GUID
displayData wtFwpmDisplayData0
flags wtFwpmSessionFlagsValue // Windows type UINT32
txnWaitTimeoutInMSec uint32
processId uint32 // Windows type: DWORD
sid *windows.SID
username *uint16 // Windows type: *wchar_t
kernelMode uint8 // Windows type: BOOL
}
type wtFwpmSublayerFlags uint32
const (
cFWPM_SUBLAYER_FLAG_PERSISTENT wtFwpmSublayerFlags = 0x00000001 // FWPM_SUBLAYER_FLAG_PERSISTENT defined in fwpmtypes.h
)
// FWPM_SUBLAYER0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_sublayer0_)
type wtFwpmSublayer0 struct {
subLayerKey windows.GUID // Windows type: GUID
displayData wtFwpmDisplayData0
flags wtFwpmSublayerFlags
providerKey *windows.GUID // Windows type: *GUID
providerData wtFwpByteBlob
weight uint16
}
// Defined in rpcdce.h
type wtRpcCAuthN uint32
const (
cRPC_C_AUTHN_NONE wtRpcCAuthN = 0
cRPC_C_AUTHN_WINNT wtRpcCAuthN = 10
cRPC_C_AUTHN_DEFAULT wtRpcCAuthN = 0xFFFFFFFF
)
// FWPM_PROVIDER0 defined in fwpmtypes.h
// (https://docs.microsoft.com/sv-se/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_provider0).
type wtFwpmProvider0 struct {
providerKey windows.GUID
displayData wtFwpmDisplayData0
flags uint32
providerData wtFwpByteBlob
serviceName *uint16
}
type wtIPProto uint32
const (
cIPPROTO_ICMP wtIPProto = 1
cIPPROTO_ICMPV6 wtIPProto = 58
cIPPROTO_TCP wtIPProto = 6
cIPPROTO_UDP wtIPProto = 17
)
const (
cFWP_ACTRL_MATCH_FILTER = 1
)

View File

@ -0,0 +1,90 @@
// +build windows,386 windows,arm
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
import "golang.org/x/sys/windows"
const (
wtFwpByteBlob_Size = 8
wtFwpByteBlob_data_Offset = 4
wtFwpConditionValue0_Size = 8
wtFwpConditionValue0_uint8_Offset = 4
wtFwpmDisplayData0_Size = 8
wtFwpmDisplayData0_description_Offset = 4
wtFwpmFilter0_Size = 152
wtFwpmFilter0_displayData_Offset = 16
wtFwpmFilter0_flags_Offset = 24
wtFwpmFilter0_providerKey_Offset = 28
wtFwpmFilter0_providerData_Offset = 32
wtFwpmFilter0_layerKey_Offset = 40
wtFwpmFilter0_subLayerKey_Offset = 56
wtFwpmFilter0_weight_Offset = 72
wtFwpmFilter0_numFilterConditions_Offset = 80
wtFwpmFilter0_filterCondition_Offset = 84
wtFwpmFilter0_action_Offset = 88
wtFwpmFilter0_providerContextKey_Offset = 112
wtFwpmFilter0_reserved_Offset = 128
wtFwpmFilter0_filterID_Offset = 136
wtFwpmFilter0_effectiveWeight_Offset = 144
wtFwpmFilterCondition0_Size = 28
wtFwpmFilterCondition0_matchType_Offset = 16
wtFwpmFilterCondition0_conditionValue_Offset = 20
wtFwpmSession0_Size = 48
wtFwpmSession0_displayData_Offset = 16
wtFwpmSession0_flags_Offset = 24
wtFwpmSession0_txnWaitTimeoutInMSec_Offset = 28
wtFwpmSession0_processId_Offset = 32
wtFwpmSession0_sid_Offset = 36
wtFwpmSession0_username_Offset = 40
wtFwpmSession0_kernelMode_Offset = 44
wtFwpmSublayer0_Size = 44
wtFwpmSublayer0_displayData_Offset = 16
wtFwpmSublayer0_flags_Offset = 24
wtFwpmSublayer0_providerKey_Offset = 28
wtFwpmSublayer0_providerData_Offset = 32
wtFwpmSublayer0_weight_Offset = 40
wtFwpProvider0_Size = 40
wtFwpProvider0_displayData_Offset = 16
wtFwpProvider0_flags_Offset = 24
wtFwpProvider0_providerData_Offset = 28
wtFwpProvider0_serviceName_Offset = 36
wtFwpTokenInformation_Size = 16
wtFwpValue0_Size = 8
wtFwpValue0_value_Offset = 4
)
// FWPM_FILTER0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_filter0).
type wtFwpmFilter0 struct {
filterKey windows.GUID // Windows type: GUID
displayData wtFwpmDisplayData0
flags wtFwpmFilterFlags
providerKey *windows.GUID // Windows type: *GUID
providerData wtFwpByteBlob
layerKey windows.GUID // Windows type: GUID
subLayerKey windows.GUID // Windows type: GUID
weight wtFwpValue0
numFilterConditions uint32
filterCondition *wtFwpmFilterCondition0
action wtFwpmAction0
offset1 [4]byte // Layout correction field
providerContextKey windows.GUID // Windows type: GUID
reserved *windows.GUID // Windows type: *GUID
offset2 [4]byte // Layout correction field
filterID uint64
effectiveWeight wtFwpValue0
}

View File

@ -0,0 +1,87 @@
// +build windows,amd64 windows,arm64
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
import "golang.org/x/sys/windows"
const (
wtFwpByteBlob_Size = 16
wtFwpByteBlob_data_Offset = 8
wtFwpConditionValue0_Size = 16
wtFwpConditionValue0_uint8_Offset = 8
wtFwpmDisplayData0_Size = 16
wtFwpmDisplayData0_description_Offset = 8
wtFwpmFilter0_Size = 200
wtFwpmFilter0_displayData_Offset = 16
wtFwpmFilter0_flags_Offset = 32
wtFwpmFilter0_providerKey_Offset = 40
wtFwpmFilter0_providerData_Offset = 48
wtFwpmFilter0_layerKey_Offset = 64
wtFwpmFilter0_subLayerKey_Offset = 80
wtFwpmFilter0_weight_Offset = 96
wtFwpmFilter0_numFilterConditions_Offset = 112
wtFwpmFilter0_filterCondition_Offset = 120
wtFwpmFilter0_action_Offset = 128
wtFwpmFilter0_providerContextKey_Offset = 152
wtFwpmFilter0_reserved_Offset = 168
wtFwpmFilter0_filterID_Offset = 176
wtFwpmFilter0_effectiveWeight_Offset = 184
wtFwpmFilterCondition0_Size = 40
wtFwpmFilterCondition0_matchType_Offset = 16
wtFwpmFilterCondition0_conditionValue_Offset = 24
wtFwpmSession0_Size = 72
wtFwpmSession0_displayData_Offset = 16
wtFwpmSession0_flags_Offset = 32
wtFwpmSession0_txnWaitTimeoutInMSec_Offset = 36
wtFwpmSession0_processId_Offset = 40
wtFwpmSession0_sid_Offset = 48
wtFwpmSession0_username_Offset = 56
wtFwpmSession0_kernelMode_Offset = 64
wtFwpmSublayer0_Size = 72
wtFwpmSublayer0_displayData_Offset = 16
wtFwpmSublayer0_flags_Offset = 32
wtFwpmSublayer0_providerKey_Offset = 40
wtFwpmSublayer0_providerData_Offset = 48
wtFwpmSublayer0_weight_Offset = 64
wtFwpProvider0_Size = 64
wtFwpProvider0_displayData_Offset = 16
wtFwpProvider0_flags_Offset = 32
wtFwpProvider0_providerData_Offset = 40
wtFwpProvider0_serviceName_Offset = 56
wtFwpValue0_Size = 16
wtFwpValue0_value_Offset = 8
)
// FWPM_FILTER0 defined in fwpmtypes.h
// (https://docs.microsoft.com/en-us/windows/desktop/api/fwpmtypes/ns-fwpmtypes-fwpm_filter0).
type wtFwpmFilter0 struct {
filterKey windows.GUID // Windows type: GUID
displayData wtFwpmDisplayData0
flags wtFwpmFilterFlags // Windows type: UINT32
providerKey *windows.GUID // Windows type: *GUID
providerData wtFwpByteBlob
layerKey windows.GUID // Windows type: GUID
subLayerKey windows.GUID // Windows type: GUID
weight wtFwpValue0
numFilterConditions uint32
filterCondition *wtFwpmFilterCondition0
action wtFwpmAction0
offset1 [4]byte // Layout correction field
providerContextKey windows.GUID // Windows type: GUID
reserved *windows.GUID // Windows type: *GUID
filterID uint64
effectiveWeight wtFwpValue0
}

View File

@ -0,0 +1,540 @@
// +build windows
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2019-2021 WireGuard LLC. All Rights Reserved.
*/
package firewall
import (
"testing"
"unsafe"
)
func TestWtFwpByteBlobSize(t *testing.T) {
const actualWtFwpByteBlobSize = unsafe.Sizeof(wtFwpByteBlob{})
if actualWtFwpByteBlobSize != wtFwpByteBlob_Size {
t.Errorf("Size of FwpByteBlob is %d, although %d is expected.", actualWtFwpByteBlobSize,
wtFwpByteBlob_Size)
}
}
func TestWtFwpByteBlobOffsets(t *testing.T) {
s := wtFwpByteBlob{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.data)) - sp
if offset != wtFwpByteBlob_data_Offset {
t.Errorf("FwpByteBlob.data offset is %d although %d is expected", offset, wtFwpByteBlob_data_Offset)
return
}
}
func TestWtFwpmAction0Size(t *testing.T) {
const actualWtFwpmAction0Size = unsafe.Sizeof(wtFwpmAction0{})
if actualWtFwpmAction0Size != wtFwpmAction0_Size {
t.Errorf("Size of wtFwpmAction0 is %d, although %d is expected.", actualWtFwpmAction0Size,
wtFwpmAction0_Size)
}
}
func TestWtFwpmAction0Offsets(t *testing.T) {
s := wtFwpmAction0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.filterType)) - sp
if offset != wtFwpmAction0_filterType_Offset {
t.Errorf("wtFwpmAction0.filterType offset is %d although %d is expected", offset,
wtFwpmAction0_filterType_Offset)
return
}
}
func TestWtFwpBitmapArray64Size(t *testing.T) {
const actualWtFwpBitmapArray64Size = unsafe.Sizeof(wtFwpBitmapArray64{})
if actualWtFwpBitmapArray64Size != wtFwpBitmapArray64_Size {
t.Errorf("Size of wtFwpBitmapArray64 is %d, although %d is expected.", actualWtFwpBitmapArray64Size,
wtFwpBitmapArray64_Size)
}
}
func TestWtFwpByteArray6Size(t *testing.T) {
const actualWtFwpByteArray6Size = unsafe.Sizeof(wtFwpByteArray6{})
if actualWtFwpByteArray6Size != wtFwpByteArray6_Size {
t.Errorf("Size of wtFwpByteArray6 is %d, although %d is expected.", actualWtFwpByteArray6Size,
wtFwpByteArray6_Size)
}
}
func TestWtFwpByteArray16Size(t *testing.T) {
const actualWtFwpByteArray16Size = unsafe.Sizeof(wtFwpByteArray16{})
if actualWtFwpByteArray16Size != wtFwpByteArray16_Size {
t.Errorf("Size of wtFwpByteArray16 is %d, although %d is expected.", actualWtFwpByteArray16Size,
wtFwpByteArray16_Size)
}
}
func TestWtFwpConditionValue0Size(t *testing.T) {
const actualWtFwpConditionValue0Size = unsafe.Sizeof(wtFwpConditionValue0{})
if actualWtFwpConditionValue0Size != wtFwpConditionValue0_Size {
t.Errorf("Size of wtFwpConditionValue0 is %d, although %d is expected.", actualWtFwpConditionValue0Size,
wtFwpConditionValue0_Size)
}
}
func TestWtFwpConditionValue0Offsets(t *testing.T) {
s := wtFwpConditionValue0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.value)) - sp
if offset != wtFwpConditionValue0_uint8_Offset {
t.Errorf("wtFwpConditionValue0.value offset is %d although %d is expected", offset, wtFwpConditionValue0_uint8_Offset)
return
}
}
func TestWtFwpV4AddrAndMaskSize(t *testing.T) {
const actualWtFwpV4AddrAndMaskSize = unsafe.Sizeof(wtFwpV4AddrAndMask{})
if actualWtFwpV4AddrAndMaskSize != wtFwpV4AddrAndMask_Size {
t.Errorf("Size of wtFwpV4AddrAndMask is %d, although %d is expected.", actualWtFwpV4AddrAndMaskSize,
wtFwpV4AddrAndMask_Size)
}
}
func TestWtFwpV4AddrAndMaskOffsets(t *testing.T) {
s := wtFwpV4AddrAndMask{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.mask)) - sp
if offset != wtFwpV4AddrAndMask_mask_Offset {
t.Errorf("wtFwpV4AddrAndMask.mask offset is %d although %d is expected", offset,
wtFwpV4AddrAndMask_mask_Offset)
return
}
}
func TestWtFwpV6AddrAndMaskSize(t *testing.T) {
const actualWtFwpV6AddrAndMaskSize = unsafe.Sizeof(wtFwpV6AddrAndMask{})
if actualWtFwpV6AddrAndMaskSize != wtFwpV6AddrAndMask_Size {
t.Errorf("Size of wtFwpV6AddrAndMask is %d, although %d is expected.", actualWtFwpV6AddrAndMaskSize,
wtFwpV6AddrAndMask_Size)
}
}
func TestWtFwpV6AddrAndMaskOffsets(t *testing.T) {
s := wtFwpV6AddrAndMask{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.prefixLength)) - sp
if offset != wtFwpV6AddrAndMask_prefixLength_Offset {
t.Errorf("wtFwpV6AddrAndMask.prefixLength offset is %d although %d is expected", offset,
wtFwpV6AddrAndMask_prefixLength_Offset)
return
}
}
func TestWtFwpValue0Size(t *testing.T) {
const actualWtFwpValue0Size = unsafe.Sizeof(wtFwpValue0{})
if actualWtFwpValue0Size != wtFwpValue0_Size {
t.Errorf("Size of wtFwpValue0 is %d, although %d is expected.", actualWtFwpValue0Size, wtFwpValue0_Size)
}
}
func TestWtFwpValue0Offsets(t *testing.T) {
s := wtFwpValue0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.value)) - sp
if offset != wtFwpValue0_value_Offset {
t.Errorf("wtFwpValue0.value offset is %d although %d is expected", offset, wtFwpValue0_value_Offset)
return
}
}
func TestWtFwpmDisplayData0Size(t *testing.T) {
const actualWtFwpmDisplayData0Size = unsafe.Sizeof(wtFwpmDisplayData0{})
if actualWtFwpmDisplayData0Size != wtFwpmDisplayData0_Size {
t.Errorf("Size of wtFwpmDisplayData0 is %d, although %d is expected.", actualWtFwpmDisplayData0Size,
wtFwpmDisplayData0_Size)
}
}
func TestWtFwpmDisplayData0Offsets(t *testing.T) {
s := wtFwpmDisplayData0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.description)) - sp
if offset != wtFwpmDisplayData0_description_Offset {
t.Errorf("wtFwpmDisplayData0.description offset is %d although %d is expected", offset,
wtFwpmDisplayData0_description_Offset)
return
}
}
func TestWtFwpmFilterCondition0Size(t *testing.T) {
const actualWtFwpmFilterCondition0Size = unsafe.Sizeof(wtFwpmFilterCondition0{})
if actualWtFwpmFilterCondition0Size != wtFwpmFilterCondition0_Size {
t.Errorf("Size of wtFwpmFilterCondition0 is %d, although %d is expected.",
actualWtFwpmFilterCondition0Size, wtFwpmFilterCondition0_Size)
}
}
func TestWtFwpmFilterCondition0Offsets(t *testing.T) {
s := wtFwpmFilterCondition0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.matchType)) - sp
if offset != wtFwpmFilterCondition0_matchType_Offset {
t.Errorf("wtFwpmFilterCondition0.matchType offset is %d although %d is expected", offset,
wtFwpmFilterCondition0_matchType_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.conditionValue)) - sp
if offset != wtFwpmFilterCondition0_conditionValue_Offset {
t.Errorf("wtFwpmFilterCondition0.conditionValue offset is %d although %d is expected", offset,
wtFwpmFilterCondition0_conditionValue_Offset)
return
}
}
func TestWtFwpmFilter0Size(t *testing.T) {
const actualWtFwpmFilter0Size = unsafe.Sizeof(wtFwpmFilter0{})
if actualWtFwpmFilter0Size != wtFwpmFilter0_Size {
t.Errorf("Size of wtFwpmFilter0 is %d, although %d is expected.", actualWtFwpmFilter0Size,
wtFwpmFilter0_Size)
}
}
func TestWtFwpmFilter0Offsets(t *testing.T) {
s := wtFwpmFilter0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.displayData)) - sp
if offset != wtFwpmFilter0_displayData_Offset {
t.Errorf("wtFwpmFilter0.displayData offset is %d although %d is expected", offset,
wtFwpmFilter0_displayData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.flags)) - sp
if offset != wtFwpmFilter0_flags_Offset {
t.Errorf("wtFwpmFilter0.flags offset is %d although %d is expected", offset, wtFwpmFilter0_flags_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.providerKey)) - sp
if offset != wtFwpmFilter0_providerKey_Offset {
t.Errorf("wtFwpmFilter0.providerKey offset is %d although %d is expected", offset,
wtFwpmFilter0_providerKey_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.providerData)) - sp
if offset != wtFwpmFilter0_providerData_Offset {
t.Errorf("wtFwpmFilter0.providerData offset is %d although %d is expected", offset,
wtFwpmFilter0_providerData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.layerKey)) - sp
if offset != wtFwpmFilter0_layerKey_Offset {
t.Errorf("wtFwpmFilter0.layerKey offset is %d although %d is expected", offset,
wtFwpmFilter0_layerKey_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.subLayerKey)) - sp
if offset != wtFwpmFilter0_subLayerKey_Offset {
t.Errorf("wtFwpmFilter0.subLayerKey offset is %d although %d is expected", offset,
wtFwpmFilter0_subLayerKey_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.weight)) - sp
if offset != wtFwpmFilter0_weight_Offset {
t.Errorf("wtFwpmFilter0.weight offset is %d although %d is expected", offset,
wtFwpmFilter0_weight_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.numFilterConditions)) - sp
if offset != wtFwpmFilter0_numFilterConditions_Offset {
t.Errorf("wtFwpmFilter0.numFilterConditions offset is %d although %d is expected", offset,
wtFwpmFilter0_numFilterConditions_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.filterCondition)) - sp
if offset != wtFwpmFilter0_filterCondition_Offset {
t.Errorf("wtFwpmFilter0.filterCondition offset is %d although %d is expected", offset,
wtFwpmFilter0_filterCondition_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.action)) - sp
if offset != wtFwpmFilter0_action_Offset {
t.Errorf("wtFwpmFilter0.action offset is %d although %d is expected", offset,
wtFwpmFilter0_action_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.providerContextKey)) - sp
if offset != wtFwpmFilter0_providerContextKey_Offset {
t.Errorf("wtFwpmFilter0.providerContextKey offset is %d although %d is expected", offset,
wtFwpmFilter0_providerContextKey_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.reserved)) - sp
if offset != wtFwpmFilter0_reserved_Offset {
t.Errorf("wtFwpmFilter0.reserved offset is %d although %d is expected", offset,
wtFwpmFilter0_reserved_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.filterID)) - sp
if offset != wtFwpmFilter0_filterID_Offset {
t.Errorf("wtFwpmFilter0.filterID offset is %d although %d is expected", offset,
wtFwpmFilter0_filterID_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.effectiveWeight)) - sp
if offset != wtFwpmFilter0_effectiveWeight_Offset {
t.Errorf("wtFwpmFilter0.effectiveWeight offset is %d although %d is expected", offset,
wtFwpmFilter0_effectiveWeight_Offset)
return
}
}
func TestWtFwpProvider0Size(t *testing.T) {
const actualWtFwpProvider0Size = unsafe.Sizeof(wtFwpProvider0{})
if actualWtFwpProvider0Size != wtFwpProvider0_Size {
t.Errorf("Size of wtFwpProvider0 is %d, although %d is expected.", actualWtFwpProvider0Size,
wtFwpProvider0_Size)
}
}
func TestWtFwpProvider0Offsets(t *testing.T) {
s := wtFwpProvider0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.displayData)) - sp
if offset != wtFwpProvider0_displayData_Offset {
t.Errorf("wtFwpProvider0.displayData offset is %d although %d is expected", offset,
wtFwpProvider0_displayData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.flags)) - sp
if offset != wtFwpProvider0_flags_Offset {
t.Errorf("wtFwpProvider0.flags offset is %d although %d is expected", offset,
wtFwpProvider0_flags_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.providerData)) - sp
if offset != wtFwpProvider0_providerData_Offset {
t.Errorf("wtFwpProvider0.providerData offset is %d although %d is expected", offset,
wtFwpProvider0_providerData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.serviceName)) - sp
if offset != wtFwpProvider0_serviceName_Offset {
t.Errorf("wtFwpProvider0.serviceName offset is %d although %d is expected", offset,
wtFwpProvider0_serviceName_Offset)
return
}
}
func TestWtFwpmSession0Size(t *testing.T) {
const actualWtFwpmSession0Size = unsafe.Sizeof(wtFwpmSession0{})
if actualWtFwpmSession0Size != wtFwpmSession0_Size {
t.Errorf("Size of wtFwpmSession0 is %d, although %d is expected.", actualWtFwpmSession0Size,
wtFwpmSession0_Size)
}
}
func TestWtFwpmSession0Offsets(t *testing.T) {
s := wtFwpmSession0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.displayData)) - sp
if offset != wtFwpmSession0_displayData_Offset {
t.Errorf("wtFwpmSession0.displayData offset is %d although %d is expected", offset,
wtFwpmSession0_displayData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.flags)) - sp
if offset != wtFwpmSession0_flags_Offset {
t.Errorf("wtFwpmSession0.flags offset is %d although %d is expected", offset, wtFwpmSession0_flags_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.txnWaitTimeoutInMSec)) - sp
if offset != wtFwpmSession0_txnWaitTimeoutInMSec_Offset {
t.Errorf("wtFwpmSession0.txnWaitTimeoutInMSec offset is %d although %d is expected", offset,
wtFwpmSession0_txnWaitTimeoutInMSec_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.processId)) - sp
if offset != wtFwpmSession0_processId_Offset {
t.Errorf("wtFwpmSession0.processId offset is %d although %d is expected", offset,
wtFwpmSession0_processId_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.sid)) - sp
if offset != wtFwpmSession0_sid_Offset {
t.Errorf("wtFwpmSession0.sid offset is %d although %d is expected", offset, wtFwpmSession0_sid_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.username)) - sp
if offset != wtFwpmSession0_username_Offset {
t.Errorf("wtFwpmSession0.username offset is %d although %d is expected", offset,
wtFwpmSession0_username_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.kernelMode)) - sp
if offset != wtFwpmSession0_kernelMode_Offset {
t.Errorf("wtFwpmSession0.kernelMode offset is %d although %d is expected", offset,
wtFwpmSession0_kernelMode_Offset)
return
}
}
func TestWtFwpmSublayer0Size(t *testing.T) {
const actualWtFwpmSublayer0Size = unsafe.Sizeof(wtFwpmSublayer0{})
if actualWtFwpmSublayer0Size != wtFwpmSublayer0_Size {
t.Errorf("Size of wtFwpmSublayer0 is %d, although %d is expected.", actualWtFwpmSublayer0Size,
wtFwpmSublayer0_Size)
}
}
func TestWtFwpmSublayer0Offsets(t *testing.T) {
s := wtFwpmSublayer0{}
sp := uintptr(unsafe.Pointer(&s))
offset := uintptr(unsafe.Pointer(&s.displayData)) - sp
if offset != wtFwpmSublayer0_displayData_Offset {
t.Errorf("wtFwpmSublayer0.displayData offset is %d although %d is expected", offset,
wtFwpmSublayer0_displayData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.flags)) - sp
if offset != wtFwpmSublayer0_flags_Offset {
t.Errorf("wtFwpmSublayer0.flags offset is %d although %d is expected", offset,
wtFwpmSublayer0_flags_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.providerKey)) - sp
if offset != wtFwpmSublayer0_providerKey_Offset {
t.Errorf("wtFwpmSublayer0.providerKey offset is %d although %d is expected", offset,
wtFwpmSublayer0_providerKey_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.providerData)) - sp
if offset != wtFwpmSublayer0_providerData_Offset {
t.Errorf("wtFwpmSublayer0.providerData offset is %d although %d is expected", offset,
wtFwpmSublayer0_providerData_Offset)
return
}
offset = uintptr(unsafe.Pointer(&s.weight)) - sp
if offset != wtFwpmSublayer0_weight_Offset {
t.Errorf("wtFwpmSublayer0.weight offset is %d although %d is expected", offset,
wtFwpmSublayer0_weight_Offset)
return
}
}

View File

@ -0,0 +1,132 @@
// +build windows
// Code generated by 'go generate'; DO NOT EDIT.
package firewall
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modfwpuclnt = windows.NewLazySystemDLL("fwpuclnt.dll")
procFwpmEngineClose0 = modfwpuclnt.NewProc("FwpmEngineClose0")
procFwpmEngineOpen0 = modfwpuclnt.NewProc("FwpmEngineOpen0")
procFwpmFilterAdd0 = modfwpuclnt.NewProc("FwpmFilterAdd0")
procFwpmFreeMemory0 = modfwpuclnt.NewProc("FwpmFreeMemory0")
procFwpmGetAppIdFromFileName0 = modfwpuclnt.NewProc("FwpmGetAppIdFromFileName0")
procFwpmProviderAdd0 = modfwpuclnt.NewProc("FwpmProviderAdd0")
procFwpmSubLayerAdd0 = modfwpuclnt.NewProc("FwpmSubLayerAdd0")
procFwpmTransactionAbort0 = modfwpuclnt.NewProc("FwpmTransactionAbort0")
procFwpmTransactionBegin0 = modfwpuclnt.NewProc("FwpmTransactionBegin0")
procFwpmTransactionCommit0 = modfwpuclnt.NewProc("FwpmTransactionCommit0")
)
func fwpmEngineClose0(engineHandle uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmEngineClose0.Addr(), 1, uintptr(engineHandle), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmEngineOpen0(serverName *uint16, authnService wtRpcCAuthN, authIdentity *uintptr, session *wtFwpmSession0, engineHandle unsafe.Pointer) (err error) {
r1, _, e1 := syscall.Syscall6(procFwpmEngineOpen0.Addr(), 5, uintptr(unsafe.Pointer(serverName)), uintptr(authnService), uintptr(unsafe.Pointer(authIdentity)), uintptr(unsafe.Pointer(session)), uintptr(engineHandle), 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmFilterAdd0(engineHandle uintptr, filter *wtFwpmFilter0, sd uintptr, id *uint64) (err error) {
r1, _, e1 := syscall.Syscall6(procFwpmFilterAdd0.Addr(), 4, uintptr(engineHandle), uintptr(unsafe.Pointer(filter)), uintptr(sd), uintptr(unsafe.Pointer(id)), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmFreeMemory0(p unsafe.Pointer) {
syscall.Syscall(procFwpmFreeMemory0.Addr(), 1, uintptr(p), 0, 0)
return
}
func fwpmGetAppIdFromFileName0(fileName *uint16, appID unsafe.Pointer) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmGetAppIdFromFileName0.Addr(), 2, uintptr(unsafe.Pointer(fileName)), uintptr(appID), 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmProviderAdd0(engineHandle uintptr, provider *wtFwpmProvider0, sd uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmProviderAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(provider)), uintptr(sd))
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmSubLayerAdd0(engineHandle uintptr, subLayer *wtFwpmSublayer0, sd uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmSubLayerAdd0.Addr(), 3, uintptr(engineHandle), uintptr(unsafe.Pointer(subLayer)), uintptr(sd))
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmTransactionAbort0(engineHandle uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmTransactionAbort0.Addr(), 1, uintptr(engineHandle), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmTransactionBegin0(engineHandle uintptr, flags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmTransactionBegin0.Addr(), 2, uintptr(engineHandle), uintptr(flags), 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func fwpmTransactionCommit0(engineHandle uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procFwpmTransactionCommit0.Addr(), 1, uintptr(engineHandle), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@ -10,7 +10,7 @@ package version
// Long is a full version number for this build, of the form
// "x.y.z-commithash", or "date.yyyymmdd" if no actual version was
// provided.
const Long = "date.20210226"
const Long = "date.20210303"
// Short is a short version number for this build, of the form
// "x.y.z", or "date.yyyymmdd" if no actual version was provided.

View File

@ -1015,7 +1015,6 @@ func (c *Conn) determineEndpoints(ctx context.Context) (ipPorts []string, reason
}
if ext, err := c.portMapper.CreateOrGetMapping(ctx); err == nil {
c.logf("portmapper: using %v", ext)
addAddr(ext.String(), "portmap")
} else if !portmapper.IsNoMappingError(err) {
c.logf("portmapper: %v", err)
@ -1056,8 +1055,8 @@ func (c *Conn) determineEndpoints(ctx context.Context) (ipPorts []string, reason
ips = loopback
reason = "loopback"
}
for _, ipStr := range ips {
addAddr(net.JoinHostPort(ipStr, fmt.Sprint(localAddr.Port)), reason)
for _, ip := range ips {
addAddr(netaddr.IPPort{IP: ip, Port: uint16(localAddr.Port)}.String(), reason)
}
} else {
// Our local endpoint is bound to a particular address.
@ -1412,7 +1411,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
peerPresent := map[key.Public]bool{}
bo := backoff.NewBackoff(fmt.Sprintf("derp-%d", regionID), c.logf, 5*time.Second)
for {
msg, err := dc.Recv()
msg, connGen, err := dc.RecvDetail()
if err != nil {
// Forget that all these peers have routes.
for peer := range peerPresent {
@ -1450,6 +1449,9 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d
bo.BackOff(ctx, nil) // reset
switch m := msg.(type) {
case derp.ServerInfoMessage:
c.logf("magicsock: derp-%d connected; connGen=%v", regionID, connGen)
continue
case derp.ReceivedPacket:
pkt = m
res.n = len(m.Data)

View File

@ -8,9 +8,11 @@
package monitor
import (
"errors"
"sync"
"time"
"tailscale.com/net/interfaces"
"tailscale.com/types/logger"
)
@ -32,9 +34,11 @@ type osMon interface {
Receive() (message, error)
}
// ChangeFunc is a callback function that's called when
// an interface status changes.
type ChangeFunc func()
// ChangeFunc is a callback function that's called when the network
// changed. The changed parameter is whether the network changed
// enough for interfaces.State to have changed since the last
// callback.
type ChangeFunc func(changed bool, state *interfaces.State)
// An allocated callbackHandle's address is the Mon.cbs map key.
type callbackHandle byte
@ -46,8 +50,9 @@ type Mon struct {
change chan struct{}
stop chan struct{}
mu sync.Mutex // guards cbs
cbs map[*callbackHandle]ChangeFunc
mu sync.Mutex // guards cbs
cbs map[*callbackHandle]ChangeFunc
ifState *interfaces.State
onceStart sync.Once
started bool
@ -59,17 +64,43 @@ type Mon struct {
// Use RegisterChangeCallback to get notified of network changes.
func New(logf logger.Logf) (*Mon, error) {
logf = logger.WithPrefix(logf, "monitor: ")
om, err := newOSMon(logf)
m := &Mon{
logf: logf,
cbs: map[*callbackHandle]ChangeFunc{},
change: make(chan struct{}, 1),
stop: make(chan struct{}),
}
st, err := m.interfaceStateUncached()
if err != nil {
return nil, err
}
return &Mon{
logf: logf,
cbs: map[*callbackHandle]ChangeFunc{},
om: om,
change: make(chan struct{}, 1),
stop: make(chan struct{}),
}, nil
m.ifState = st
m.om, err = newOSMon(logf, m)
if err != nil {
return nil, err
}
if m.om == nil {
return nil, errors.New("newOSMon returned nil, nil")
}
return m, nil
}
// InterfaceState returns the state of the machine's network interfaces,
// without any Tailscale ones.
func (m *Mon) InterfaceState() *interfaces.State {
m.mu.Lock()
defer m.mu.Unlock()
return m.ifState
}
func (m *Mon) interfaceStateUncached() (*interfaces.State, error) {
s, err := interfaces.GetState()
if s != nil {
s.RemoveTailscaleInterfaces()
}
return s, err
}
// RegisterChangeCallback adds callback to the set of parties to be
@ -117,17 +148,38 @@ func (m *Mon) Close() error {
return err
}
// InjectEvent forces the monitor to pretend there was a network
// change and re-check the state of the network. Any registered
// ChangeFunc callbacks will be called within the event coalescing
// period (under a fraction of a second).
func (m *Mon) InjectEvent() {
select {
case m.change <- struct{}{}:
default:
// Another change signal is already
// buffered. Debounce will wake up soon
// enough.
}
}
func (m *Mon) stopped() bool {
select {
case <-m.stop:
return true
default:
return false
}
}
// pump continuously retrieves messages from the connection, notifying
// the change channel of changes, and stopping when a stop is issued.
func (m *Mon) pump() {
defer m.goroutines.Done()
for {
for !m.stopped() {
msg, err := m.om.Receive()
if err != nil {
select {
case <-m.stop:
if m.stopped() {
return
default:
}
// Keep retrying while we're not closed.
m.logf("error from link monitor: %v", err)
@ -137,11 +189,7 @@ func (m *Mon) pump() {
if msg.ignore() {
continue
}
select {
case m.change <- struct{}{}:
case <-m.stop:
return
}
m.InjectEvent()
}
}
@ -156,16 +204,24 @@ func (m *Mon) debounce() {
case <-m.change:
}
m.mu.Lock()
for _, cb := range m.cbs {
go cb()
if curState, err := m.interfaceStateUncached(); err != nil {
m.logf("interfaces.State: %v", err)
} else {
m.mu.Lock()
changed := !curState.Equal(m.ifState)
if changed {
m.ifState = curState
}
for _, cb := range m.cbs {
go cb(changed, m.ifState)
}
m.mu.Unlock()
}
m.mu.Unlock()
select {
case <-m.stop:
return
case <-time.After(100 * time.Millisecond):
case <-time.After(250 * time.Millisecond):
}
}
}

View File

@ -7,7 +7,7 @@ package monitor
import (
"fmt"
"log"
"os"
"sync"
"golang.org/x/net/route"
"golang.org/x/sys/unix"
@ -24,42 +24,74 @@ type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }
func newOSMon(logf logger.Logf) (osMon, error) {
func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) {
fd, err := unix.Socket(unix.AF_ROUTE, unix.SOCK_RAW, 0)
if err != nil {
return nil, err
}
return &darwinRouteMon{
logf: logf,
f: os.NewFile(uintptr(fd), "AF_ROUTE"),
fd: fd,
}, nil
}
type darwinRouteMon struct {
logf logger.Logf
f *os.File // AF_ROUTE socket
buf [2 << 10]byte
logf logger.Logf
fd int // AF_ROUTE socket
buf [2 << 10]byte
closeOnce sync.Once
}
func (m *darwinRouteMon) Close() error {
return m.f.Close()
var err error
m.closeOnce.Do(func() {
err = unix.Close(m.fd)
})
return err
}
func (m *darwinRouteMon) Receive() (message, error) {
n, err := m.f.Read(m.buf[:])
if err != nil {
return nil, err
for {
n, err := unix.Read(m.fd, m.buf[:])
if err != nil {
return nil, err
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, m.buf[:n])
if err != nil {
if debugRouteMessages {
m.logf("read %d bytes (% 02x), failed to parse RIB: %v", n, m.buf[:n], err)
}
return unspecifiedMessage{}, nil
}
if len(msgs) == 0 {
if debugRouteMessages {
m.logf("read %d bytes with no messages (% 02x)", n, m.buf[:n])
}
continue
}
nSkip := 0
for _, msg := range msgs {
if m.skipMessage(msg) {
nSkip++
}
}
if debugRouteMessages {
m.logf("read %d bytes, %d messages (%d skipped)", n, len(msgs), nSkip)
m.logMessages(msgs)
}
if nSkip == len(msgs) {
continue
}
return unspecifiedMessage{}, nil
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, m.buf[:n])
if err != nil {
m.logf("read %d bytes (% 02x), failed to parse RIB: %v", n, m.buf[:n], err)
return nil, nil
}
func (m *darwinRouteMon) skipMessage(msg route.Message) bool {
switch msg.(type) {
case *route.InterfaceMulticastAddrMessage:
return true
}
if debugRouteMessages {
m.logf("read: %d bytes, %d msgs", n, len(msgs))
m.logMessages(msgs)
}
return unspecifiedMessage{}, nil
return false
}
func (m *darwinRouteMon) logMessages(msgs []route.Message) {

View File

@ -0,0 +1,28 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package monitor
import (
"encoding/hex"
"strings"
"testing"
"golang.org/x/net/route"
)
func TestIssue1416RIB(t *testing.T) {
const ribHex = `32 00 05 10 30 00 00 00 00 00 00 00 04 00 00 00 14 12 04 00 06 03 06 00 65 6e 30 ac 87 a3 19 7f 82 00 00 00 0e 12 00 00 00 00 06 00 91 e0 f0 01 00 00`
rtmMsg, err := hex.DecodeString(strings.ReplaceAll(ribHex, " ", ""))
if err != nil {
t.Fatal(err)
}
msgs, err := route.ParseRIB(route.RIBTypeRoute, rtmMsg)
if err != nil {
t.Logf("ParseRIB: %v", err)
t.Skip("skipping on known failure; see https://github.com/tailscale/tailscale/issues/1416")
t.Fatal(err)
}
t.Logf("Got: %#v", msgs)
}

View File

@ -25,7 +25,7 @@ type devdConn struct {
conn net.Conn
}
func newOSMon(logf logger.Logf) (osMon, error) {
func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) {
conn, err := net.Dial("unixpacket", "/var/run/devd.seqpacket.pipe")
if err != nil {
return nil, fmt.Errorf("devd dial error: %v", err)

View File

@ -37,7 +37,7 @@ type nlConn struct {
buffered []netlink.Message
}
func newOSMon(logf logger.Logf) (osMon, error) {
func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) {
conn, err := netlink.Dial(unix.NETLINK_ROUTE, &netlink.Config{
// Routes get us most of the events of interest, but we need
// address as well to cover things like DHCP deciding to give

View File

@ -0,0 +1,72 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux,!freebsd,!windows,!darwin android
package monitor
import (
"errors"
"runtime"
"sync"
"time"
"tailscale.com/types/logger"
)
func newOSMon(logf logger.Logf, m *Mon) (osMon, error) {
return &pollingMon{
logf: logf,
m: m,
stop: make(chan struct{}),
}, nil
}
// pollingMon is a bad but portable implementation of the link monitor
// that works by polling the interface state every 10 seconds, in lieu
// of anything to subscribe to. A good implementation
type pollingMon struct {
logf logger.Logf
m *Mon
closeOnce sync.Once
stop chan struct{}
}
func (pm *pollingMon) Close() error {
pm.closeOnce.Do(func() {
close(pm.stop)
})
return nil
}
func (pm *pollingMon) Receive() (message, error) {
d := 10 * time.Second
if runtime.GOOS == "android" {
// We'll have Android notify the link monitor to wake up earlier,
// so this can go very slowly there, to save battery.
// https://github.com/tailscale/tailscale/issues/1427
d = 10 * time.Minute
}
ticker := time.NewTicker(d)
defer ticker.Stop()
base := pm.m.InterfaceState()
for {
if cur, err := pm.m.interfaceStateUncached(); err == nil && !cur.Equal(base) {
return unspecifiedMessage{}, nil
}
select {
case <-ticker.C:
case <-pm.stop:
return nil, errors.New("stopped")
}
}
}
// unspecifiedMessage is a minimal message implementation that should not
// be ignored. In general, OS-specific implementations should use better
// types and avoid this if they can.
type unspecifiedMessage struct{}
func (unspecifiedMessage) ignore() bool { return false }

View File

@ -0,0 +1,89 @@
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package monitor
import (
"flag"
"testing"
"time"
"tailscale.com/net/interfaces"
)
func TestMonitorStartClose(t *testing.T) {
mon, err := New(t.Logf)
if err != nil {
t.Fatal(err)
}
mon.Start()
if err := mon.Close(); err != nil {
t.Fatal(err)
}
}
func TestMonitorJustClose(t *testing.T) {
mon, err := New(t.Logf)
if err != nil {
t.Fatal(err)
}
if err := mon.Close(); err != nil {
t.Fatal(err)
}
}
func TestMonitorInjectEvent(t *testing.T) {
mon, err := New(t.Logf)
if err != nil {
t.Fatal(err)
}
defer mon.Close()
got := make(chan bool, 1)
mon.RegisterChangeCallback(func(changed bool, state *interfaces.State) {
select {
case got <- true:
default:
}
})
mon.Start()
mon.InjectEvent()
select {
case <-got:
// Pass.
case <-time.After(5 * time.Second):
t.Fatal("timeout waiting for callback")
}
}
var monitor = flag.String("monitor", "", `go into monitor mode like 'route monitor'; test never terminates. Value can be either "raw" or "callback"`)
func TestMonitorMode(t *testing.T) {
switch *monitor {
case "":
t.Skip("skipping non-test without --monitor")
case "raw", "callback":
default:
t.Skipf(`invalid --monitor value: must be "raw" or "callback"`)
}
mon, err := New(t.Logf)
if err != nil {
t.Fatal(err)
}
switch *monitor {
case "raw":
for {
msg, err := mon.om.Receive()
if err != nil {
t.Fatal(err)
}
t.Logf("msg: %#v", msg)
}
case "callback":
mon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
t.Logf("cb: changed=%v, ifSt=%v", changed, st)
})
mon.Start()
select {}
}
}

View File

@ -1,11 +0,0 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !linux,!freebsd,!windows,!darwin android
package monitor
import "tailscale.com/types/logger"
func newOSMon(logger.Logf) (osMon, error) { return nil, nil }

View File

@ -65,7 +65,7 @@ type winMon struct {
inFastPoll bool // recent net change event made us go into fast polling mode (to detect proxy changes)
}
func newOSMon(logf logger.Logf) (osMon, error) {
func newOSMon(logf logger.Logf, _ *Mon) (osMon, error) {
closeHandle, err := windows.CreateEvent(nil, 1 /* manual reset */, 0 /* unsignaled */, nil /* no name */)
if err != nil {
return nil, fmt.Errorf("CreateEvent: %w", err)

View File

@ -15,7 +15,9 @@ import (
"io"
"log"
"net"
"strconv"
"strings"
"sync"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
@ -31,15 +33,17 @@ import (
"gvisor.dev/gvisor/pkg/waiter"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/net/socks5"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/tstun"
)
const debugNetstack = false
// Impl contains the state for the netstack implementation,
// and implements wgengine.FakeImpl to act as a userspace network
// stack when Tailscale is running in fake mode.
@ -50,12 +54,15 @@ type Impl struct {
e wgengine.Engine
mc *magicsock.Conn
logf logger.Logf
mu sync.Mutex
dns DNSMap
}
const nicID = 1
// Create creates and populates a new Impl.
func Create(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) (wgengine.FakeImpl, error) {
func Create(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) (*Impl, error) {
if mc == nil {
return nil, errors.New("nil magicsock.Conn")
}
@ -115,12 +122,45 @@ func (ns *Impl) Start() error {
ns.ipstack.SetTransportProtocolHandler(udp.ProtocolNumber, udpFwd.HandlePacket)
go ns.injectOutbound()
ns.tundev.PostFilterIn = ns.injectInbound
go ns.socks5Server()
return nil
}
// DNSMap maps MagicDNS names (both base + FQDN) to their first IP.
// It should not be mutated once created.
type DNSMap map[string]netaddr.IP
func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap {
ret := make(DNSMap)
suffix := nm.MagicDNSSuffix()
if nm.Name != "" && len(nm.Addresses) > 0 {
ip := nm.Addresses[0].IP
ret[strings.TrimRight(nm.Name, ".")] = ip
if dnsname.HasSuffix(nm.Name, suffix) {
ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip
}
}
for _, p := range nm.Peers {
if p.Name != "" && len(p.Addresses) > 0 {
ip := p.Addresses[0].IP
ret[strings.TrimRight(p.Name, ".")] = ip
if dnsname.HasSuffix(p.Name, suffix) {
ret[dnsname.TrimSuffix(p.Name, suffix)] = ip
}
}
}
return ret
}
func (ns *Impl) updateDNS(nm *netmap.NetworkMap) {
ns.mu.Lock()
defer ns.mu.Unlock()
ns.dns = DNSMapFromNetworkMap(nm)
}
func (ns *Impl) updateIPs(nm *netmap.NetworkMap) {
ns.updateDNS(nm)
oldIPs := make(map[tcpip.Address]bool)
for _, ip := range ns.ipstack.AllAddresses()[nicID] {
oldIPs[ip.AddressWithPrefix.Address] = true
@ -166,10 +206,57 @@ func (ns *Impl) updateIPs(nm *netmap.NetworkMap) {
}
}
func (ns *Impl) dialContextTCP(ctx context.Context, address string) (*gonet.TCPConn, error) {
remoteIPPort, err := netaddr.ParseIPPort(address)
// Resolve resolves addr into an IP:port using first the MagicDNS contents
// of m, else using the system resolver.
func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error) {
ipp, pippErr := netaddr.ParseIPPort(addr)
if pippErr == nil {
return ipp, nil
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("could not parse IP:port: %w", err)
// addr is malformed.
return netaddr.IPPort{}, err
}
if net.ParseIP(host) != nil {
// The host part of addr was an IP, so the netaddr.ParseIPPort above should've
// passed. Must've been a bad port number. Return the original error.
return netaddr.IPPort{}, pippErr
}
port16, err := strconv.ParseUint(port, 10, 16)
if err != nil {
return netaddr.IPPort{}, fmt.Errorf("invalid port in address %q", addr)
}
// Host is not an IP, so assume it's a DNS name.
// Try MagicDNS first, else otherwise a real DNS lookup.
ip := m[host]
if !ip.IsZero() {
return netaddr.IPPort{IP: ip, Port: uint16(port16)}, nil
}
// No Magic DNS name so try real DNS.
var r net.Resolver
ips, err := r.LookupIP(ctx, "ip", host)
if err != nil {
return netaddr.IPPort{}, err
}
if len(ips) == 0 {
return netaddr.IPPort{}, fmt.Errorf("DNS lookup returned no results for %q", host)
}
ip, _ = netaddr.FromStdIP(ips[0])
return netaddr.IPPort{IP: ip, Port: uint16(port16)}, nil
}
func (ns *Impl) DialContextTCP(ctx context.Context, addr string) (*gonet.TCPConn, error) {
ns.mu.Lock()
dnsMap := ns.dns
ns.mu.Unlock()
remoteIPPort, err := dnsMap.Resolve(ctx, addr)
if err != nil {
return nil, err
}
remoteAddress := tcpip.FullAddress{
NIC: nicID,
@ -201,8 +288,9 @@ func (ns *Impl) injectOutbound() {
full = append(full, hdrNetwork.View()...)
full = append(full, hdrTransport.View()...)
full = append(full, pkt.Data.ToView()...)
ns.logf("[v2] packet Write out: % x", full)
if debugNetstack {
ns.logf("[v2] packet Write out: % x", full)
}
if err := ns.tundev.InjectOutbound(full); err != nil {
log.Printf("netstack inject outbound: %v", err)
return
@ -219,7 +307,9 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.TUN) filter.Response {
case 6:
pn = header.IPv6ProtocolNumber
}
ns.logf("[v2] packet in (from %v): % x", p.Src, p.Buffer())
if debugNetstack {
ns.logf("[v2] packet in (from %v): % x", p.Src, p.Buffer())
}
vv := buffer.View(append([]byte(nil), p.Buffer()...)).ToVectorisedView()
packetBuf := stack.NewPacketBuffer(stack.PacketBufferOptions{
Data: vv,
@ -229,7 +319,11 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.TUN) filter.Response {
}
func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
ns.logf("[v2] ForwarderRequest: %v", r)
if debugNetstack {
// Kinda ugly:
// ForwarderRequest: &{{{{0 0}}} 0xc0001c30b0 0xc0004c3d40 {1240 6 true 826109390 0 true}
ns.logf("[v2] ForwarderRequest: %v", r)
}
var wq waiter.Queue
ep, err := r.CreateEndpoint(&wq)
if err != nil {
@ -237,19 +331,18 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
return
}
localAddr, err := ep.GetLocalAddress()
ns.logf("[v2] forwarding port %v to 100.101.102.103:80", localAddr.Port)
if err != nil {
r.Complete(true)
return
}
r.Complete(false)
c := gonet.NewTCPConn(&wq, ep)
go ns.forwardTCP(c, &wq, "100.101.102.103:80")
go ns.forwardTCP(c, &wq, localAddr.Port)
}
func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, address string) {
func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, port uint16) {
defer client.Close()
ns.logf("[v2] netstack: forwarding to address %s", address)
ns.logf("[v2] netstack: forwarding incoming connection on port %v", port)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
@ -266,38 +359,27 @@ func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, address stri
}
cancel()
}()
server, err := ns.dialContextTCP(ctx, address)
var stdDialer net.Dialer
server, err := stdDialer.DialContext(ctx, "tcp", net.JoinHostPort("localhost", strconv.Itoa(int(port))))
if err != nil {
ns.logf("netstack: could not connect to server %s: %s", address, err)
ns.logf("netstack: could not connect to local server on port %v: %v", port, err)
return
}
defer server.Close()
connClosed := make(chan bool, 2)
connClosed := make(chan error, 2)
go func() {
io.Copy(server, client)
connClosed <- true
_, err := io.Copy(server, client)
connClosed <- err
}()
go func() {
io.Copy(client, server)
connClosed <- true
_, err := io.Copy(client, server)
connClosed <- err
}()
<-connClosed
ns.logf("[v2] netstack: forwarder connection to %s closed", address)
}
func (ns *Impl) socks5Server() {
ln, err := net.Listen("tcp", "localhost:1080")
err = <-connClosed
if err != nil {
ns.logf("could not start SOCKS5 listener: %v", err)
return
ns.logf("proxy connection closed with error: %v", err)
}
srv := &socks5.Server{
Logf: ns.logf,
Dialer: func(ctx context.Context, network, addr string) (net.Conn, error) {
return ns.dialContextTCP(ctx, addr)
},
}
ns.logf("SOCKS5 server exited: %v", srv.Serve(ln))
ns.logf("[v2] netstack: forwarder connection on port %v closed", port)
}
func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {

View File

@ -8,14 +8,30 @@
package netstack
import (
"context"
"errors"
"net"
"inet.af/netaddr"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/wgengine"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/tstun"
)
func Create(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) (wgengine.FakeImpl, error) {
type Impl struct{}
func (*Impl) Start() error { panic("noimpl") }
func (*Impl) DialContextTCP(ctx context.Context, addr string) (net.Conn, error) { panic("noimpl") }
type DNSMap map[string]netaddr.IP
func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { panic("noimpl") }
func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap { return nil }
func Create(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock.Conn) (*Impl, error) {
return nil, errors.New("netstack is not supported on 32-bit platforms for now; see https://github.com/google/gvisor/issues/5241")
}

View File

@ -0,0 +1,39 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by tailscale.com/cmd/cloner -type Config; DO NOT EDIT.
package router
import (
"inet.af/netaddr"
"tailscale.com/types/preftype"
"tailscale.com/wgengine/router/dns"
)
// Clone makes a deep copy of Config.
// The result aliases no memory with the original.
func (src *Config) Clone() *Config {
if src == nil {
return nil
}
dst := new(Config)
*dst = *src
dst.LocalAddrs = append(src.LocalAddrs[:0:0], src.LocalAddrs...)
dst.Routes = append(src.Routes[:0:0], src.Routes...)
dst.DNS = *src.DNS.Clone()
dst.SubnetRoutes = append(src.SubnetRoutes[:0:0], src.SubnetRoutes...)
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Config
var _ConfigNeedsRegeneration = Config(struct {
LocalAddrs []netaddr.IPPrefix
Routes []netaddr.IPPrefix
DNS dns.Config
SubnetRoutes []netaddr.IPPrefix
SNATSubnetRoutes bool
NetfilterMode preftype.NetfilterMode
}{})

View File

@ -10,6 +10,8 @@ import (
"tailscale.com/types/logger"
)
//go:generate go run tailscale.com/cmd/cloner -type=Config -output=config_clone.go
// Config is the set of parameters that uniquely determine
// the state to which a manager should bring system DNS settings.
type Config struct {

View File

@ -0,0 +1,33 @@
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by tailscale.com/cmd/cloner -type Config; DO NOT EDIT.
package dns
import (
"inet.af/netaddr"
)
// Clone makes a deep copy of Config.
// The result aliases no memory with the original.
func (src *Config) Clone() *Config {
if src == nil {
return nil
}
dst := new(Config)
*dst = *src
dst.Nameservers = append(src.Nameservers[:0:0], src.Nameservers...)
dst.Domains = append(src.Domains[:0:0], src.Domains...)
return dst
}
// A compilation failure here means this code must be regenerated, with command:
// tailscale.com/cmd/cloner -type Config
var _ConfigNeedsRegeneration = Config(struct {
Nameservers []netaddr.IP
Domains []string
PerDomain bool
Proxied bool
}{})

View File

@ -21,6 +21,7 @@ import (
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"tailscale.com/net/interfaces"
"tailscale.com/net/tsaddr"
"tailscale.com/wgengine/winnet"
)
@ -50,24 +51,27 @@ func monitorDefaultRoutes(tun *tun.NativeTun) (*winipcfg.RouteChangeCallback, er
if mtu > 0 && (lastMtu == 0 || lastMtu != mtu) {
iface, err := ourLuid.IPInterface(windows.AF_INET)
if err != nil {
return fmt.Errorf("error getting v4 interface: %w", err)
if !errors.Is(err, windows.ERROR_NOT_FOUND) {
return fmt.Errorf("getting v4 interface: %w", err)
}
} else {
iface.NLMTU = mtu - 80
// If the TUN device was created with a smaller MTU,
// though, such as 1280, we don't want to go bigger
// than configured. (See the comment on minimalMTU in
// the wgengine package.)
if min, err := tun.MTU(); err == nil && min < int(iface.NLMTU) {
iface.NLMTU = uint32(min)
}
if iface.NLMTU < 576 {
iface.NLMTU = 576
}
err = iface.Set()
if err != nil {
return fmt.Errorf("error setting v4 MTU: %w", err)
}
tun.ForceMTU(int(iface.NLMTU))
}
iface.NLMTU = mtu - 80
// If the TUN device was created with a smaller MTU,
// though, such as 1280, we don't want to go bigger than
// configured. (See the comment on minimalMTU in the
// wgengine package.)
if min, err := tun.MTU(); err == nil && min < int(iface.NLMTU) {
iface.NLMTU = uint32(min)
}
if iface.NLMTU < 576 {
iface.NLMTU = 576
}
err = iface.Set()
if err != nil {
return fmt.Errorf("error setting v4 MTU: %w", err)
}
tun.ForceMTU(int(iface.NLMTU))
iface, err = ourLuid.IPInterface(windows.AF_INET6)
if err != nil {
if !errors.Is(err, windows.ERROR_NOT_FOUND) {
@ -248,7 +252,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
winipcfg.GAAFlagIncludeAllInterfaces,
)
if err != nil {
return err
return fmt.Errorf("getting interface: %w", err)
}
// Send non-nil return errors to retErrc, to interupt our background
@ -287,12 +291,42 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
log.Printf("setPrivateNetwork: adapter LUID %v not found after %d tries, giving up", luid, tries)
}()
// Figure out which of IPv4 and IPv6 are available. Both protocols
// can be disabled on a per-interface basis by the user, as well
// as globally via a registry policy. We skip programming anything
// related to the disabled protocols, since by definition they're
// unusable.
ipif4, err := iface.LUID.IPInterface(windows.AF_INET)
if err != nil {
if !errors.Is(err, windows.ERROR_NOT_FOUND) {
return fmt.Errorf("getting AF_INET interface: %w", err)
}
log.Printf("AF_INET interface not found on Tailscale adapter, skipping IPv4 programming")
ipif4 = nil
}
ipif6, err := iface.LUID.IPInterface(windows.AF_INET6)
if err != nil {
if !errors.Is(err, windows.ERROR_NOT_FOUND) {
return fmt.Errorf("getting AF_INET6 interface: %w", err)
}
log.Printf("AF_INET6 interface not found on Tailscale adapter, skipping IPv6 programming")
ipif6 = nil
}
// Windows requires routes to have a nexthop. For routes such as
// ours where the nexthop is meaningless, you're supposed to use
// one of the local IP addresses of the interface. Find an IPv4
// and IPv6 address we can use for this purpose.
var firstGateway4 *net.IP
var firstGateway6 *net.IP
addresses := make([]*net.IPNet, len(cfg.LocalAddrs))
for i, addr := range cfg.LocalAddrs {
addresses := make([]*net.IPNet, 0, len(cfg.LocalAddrs))
for _, addr := range cfg.LocalAddrs {
if (addr.IP.Is4() && ipif4 == nil) || (addr.IP.Is6() && ipif6 == nil) {
// Can't program addresses for disabled protocol.
continue
}
ipnet := addr.IPNet()
addresses[i] = ipnet
addresses = append(addresses, ipnet)
gateway := ipnet.IP
if addr.IP.Is4() && firstGateway4 == nil {
firstGateway4 = &gateway
@ -305,7 +339,23 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
foundDefault4 := false
foundDefault6 := false
for _, route := range cfg.Routes {
if (route.IP.Is4() && firstGateway4 == nil) || (route.IP.Is6() && firstGateway6 == nil) {
if (route.IP.Is4() && ipif4 == nil) || (route.IP.Is6() && ipif6 == nil) {
// Can't program routes for disabled protocol.
continue
}
if route.IP.Is6() && firstGateway6 == nil {
// Windows won't let us set IPv6 routes without having an
// IPv6 local address set. However, when we've configured
// a default route, we want to forcibly grab IPv6 traffic
// even if the v6 overlay network isn't configured. To do
// that, we add a dummy local IPv6 address to serve as a
// route source.
ipnet := &net.IPNet{tsaddr.Tailscale4To6Placeholder().IPAddr().IP, net.CIDRMask(128, 128)}
addresses = append(addresses, ipnet)
firstGateway6 = &ipnet.IP
} else if route.IP.Is4() && firstGateway4 == nil {
// TODO: do same dummy behavior as v6?
return errors.New("Due to a Windows limitation, one cannot have interface routes without an interface address")
}
@ -348,7 +398,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
err = syncAddresses(iface, addresses)
if err != nil {
return err
return fmt.Errorf("syncAddresses: %w", err)
}
sort.Slice(routes, func(i, j int) bool { return routeLess(&routes[i], &routes[j]) })
@ -374,7 +424,7 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
winipcfg.GAAFlagIncludeAllInterfaces,
)
if err != nil {
return err
return fmt.Errorf("getting interface: %w", err)
}
var errAcc error
@ -384,45 +434,46 @@ func configureInterface(cfg *Config, tun *tun.NativeTun) (retErr error) {
errAcc = err
}
ipif, err := iface.LUID.IPInterface(windows.AF_INET)
if err != nil {
log.Printf("getipif: %v", err)
return err
}
if foundDefault4 {
ipif.UseAutomaticMetric = false
ipif.Metric = 0
}
if mtu > 0 {
ipif.NLMTU = uint32(mtu)
tun.ForceMTU(int(ipif.NLMTU))
}
err = ipif.Set()
if err != nil && errAcc == nil {
errAcc = err
}
ipif, err = iface.LUID.IPInterface(windows.AF_INET6)
if err != nil {
if !errors.Is(err, windows.ERROR_NOT_FOUND) {
return err
if ipif4 != nil {
ipif4, err = iface.LUID.IPInterface(windows.AF_INET)
if err != nil {
return fmt.Errorf("getting AF_INET interface: %w", err)
}
} else {
if foundDefault6 {
ipif.UseAutomaticMetric = false
ipif.Metric = 0
if foundDefault4 {
ipif4.UseAutomaticMetric = false
ipif4.Metric = 0
}
if mtu > 0 {
ipif.NLMTU = uint32(mtu)
ipif4.NLMTU = uint32(mtu)
tun.ForceMTU(int(ipif4.NLMTU))
}
ipif.DadTransmits = 0
ipif.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
err = ipif.Set()
err = ipif4.Set()
if err != nil && errAcc == nil {
errAcc = err
}
}
if ipif6 != nil {
ipif6, err = iface.LUID.IPInterface(windows.AF_INET6)
if err != nil {
return fmt.Errorf("getting AF_INET6 interface: %w", err)
} else {
if foundDefault6 {
ipif6.UseAutomaticMetric = false
ipif6.Metric = 0
}
if mtu > 0 {
ipif6.NLMTU = uint32(mtu)
}
ipif6.DadTransmits = 0
ipif6.RouterDiscoveryBehavior = winipcfg.RouterDiscoveryDisabled
err = ipif6.Set()
if err != nil && errAcc == nil {
errAcc = err
}
}
}
return errAcc
}
@ -565,14 +616,14 @@ func syncAddresses(ifc *winipcfg.IPAdapterAddresses, want []*net.IPNet) error {
for _, a := range del {
err := ifc.LUID.DeleteIPAddress(*a)
if err != nil {
erracc = err
erracc = fmt.Errorf("deleting IP %q: %w", *a, err)
}
}
for _, a := range add {
err := ifc.LUID.AddIPAddress(*a)
if err != nil {
erracc = err
erracc = fmt.Errorf("adding IP %q: %w", *a, err)
}
}

View File

@ -54,6 +54,8 @@ func Cleanup(logf logger.Logf, interfaceName string) {
cleanup(logf, interfaceName)
}
//go:generate go run tailscale.com/cmd/cloner -type=Config -output=config_clone.go
// Config is the subset of Tailscale configuration that is relevant to
// the OS's network stack.
type Config struct {

View File

@ -10,6 +10,7 @@ import (
"fmt"
"log"
"os/exec"
"runtime"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
@ -101,9 +102,40 @@ func inet(p netaddr.IPPrefix) string {
return "inet"
}
// See https://github.com/tailscale/tailscale/issues/1307#issuecomment-786045280
// Remove all IPv6 entries.
func (r *userspaceBSDRouter) modifiedConfigForFreeBSDBugWorkaround(cfg *Config) *Config {
n := cfg.Clone()
n.LocalAddrs = n.LocalAddrs[:0]
for _, addr := range cfg.LocalAddrs {
if !addr.IP.Is6() {
n.LocalAddrs = append(n.LocalAddrs, addr)
}
}
n.Routes = n.Routes[:0]
for _, addr := range cfg.Routes {
if !addr.IP.Is6() {
n.Routes = append(n.Routes, addr)
}
}
n.SubnetRoutes = n.SubnetRoutes[:0]
for _, addr := range cfg.SubnetRoutes {
if !addr.IP.Is6() {
n.SubnetRoutes = append(n.SubnetRoutes, addr)
}
}
return n
}
func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) {
if cfg == nil {
cfg = &shutdownConfig
} else if runtime.GOOS == "freebsd" {
cfg = r.modifiedConfigForFreeBSDBugWorkaround(cfg)
}
var errq error

View File

@ -5,17 +5,22 @@
package router
import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"time"
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"golang.org/x/sys/windows"
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
"inet.af/netaddr"
"tailscale.com/logtail/backoff"
"tailscale.com/types/logger"
"tailscale.com/wgengine/router/dns"
@ -29,6 +34,15 @@ type winRouter struct {
routeChangeCallback *winipcfg.RouteChangeCallback
dns *dns.Manager
firewall *firewallTweaker
// firewallSubproc is a subprocess that runs a tweaked version of
// wireguard-windows's "default route killswitch" code. We run it
// as a subprocess because it does unsafe callouts to the WFP API,
// and we want to defend against memory corruption in our main
// process. Owned and mutated only by Set, and doesn't need a lock
// because Set is only called with wgengine's lock held,
// preventing concurrent reconfigs.
firewallSubproc *exec.Cmd
}
func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (Router, error) {
@ -55,7 +69,10 @@ func newUserspaceRouter(logf logger.Logf, wgdev *device.Device, tundev tun.Devic
tunname: tunname,
nativeTun: nativeTun,
dns: dns.NewManager(mconfig),
firewall: &firewallTweaker{logf: logger.WithPrefix(logf, "firewall: ")},
firewall: &firewallTweaker{
logf: logger.WithPrefix(logf, "firewall: "),
tunGUID: *guid,
},
}, nil
}
@ -82,7 +99,7 @@ func (r *winRouter) Set(cfg *Config) error {
for _, la := range cfg.LocalAddrs {
localAddrs = append(localAddrs, la.String())
}
r.firewall.set(localAddrs)
r.firewall.set(localAddrs, cfg.Routes)
err := configureInterface(cfg, r.nativeTun)
if err != nil {
@ -97,6 +114,15 @@ func (r *winRouter) Set(cfg *Config) error {
return nil
}
func hasDefaultRoute(routes []netaddr.IPPrefix) bool {
for _, route := range routes {
if route.Bits == 0 {
return true
}
}
return false
}
func (r *winRouter) Close() error {
r.firewall.clear()
@ -120,23 +146,36 @@ func cleanup(logf logger.Logf, interfaceName string) {
// See https://github.com/tailscale/tailscale/issues/785.
// So this tracks the desired state and runs the actual adjusting code asynchrounsly.
type firewallTweaker struct {
logf logger.Logf
logf logger.Logf
tunGUID windows.GUID
mu sync.Mutex
didProcRule bool
running bool // doAsyncSet goroutine is running
known bool // firewall is in known state (in lastVal)
want []string // next value we want, or "" to delete the firewall rule
lastVal []string // last set value, if known
mu sync.Mutex
didProcRule bool
running bool // doAsyncSet goroutine is running
known bool // firewall is in known state (in lastVal)
wantLocal []string // next value we want, or "" to delete the firewall rule
lastLocal []string // last set value, if known
wantKillswitch bool
lastKillswitch bool
// Only touched by doAsyncSet, so mu doesn't need to be held.
// fwProc is a subprocess that runs the wireguard-windows firewall
// killswitch code. It is only non-nil when the default route
// killswitch is active, and may go back and forth between nil and
// non-nil any number of times during the process's lifetime.
fwProc *exec.Cmd
// stop makes fwProc exit when closed.
stop io.Closer
}
func (ft *firewallTweaker) clear() { ft.set(nil) }
func (ft *firewallTweaker) clear() { ft.set(nil, nil) }
// set takes the IPv4 and/or IPv6 CIDRs to allow; an empty slice
// removes the firwall rules.
// set takes CIDRs to allow, and the routes that point into the Tailscale tun interface.
// Empty slices remove firewall rules.
//
// set takes ownership of the slice.
func (ft *firewallTweaker) set(cidrs []string) {
// set takes ownership of cidrs, but not routes.
func (ft *firewallTweaker) set(cidrs []string, routes []netaddr.IPPrefix) {
ft.mu.Lock()
defer ft.mu.Unlock()
@ -145,9 +184,10 @@ func (ft *firewallTweaker) set(cidrs []string) {
} else {
ft.logf("marking allowed %v", cidrs)
}
ft.want = cidrs
ft.wantLocal = cidrs
ft.wantKillswitch = hasDefaultRoute(routes)
if ft.running {
// The doAsyncSet goroutine will check ft.want
// The doAsyncSet goroutine will check ft.wantLocal/wantKillswitch
// before returning.
return
}
@ -171,77 +211,144 @@ func (ft *firewallTweaker) doAsyncSet() {
ft.mu.Lock()
for { // invariant: ft.mu must be locked when beginning this block
val := ft.want
if ft.known && strsEqual(ft.lastVal, val) {
val := ft.wantLocal
if ft.known && strsEqual(ft.lastLocal, val) && ft.wantKillswitch == ft.lastKillswitch {
ft.running = false
ft.logf("ending netsh goroutine")
ft.mu.Unlock()
return
}
needClear := !ft.known || len(ft.lastVal) > 0 || len(val) == 0
wantKillswitch := ft.wantKillswitch
needClear := !ft.known || len(ft.lastLocal) > 0 || len(val) == 0
needProcRule := !ft.didProcRule
ft.mu.Unlock()
if needClear {
ft.logf("clearing Tailscale-In firewall rules...")
// We ignore the error here, because netsh returns an error for
// deleting something that doesn't match.
// TODO(bradfitz): care? That'd involve querying it before/after to see
// whether it was necessary/worked. But the output format is localized,
// so can't rely on parsing English. Maybe need to use OLE, not netsh.exe?
d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in")
ft.logf("cleared Tailscale-In firewall rules in %v", d)
}
if needProcRule {
ft.logf("deleting any prior Tailscale-Process rule...")
d, err := ft.runFirewall("delete", "rule", "name=Tailscale-Process", "dir=in") // best effort
if err == nil {
ft.logf("removed old Tailscale-Process rule in %v", d)
}
var exe string
exe, err = os.Executable()
if err != nil {
ft.logf("failed to find Executable for Tailscale-Process rule: %v", err)
} else {
ft.logf("adding Tailscale-Process rule to allow UDP for %q ...", exe)
d, err = ft.runFirewall("add", "rule", "name=Tailscale-Process",
"dir=in",
"action=allow",
"edge=yes",
"program="+exe,
"protocol=udp",
"profile=any",
"enable=yes",
)
if err != nil {
ft.logf("error adding Tailscale-Process rule: %v", err)
} else {
ft.mu.Lock()
ft.didProcRule = true
ft.mu.Unlock()
ft.logf("added Tailscale-Process rule in %v", d)
}
}
}
var err error
for _, cidr := range val {
ft.logf("adding Tailscale-In rule to allow %v ...", cidr)
var d time.Duration
d, err = ft.runFirewall("add", "rule", "name=Tailscale-In", "dir=in", "action=allow", "localip="+cidr, "profile=private", "enable=yes")
if err != nil {
ft.logf("error adding Tailscale-In rule to allow %v: %v", cidr, err)
break
}
ft.logf("added Tailscale-In rule to allow %v in %v", cidr, d)
}
err := ft.doSet(val, wantKillswitch, needClear, needProcRule)
bo.BackOff(ctx, err)
ft.mu.Lock()
ft.lastVal = val
ft.lastLocal = val
ft.lastKillswitch = wantKillswitch
ft.known = (err == nil)
}
}
// doSet creates and deletes firewall rules to make the system state
// match the values of local, killswitch, clear and procRule.
//
// local is the list of local Tailscale addresses (formatted as CIDR
// prefixes) to allow through the Windows firewall.
// killswitch, if true, enables the wireguard-windows based internet
// killswitch to prevent use of non-Tailscale default routes.
// clear, if true, removes all tailscale address firewall rules before
// adding local.
// procRule, if true, installs a firewall rule that permits the Tailscale
// process to dial out as it pleases.
//
// Must only be invoked from doAsyncSet.
func (ft *firewallTweaker) doSet(local []string, killswitch bool, clear bool, procRule bool) error {
if clear {
ft.logf("clearing Tailscale-In firewall rules...")
// We ignore the error here, because netsh returns an error for
// deleting something that doesn't match.
// TODO(bradfitz): care? That'd involve querying it before/after to see
// whether it was necessary/worked. But the output format is localized,
// so can't rely on parsing English. Maybe need to use OLE, not netsh.exe?
d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in")
ft.logf("cleared Tailscale-In firewall rules in %v", d)
}
if procRule {
ft.logf("deleting any prior Tailscale-Process rule...")
d, err := ft.runFirewall("delete", "rule", "name=Tailscale-Process", "dir=in") // best effort
if err == nil {
ft.logf("removed old Tailscale-Process rule in %v", d)
}
var exe string
exe, err = os.Executable()
if err != nil {
ft.logf("failed to find Executable for Tailscale-Process rule: %v", err)
} else {
ft.logf("adding Tailscale-Process rule to allow UDP for %q ...", exe)
d, err = ft.runFirewall("add", "rule", "name=Tailscale-Process",
"dir=in",
"action=allow",
"edge=yes",
"program="+exe,
"protocol=udp",
"profile=any",
"enable=yes",
)
if err != nil {
ft.logf("error adding Tailscale-Process rule: %v", err)
} else {
ft.mu.Lock()
ft.didProcRule = true
ft.mu.Unlock()
ft.logf("added Tailscale-Process rule in %v", d)
}
}
}
for _, cidr := range local {
ft.logf("adding Tailscale-In rule to allow %v ...", cidr)
var d time.Duration
d, err := ft.runFirewall("add", "rule", "name=Tailscale-In", "dir=in", "action=allow", "localip="+cidr, "profile=private", "enable=yes")
if err != nil {
ft.logf("error adding Tailscale-In rule to allow %v: %v", cidr, err)
return err
}
ft.logf("added Tailscale-In rule to allow %v in %v", cidr, d)
}
if killswitch && ft.fwProc == nil {
exe, err := os.Executable()
if err != nil {
return err
}
proc := exec.Command(exe, "/firewall", ft.tunGUID.String())
var (
out io.ReadCloser
in io.WriteCloser
)
out, err = proc.StdoutPipe()
if err != nil {
return err
}
proc.Stderr = proc.Stdout
in, err = proc.StdinPipe()
if err != nil {
out.Close()
return err
}
go func(out io.ReadCloser) {
b := bufio.NewReaderSize(out, 1<<10)
for {
line, err := b.ReadString('\n')
if err != nil {
return
}
line = strings.TrimSpace(line)
if line != "" {
ft.logf("fw-child: %s", line)
}
}
}(out)
if err := proc.Start(); err != nil {
return err
}
ft.stop = in
ft.fwProc = proc
} else if !killswitch && ft.fwProc != nil {
ft.stop.Close()
ft.stop = nil
ft.fwProc.Wait()
ft.fwProc = nil
}
return nil
}
func strsEqual(a, b []string) bool {
if len(a) != len(b) {
return false

View File

@ -194,6 +194,11 @@ func (r *Resolver) Resolve(domain string, tp dns.Type) (netaddr.IP, dns.RCode, e
return netaddr.IP{}, dns.RCodeServerFailure, errMapNotSet
}
// Reject .onion domains per RFC 7686.
if dnsname.HasSuffix(domain, ".onion") {
return netaddr.IP{}, dns.RCodeNameError, nil
}
anyHasSuffix := false
for _, suffix := range dnsMap.rootDomains {
if dnsname.HasSuffix(domain, suffix) {

View File

@ -219,6 +219,7 @@ func TestResolve(t *testing.T) {
{"mx-ipv6", "test2.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeSuccess},
{"mx-nxdomain", "test3.ipn.dev.", dns.TypeMX, netaddr.IP{}, dns.RCodeNameError},
{"ns-nxdomain", "test3.ipn.dev.", dns.TypeNS, netaddr.IP{}, dns.RCodeNameError},
{"onion-domain", "footest.onion.", dns.TypeA, netaddr.IP{}, dns.RCodeNameError},
}
for _, tt := range tests {

View File

@ -120,17 +120,24 @@ type userspaceEngine struct {
mu sync.Mutex // guards following; see lock order comment below
closing bool // Close was called (even if we're still closing)
statusCallback StatusCallback
linkChangeCallback func(major bool, newState *interfaces.State)
peerSequence []wgkey.Key
endpoints []string
pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers
linkState *interfaces.State
pingers map[wgkey.Key]*pinger // legacy pingers for pre-discovery peers
pendOpen map[flowtrack.Tuple]*pendingOpenFlow // see pendopen.go
networkMapCallbacks map[*someHandle]NetworkMapCallback
// Lock ordering: magicsock.Conn.mu, wgLock, then mu.
}
// InternalsGetter is implemented by Engines that can export their internals.
type InternalsGetter interface {
GetInternals() (*tstun.TUN, *magicsock.Conn)
}
func (e *userspaceEngine) GetInternals() (*tstun.TUN, *magicsock.Conn) {
return e.tundev, e.magicConn
}
// RouterGen is the signature for a function that creates a
// router.Router.
type RouterGen func(logf logger.Logf, wgdev *device.Device, tundev tun.Device) (router.Router, error)
@ -157,36 +164,18 @@ type Config struct {
// If zero, a port is automatically selected.
ListenPort uint16
// Fake determines whether this engine is running in fake mode,
// which disables such features as DNS configuration and unrestricted ICMP Echo responses.
// Fake determines whether this engine should automatically
// reply to ICMP pings.
Fake bool
// FakeImplFactory, if non-nil, creates a FakeImpl to use as a fake engine
// implementation. Two values are typical: nil, for a basic ping-only fake
// implementation, and netstack.Create, which creates a userspace network
// stack using gvisor's netstack. The desire to keep netstack out of some
// binaries is why the FakeImpl interface exists, so wgengine need not
// depend on gvisor.
FakeImplFactory FakeImplFactory
}
// FakeImpl is a fake or alternate version of Engine that can be started. See
// Config.FakeImplFactory for details.
type FakeImpl interface {
Start() error
}
// FakeImplFactory is the type of a function used to create FakeImpls.
type FakeImplFactory func(logger.Logf, *tstun.TUN, Engine, *magicsock.Conn) (FakeImpl, error)
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16, impl FakeImplFactory) (Engine, error) {
func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16) (Engine, error) {
logf("Starting userspace wireguard engine (with fake TUN device)")
return NewUserspaceEngine(logf, Config{
TUN: tstun.NewFakeTUN(),
RouterGen: router.NewFake,
ListenPort: listenPort,
Fake: true,
FakeImplFactory: impl,
TUN: tstun.NewFakeTUN(),
RouterGen: router.NewFake,
ListenPort: listenPort,
Fake: true,
})
}
@ -244,8 +233,6 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
pingers: make(map[wgkey.Key]*pinger),
}
e.localAddrs.Store(map[netaddr.IP]bool{})
e.linkState, _ = getLinkState()
logf("link state: %+v", e.linkState)
if conf.LinkMonitor != nil {
e.linkMon = conf.LinkMonitor
@ -258,9 +245,12 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
e.linkMon = mon
e.linkMonOwned = true
}
unregisterMonWatch := e.linkMon.RegisterChangeCallback(func() {
e.LinkChange(false)
logf("link state: %+v", e.linkMon.InterfaceState())
unregisterMonWatch := e.linkMon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {
tshttpproxy.InvalidateCache()
e.linkChange(changed, st)
})
closePool.addFunc(unregisterMonWatch)
e.linkMonUnregister = unregisterMonWatch
@ -286,22 +276,11 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_
return nil, fmt.Errorf("wgengine: %v", err)
}
closePool.add(e.magicConn)
e.magicConn.SetNetworkUp(e.linkState.AnyInterfaceUp())
e.magicConn.SetNetworkUp(e.linkMon.InterfaceState().AnyInterfaceUp())
// Respond to all pings only in fake mode.
if conf.Fake {
if f := conf.FakeImplFactory; f != nil {
impl, err := f(logf, e.tundev, e, e.magicConn)
if err != nil {
return nil, err
}
if err := impl.Start(); err != nil {
return nil, err
}
} else {
// Respond to all pings only in fake mode.
e.tundev.PostFilterIn = echoRespondToAll
}
e.tundev.PostFilterIn = echoRespondToAll
}
e.tundev.PreFilterOut = e.handleLocalPackets
@ -1268,30 +1247,21 @@ func (e *userspaceEngine) Wait() {
<-e.waitCh
}
func (e *userspaceEngine) setLinkState(st *interfaces.State) (changed bool, cb func(major bool, newState *interfaces.State)) {
if st == nil {
return false, nil
}
e.mu.Lock()
defer e.mu.Unlock()
changed = e.linkState == nil || !st.Equal(e.linkState)
e.linkState = st
return changed, e.linkChangeCallback
func (e *userspaceEngine) GetLinkMonitor() *monitor.Mon {
return e.linkMon
}
func (e *userspaceEngine) LinkChange(isExpensive bool) {
cur, err := getLinkState()
if err != nil {
e.logf("LinkChange: interfaces.GetState: %v", err)
return
}
cur.IsExpensive = isExpensive
needRebind, linkChangeCallback := e.setLinkState(cur)
// LinkChange signals a network change event. It's currently
// (2021-03-03) only called on Android.
func (e *userspaceEngine) LinkChange(_ bool) {
e.linkMon.InjectEvent()
}
func (e *userspaceEngine) linkChange(changed bool, cur *interfaces.State) {
up := cur.AnyInterfaceUp()
if !up {
e.logf("LinkChange: all links down; pausing: %v", cur)
} else if needRebind {
} else if changed {
e.logf("LinkChange: major, rebinding. New state: %v", cur)
} else {
e.logf("[v1] LinkChange: minor")
@ -1300,23 +1270,11 @@ func (e *userspaceEngine) LinkChange(isExpensive bool) {
e.magicConn.SetNetworkUp(up)
why := "link-change-minor"
if needRebind {
if changed {
why = "link-change-major"
e.magicConn.Rebind()
}
e.magicConn.ReSTUN(why)
if linkChangeCallback != nil {
go linkChangeCallback(needRebind, cur)
}
}
func (e *userspaceEngine) SetLinkChangeCallback(cb func(major bool, newState *interfaces.State)) {
e.mu.Lock()
defer e.mu.Unlock()
e.linkChangeCallback = cb
if e.linkState != nil {
go cb(false, e.linkState)
}
}
func (e *userspaceEngine) AddNetworkMapCallback(cb NetworkMapCallback) func() {
@ -1334,14 +1292,6 @@ func (e *userspaceEngine) AddNetworkMapCallback(cb NetworkMapCallback) func() {
}
}
func getLinkState() (*interfaces.State, error) {
s, err := interfaces.GetState()
if s != nil {
s.RemoveTailscaleInterfaces()
}
return s, err
}
func (e *userspaceEngine) SetNetInfoCallback(cb NetInfoCallback) {
e.magicConn.SetNetInfoCallback(cb)
}

View File

@ -84,7 +84,7 @@ func TestNoteReceiveActivity(t *testing.T) {
}
func TestUserspaceEngineReconfig(t *testing.T) {
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
e, err := NewFakeUserspaceEngine(t.Logf, 0)
if err != nil {
t.Fatal(err)
}

View File

@ -14,10 +14,10 @@ import (
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/wgcfg"
@ -75,10 +75,11 @@ func (e *watchdogEngine) watchdog(name string, fn func()) {
func (e *watchdogEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config) error {
return e.watchdogErr("Reconfig", func() error { return e.wrap.Reconfig(cfg, routerCfg) })
}
func (e *watchdogEngine) GetLinkMonitor() *monitor.Mon {
return e.wrap.GetLinkMonitor()
}
func (e *watchdogEngine) GetFilter() *filter.Filter {
var x *filter.Filter
e.watchdog("GetFilter", func() { x = e.wrap.GetFilter() })
return x
return e.wrap.GetFilter()
}
func (e *watchdogEngine) SetFilter(filt *filter.Filter) {
e.watchdog("SetFilter", func() { e.wrap.SetFilter(filt) })
@ -101,9 +102,6 @@ func (e *watchdogEngine) RequestStatus() {
func (e *watchdogEngine) LinkChange(isExpensive bool) {
e.watchdog("LinkChange", func() { e.wrap.LinkChange(isExpensive) })
}
func (e *watchdogEngine) SetLinkChangeCallback(cb func(major bool, newState *interfaces.State)) {
e.watchdog("SetLinkChangeCallback", func() { e.wrap.SetLinkChangeCallback(cb) })
}
func (e *watchdogEngine) SetDERPMap(m *tailcfg.DERPMap) {
e.watchdog("SetDERPMap", func() { e.wrap.SetDERPMap(m) })
}

View File

@ -17,7 +17,7 @@ func TestWatchdog(t *testing.T) {
t.Run("default watchdog does not fire", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
e, err := NewFakeUserspaceEngine(t.Logf, 0)
if err != nil {
t.Fatal(err)
}
@ -35,7 +35,7 @@ func TestWatchdog(t *testing.T) {
t.Run("watchdog fires on blocked getStatus", func(t *testing.T) {
t.Parallel()
e, err := NewFakeUserspaceEngine(t.Logf, 0, nil)
e, err := NewFakeUserspaceEngine(t.Logf, 0)
if err != nil {
t.Fatal(err)
}

View File

@ -9,10 +9,10 @@ import (
"inet.af/netaddr"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/interfaces"
"tailscale.com/tailcfg"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/monitor"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/tsdns"
"tailscale.com/wgengine/wgcfg"
@ -72,6 +72,9 @@ type Engine interface {
// WireGuard status changes.
SetStatusCallback(StatusCallback)
// GetLinkMonitor returns the link monitor.
GetLinkMonitor() *monitor.Mon
// RequestStatus requests a WireGuard status update right
// away, sent to the callback registered via SetStatusCallback.
RequestStatus()
@ -87,15 +90,18 @@ type Engine interface {
Wait()
// LinkChange informs the engine that the system network
// link has changed. The isExpensive parameter is set on links
// where sending packets uses substantial power or money,
// such as mobile data on a phone.
// link has changed.
//
// The isExpensive parameter is not used.
//
// LinkChange should be called whenever something changed with
// the network, no matter how minor. The implementation should
// look at the state of the network and decide whether the
// change from before is interesting enough to warrant taking
// action on.
// the network, no matter how minor.
//
// Deprecated: don't use this method. It was removed shortly
// before the Tailscale 1.6 release when we remembered that
// Android doesn't use the Linux-based link monitor and has
// its own mechanism that uses LinkChange. Android is the only
// caller of this method now. Don't add more.
LinkChange(isExpensive bool)
// SetDERPMap controls which (if any) DERP servers are used.
@ -120,13 +126,6 @@ type Engine interface {
// new NetInfo summary is available.
SetNetInfoCallback(NetInfoCallback)
// SetLinkChangeCallback sets the function to call when the
// link state changes.
// The provided function is run in a new goroutine once upon
// initial call (if the engine has a known link state) and
// upon any change.
SetLinkChangeCallback(func(major bool, newState *interfaces.State))
// DiscoPublicKey gets the public key used for path discovery
// messages.
DiscoPublicKey() tailcfg.DiscoKey