mirror of
https://github.com/tailscale/tailscale.git
synced 2026-05-05 12:16:44 +02:00
Merge branch 'main' into Xe/reset-logid-on-logout-login
This commit is contained in:
commit
e951666778
5
Makefile
5
Makefile
@ -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)
|
||||
|
||||
90
client/tailscale/tailscale.go
Normal file
90
client/tailscale/tailscale.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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+
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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+
|
||||
|
||||
@ -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() }
|
||||
|
||||
@ -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
|
||||
|
||||
69
control/controlclient/debug.go
Normal file
69
control/controlclient/debug.go
Normal 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
|
||||
})
|
||||
}
|
||||
11
control/controlclient/debug_test.go
Normal file
11
control/controlclient/debug_test.go
Normal 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())
|
||||
}
|
||||
@ -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
4
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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":
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
117
net/dnsfallback/dnsfallback.go
Normal file
117
net/dnsfallback/dnsfallback.go
Normal 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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
34
portlist/clean.go
Normal 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
42
portlist/clean_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
portlist/portlist_linux_test.go
Normal file
67
portlist/portlist_linux_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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[:]) }
|
||||
|
||||
9
tempfork/wireguard-windows/firewall/README.md
Normal file
9
tempfork/wireguard-windows/firewall/README.md
Normal 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.
|
||||
190
tempfork/wireguard-windows/firewall/blocker.go
Normal file
190
tempfork/wireguard-windows/firewall/blocker.go
Normal 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
|
||||
}
|
||||
}
|
||||
150
tempfork/wireguard-windows/firewall/helpers.go
Normal file
150
tempfork/wireguard-windows/firewall/helpers.go
Normal 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
|
||||
}
|
||||
8
tempfork/wireguard-windows/firewall/mksyscall.go
Normal file
8
tempfork/wireguard-windows/firewall/mksyscall.go
Normal 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
|
||||
1086
tempfork/wireguard-windows/firewall/rules.go
Normal file
1086
tempfork/wireguard-windows/firewall/rules.go
Normal file
File diff suppressed because it is too large
Load Diff
36
tempfork/wireguard-windows/firewall/syscall_windows.go
Normal file
36
tempfork/wireguard-windows/firewall/syscall_windows.go
Normal 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
|
||||
412
tempfork/wireguard-windows/firewall/types_windows.go
Normal file
412
tempfork/wireguard-windows/firewall/types_windows.go
Normal 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
|
||||
)
|
||||
90
tempfork/wireguard-windows/firewall/types_windows_32.go
Normal file
90
tempfork/wireguard-windows/firewall/types_windows_32.go
Normal 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
|
||||
}
|
||||
87
tempfork/wireguard-windows/firewall/types_windows_64.go
Normal file
87
tempfork/wireguard-windows/firewall/types_windows_64.go
Normal 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
|
||||
}
|
||||
540
tempfork/wireguard-windows/firewall/types_windows_test.go
Normal file
540
tempfork/wireguard-windows/firewall/types_windows_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
132
tempfork/wireguard-windows/firewall/zsyscall_windows.go
Normal file
132
tempfork/wireguard-windows/firewall/zsyscall_windows.go
Normal 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
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
28
wgengine/monitor/monitor_darwin_test.go
Normal file
28
wgengine/monitor/monitor_darwin_test.go
Normal 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)
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
72
wgengine/monitor/monitor_polling.go
Normal file
72
wgengine/monitor/monitor_polling.go
Normal 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 }
|
||||
89
wgengine/monitor/monitor_test.go
Normal file
89
wgengine/monitor/monitor_test.go
Normal 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 {}
|
||||
}
|
||||
}
|
||||
@ -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 }
|
||||
@ -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)
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
39
wgengine/router/config_clone.go
Normal file
39
wgengine/router/config_clone.go
Normal 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
|
||||
}{})
|
||||
@ -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 {
|
||||
|
||||
33
wgengine/router/dns/config_clone.go
Normal file
33
wgengine/router/dns/config_clone.go
Normal 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
|
||||
}{})
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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) })
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user