Compare commits
1 Commits
dsnet/admi
...
upnpdebug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e0b588618 |
@@ -1,155 +0,0 @@
|
||||
// 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 cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/dsnet/golib/jsonfmt"
|
||||
"github.com/peterbourgon/ff/v2/ffcli"
|
||||
)
|
||||
|
||||
const tailscaleAPIURL = "https://api.tailscale.com/api"
|
||||
|
||||
var adminCmd = &ffcli.Command{
|
||||
Name: "admin",
|
||||
ShortUsage: "admin <subcommand> [command flags]",
|
||||
ShortHelp: "Administrate a tailnet",
|
||||
LongHelp: strings.TrimSpace(`
|
||||
The "tailscale admin" command administrates a tailnet through the CLI.
|
||||
It is a wrapper over the RESTful API served at ` + tailscaleAPIURL + `.
|
||||
See https://github.com/tailscale/tailscale/blob/main/api.md for more information
|
||||
about the API itself.
|
||||
|
||||
In order for the "admin" command to call the API, it needs an API key,
|
||||
which is specified by setting the TAILSCALE_API_KEY environment variable.
|
||||
Also, to easy usage, the tailnet to administrate can be specified through the
|
||||
TAILSCALE_NET_NAME environment variable, or specified with the -tailnet flag.
|
||||
|
||||
Visit https://login.tailscale.com/admin/settings/authkeys in order to obtain
|
||||
an API key.
|
||||
`),
|
||||
FlagSet: (func() *flag.FlagSet {
|
||||
fs := flag.NewFlagSet("status", flag.ExitOnError)
|
||||
// TODO(dsnet): Can we determine the default tailnet from what this
|
||||
// device is currently part of? Alternatively, when add specific logic
|
||||
// to handle auth keys, we can always associate a given key with a
|
||||
// specific tailnet.
|
||||
fs.StringVar(&adminArgs.tailnet, "tailnet", os.Getenv("TAILSCALE_NET_NAME"), "which tailnet to administrate")
|
||||
return fs
|
||||
})(),
|
||||
// TODO(dsnet): Handle users, groups, dns.
|
||||
Subcommands: []*ffcli.Command{{
|
||||
Name: "acl",
|
||||
ShortUsage: "acl <subcommand> [command flags]",
|
||||
ShortHelp: "Manage the ACL for a tailnet",
|
||||
// TODO(dsnet): Handle preview.
|
||||
Subcommands: []*ffcli.Command{{
|
||||
Name: "get",
|
||||
ShortUsage: "get",
|
||||
ShortHelp: "Downloads the HuJSON ACL file to stdout",
|
||||
Exec: checkAdminKey(runAdminACLGet),
|
||||
}, {
|
||||
Name: "set",
|
||||
ShortUsage: "set",
|
||||
ShortHelp: "Uploads the HuJSON ACL file from stdin",
|
||||
Exec: checkAdminKey(runAdminACLSet),
|
||||
}},
|
||||
Exec: runHelp,
|
||||
}, {
|
||||
Name: "devices",
|
||||
ShortUsage: "devices <subcommand> [command flags]",
|
||||
ShortHelp: "Manage devices in a tailnet",
|
||||
Subcommands: []*ffcli.Command{{
|
||||
Name: "list",
|
||||
ShortUsage: "list",
|
||||
ShortHelp: "List all devices in a tailnet",
|
||||
Exec: checkAdminKey(runAdminDevicesList),
|
||||
}, {
|
||||
Name: "get",
|
||||
ShortUsage: "get <id>",
|
||||
ShortHelp: "Get information about a specific device",
|
||||
Exec: checkAdminKey(runAdminDevicesGet),
|
||||
}},
|
||||
Exec: runHelp,
|
||||
}},
|
||||
Exec: runHelp,
|
||||
}
|
||||
|
||||
var adminArgs struct {
|
||||
tailnet string // which tailnet to operate upon
|
||||
}
|
||||
|
||||
func checkAdminKey(f func(context.Context, string, []string) error) func(context.Context, []string) error {
|
||||
return func(ctx context.Context, args []string) error {
|
||||
// TODO(dsnet): We should have a subcommand or flag to manage keys.
|
||||
// Use of an environment variable is a temporary hack.
|
||||
key := os.Getenv("TAILSCALE_API_KEY")
|
||||
if !strings.HasPrefix(key, "tskey-") {
|
||||
return errors.New("no API key specified")
|
||||
}
|
||||
return f(ctx, key, args)
|
||||
}
|
||||
}
|
||||
|
||||
func runAdminACLGet(ctx context.Context, key string, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
return adminCallAPI(ctx, key, http.MethodGet, "/v2/tailnet/"+adminArgs.tailnet+"/acl", nil, os.Stdout)
|
||||
}
|
||||
|
||||
func runAdminACLSet(ctx context.Context, key string, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
return adminCallAPI(ctx, key, http.MethodPost, "/v2/tailnet/"+adminArgs.tailnet+"/acl", os.Stdin, os.Stdout)
|
||||
}
|
||||
|
||||
func runAdminDevicesList(ctx context.Context, key string, args []string) error {
|
||||
if len(args) > 0 {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
return adminCallAPI(ctx, key, http.MethodGet, "/v2/tailnet/"+adminArgs.tailnet+"/devices", nil, os.Stdout)
|
||||
}
|
||||
|
||||
func runAdminDevicesGet(ctx context.Context, key string, args []string) error {
|
||||
if len(args) != 1 {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
return adminCallAPI(ctx, key, http.MethodGet, "/v2/device/"+args[0], nil, os.Stdout)
|
||||
}
|
||||
|
||||
func adminCallAPI(ctx context.Context, key, method, path string, in io.Reader, out io.Writer) error {
|
||||
req, err := http.NewRequestWithContext(ctx, method, tailscaleAPIURL+path, in)
|
||||
req.SetBasicAuth(key, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send HTTP request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to receive HTTP response: %w", err)
|
||||
}
|
||||
b, err = jsonfmt.Format(b)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to format JSON response: %w", err)
|
||||
}
|
||||
_, err = out.Write(b)
|
||||
return err
|
||||
|
||||
}
|
||||
@@ -76,10 +76,6 @@ func ActLikeCLI() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func runHelp(context.Context, []string) error {
|
||||
return flag.ErrHelp
|
||||
}
|
||||
|
||||
// Run runs the CLI. The args do not include the binary name.
|
||||
func Run(args []string) error {
|
||||
if len(args) == 1 && (args[0] == "-V" || args[0] == "--version") {
|
||||
@@ -103,7 +99,6 @@ change in the future.
|
||||
upCmd,
|
||||
downCmd,
|
||||
logoutCmd,
|
||||
adminCmd,
|
||||
netcheckCmd,
|
||||
ipCmd,
|
||||
statusCmd,
|
||||
@@ -114,7 +109,7 @@ change in the future.
|
||||
bugReportCmd,
|
||||
},
|
||||
FlagSet: rootfs,
|
||||
Exec: runHelp,
|
||||
Exec: func(context.Context, []string) error { return flag.ErrHelp },
|
||||
UsageFunc: usageFunc,
|
||||
}
|
||||
for _, c := range rootCmd.Subcommands {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || windows || darwin
|
||||
// +build linux windows darwin
|
||||
|
||||
package cli
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !linux && !windows && !darwin
|
||||
// +build !linux,!windows,!darwin
|
||||
|
||||
package cli
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/peterbourgon/ff/v2/ffcli"
|
||||
"github.com/toqueteos/webbrowser"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/interfaces"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
|
||||
@@ -61,7 +63,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
if statusArgs.json {
|
||||
if statusArgs.active {
|
||||
for peer, ps := range st.Peer {
|
||||
if !ps.Active {
|
||||
if !peerActive(ps) {
|
||||
delete(st.Peer, peer)
|
||||
}
|
||||
}
|
||||
@@ -129,6 +131,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
var buf bytes.Buffer
|
||||
f := func(format string, a ...interface{}) { fmt.Fprintf(&buf, format, a...) }
|
||||
printPS := func(ps *ipnstate.PeerStatus) {
|
||||
active := peerActive(ps)
|
||||
f("%-15s %-20s %-12s %-7s ",
|
||||
firstIPString(ps.TailscaleIPs),
|
||||
dnsOrQuoteHostname(st, ps),
|
||||
@@ -137,7 +140,7 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
)
|
||||
relay := ps.Relay
|
||||
anyTraffic := ps.TxBytes != 0 || ps.RxBytes != 0
|
||||
if !ps.Active {
|
||||
if !active {
|
||||
if ps.ExitNode {
|
||||
f("idle; exit node")
|
||||
} else if anyTraffic {
|
||||
@@ -176,7 +179,8 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
}
|
||||
ipnstate.SortPeers(peers)
|
||||
for _, ps := range peers {
|
||||
if statusArgs.active && !ps.Active {
|
||||
active := peerActive(ps)
|
||||
if statusArgs.active && !active {
|
||||
continue
|
||||
}
|
||||
printPS(ps)
|
||||
@@ -186,6 +190,13 @@ func runStatus(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// peerActive reports whether ps has recent activity.
|
||||
//
|
||||
// TODO: have the server report this bool instead.
|
||||
func peerActive(ps *ipnstate.PeerStatus) bool {
|
||||
return !ps.LastWrite.IsZero() && mono.Since(ps.LastWrite) < 2*time.Minute
|
||||
}
|
||||
|
||||
func dnsOrQuoteHostname(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
|
||||
baseName := dnsname.TrimSuffix(ps.DNSName, st.MagicDNSSuffix)
|
||||
if baseName != "" {
|
||||
|
||||
@@ -3,12 +3,11 @@ 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/internal/common from github.com/alexbrainman/sspi/negotiate
|
||||
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
|
||||
github.com/dsnet/golib/jsonfmt from tailscale.com/cmd/tailscale/cli
|
||||
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
|
||||
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli
|
||||
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/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2
|
||||
github.com/tailscale/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||
github.com/tailscale/goupnp/httpu from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||
@@ -50,16 +49,14 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
||||
tailscale.com/syncs from tailscale.com/net/interfaces+
|
||||
tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+
|
||||
W tailscale.com/tsconst from tailscale.com/net/interfaces
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/tstime/rate
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||
tailscale.com/types/dnstype from tailscale.com/tailcfg
|
||||
tailscale.com/types/empty from tailscale.com/ipn
|
||||
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
||||
tailscale.com/types/key from tailscale.com/derp+
|
||||
tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/types/netmap from tailscale.com/ipn
|
||||
tailscale.com/types/opt from tailscale.com/net/netcheck+
|
||||
tailscale.com/types/pad32 from tailscale.com/derp
|
||||
tailscale.com/types/persist from tailscale.com/ipn
|
||||
tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+
|
||||
tailscale.com/types/structs from tailscale.com/ipn+
|
||||
|
||||
@@ -207,20 +207,6 @@ func debugPortmap(ctx context.Context) error {
|
||||
defer cancel()
|
||||
|
||||
portmapper.VerboseLogs = true
|
||||
switch os.Getenv("TS_DEBUG_PORTMAP_TYPE") {
|
||||
case "":
|
||||
case "pmp":
|
||||
portmapper.DisablePCP = true
|
||||
portmapper.DisableUPnP = true
|
||||
case "pcp":
|
||||
portmapper.DisablePMP = true
|
||||
portmapper.DisableUPnP = true
|
||||
case "upnp":
|
||||
portmapper.DisablePCP = true
|
||||
portmapper.DisablePMP = true
|
||||
default:
|
||||
log.Fatalf("TS_DEBUG_PORTMAP_TYPE must be one of pmp,pcp,upnp")
|
||||
}
|
||||
|
||||
done := make(chan bool, 1)
|
||||
|
||||
@@ -264,13 +250,6 @@ func debugPortmap(ctx context.Context) error {
|
||||
}
|
||||
logf("gw=%v; self=%v", gw, selfIP)
|
||||
|
||||
uc, err := net.ListenPacket("udp", "0.0.0.0:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer uc.Close()
|
||||
c.SetLocalPort(uint16(uc.LocalAddr().(*net.UDPAddr).Port))
|
||||
|
||||
res, err := c.Probe(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Probe: %v", err)
|
||||
@@ -282,6 +261,13 @@ func debugPortmap(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
uc, err := net.ListenPacket("udp", "0.0.0.0:0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer uc.Close()
|
||||
c.SetLocalPort(uint16(uc.LocalAddr().(*net.UDPAddr).Port))
|
||||
|
||||
if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok {
|
||||
logf("mapping: %v", ext)
|
||||
} else {
|
||||
|
||||
@@ -10,10 +10,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns
|
||||
github.com/golang/snappy from github.com/klauspost/compress/zstd
|
||||
github.com/google/btree from inet.af/netstack/tcpip/header+
|
||||
L github.com/insomniacslk/dhcp/dhcpv4 from tailscale.com/net/tstun
|
||||
L github.com/insomniacslk/dhcp/iana from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/insomniacslk/dhcp/interfaces from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/insomniacslk/dhcp/rfc1035label from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/josharian/native from github.com/mdlayher/netlink+
|
||||
L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor
|
||||
L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink
|
||||
@@ -27,16 +23,13 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
|
||||
W github.com/pkg/errors from github.com/tailscale/certstore
|
||||
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+
|
||||
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2
|
||||
github.com/tailscale/goupnp/dcps/internetgateway2 from tailscale.com/net/portmapper
|
||||
github.com/tailscale/goupnp/httpu from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/scpd from github.com/tailscale/goupnp
|
||||
github.com/tailscale/goupnp/soap from github.com/tailscale/goupnp+
|
||||
github.com/tailscale/goupnp/ssdp from github.com/tailscale/goupnp
|
||||
github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck
|
||||
L github.com/u-root/uio/rand from github.com/insomniacslk/dhcp/dhcpv4
|
||||
L github.com/u-root/uio/ubinary from github.com/u-root/uio/uio
|
||||
L github.com/u-root/uio/uio from github.com/insomniacslk/dhcp/dhcpv4+
|
||||
💣 go4.org/intern from inet.af/netaddr
|
||||
💣 go4.org/mem from tailscale.com/derp+
|
||||
go4.org/unsafe/assume-no-moving-gc from go4.org/intern
|
||||
@@ -73,7 +66,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
inet.af/netstack/tcpip/network/hash from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/internal/fragmentation from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/internal/ip from inet.af/netstack/tcpip/network/ipv4+
|
||||
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/wgengine/netstack+
|
||||
inet.af/netstack/tcpip/network/ipv4 from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/network/ipv6 from tailscale.com/wgengine/netstack
|
||||
inet.af/netstack/tcpip/ports from inet.af/netstack/tcpip/stack+
|
||||
inet.af/netstack/tcpip/seqnum from inet.af/netstack/tcpip/header+
|
||||
@@ -128,7 +121,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/tlsdial from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+
|
||||
💣 tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/paths from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/portlist from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/safesocket from tailscale.com/ipn/ipnserver+
|
||||
@@ -139,7 +132,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/tstime from tailscale.com/wgengine/magicsock
|
||||
💣 tailscale.com/tstime/mono from tailscale.com/net/tstun+
|
||||
tailscale.com/tstime/rate from tailscale.com/wgengine/filter
|
||||
tailscale.com/types/dnstype from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/types/empty from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/types/ipproto from tailscale.com/net/flowtrack+
|
||||
@@ -148,7 +140,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/types/netmap from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/nettype from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/types/opt from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/pad32 from tailscale.com/net/tstun+
|
||||
tailscale.com/types/persist from tailscale.com/control/controlclient+
|
||||
tailscale.com/types/preftype from tailscale.com/ipn+
|
||||
tailscale.com/types/structs from tailscale.com/control/controlclient+
|
||||
|
||||
@@ -68,13 +68,9 @@ func defaultTunName() string {
|
||||
}
|
||||
|
||||
var args struct {
|
||||
// tunname is a /dev/net/tun tunnel name ("tailscale0"), the
|
||||
// string "userspace-networking", "tap:TAPNAME[:BRIDGENAME]"
|
||||
// or comma-separated list thereof.
|
||||
tunname string
|
||||
|
||||
cleanup bool
|
||||
debug string
|
||||
tunname string // tun name, "userspace-networking", or comma-separated list thereof
|
||||
port uint16
|
||||
statepath string
|
||||
socketpath string
|
||||
@@ -142,7 +138,7 @@ func main() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" && os.Getuid() != 0 && !strings.Contains(args.tunname, "userspace-networking") && !args.cleanup {
|
||||
if runtime.GOOS == "darwin" && os.Getuid() != 0 && !strings.Contains(args.tunname, "userspace-networking") {
|
||||
log.SetFlags(0)
|
||||
log.Fatalf("tailscaled requires root; use sudo tailscaled (or use --tun=userspace-networking)")
|
||||
}
|
||||
@@ -356,12 +352,6 @@ func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.
|
||||
return nil, false, err
|
||||
}
|
||||
conf.Tun = dev
|
||||
if strings.HasPrefix(name, "tap:") {
|
||||
conf.IsTAP = true
|
||||
e, err := wgengine.NewUserspaceEngine(logf, conf)
|
||||
return e, false, err
|
||||
}
|
||||
|
||||
r, err := router.New(logf, dev, linkMon)
|
||||
if err != nil {
|
||||
dev.Close()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package main // import "tailscale.com/cmd/tailscaled"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
// The tsshd binary is an SSH server that accepts connections
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux && !android
|
||||
// +build linux,!android
|
||||
|
||||
package controlclient
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && cgo
|
||||
// +build windows,cgo
|
||||
|
||||
// darwin,cgo is also supported by certstore but machineCertificateSubject will
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows || !cgo
|
||||
// +build !windows !cgo
|
||||
|
||||
package controlclient
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"tailscale.com/metrics"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/pad32"
|
||||
"tailscale.com/version"
|
||||
)
|
||||
|
||||
@@ -77,6 +76,13 @@ const (
|
||||
writeTimeout = 2 * time.Second
|
||||
)
|
||||
|
||||
const host64bit = (^uint(0) >> 32) & 1 // 1 on 64-bit, 0 on 32-bit
|
||||
|
||||
// pad32bit is 4 on 32-bit machines and 0 on 64-bit.
|
||||
// It exists so the Server struct's atomic fields can be aligned to 8
|
||||
// byte boundaries. (As tested by GOARCH=386 go test, etc)
|
||||
const pad32bit = 4 - host64bit*4 // 0 on 64-bit, 4 on 32-bit
|
||||
|
||||
// Server is a DERP server.
|
||||
type Server struct {
|
||||
// WriteTimeout, if non-zero, specifies how long to wait
|
||||
@@ -92,20 +98,20 @@ type Server struct {
|
||||
metaCert []byte // the encoded x509 cert to send after LetsEncrypt cert+intermediate
|
||||
|
||||
// Counters:
|
||||
_ pad32.Four
|
||||
_ [pad32bit]byte
|
||||
packetsSent, bytesSent expvar.Int
|
||||
packetsRecv, bytesRecv expvar.Int
|
||||
packetsRecvByKind metrics.LabelMap
|
||||
packetsRecvDisco *expvar.Int
|
||||
packetsRecvOther *expvar.Int
|
||||
_ pad32.Four
|
||||
_ [pad32bit]byte
|
||||
packetsDropped expvar.Int
|
||||
packetsDroppedReason metrics.LabelMap
|
||||
packetsDroppedReasonCounters []*expvar.Int // indexed by dropReason
|
||||
packetsDroppedType metrics.LabelMap
|
||||
packetsDroppedTypeDisco *expvar.Int
|
||||
packetsDroppedTypeOther *expvar.Int
|
||||
_ pad32.Four
|
||||
_ [pad32bit]byte
|
||||
packetsForwardedOut expvar.Int
|
||||
packetsForwardedIn expvar.Int
|
||||
peerGoneFrames expvar.Int // number of peer gone frames sent
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package disco
|
||||
|
||||
2
go.mod
2
go.mod
@@ -9,7 +9,6 @@ require (
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/creack/pty v1.1.9
|
||||
github.com/dave/jennifer v1.4.1
|
||||
github.com/dsnet/golib/jsonfmt v1.0.0
|
||||
github.com/frankban/quicktest v1.13.0
|
||||
github.com/gliderlabs/ssh v0.3.2
|
||||
github.com/go-multierror/multierror v1.0.2
|
||||
@@ -20,7 +19,6 @@ require (
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/goreleaser/nfpm v1.10.3
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/klauspost/compress v1.12.2
|
||||
|
||||
16
go.sum
16
go.sum
@@ -96,15 +96,12 @@ github.com/denis-tingajkin/go-header v0.3.1 h1:ymEpSiFjeItCy1FOP+x0M2KdCELdEAHUs
|
||||
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dsnet/golib/jsonfmt v1.0.0 h1:qrfqvbua2pQvj+dt3BcxEwwqy86F7ri2NdLQLm6g2TQ=
|
||||
github.com/dsnet/golib/jsonfmt v1.0.0/go.mod h1:C0/DCakJBCSVJ3mWBjDVzym2Wf7w5hpvwgHCwI/M7/w=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
@@ -300,7 +297,6 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
|
||||
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
@@ -308,8 +304,6 @@ github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e h1:sgh63o+pm5kcdrgyYaCIoeD7mccyL6MscVmy+DvY6C4=
|
||||
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -332,7 +326,6 @@ github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/rasw
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
@@ -398,7 +391,6 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mbilski/exhaustivestruct v1.1.0 h1:4ykwscnAFeHJruT+EY3M3vdeP8uXMh0VV2E61iR7XD8=
|
||||
github.com/mbilski/exhaustivestruct v1.1.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc=
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43 h1:WgyLFv10Ov49JAQI/ZLUkCZ7VJS3r74hwFIGXJsgZlY=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
@@ -414,8 +406,6 @@ github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuri
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/mdlayher/netlink v1.4.1 h1:I154BCU+mKlIf7BgcAJB2r7QjveNPty6uNY1g9ChVfI=
|
||||
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
|
||||
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697 h1:PBb7ld5cQGfxHF2pKvb/ydtuPwdRaltGI4e0QSCuiNI=
|
||||
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697/go.mod h1:HtjVsQfsrBm1GDcDTUFn4ZXhftxTwO/hxrvEiRc61U4=
|
||||
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00 h1:qEtkL8n1DAHpi5/AOgAckwGQUlMe4+jhL/GMt+GKIks=
|
||||
@@ -614,8 +604,6 @@ github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLK
|
||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
|
||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c h1:BFvcl34IGnw8yvJi8hlqLFo9EshRInwWBs2M5fGWzQA=
|
||||
github.com/u-root/uio v0.0.0-20210528114334-82958018845c/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||
github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
|
||||
github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA=
|
||||
@@ -711,7 +699,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190419010253-1f3472d942ba/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
@@ -768,11 +755,9 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190418153312-f0ce4c0180be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606122018-79a91cf218c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -792,7 +777,6 @@ golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201109165425-215b40eba54c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@@ -38,7 +38,6 @@ import (
|
||||
"tailscale.com/paths"
|
||||
"tailscale.com/portlist"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/empty"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -1821,7 +1820,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
}
|
||||
|
||||
if uc.CorpDNS {
|
||||
addDefault := func(resolvers []dnstype.Resolver) {
|
||||
addDefault := func(resolvers []tailcfg.DNSResolver) {
|
||||
for _, resolver := range resolvers {
|
||||
res, err := parseResolver(resolver)
|
||||
if err != nil {
|
||||
@@ -1897,7 +1896,7 @@ func (b *LocalBackend) authReconfig() {
|
||||
b.initPeerAPIListener()
|
||||
}
|
||||
|
||||
func parseResolver(cfg dnstype.Resolver) (netaddr.IPPort, error) {
|
||||
func parseResolver(cfg tailcfg.DNSResolver) (netaddr.IPPort, error) {
|
||||
ip, err := netaddr.ParseIP(cfg.Addr)
|
||||
if err != nil {
|
||||
return netaddr.IPPort{}, fmt.Errorf("[unexpected] non-IP resolver %q", cfg.Addr)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (darwin && ts_macext) || (ios && ts_macext)
|
||||
// +build darwin,ts_macext ios,ts_macext
|
||||
|
||||
package ipnlocal
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/util/dnsname"
|
||||
)
|
||||
@@ -90,19 +91,12 @@ type PeerStatus struct {
|
||||
RxBytes int64
|
||||
TxBytes int64
|
||||
Created time.Time // time registered with tailcontrol
|
||||
LastWrite time.Time // time last packet sent
|
||||
LastWrite mono.Time // time last packet sent
|
||||
LastSeen time.Time // last seen to tailcontrol
|
||||
LastHandshake time.Time // with local wireguard
|
||||
KeepAlive bool
|
||||
ExitNode bool // true if this is the currently selected exit node.
|
||||
|
||||
// Active is whether the node was recently active. The
|
||||
// definition is somewhat undefined but has historically and
|
||||
// currently means that there was some packet sent to this
|
||||
// peer in the past two minutes. That definition is subject to
|
||||
// change.
|
||||
Active bool
|
||||
|
||||
PeerAPIURL []string
|
||||
Capabilities []string `json:",omitempty"`
|
||||
|
||||
@@ -284,9 +278,6 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) {
|
||||
if st.ShareeNode {
|
||||
e.ShareeNode = true
|
||||
}
|
||||
if st.Active {
|
||||
e.Active = true
|
||||
}
|
||||
}
|
||||
|
||||
type StatusUpdater interface {
|
||||
@@ -330,7 +321,7 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
|
||||
f("<tr><th>Peer</th><th>OS</th><th>Node</th><th>Owner</th><th>Rx</th><th>Tx</th><th>Activity</th><th>Connection</th></tr>\n")
|
||||
f("</thead>\n<tbody>\n")
|
||||
|
||||
now := time.Now()
|
||||
now := mono.Now()
|
||||
|
||||
var peers []*PeerStatus
|
||||
for _, peer := range st.Peers() {
|
||||
@@ -387,7 +378,9 @@ table tbody tr:nth-child(even) td { background-color: #f5f5f5; }
|
||||
)
|
||||
f("<td>")
|
||||
|
||||
if ps.Active {
|
||||
// TODO: let server report this active bool instead
|
||||
active := !ps.LastWrite.IsZero() && mono.Since(ps.LastWrite) < 2*time.Minute
|
||||
if active {
|
||||
if ps.Relay != "" && ps.CurAddr == "" {
|
||||
f("relay <b>%s</b>", html.EscapeString(ps.Relay))
|
||||
} else if ps.CurAddr != "" {
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
//+build !windows
|
||||
|
||||
package filch
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !linux && !freebsd && !openbsd && !windows
|
||||
// +build !linux,!freebsd,!openbsd,!windows
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || freebsd || openbsd
|
||||
// +build linux freebsd openbsd
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package dns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (darwin && ts_macext) || (ios && ts_macext)
|
||||
// +build darwin,ts_macext ios,ts_macext
|
||||
|
||||
package resolver
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !darwin && !windows
|
||||
// +build !darwin,!windows
|
||||
|
||||
package resolver
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux || (darwin && !ts_macext)
|
||||
// +build linux darwin,!ts_macext
|
||||
|
||||
package interfaces
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !linux && !windows && !darwin
|
||||
// +build !linux,!windows,!darwin
|
||||
|
||||
package interfaces
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build android
|
||||
// +build android
|
||||
|
||||
package netns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin && !ts_macext
|
||||
// +build darwin,!ts_macext
|
||||
|
||||
package netns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (!linux && !windows && !darwin) || (darwin && ts_macext)
|
||||
// +build !linux,!windows,!darwin darwin,ts_macext
|
||||
|
||||
package netns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux && !android
|
||||
// +build linux,!android
|
||||
|
||||
package netns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || ios
|
||||
// +build darwin ios
|
||||
|
||||
package netns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !ios
|
||||
// +build !ios
|
||||
|
||||
package netns
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package netstat
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ios
|
||||
// +build ios
|
||||
|
||||
// (https://github.com/tailscale/tailscale/issues/2495)
|
||||
|
||||
package portmapper
|
||||
@@ -17,12 +15,6 @@ import (
|
||||
|
||||
type upnpClient interface{}
|
||||
|
||||
type uPnPDiscoResponse struct{}
|
||||
|
||||
func parseUPnPDiscoResponse([]byte) (uPnPDiscoResponse, error) {
|
||||
return uPnPDiscoResponse{}, nil
|
||||
}
|
||||
|
||||
func (c *Client) getUPnPPortMapping(
|
||||
ctx context.Context,
|
||||
gw netaddr.IP,
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
// 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 portmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
// TestIGD is an IGD (Intenet Gateway Device) for testing. It supports fake
|
||||
// implementations of NAT-PMP, PCP, and/or UPnP to test clients against.
|
||||
type TestIGD struct {
|
||||
upnpConn net.PacketConn // for UPnP discovery
|
||||
pxpConn net.PacketConn // for NAT-PMP and/or PCP
|
||||
ts *httptest.Server
|
||||
|
||||
doPMP bool
|
||||
doPCP bool
|
||||
doUPnP bool // TODO: more options for 3 flavors of UPnP services
|
||||
|
||||
mu sync.Mutex // guards below
|
||||
counters igdCounters
|
||||
}
|
||||
|
||||
type igdCounters struct {
|
||||
numUPnPDiscoRecv int32
|
||||
numUPnPOtherUDPRecv int32
|
||||
numUPnPHTTPRecv int32
|
||||
numPMPRecv int32
|
||||
numPMPDiscoRecv int32
|
||||
numPCPRecv int32
|
||||
numPCPDiscoRecv int32
|
||||
numPMPPublicAddrRecv int32
|
||||
numPMPBogusRecv int32
|
||||
}
|
||||
|
||||
func NewTestIGD() (*TestIGD, error) {
|
||||
d := &TestIGD{
|
||||
doPMP: true,
|
||||
doPCP: true,
|
||||
doUPnP: true,
|
||||
}
|
||||
var err error
|
||||
if d.upnpConn, err = net.ListenPacket("udp", "127.0.0.1:1900"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if d.pxpConn, err = net.ListenPacket("udp", "127.0.0.1:5351"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.ts = httptest.NewServer(http.HandlerFunc(d.serveUPnPHTTP))
|
||||
go d.serveUPnPDiscovery()
|
||||
go d.servePxP()
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (d *TestIGD) Close() error {
|
||||
d.ts.Close()
|
||||
d.upnpConn.Close()
|
||||
d.pxpConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *TestIGD) inc(p *int32) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
(*p)++
|
||||
}
|
||||
|
||||
func (d *TestIGD) stats() igdCounters {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.counters
|
||||
}
|
||||
|
||||
func (d *TestIGD) serveUPnPHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.NotFound(w, r) // TODO
|
||||
}
|
||||
|
||||
func (d *TestIGD) serveUPnPDiscovery() {
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
n, src, err := d.upnpConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pkt := buf[:n]
|
||||
if bytes.Equal(pkt, uPnPPacket) { // a super lazy "parse"
|
||||
d.inc(&d.counters.numUPnPDiscoRecv)
|
||||
resPkt := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=120\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nUSN: uuid:bee7052b-49e8-3597-b545-55a1e38ac11::urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\nEXT:\r\nSERVER: Tailscale-Test/1.0 UPnP/1.1 MiniUPnPd/2.2.1\r\nLOCATION: %s\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1627958564\r\nBOOTID.UPNP.ORG: 1627958564\r\nCONFIGID.UPNP.ORG: 1337\r\n\r\n", d.ts.URL+"/rootDesc.xml"))
|
||||
d.upnpConn.WriteTo(resPkt, src)
|
||||
} else {
|
||||
d.inc(&d.counters.numUPnPOtherUDPRecv)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// servePxP serves NAT-PMP and PCP, which share a port number.
|
||||
func (d *TestIGD) servePxP() {
|
||||
buf := make([]byte, 1500)
|
||||
for {
|
||||
n, a, err := d.pxpConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ua := a.(*net.UDPAddr)
|
||||
src, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone)
|
||||
if !ok {
|
||||
panic("bogus addr")
|
||||
}
|
||||
pkt := buf[:n]
|
||||
if len(pkt) < 2 {
|
||||
continue
|
||||
}
|
||||
ver := pkt[0]
|
||||
switch ver {
|
||||
default:
|
||||
continue
|
||||
case pmpVersion:
|
||||
d.handlePMPQuery(pkt, src)
|
||||
case pcpVersion:
|
||||
d.handlePCPQuery(pkt, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *TestIGD) handlePMPQuery(pkt []byte, src netaddr.IPPort) {
|
||||
d.inc(&d.counters.numPMPRecv)
|
||||
if len(pkt) < 2 {
|
||||
return
|
||||
}
|
||||
op := pkt[1]
|
||||
switch op {
|
||||
case pmpOpMapPublicAddr:
|
||||
if len(pkt) != 2 {
|
||||
d.inc(&d.counters.numPMPBogusRecv)
|
||||
return
|
||||
}
|
||||
d.inc(&d.counters.numPMPPublicAddrRecv)
|
||||
|
||||
}
|
||||
// TODO
|
||||
}
|
||||
|
||||
func (d *TestIGD) handlePCPQuery(pkt []byte, src netaddr.IPPort) {
|
||||
d.inc(&d.counters.numPCPRecv)
|
||||
// TODO
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
// 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 portmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/net/netns"
|
||||
)
|
||||
|
||||
// References:
|
||||
//
|
||||
// https://www.rfc-editor.org/rfc/pdfrfc/rfc6887.txt.pdf
|
||||
// https://tools.ietf.org/html/rfc6887
|
||||
|
||||
// PCP constants
|
||||
const (
|
||||
pcpVersion = 2
|
||||
pcpPort = 5351
|
||||
|
||||
pcpMapLifetimeSec = 7200 // TODO does the RFC recommend anything? This is taken from PMP.
|
||||
|
||||
pcpCodeOK = 0
|
||||
pcpCodeNotAuthorized = 2
|
||||
|
||||
pcpOpReply = 0x80 // OR'd into request's op code on response
|
||||
pcpOpAnnounce = 0
|
||||
pcpOpMap = 1
|
||||
|
||||
pcpUDPMapping = 17 // portmap UDP
|
||||
pcpTCPMapping = 6 // portmap TCP
|
||||
)
|
||||
|
||||
type pcpMapping struct {
|
||||
gw netaddr.IP
|
||||
internal netaddr.IPPort
|
||||
external netaddr.IPPort
|
||||
|
||||
renewAfter time.Time
|
||||
goodUntil time.Time
|
||||
|
||||
// TODO should this also contain an epoch?
|
||||
// Doesn't seem to be used elsewhere, but can use it for validation at some point.
|
||||
}
|
||||
|
||||
func (p *pcpMapping) GoodUntil() time.Time { return p.goodUntil }
|
||||
func (p *pcpMapping) RenewAfter() time.Time { return p.renewAfter }
|
||||
func (p *pcpMapping) External() netaddr.IPPort { return p.external }
|
||||
func (p *pcpMapping) Release(ctx context.Context) {
|
||||
uc, err := netns.Listener().ListenPacket(ctx, "udp4", ":0")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer uc.Close()
|
||||
pkt := buildPCPRequestMappingPacket(p.internal.IP(), p.internal.Port(), p.external.Port(), 0, p.external.IP())
|
||||
uc.WriteTo(pkt, netaddr.IPPortFrom(p.gw, pcpPort).UDPAddr())
|
||||
}
|
||||
|
||||
// buildPCPRequestMappingPacket generates a PCP packet with a MAP opcode.
|
||||
// To create a packet which deletes a mapping, lifetimeSec should be set to 0.
|
||||
// If prevPort is not known, it should be set to 0.
|
||||
// If prevExternalIP is not known, it should be set to 0.0.0.0.
|
||||
func buildPCPRequestMappingPacket(
|
||||
myIP netaddr.IP,
|
||||
localPort, prevPort uint16,
|
||||
lifetimeSec uint32,
|
||||
prevExternalIP netaddr.IP,
|
||||
) (pkt []byte) {
|
||||
// 24 byte common PCP header + 36 bytes of MAP-specific fields
|
||||
pkt = make([]byte, 24+36)
|
||||
pkt[0] = pcpVersion
|
||||
pkt[1] = pcpOpMap
|
||||
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSec)
|
||||
myIP16 := myIP.As16()
|
||||
copy(pkt[8:24], myIP16[:])
|
||||
|
||||
mapOp := pkt[24:]
|
||||
rand.Read(mapOp[:12]) // 96 bit mapping nonce
|
||||
|
||||
// TODO: should this be a UDP mapping? It looks like it supports "all protocols" with 0, but
|
||||
// also doesn't support a local port then.
|
||||
mapOp[12] = pcpUDPMapping
|
||||
binary.BigEndian.PutUint16(mapOp[16:18], localPort)
|
||||
binary.BigEndian.PutUint16(mapOp[18:20], prevPort)
|
||||
|
||||
prevExternalIP16 := prevExternalIP.As16()
|
||||
copy(mapOp[20:], prevExternalIP16[:])
|
||||
return pkt
|
||||
}
|
||||
|
||||
func parsePCPMapResponse(resp []byte) (*pcpMapping, error) {
|
||||
if len(resp) < 60 {
|
||||
return nil, fmt.Errorf("Does not appear to be PCP MAP response")
|
||||
}
|
||||
res, ok := parsePCPResponse(resp[:24])
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Invalid PCP common header")
|
||||
}
|
||||
if res.ResultCode != pcpCodeOK {
|
||||
return nil, fmt.Errorf("PCP response not ok, code %d", res.ResultCode)
|
||||
}
|
||||
// TODO: don't ignore the nonce and make sure it's the same?
|
||||
externalPort := binary.BigEndian.Uint16(resp[42:44])
|
||||
externalIPBytes := [16]byte{}
|
||||
copy(externalIPBytes[:], resp[44:])
|
||||
externalIP := netaddr.IPFrom16(externalIPBytes)
|
||||
|
||||
external := netaddr.IPPortFrom(externalIP, externalPort)
|
||||
|
||||
lifetime := time.Second * time.Duration(res.Lifetime)
|
||||
now := time.Now()
|
||||
mapping := &pcpMapping{
|
||||
external: external,
|
||||
renewAfter: now.Add(lifetime / 2),
|
||||
goodUntil: now.Add(lifetime),
|
||||
}
|
||||
|
||||
return mapping, nil
|
||||
}
|
||||
|
||||
// pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode.
|
||||
func pcpAnnounceRequest(myIP netaddr.IP) []byte {
|
||||
// See https://tools.ietf.org/html/rfc6887#section-7.1
|
||||
pkt := make([]byte, 24)
|
||||
pkt[0] = pcpVersion
|
||||
pkt[1] = pcpOpAnnounce
|
||||
myIP16 := myIP.As16()
|
||||
copy(pkt[8:], myIP16[:])
|
||||
return pkt
|
||||
}
|
||||
|
||||
type pcpResponse struct {
|
||||
OpCode uint8
|
||||
ResultCode uint8
|
||||
Lifetime uint32
|
||||
Epoch uint32
|
||||
}
|
||||
|
||||
func parsePCPResponse(b []byte) (res pcpResponse, ok bool) {
|
||||
if len(b) < 24 || b[0] != pcpVersion {
|
||||
return
|
||||
}
|
||||
res.OpCode = b[1]
|
||||
res.ResultCode = b[3]
|
||||
res.Lifetime = binary.BigEndian.Uint32(b[4:])
|
||||
res.Epoch = binary.BigEndian.Uint32(b[8:])
|
||||
return res, true
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// 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 portmapper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
var examplePCPMapResponse = []byte{2, 129, 0, 0, 0, 0, 28, 32, 0, 2, 155, 237, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 129, 112, 9, 24, 241, 208, 251, 45, 157, 76, 10, 188, 17, 0, 0, 0, 4, 210, 4, 210, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 135, 180, 175, 246}
|
||||
|
||||
func TestParsePCPMapResponse(t *testing.T) {
|
||||
mapping, err := parsePCPMapResponse(examplePCPMapResponse)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse PCP Map Response: %v", err)
|
||||
}
|
||||
if mapping == nil {
|
||||
t.Fatalf("got nil mapping when expected non-nil")
|
||||
}
|
||||
expectedAddr := netaddr.MustParseIPPort("135.180.175.246:1234")
|
||||
if mapping.external != expectedAddr {
|
||||
t.Errorf("mismatched external address, got: %v, want: %v", mapping.external, expectedAddr)
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,12 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package portmapper is a UDP port mapping client. It currently allows for mapping over
|
||||
// NAT-PMP, UPnP, and PCP.
|
||||
// NAT-PMP and UPnP, but will perhaps do PCP later.
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -24,20 +25,10 @@ import (
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// Debug knobs for "tailscaled debug --portmap".
|
||||
var (
|
||||
VerboseLogs bool
|
||||
|
||||
// Disable* disables a specific service from mapping.
|
||||
|
||||
DisableUPnP bool
|
||||
DisablePMP bool
|
||||
DisablePCP bool
|
||||
)
|
||||
|
||||
// References:
|
||||
//
|
||||
// NAT-PMP: https://tools.ietf.org/html/rfc6886
|
||||
// PCP: https://tools.ietf.org/html/rfc6887
|
||||
|
||||
// portMapServiceTimeout is the time we wait for port mapping
|
||||
// services (UPnP, NAT-PMP, PCP) to respond before we give up and
|
||||
@@ -77,7 +68,7 @@ type Client struct {
|
||||
|
||||
uPnPSawTime time.Time // time we last saw UPnP was available
|
||||
uPnPMeta uPnPDiscoResponse // Location header from UPnP UDP discovery response
|
||||
uPnPHTTPClient *http.Client // netns-configured HTTP client for UPnP; nil until needed
|
||||
uPnPHTTPClient *http.Client // nil until needed
|
||||
|
||||
localPort uint16
|
||||
|
||||
@@ -224,7 +215,6 @@ func (c *Client) invalidateMappingsLocked(releaseOld bool) {
|
||||
c.pmpPubIPTime = time.Time{}
|
||||
c.pcpSawTime = time.Time{}
|
||||
c.uPnPSawTime = time.Time{}
|
||||
c.uPnPMeta = uPnPDiscoResponse{}
|
||||
}
|
||||
|
||||
func (c *Client) sawPMPRecently() bool {
|
||||
@@ -240,10 +230,6 @@ func (c *Client) sawPMPRecentlyLocked() bool {
|
||||
func (c *Client) sawPCPRecently() bool {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.sawPCPRecentlyLocked()
|
||||
}
|
||||
|
||||
func (c *Client) sawPCPRecentlyLocked() bool {
|
||||
return c.pcpSawTime.After(time.Now().Add(-trustServiceStillAvailableDuration))
|
||||
}
|
||||
|
||||
@@ -342,18 +328,12 @@ func (c *Client) createMapping() {
|
||||
}
|
||||
}
|
||||
|
||||
// wildcardIP is used when the previous external IP is not known for PCP port mapping.
|
||||
var wildcardIP = netaddr.MustParseIP("0.0.0.0")
|
||||
|
||||
// createOrGetMapping either creates a new mapping or returns a cached
|
||||
// valid one.
|
||||
//
|
||||
// If no mapping is available, the error will be of type
|
||||
// NoMappingError; see IsNoMappingError.
|
||||
func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPort, err error) {
|
||||
if DisableUPnP && DisablePCP && DisablePMP {
|
||||
return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
gw, myIP, ok := c.gatewayAndSelfIP()
|
||||
if !ok {
|
||||
return netaddr.IPPort{}, NoMappingError{ErrGatewayRange}
|
||||
@@ -362,6 +342,10 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor
|
||||
c.mu.Lock()
|
||||
localPort := c.localPort
|
||||
internalAddr := netaddr.IPPortFrom(myIP, localPort)
|
||||
m := &pmpMapping{
|
||||
gw: gw,
|
||||
internal: internalAddr,
|
||||
}
|
||||
|
||||
// prevPort is the port we had most previously, if any. We try
|
||||
// to ask for the same port. 0 means to give us any port.
|
||||
@@ -378,41 +362,22 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor
|
||||
prevPort = m.External().Port()
|
||||
}
|
||||
|
||||
if DisablePCP && DisablePMP {
|
||||
c.mu.Unlock()
|
||||
if external, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok {
|
||||
return external, nil
|
||||
}
|
||||
return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
|
||||
// If we just did a Probe (e.g. via netchecker) but didn't
|
||||
// find a PMP service, bail out early rather than probing
|
||||
// again. Cuts down latency for most clients.
|
||||
haveRecentPMP := c.sawPMPRecentlyLocked()
|
||||
haveRecentPCP := c.sawPCPRecentlyLocked()
|
||||
|
||||
// Since PMP mapping may require multiple calls, and it's not clear from the outset
|
||||
// whether we're doing a PCP or PMP call, initialize the PMP mapping here,
|
||||
// and only return it once completed.
|
||||
//
|
||||
// PCP returns all the information necessary for a mapping in a single packet, so we can
|
||||
// construct it upon receiving that packet.
|
||||
m := &pmpMapping{
|
||||
gw: gw,
|
||||
internal: internalAddr,
|
||||
}
|
||||
if haveRecentPMP {
|
||||
m.external = m.external.WithIP(c.pmpPubIP)
|
||||
}
|
||||
if c.lastProbe.After(now.Add(-5*time.Second)) && !haveRecentPMP && !haveRecentPCP {
|
||||
if c.lastProbe.After(now.Add(-5*time.Second)) && !haveRecentPMP {
|
||||
c.mu.Unlock()
|
||||
// fallback to UPnP portmapping
|
||||
if external, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok {
|
||||
return external, nil
|
||||
if mapping, ok := c.getUPnPPortMapping(ctx, gw, internalAddr, prevPort); ok {
|
||||
return mapping, nil
|
||||
}
|
||||
return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
|
||||
c.mu.Unlock()
|
||||
|
||||
uc, err := netns.Listener().ListenPacket(ctx, "udp4", ":0")
|
||||
@@ -424,31 +389,20 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor
|
||||
uc.SetReadDeadline(time.Now().Add(portMapServiceTimeout))
|
||||
defer closeCloserOnContextDone(ctx, uc)()
|
||||
|
||||
pxpAddr := netaddr.IPPortFrom(gw, pmpPort)
|
||||
pxpAddru := pxpAddr.UDPAddr()
|
||||
pmpAddr := netaddr.IPPortFrom(gw, pmpPort)
|
||||
pmpAddru := pmpAddr.UDPAddr()
|
||||
|
||||
preferPCP := !DisablePCP && (DisablePMP || (!haveRecentPMP && haveRecentPCP))
|
||||
|
||||
// Create a mapping, defaulting to PMP unless only PCP was seen recently.
|
||||
if preferPCP {
|
||||
// TODO replace wildcardIP here with previous external if known.
|
||||
// Only do PCP mapping in the case when PMP did not appear to be available recently.
|
||||
pkt := buildPCPRequestMappingPacket(myIP, localPort, prevPort, pcpMapLifetimeSec, wildcardIP)
|
||||
if _, err := uc.WriteTo(pkt, pxpAddru); err != nil {
|
||||
// Ask for our external address if needed.
|
||||
if m.external.IP().IsZero() {
|
||||
if _, err := uc.WriteTo(pmpReqExternalAddrPacket, pmpAddru); err != nil {
|
||||
return netaddr.IPPort{}, err
|
||||
}
|
||||
} else {
|
||||
// Ask for our external address if needed.
|
||||
if m.external.IP().IsZero() {
|
||||
if _, err := uc.WriteTo(pmpReqExternalAddrPacket, pxpAddru); err != nil {
|
||||
return netaddr.IPPort{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pkt := buildPMPRequestMappingPacket(localPort, prevPort, pmpMapLifetimeSec)
|
||||
if _, err := uc.WriteTo(pkt, pxpAddru); err != nil {
|
||||
return netaddr.IPPort{}, err
|
||||
}
|
||||
// And ask for a mapping.
|
||||
pmpReqMapping := buildPMPRequestMappingPacket(localPort, prevPort, pmpMapLifetimeSec)
|
||||
if _, err := uc.WriteTo(pmpReqMapping, pmpAddru); err != nil {
|
||||
return netaddr.IPPort{}, err
|
||||
}
|
||||
|
||||
res := make([]byte, 1500)
|
||||
@@ -469,45 +423,25 @@ func (c *Client) createOrGetMapping(ctx context.Context) (external netaddr.IPPor
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if src == pxpAddr {
|
||||
version := res[0]
|
||||
switch version {
|
||||
case pmpVersion:
|
||||
pres, ok := parsePMPResponse(res[:n])
|
||||
if !ok {
|
||||
c.logf("unexpected PMP response: % 02x", res[:n])
|
||||
continue
|
||||
}
|
||||
if pres.ResultCode != 0 {
|
||||
return netaddr.IPPort{}, NoMappingError{fmt.Errorf("PMP response Op=0x%x,Res=0x%x", pres.OpCode, pres.ResultCode)}
|
||||
}
|
||||
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr {
|
||||
m.external = m.external.WithIP(pres.PublicAddr)
|
||||
}
|
||||
if pres.OpCode == pmpOpReply|pmpOpMapUDP {
|
||||
m.external = m.external.WithPort(pres.ExternalPort)
|
||||
d := time.Duration(pres.MappingValidSeconds) * time.Second
|
||||
now := time.Now()
|
||||
m.goodUntil = now.Add(d)
|
||||
m.renewAfter = now.Add(d / 2) // renew in half the time
|
||||
m.epoch = pres.SecondsSinceEpoch
|
||||
}
|
||||
case pcpVersion:
|
||||
pcpMapping, err := parsePCPMapResponse(res[:n])
|
||||
if err != nil {
|
||||
c.logf("failed to get PCP mapping: %v", err)
|
||||
// PCP should only have a single packet response
|
||||
return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
}
|
||||
pcpMapping.internal = m.internal
|
||||
pcpMapping.gw = gw
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.mapping = pcpMapping
|
||||
return pcpMapping.external, nil
|
||||
default:
|
||||
c.logf("unknown PMP/PCP version number: %d %v", version, res[:n])
|
||||
return netaddr.IPPort{}, NoMappingError{ErrNoPortMappingServices}
|
||||
if src == pmpAddr {
|
||||
pres, ok := parsePMPResponse(res[:n])
|
||||
if !ok {
|
||||
c.logf("unexpected PMP response: % 02x", res[:n])
|
||||
continue
|
||||
}
|
||||
if pres.ResultCode != 0 {
|
||||
return netaddr.IPPort{}, NoMappingError{fmt.Errorf("PMP response Op=0x%x,Res=0x%x", pres.OpCode, pres.ResultCode)}
|
||||
}
|
||||
if pres.OpCode == pmpOpReply|pmpOpMapPublicAddr {
|
||||
m.external = m.external.WithIP(pres.PublicAddr)
|
||||
}
|
||||
if pres.OpCode == pmpOpReply|pmpOpMapUDP {
|
||||
m.external = m.external.WithPort(pres.ExternalPort)
|
||||
d := time.Duration(pres.MappingValidSeconds) * time.Second
|
||||
now := time.Now()
|
||||
m.goodUntil = now.Add(d)
|
||||
m.renewAfter = now.Add(d / 2) // renew in half the time
|
||||
m.epoch = pres.SecondsSinceEpoch
|
||||
}
|
||||
}
|
||||
|
||||
@@ -528,7 +462,6 @@ const (
|
||||
pmpMapLifetimeSec = 7200 // RFC recommended 2 hour map duration
|
||||
pmpMapLifetimeDelete = 0 // 0 second lifetime deletes
|
||||
|
||||
pmpVersion = 0
|
||||
pmpOpMapPublicAddr = 0
|
||||
pmpOpMapUDP = 1
|
||||
pmpOpReply = 0x80 // OR'd into request's op code on response
|
||||
@@ -641,17 +574,17 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
// https://github.com/tailscale/tailscale/issues/1001
|
||||
if c.sawPMPRecently() {
|
||||
res.PMP = true
|
||||
} else if !DisablePMP {
|
||||
} else {
|
||||
uc.WriteTo(pmpReqExternalAddrPacket, pmpAddr)
|
||||
}
|
||||
if c.sawPCPRecently() {
|
||||
res.PCP = true
|
||||
} else if !DisablePCP {
|
||||
} else {
|
||||
uc.WriteTo(pcpAnnounceRequest(myIP), pcpAddr)
|
||||
}
|
||||
if c.sawUPnPRecently() {
|
||||
res.UPnP = true
|
||||
} else if !DisableUPnP {
|
||||
} else {
|
||||
uc.WriteTo(uPnPPacket, upnpAddr)
|
||||
}
|
||||
|
||||
@@ -677,9 +610,7 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
if err != nil {
|
||||
c.logf("unrecognized UPnP discovery response; ignoring")
|
||||
}
|
||||
if VerboseLogs {
|
||||
c.logf("UPnP reply %+v, %q", meta, buf[:n])
|
||||
}
|
||||
// log.Printf("UPnP reply %+v, %q", meta, buf[:n])
|
||||
res.UPnP = true
|
||||
c.mu.Lock()
|
||||
c.uPnPSawTime = time.Now()
|
||||
@@ -726,13 +657,83 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
var pmpReqExternalAddrPacket = []byte{pmpVersion, pmpOpMapPublicAddr} // 0, 0
|
||||
|
||||
const (
|
||||
upnpPort = 1900 // for UDP discovery only; TCP port discovered later
|
||||
pcpVersion = 2
|
||||
pcpPort = 5351
|
||||
|
||||
pcpCodeOK = 0
|
||||
pcpCodeNotAuthorized = 2
|
||||
|
||||
pcpOpReply = 0x80 // OR'd into request's op code on response
|
||||
pcpOpAnnounce = 0
|
||||
pcpOpMap = 1
|
||||
)
|
||||
|
||||
// pcpAnnounceRequest generates a PCP packet with an ANNOUNCE opcode.
|
||||
func pcpAnnounceRequest(myIP netaddr.IP) []byte {
|
||||
// See https://tools.ietf.org/html/rfc6887#section-7.1
|
||||
pkt := make([]byte, 24)
|
||||
pkt[0] = pcpVersion // version
|
||||
pkt[1] = pcpOpAnnounce
|
||||
myIP16 := myIP.As16()
|
||||
copy(pkt[8:], myIP16[:])
|
||||
return pkt
|
||||
}
|
||||
|
||||
// pcpMapRequest generates a PCP packet with a MAP opcode.
|
||||
func pcpMapRequest(myIP netaddr.IP, mapToLocalPort int, delete bool) []byte {
|
||||
const udpProtoNumber = 17
|
||||
lifetimeSeconds := uint32(1)
|
||||
if delete {
|
||||
lifetimeSeconds = 0
|
||||
}
|
||||
const opMap = 1
|
||||
|
||||
// 24 byte header + 36 byte map opcode
|
||||
pkt := make([]byte, (32+32+128)/8+(96+8+24+16+16+128)/8)
|
||||
|
||||
// The header (https://tools.ietf.org/html/rfc6887#section-7.1)
|
||||
pkt[0] = 2 // version
|
||||
pkt[1] = opMap
|
||||
binary.BigEndian.PutUint32(pkt[4:8], lifetimeSeconds)
|
||||
myIP16 := myIP.As16()
|
||||
copy(pkt[8:], myIP16[:])
|
||||
|
||||
// The map opcode body (https://tools.ietf.org/html/rfc6887#section-11.1)
|
||||
mapOp := pkt[24:]
|
||||
rand.Read(mapOp[:12]) // 96 bit mappping nonce
|
||||
mapOp[12] = udpProtoNumber
|
||||
binary.BigEndian.PutUint16(mapOp[16:], uint16(mapToLocalPort))
|
||||
v4unspec := netaddr.MustParseIP("0.0.0.0")
|
||||
v4unspec16 := v4unspec.As16()
|
||||
copy(mapOp[20:], v4unspec16[:])
|
||||
return pkt
|
||||
}
|
||||
|
||||
type pcpResponse struct {
|
||||
OpCode uint8
|
||||
ResultCode uint8
|
||||
Lifetime uint32
|
||||
Epoch uint32
|
||||
}
|
||||
|
||||
func parsePCPResponse(b []byte) (res pcpResponse, ok bool) {
|
||||
if len(b) < 24 || b[0] != pcpVersion {
|
||||
return
|
||||
}
|
||||
res.OpCode = b[1]
|
||||
res.ResultCode = b[3]
|
||||
res.Lifetime = binary.BigEndian.Uint32(b[4:])
|
||||
res.Epoch = binary.BigEndian.Uint32(b[8:])
|
||||
return res, true
|
||||
}
|
||||
|
||||
var pmpReqExternalAddrPacket = []byte{0, 0} // version 0, opcode 0 = "Public address request"
|
||||
|
||||
const (
|
||||
upnpPort = 1900
|
||||
)
|
||||
|
||||
// uPnPPacket is the UPnP UDP discovery packet's request body.
|
||||
var uPnPPacket = []byte("M-SEARCH * HTTP/1.1\r\n" +
|
||||
"HOST: 239.255.255.250:1900\r\n" +
|
||||
"ST: ssdp:all\r\n" +
|
||||
|
||||
@@ -10,9 +10,6 @@ import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func TestCreateOrGetMapping(t *testing.T) {
|
||||
@@ -58,30 +55,3 @@ func TestClientProbeThenMap(t *testing.T) {
|
||||
ext, err := c.createOrGetMapping(context.Background())
|
||||
t.Logf("createOrGetMapping: %v, %v", ext, err)
|
||||
}
|
||||
|
||||
func TestProbeIntegration(t *testing.T) {
|
||||
igd, err := NewTestIGD()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer igd.Close()
|
||||
|
||||
logf := t.Logf
|
||||
var c *Client
|
||||
c = NewClient(logger.WithPrefix(logf, "portmapper: "), func() {
|
||||
logf("portmapping changed.")
|
||||
logf("have mapping: %v", c.HaveMapping())
|
||||
})
|
||||
|
||||
c.SetGatewayLookupFunc(func() (gw, self netaddr.IP, ok bool) {
|
||||
return netaddr.IPv4(127, 0, 0, 1), netaddr.IPv4(1, 2, 3, 4), true
|
||||
})
|
||||
|
||||
res, err := c.Probe(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("Probe: %v", err)
|
||||
}
|
||||
t.Logf("Probe: %+v", res)
|
||||
t.Logf("IGD stats: %+v", igd.stats())
|
||||
// TODO(bradfitz): finish
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !ios
|
||||
// +build !ios
|
||||
|
||||
// (https://github.com/tailscale/tailscale/issues/2495)
|
||||
|
||||
package portmapper
|
||||
@@ -14,10 +12,10 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tailscale/goupnp"
|
||||
@@ -25,9 +23,12 @@ import (
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
// VerboseLogs controls verbose debug logging.
|
||||
// It exists for use by "tailscaled debug --portmap".
|
||||
var VerboseLogs bool
|
||||
|
||||
// References:
|
||||
//
|
||||
// WANIP Connection v2: http://upnp.org/specs/gw/UPnP-gw-WANIPConnection-v2-Service.pdf
|
||||
@@ -153,8 +154,8 @@ func addAnyPortMapping(
|
||||
//
|
||||
// The provided ctx is not retained in the returned upnpClient, but
|
||||
// its associated HTTP client is (if set via goupnp.WithHTTPClient).
|
||||
func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uPnPDiscoResponse) (client upnpClient, err error) {
|
||||
if controlknobs.DisableUPnP() || DisableUPnP {
|
||||
func getUPnPClient(ctx context.Context, gw netaddr.IP, meta uPnPDiscoResponse) (upnpClient, error) {
|
||||
if controlknobs.DisableUPnP() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -163,7 +164,7 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uP
|
||||
}
|
||||
|
||||
if VerboseLogs {
|
||||
logf("fetching %v", meta.Location)
|
||||
log.Printf("fetching %v", meta.Location)
|
||||
}
|
||||
u, err := url.Parse(meta.Location)
|
||||
if err != nil {
|
||||
@@ -179,11 +180,7 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uP
|
||||
meta.Location, gw)
|
||||
}
|
||||
|
||||
// We're fetching a smallish XML document over plain HTTP
|
||||
// across the local LAN, without using DNS. There should be
|
||||
// very few round trips and low latency, so one second is a
|
||||
// long time.
|
||||
ctx, cancel := context.WithTimeout(ctx, time.Second)
|
||||
ctx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// This part does a network fetch.
|
||||
@@ -192,15 +189,6 @@ func getUPnPClient(ctx context.Context, logf logger.Logf, gw netaddr.IP, meta uP
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if client == nil {
|
||||
return
|
||||
}
|
||||
logf("saw UPnP type %v at %v; %v (%v)",
|
||||
strings.TrimPrefix(fmt.Sprintf("%T", client), "*internetgateway2."),
|
||||
meta.Location, root.Device.FriendlyName, root.Device.Manufacturer)
|
||||
}()
|
||||
|
||||
// These parts don't do a network fetch.
|
||||
// Pick the best service type available.
|
||||
if cc, _ := internetgateway2.NewWANIPConnection2ClientsFromRootDevice(ctx, root, u); len(cc) > 0 {
|
||||
@@ -236,7 +224,7 @@ func (c *Client) getUPnPPortMapping(
|
||||
internal netaddr.IPPort,
|
||||
prevPort uint16,
|
||||
) (external netaddr.IPPort, ok bool) {
|
||||
if controlknobs.DisableUPnP() || DisableUPnP {
|
||||
if controlknobs.DisableUPnP() {
|
||||
return netaddr.IPPort{}, false
|
||||
}
|
||||
now := time.Now()
|
||||
@@ -256,9 +244,9 @@ func (c *Client) getUPnPPortMapping(
|
||||
client = oldMapping.client
|
||||
} else {
|
||||
ctx := goupnp.WithHTTPClient(ctx, httpClient)
|
||||
client, err = getUPnPClient(ctx, c.logf, gw, meta)
|
||||
client, err = getUPnPClient(ctx, gw, meta)
|
||||
if VerboseLogs {
|
||||
c.logf("getUPnPClient: %T, %v", client, err)
|
||||
log.Printf("getUPnPClient: %T, %v", client, err)
|
||||
}
|
||||
if err != nil {
|
||||
return netaddr.IPPort{}, false
|
||||
@@ -278,7 +266,7 @@ func (c *Client) getUPnPPortMapping(
|
||||
time.Second*pmpMapLifetimeSec,
|
||||
)
|
||||
if VerboseLogs {
|
||||
c.logf("addAnyPortMapping: %v, %v", newPort, err)
|
||||
log.Printf("addAnyPortMapping: %v, %v", newPort, err)
|
||||
}
|
||||
if err != nil {
|
||||
return netaddr.IPPort{}, false
|
||||
@@ -286,7 +274,7 @@ func (c *Client) getUPnPPortMapping(
|
||||
// TODO cache this ip somewhere?
|
||||
extIP, err := client.GetExternalIPAddress(ctx)
|
||||
if VerboseLogs {
|
||||
c.logf("client.GetExternalIPAddress: %v, %v", extIP, err)
|
||||
log.Printf("client.GetExternalIPAddress: %v, %v", extIP, err)
|
||||
}
|
||||
if err != nil {
|
||||
// TODO this doesn't seem right
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package portmapper
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -13,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"inet.af/netaddr"
|
||||
@@ -66,20 +64,9 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
name string
|
||||
xmlBody string
|
||||
want string
|
||||
wantLog string
|
||||
}{
|
||||
{
|
||||
"google",
|
||||
googleWifiRootDescXML,
|
||||
"*internetgateway2.WANIPConnection2",
|
||||
"saw UPnP type WANIPConnection2 at http://127.0.0.1:NNN/rootDesc.xml; OnHub (Google)\n",
|
||||
},
|
||||
{
|
||||
"pfsense",
|
||||
pfSenseRootDescXML,
|
||||
"*internetgateway2.WANIPConnection1",
|
||||
"saw UPnP type WANIPConnection1 at http://127.0.0.1:NNN/rootDesc.xml; FreeBSD router (FreeBSD)\n",
|
||||
},
|
||||
{"google", googleWifiRootDescXML, "*internetgateway2.WANIPConnection2"},
|
||||
{"pfsense", pfSenseRootDescXML, "*internetgateway2.WANIPConnection1"},
|
||||
// TODO(bradfitz): find a PPP one in the wild
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -93,12 +80,7 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
gw, _ := netaddr.FromStdIP(ts.Listener.Addr().(*net.TCPAddr).IP)
|
||||
var logBuf bytes.Buffer
|
||||
logf := func(format string, a ...interface{}) {
|
||||
fmt.Fprintf(&logBuf, format, a...)
|
||||
logBuf.WriteByte('\n')
|
||||
}
|
||||
c, err := getUPnPClient(context.Background(), logf, gw, uPnPDiscoResponse{
|
||||
c, err := getUPnPClient(context.Background(), gw, uPnPDiscoResponse{
|
||||
Location: ts.URL + "/rootDesc.xml",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -108,10 +90,6 @@ func TestGetUPnPClient(t *testing.T) {
|
||||
if got != tt.want {
|
||||
t.Errorf("got %v; want %v", got, tt.want)
|
||||
}
|
||||
gotLog := regexp.MustCompile(`127\.0\.0\.1:\d+`).ReplaceAllString(logBuf.String(), "127.0.0.1:NNN")
|
||||
if gotLog != tt.wantLog {
|
||||
t.Errorf("logged %q; want %q", gotLog, tt.wantLog)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.
|
||||
//go:build gofuzz
|
||||
// +build gofuzz
|
||||
|
||||
package stun
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build tailscale_go
|
||||
// +build tailscale_go
|
||||
|
||||
// We want to use https://github.com/golang/go/issues/41048 but it's only in the
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package tstun
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
// 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 tstun
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
"inet.af/netaddr"
|
||||
"inet.af/netstack/tcpip"
|
||||
"inet.af/netstack/tcpip/buffer"
|
||||
"inet.af/netstack/tcpip/header"
|
||||
"inet.af/netstack/tcpip/network/ipv4"
|
||||
"inet.af/netstack/tcpip/transport/udp"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
|
||||
// TODO: this was randomly generated once. Maybe do it per process start? But
|
||||
// then an upgraded tailscaled would be visible to devices behind it. So
|
||||
// maybe instead make it a function of the tailscaled's wireguard public key?
|
||||
// For now just hard code it.
|
||||
var ourMAC = net.HardwareAddr{0x30, 0x2D, 0x66, 0xEC, 0x7A, 0x93}
|
||||
|
||||
func init() { createTAP = createTAPLinux }
|
||||
|
||||
func createTAPLinux(tapName, bridgeName string) (dev tun.Device, err error) {
|
||||
fd, err := syscall.Open("/dev/net/tun", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ifr struct {
|
||||
name [16]byte
|
||||
flags uint16
|
||||
_ [22]byte
|
||||
}
|
||||
copy(ifr.name[:], tapName)
|
||||
ifr.flags = syscall.IFF_TAP | syscall.IFF_NO_PI
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
|
||||
if errno != 0 {
|
||||
syscall.Close(fd)
|
||||
return nil, errno
|
||||
}
|
||||
if err = syscall.SetNonblock(fd, true); err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := run("ip", "link", "set", "dev", tapName, "up"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bridgeName != "" {
|
||||
if err := run("brctl", "addif", bridgeName, tapName); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
dev, _, err = tun.CreateUnmonitoredTUNFromFD(fd) // TODO: MTU
|
||||
if err != nil {
|
||||
syscall.Close(fd)
|
||||
return nil, err
|
||||
}
|
||||
return dev, nil
|
||||
}
|
||||
|
||||
type etherType [2]byte
|
||||
|
||||
var (
|
||||
etherTypeARP = etherType{0x08, 0x06}
|
||||
etherTypeIPv4 = etherType{0x08, 0x00}
|
||||
etherTypeIPv6 = etherType{0x86, 0xDD}
|
||||
)
|
||||
|
||||
const ipv4HeaderLen = 20
|
||||
|
||||
const (
|
||||
consumePacket = true
|
||||
passOnPacket = false
|
||||
)
|
||||
|
||||
// handleTAPFrame handles receiving a raw TAP ethernet frame and reports whether
|
||||
// it's been handled (that is, whether it should NOT be passed to wireguard).
|
||||
func (t *Wrapper) handleTAPFrame(ethBuf []byte) bool {
|
||||
|
||||
if len(ethBuf) < ethernetFrameSize {
|
||||
// Corrupt. Ignore.
|
||||
if tapDebug {
|
||||
t.logf("tap: short TAP frame")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
||||
_ = ethDstMAC
|
||||
et := etherType{ethBuf[12], ethBuf[13]}
|
||||
switch et {
|
||||
default:
|
||||
if tapDebug {
|
||||
t.logf("tap: ignoring etherType %v", et)
|
||||
}
|
||||
return consumePacket // filter out packet we should ignore
|
||||
case etherTypeIPv6:
|
||||
// TODO: support DHCPv6/ND/etc later. For now pass all to WireGuard.
|
||||
if tapDebug {
|
||||
t.logf("tap: ignoring IPv6 %v", et)
|
||||
}
|
||||
return passOnPacket
|
||||
case etherTypeIPv4:
|
||||
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen {
|
||||
// Bogus IPv4. Eat.
|
||||
if tapDebug {
|
||||
t.logf("tap: short ipv4")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
return t.handleDHCPRequest(ethBuf)
|
||||
case etherTypeARP:
|
||||
arpPacket := header.ARP(ethBuf[ethernetFrameSize:])
|
||||
if !arpPacket.IsValid() {
|
||||
// Bogus ARP. Eat.
|
||||
return consumePacket
|
||||
}
|
||||
switch arpPacket.Op() {
|
||||
case header.ARPRequest:
|
||||
req := arpPacket // better name at this point
|
||||
buf := make([]byte, header.EthernetMinimumSize+header.ARPSize)
|
||||
|
||||
// Our ARP "Table" of one:
|
||||
var srcMAC [6]byte
|
||||
copy(srcMAC[:], ethSrcMAC)
|
||||
if old := t.destMAC(); old != srcMAC {
|
||||
t.destMACAtomic.Store(srcMAC)
|
||||
}
|
||||
|
||||
eth := header.Ethernet(buf)
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: tcpip.LinkAddress(ourMAC[:]),
|
||||
DstAddr: tcpip.LinkAddress(ethSrcMAC),
|
||||
Type: 0x0806, // arp
|
||||
})
|
||||
res := header.ARP(buf[header.EthernetMinimumSize:])
|
||||
res.SetIPv4OverEthernet()
|
||||
res.SetOp(header.ARPReply)
|
||||
|
||||
// If the client's asking about their own IP, tell them it's
|
||||
// their own MAC. TODO(bradfitz): remove String allocs.
|
||||
if net.IP(req.ProtocolAddressTarget()).String() == theClientIP {
|
||||
copy(res.HardwareAddressSender(), ethSrcMAC)
|
||||
} else {
|
||||
copy(res.HardwareAddressSender(), ourMAC[:])
|
||||
}
|
||||
|
||||
copy(res.ProtocolAddressSender(), req.ProtocolAddressTarget())
|
||||
copy(res.HardwareAddressTarget(), req.HardwareAddressSender())
|
||||
copy(res.ProtocolAddressTarget(), req.ProtocolAddressSender())
|
||||
|
||||
n, err := t.tdev.Write(buf, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote ARP reply %v, %v", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
return consumePacket
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(bradfitz): remove these hard-coded values and move from a /24 to a /10 CGNAT as the range.
|
||||
const theClientIP = "100.70.145.3" // TODO: make dynamic from netmap
|
||||
const routerIP = "100.70.145.1" // must be in same netmask (currently hack at /24) as theClientIP
|
||||
|
||||
// handleDHCPRequest handles receiving a raw TAP ethernet frame and reports whether
|
||||
// it's been handled as a DHCP request. That is, it reports whether the frame should
|
||||
// be ignored by the caller and not passed on.
|
||||
func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
|
||||
const udpHeader = 8
|
||||
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen+udpHeader {
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP short")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
ethDstMAC, ethSrcMAC := ethBuf[:6], ethBuf[6:12]
|
||||
|
||||
if string(ethDstMAC) != "\xff\xff\xff\xff\xff\xff" {
|
||||
// Not a broadcast
|
||||
if tapDebug {
|
||||
t.logf("tap: dhcp no broadcast")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(ethBuf[ethernetFrameSize:])
|
||||
|
||||
if p.IPProto != ipproto.UDP || p.Src.Port() != 68 || p.Dst.Port() != 67 {
|
||||
// Not a DHCP request.
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP wrong meta")
|
||||
}
|
||||
return passOnPacket
|
||||
}
|
||||
|
||||
dp, err := dhcpv4.FromBytes(ethBuf[ethernetFrameSize+ipv4HeaderLen+udpHeader:])
|
||||
if err != nil {
|
||||
// Bogus. Trash it.
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP FromBytes bad")
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
if tapDebug {
|
||||
t.logf("tap: DHCP request: %+v", dp)
|
||||
}
|
||||
switch dp.MessageType() {
|
||||
case dhcpv4.MessageTypeDiscover:
|
||||
offer, err := dhcpv4.New(
|
||||
dhcpv4.WithReply(dp),
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeOffer),
|
||||
dhcpv4.WithRouter(net.ParseIP(routerIP)), // the default route
|
||||
dhcpv4.WithDNS(net.ParseIP("100.100.100.100")),
|
||||
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // TODO: what is this?
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
|
||||
dhcpv4.WithYourIP(net.ParseIP(theClientIP)),
|
||||
dhcpv4.WithLeaseTime(3600), // hour works
|
||||
//dhcpv4.WithHwAddr(ethSrcMAC),
|
||||
dhcpv4.WithNetmask(net.IPMask(net.ParseIP("255.255.255.0").To4())), // TODO: wrong
|
||||
//dhcpv4.WithTransactionID(dp.TransactionID),
|
||||
)
|
||||
if err != nil {
|
||||
t.logf("error building DHCP offer: %v", err)
|
||||
return consumePacket
|
||||
}
|
||||
// Make a layer 2 packet to write out:
|
||||
pkt := packLayer2UDP(
|
||||
offer.ToBytes(),
|
||||
ourMAC, ethSrcMAC,
|
||||
netaddr.IPPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
||||
netaddr.IPPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
||||
)
|
||||
n, err := t.tdev.Write(pkt, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote DHCP OFFER %v, %v", n, err)
|
||||
}
|
||||
case dhcpv4.MessageTypeRequest:
|
||||
ack, err := dhcpv4.New(
|
||||
dhcpv4.WithReply(dp),
|
||||
dhcpv4.WithMessageType(dhcpv4.MessageTypeAck),
|
||||
dhcpv4.WithDNS(net.ParseIP("100.100.100.100")),
|
||||
dhcpv4.WithRouter(net.ParseIP(routerIP)), // the default route
|
||||
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // TODO: what is this?
|
||||
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
|
||||
dhcpv4.WithYourIP(net.ParseIP(theClientIP)), // Hello world
|
||||
dhcpv4.WithLeaseTime(3600), // hour works
|
||||
dhcpv4.WithNetmask(net.IPMask(net.ParseIP("255.255.255.0").To4())),
|
||||
)
|
||||
if err != nil {
|
||||
t.logf("error building DHCP ack: %v", err)
|
||||
return consumePacket
|
||||
}
|
||||
// Make a layer 2 packet to write out:
|
||||
pkt := packLayer2UDP(
|
||||
ack.ToBytes(),
|
||||
ourMAC, ethSrcMAC,
|
||||
netaddr.IPPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
|
||||
netaddr.IPPortFrom(netaddr.IPv4(255, 255, 255, 255), 68), // dst
|
||||
)
|
||||
n, err := t.tdev.Write(pkt, 0)
|
||||
if tapDebug {
|
||||
t.logf("tap: wrote DHCP ACK %v, %v", n, err)
|
||||
}
|
||||
default:
|
||||
if tapDebug {
|
||||
t.logf("tap: unknown DHCP type")
|
||||
}
|
||||
}
|
||||
return consumePacket
|
||||
}
|
||||
|
||||
func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst netaddr.IPPort) []byte {
|
||||
buf := buffer.NewView(header.EthernetMinimumSize + header.UDPMinimumSize + header.IPv4MinimumSize + len(payload))
|
||||
payloadStart := len(buf) - len(payload)
|
||||
copy(buf[payloadStart:], payload)
|
||||
srcB := src.IP().As4()
|
||||
srcIP := tcpip.Address(srcB[:])
|
||||
dstB := dst.IP().As4()
|
||||
dstIP := tcpip.Address(dstB[:])
|
||||
// Ethernet header
|
||||
eth := header.Ethernet(buf)
|
||||
eth.Encode(&header.EthernetFields{
|
||||
SrcAddr: tcpip.LinkAddress(srcMAC),
|
||||
DstAddr: tcpip.LinkAddress(dstMAC),
|
||||
Type: ipv4.ProtocolNumber,
|
||||
})
|
||||
// IP header
|
||||
ipbuf := buf[header.EthernetMinimumSize:]
|
||||
ip := header.IPv4(ipbuf)
|
||||
ip.Encode(&header.IPv4Fields{
|
||||
TotalLength: uint16(len(ipbuf)),
|
||||
TTL: 65,
|
||||
Protocol: uint8(udp.ProtocolNumber),
|
||||
SrcAddr: srcIP,
|
||||
DstAddr: dstIP,
|
||||
})
|
||||
ip.SetChecksum(^ip.CalculateChecksum())
|
||||
// UDP header
|
||||
u := header.UDP(buf[header.EthernetMinimumSize+header.IPv4MinimumSize:])
|
||||
u.Encode(&header.UDPFields{
|
||||
SrcPort: src.Port(),
|
||||
DstPort: dst.Port(),
|
||||
Length: uint16(header.UDPMinimumSize + len(payload)),
|
||||
})
|
||||
// Calculate the UDP pseudo-header checksum.
|
||||
xsum := header.PseudoHeaderChecksum(udp.ProtocolNumber, srcIP, dstIP, uint16(len(u)))
|
||||
// Calculate the UDP checksum and set it.
|
||||
xsum = header.Checksum(payload, xsum)
|
||||
u.SetChecksum(^u.CalculateChecksum(xsum))
|
||||
return []byte(buf)
|
||||
}
|
||||
|
||||
func run(prog string, args ...string) error {
|
||||
cmd := exec.Command(prog, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("error running %v: %v", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Wrapper) destMAC() [6]byte {
|
||||
mac, _ := t.destMACAtomic.Load().([6]byte)
|
||||
return mac
|
||||
}
|
||||
|
||||
func (t *Wrapper) tapWrite(buf []byte, offset int) (int, error) {
|
||||
if offset < ethernetFrameSize {
|
||||
return 0, fmt.Errorf("[unexpected] weird offset %d for TAP write", offset)
|
||||
}
|
||||
eth := buf[offset-ethernetFrameSize:]
|
||||
dst := t.destMAC()
|
||||
copy(eth[:6], dst[:])
|
||||
copy(eth[6:12], ourMAC[:])
|
||||
et := etherTypeIPv4
|
||||
if buf[offset]>>4 == 6 {
|
||||
et = etherTypeIPv6
|
||||
}
|
||||
eth[12], eth[13] = et[0], et[1]
|
||||
if tapDebug {
|
||||
t.logf("tap: tapWrite off=%v % x", offset, buf)
|
||||
}
|
||||
return t.tdev.Write(buf, offset-ethernetFrameSize)
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// 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.
|
||||
|
||||
//go:build !linux
|
||||
// +build !linux
|
||||
|
||||
package tstun
|
||||
|
||||
func (*Wrapper) handleTAPFrame([]byte) bool { panic("unreachable") }
|
||||
func (*Wrapper) tapWrite([]byte, int) (int, error) { panic("unreachable") }
|
||||
@@ -8,12 +8,10 @@ package tstun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.zx2c4.com/wireguard/tun"
|
||||
@@ -37,32 +35,10 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// createTAP is non-nil on Linux.
|
||||
var createTAP func(tapName, bridgeName string) (tun.Device, error)
|
||||
|
||||
// New returns a tun.Device for the requested device name, along with
|
||||
// the OS-dependent name that was allocated to the device.
|
||||
func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
|
||||
var dev tun.Device
|
||||
var err error
|
||||
if strings.HasPrefix(tunName, "tap:") {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil, "", errors.New("tap only works on Linux")
|
||||
}
|
||||
f := strings.Split(tunName, ":")
|
||||
var tapName, bridgeName string
|
||||
switch len(f) {
|
||||
case 2:
|
||||
tapName = f[1]
|
||||
case 3:
|
||||
tapName, bridgeName = f[1], f[2]
|
||||
default:
|
||||
return nil, "", errors.New("bogus tap argument")
|
||||
}
|
||||
dev, err = createTAP(tapName, bridgeName)
|
||||
} else {
|
||||
dev, err = tun.CreateTUN(tunName, tunMTU)
|
||||
}
|
||||
dev, err := tun.CreateTUN(tunName, tunMTU)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package tstun
|
||||
|
||||
@@ -8,10 +8,8 @@ package tstun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -23,7 +21,6 @@ import (
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/types/pad32"
|
||||
"tailscale.com/wgengine/filter"
|
||||
)
|
||||
|
||||
@@ -38,8 +35,6 @@ const PacketStartOffset = device.MessageTransportHeaderSize
|
||||
// of a packet that can be injected into a tstun.Wrapper.
|
||||
const MaxPacketSize = device.MaxContentSize
|
||||
|
||||
const tapDebug = false // for super verbose TAP debugging
|
||||
|
||||
var (
|
||||
// ErrClosed is returned when attempting an operation on a closed Wrapper.
|
||||
ErrClosed = errors.New("device closed")
|
||||
@@ -66,16 +61,13 @@ type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
|
||||
type Wrapper struct {
|
||||
logf logger.Logf
|
||||
// tdev is the underlying Wrapper device.
|
||||
tdev tun.Device
|
||||
isTAP bool // whether tdev is a TAP device
|
||||
tdev tun.Device
|
||||
|
||||
closeOnce sync.Once
|
||||
|
||||
_ pad32.Four
|
||||
lastActivityAtomic mono.Time // time of last send or receive
|
||||
|
||||
destIPActivity atomic.Value // of map[netaddr.IP]func()
|
||||
destMACAtomic atomic.Value // of [6]byte
|
||||
|
||||
// buffer stores the oldest unconsumed packet from tdev.
|
||||
// It is made a static buffer in order to avoid allocations.
|
||||
@@ -154,19 +146,10 @@ type tunReadResult struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func WrapTAP(logf logger.Logf, tdev tun.Device) *Wrapper {
|
||||
return wrap(logf, tdev, true)
|
||||
}
|
||||
|
||||
func Wrap(logf logger.Logf, tdev tun.Device) *Wrapper {
|
||||
return wrap(logf, tdev, false)
|
||||
}
|
||||
|
||||
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool) *Wrapper {
|
||||
tun := &Wrapper{
|
||||
logf: logger.WithPrefix(logf, "tstun: "),
|
||||
isTAP: isTAP,
|
||||
tdev: tdev,
|
||||
logf: logger.WithPrefix(logf, "tstun: "),
|
||||
tdev: tdev,
|
||||
// bufferConsumed is conceptually a condition variable:
|
||||
// a goroutine should not block when setting it, even with no listeners.
|
||||
bufferConsumed: make(chan struct{}, 1),
|
||||
@@ -301,14 +284,11 @@ func allowSendOnClosedChannel() {
|
||||
panic(r)
|
||||
}
|
||||
|
||||
const ethernetFrameSize = 14 // 2 six byte MACs, 2 bytes ethertype
|
||||
|
||||
// poll polls t.tdev.Read, placing the oldest unconsumed packet into t.buffer.
|
||||
// This is needed because t.tdev.Read in general may block (it does on Windows),
|
||||
// so packets may be stuck in t.outbound if t.Read called t.tdev.Read directly.
|
||||
func (t *Wrapper) poll() {
|
||||
for range t.bufferConsumed {
|
||||
DoRead:
|
||||
var n int
|
||||
var err error
|
||||
// Read may use memory in t.buffer before PacketStartOffset for mandatory headers.
|
||||
@@ -323,33 +303,7 @@ func (t *Wrapper) poll() {
|
||||
if t.isClosed() {
|
||||
return
|
||||
}
|
||||
if t.isTAP {
|
||||
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset-ethernetFrameSize)
|
||||
if tapDebug {
|
||||
s := fmt.Sprintf("% x", t.buffer[:])
|
||||
for strings.HasSuffix(s, " 00") {
|
||||
s = strings.TrimSuffix(s, " 00")
|
||||
}
|
||||
t.logf("TAP read %v, %v: %s", n, err, s)
|
||||
}
|
||||
} else {
|
||||
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
|
||||
}
|
||||
}
|
||||
if t.isTAP {
|
||||
if err == nil {
|
||||
ethernetFrame := t.buffer[PacketStartOffset-ethernetFrameSize:][:n]
|
||||
if t.handleTAPFrame(ethernetFrame) {
|
||||
goto DoRead
|
||||
}
|
||||
}
|
||||
// Fall through. We got an IP packet.
|
||||
if n >= ethernetFrameSize {
|
||||
n -= ethernetFrameSize
|
||||
}
|
||||
if tapDebug {
|
||||
t.logf("tap regular frame: %x", t.buffer[PacketStartOffset:PacketStartOffset+n])
|
||||
}
|
||||
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
|
||||
}
|
||||
t.sendOutbound(tunReadResult{data: t.buffer[PacketStartOffset : PacketStartOffset+n], err: err})
|
||||
}
|
||||
@@ -567,13 +521,6 @@ func (t *Wrapper) Write(buf []byte, offset int) (int, error) {
|
||||
}
|
||||
|
||||
t.noteActivity()
|
||||
return t.tdevWrite(buf, offset)
|
||||
}
|
||||
|
||||
func (t *Wrapper) tdevWrite(buf []byte, offset int) (int, error) {
|
||||
if t.isTAP {
|
||||
return t.tapWrite(buf, offset)
|
||||
}
|
||||
return t.tdev.Write(buf, offset)
|
||||
}
|
||||
|
||||
@@ -606,7 +553,7 @@ func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
|
||||
}
|
||||
|
||||
// Write to the underlying device to skip filters.
|
||||
_, err := t.tdevWrite(buf, offset)
|
||||
_, err := t.tdev.Write(buf, offset)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package paths
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (go1.16 && !ios) || (!go1.16 && !darwin) || (!go1.16 && !arm64)
|
||||
// +build go1.16,!ios !go1.16,!darwin !go1.16,!arm64
|
||||
|
||||
package portlist
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (windows || freebsd || openbsd || (darwin && go1.16) || (darwin && !go1.16 && !arm64)) && !ios
|
||||
// +build windows freebsd openbsd darwin,go1.16 darwin,!go1.16,!arm64
|
||||
// +build !ios
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build (go1.16 && ios) || (!go1.16 && darwin && !amd64)
|
||||
// +build go1.16,ios !go1.16,darwin,!amd64
|
||||
|
||||
package portlist
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ((darwin && amd64 && !go1.16) || (darwin && go1.16)) && !ios
|
||||
// +build darwin,amd64,!go1.16 darwin,go1.16
|
||||
// +build !ios
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !linux && !windows && !darwin
|
||||
// +build !linux,!windows,!darwin
|
||||
|
||||
package portlist
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package safesocket
|
||||
|
||||
@@ -46,11 +46,11 @@ main() {
|
||||
VERSION="$VERSION_CODENAME"
|
||||
PACKAGETYPE="apt"
|
||||
;;
|
||||
centos|ol)
|
||||
centos)
|
||||
OS="$ID"
|
||||
VERSION="$VERSION_ID"
|
||||
PACKAGETYPE="dnf"
|
||||
if [ "$VERSION" =~ ^7 ]; then
|
||||
if [ "$VERSION" = "7" ]; then
|
||||
PACKAGETYPE="yum"
|
||||
fi
|
||||
;;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13 && !go1.16
|
||||
// +build go1.13,!go1.16
|
||||
|
||||
// This file makes assumptions about the inner workings of sync.Mutex and sync.RWMutex.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.13 && !go1.16
|
||||
// +build go1.13,!go1.16
|
||||
|
||||
package syncs
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
package tailcfg
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode --clonefunc=true --output=tailcfg_clone.go
|
||||
//go:generate go run tailscale.com/cmd/cloner --type=User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode --clonefunc=true --output=tailcfg_clone.go
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"go4.org/mem"
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/structs"
|
||||
@@ -833,21 +832,38 @@ var FilterAllowAll = []FilterRule{
|
||||
},
|
||||
}
|
||||
|
||||
// DNSResolver is the configuration for one DNS resolver.
|
||||
type DNSResolver struct {
|
||||
// Addr is the address of the DNS resolver, one of:
|
||||
// - A plain IP address for a "classic" UDP+TCP DNS resolver
|
||||
// - [TODO] "tls://resolver.com" for DNS over TCP+TLS
|
||||
// - [TODO] "https://resolver.com/query-tmpl" for DNS over HTTPS
|
||||
Addr string `json:",omitempty"`
|
||||
|
||||
// BootstrapResolution is an optional suggested resolution for the
|
||||
// DoT/DoH resolver, if the resolver URL does not reference an IP
|
||||
// address directly.
|
||||
// BootstrapResolution may be empty, in which case clients should
|
||||
// look up the DoT/DoH server using their local "classic" DNS
|
||||
// resolver.
|
||||
BootstrapResolution []netaddr.IP `json:",omitempty"`
|
||||
}
|
||||
|
||||
// DNSConfig is the DNS configuration.
|
||||
type DNSConfig struct {
|
||||
// Resolvers are the DNS resolvers to use, in order of preference.
|
||||
Resolvers []dnstype.Resolver `json:",omitempty"`
|
||||
Resolvers []DNSResolver `json:",omitempty"`
|
||||
// Routes maps DNS name suffixes to a set of DNS resolvers to
|
||||
// use. It is used to implement "split DNS" and other advanced DNS
|
||||
// routing overlays.
|
||||
// Map keys must be fully-qualified DNS name suffixes, with a
|
||||
// trailing dot but no leading dot.
|
||||
Routes map[string][]dnstype.Resolver `json:",omitempty"`
|
||||
Routes map[string][]DNSResolver `json:",omitempty"`
|
||||
// FallbackResolvers is like Resolvers, but is only used if a
|
||||
// split DNS configuration is requested in a configuration that
|
||||
// doesn't work yet without explicit default resolvers.
|
||||
// https://github.com/tailscale/tailscale/issues/1743
|
||||
FallbackResolvers []dnstype.Resolver `json:",omitempty"`
|
||||
FallbackResolvers []DNSResolver `json:",omitempty"`
|
||||
// Domains are the search domains to use.
|
||||
// Search domains must be FQDNs, but *without* the trailing dot.
|
||||
Domains []string `json:",omitempty"`
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
// 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 User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode; DO NOT EDIT.
|
||||
// Code generated by tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode; DO NOT EDIT.
|
||||
|
||||
package tailcfg
|
||||
|
||||
import (
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/opt"
|
||||
"tailscale.com/types/structs"
|
||||
"time"
|
||||
@@ -27,7 +26,7 @@ func (src *User) Clone() *User {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _UserNeedsRegeneration = User(struct {
|
||||
ID UserID
|
||||
LoginName string
|
||||
@@ -64,7 +63,7 @@ func (src *Node) Clone() *Node {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _NodeNeedsRegeneration = Node(struct {
|
||||
ID NodeID
|
||||
StableID StableNodeID
|
||||
@@ -108,7 +107,7 @@ func (src *Hostinfo) Clone() *Hostinfo {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _HostinfoNeedsRegeneration = Hostinfo(struct {
|
||||
IPNVersion string
|
||||
FrontendLogID string
|
||||
@@ -145,7 +144,7 @@ func (src *NetInfo) Clone() *NetInfo {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _NetInfoNeedsRegeneration = NetInfo(struct {
|
||||
MappingVariesByDestIP opt.Bool
|
||||
HairPinning opt.Bool
|
||||
@@ -172,7 +171,7 @@ func (src *Login) Clone() *Login {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _LoginNeedsRegeneration = Login(struct {
|
||||
_ structs.Incomparable
|
||||
ID LoginID
|
||||
@@ -191,17 +190,17 @@ func (src *DNSConfig) Clone() *DNSConfig {
|
||||
}
|
||||
dst := new(DNSConfig)
|
||||
*dst = *src
|
||||
dst.Resolvers = make([]dnstype.Resolver, len(src.Resolvers))
|
||||
dst.Resolvers = make([]DNSResolver, len(src.Resolvers))
|
||||
for i := range dst.Resolvers {
|
||||
dst.Resolvers[i] = *src.Resolvers[i].Clone()
|
||||
}
|
||||
if dst.Routes != nil {
|
||||
dst.Routes = map[string][]dnstype.Resolver{}
|
||||
dst.Routes = map[string][]DNSResolver{}
|
||||
for k := range src.Routes {
|
||||
dst.Routes[k] = append([]dnstype.Resolver{}, src.Routes[k]...)
|
||||
dst.Routes[k] = append([]DNSResolver{}, src.Routes[k]...)
|
||||
}
|
||||
}
|
||||
dst.FallbackResolvers = make([]dnstype.Resolver, len(src.FallbackResolvers))
|
||||
dst.FallbackResolvers = make([]DNSResolver, len(src.FallbackResolvers))
|
||||
for i := range dst.FallbackResolvers {
|
||||
dst.FallbackResolvers[i] = *src.FallbackResolvers[i].Clone()
|
||||
}
|
||||
@@ -213,11 +212,11 @@ func (src *DNSConfig) Clone() *DNSConfig {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _DNSConfigNeedsRegeneration = DNSConfig(struct {
|
||||
Resolvers []dnstype.Resolver
|
||||
Routes map[string][]dnstype.Resolver
|
||||
FallbackResolvers []dnstype.Resolver
|
||||
Resolvers []DNSResolver
|
||||
Routes map[string][]DNSResolver
|
||||
FallbackResolvers []DNSResolver
|
||||
Domains []string
|
||||
Proxied bool
|
||||
Nameservers []netaddr.IP
|
||||
@@ -226,6 +225,25 @@ var _DNSConfigNeedsRegeneration = DNSConfig(struct {
|
||||
ExtraRecords []DNSRecord
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of DNSResolver.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *DNSResolver) Clone() *DNSResolver {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(DNSResolver)
|
||||
*dst = *src
|
||||
dst.BootstrapResolution = append(src.BootstrapResolution[:0:0], src.BootstrapResolution...)
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _DNSResolverNeedsRegeneration = DNSResolver(struct {
|
||||
Addr string
|
||||
BootstrapResolution []netaddr.IP
|
||||
}{})
|
||||
|
||||
// Clone makes a deep copy of RegisterResponse.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *RegisterResponse) Clone() *RegisterResponse {
|
||||
@@ -239,7 +257,7 @@ func (src *RegisterResponse) Clone() *RegisterResponse {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _RegisterResponseNeedsRegeneration = RegisterResponse(struct {
|
||||
User User
|
||||
Login Login
|
||||
@@ -264,7 +282,7 @@ func (src *DERPRegion) Clone() *DERPRegion {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _DERPRegionNeedsRegeneration = DERPRegion(struct {
|
||||
RegionID int
|
||||
RegionCode string
|
||||
@@ -291,7 +309,7 @@ func (src *DERPMap) Clone() *DERPMap {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _DERPMapNeedsRegeneration = DERPMap(struct {
|
||||
Regions map[int]*DERPRegion
|
||||
OmitDefaultRegions bool
|
||||
@@ -309,7 +327,7 @@ func (src *DERPNode) Clone() *DERPNode {
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
// tailscale.com/cmd/cloner -type User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode
|
||||
var _DERPNodeNeedsRegeneration = DERPNode(struct {
|
||||
Name string
|
||||
RegionID int
|
||||
@@ -326,7 +344,7 @@ var _DERPNodeNeedsRegeneration = DERPNode(struct {
|
||||
|
||||
// Clone duplicates src into dst and reports whether it succeeded.
|
||||
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
|
||||
// where T is one of User,Node,Hostinfo,NetInfo,Login,DNSConfig,RegisterResponse,DERPRegion,DERPMap,DERPNode.
|
||||
// where T is one of User,Node,Hostinfo,NetInfo,Login,DNSConfig,DNSResolver,RegisterResponse,DERPRegion,DERPMap,DERPNode.
|
||||
func Clone(dst, src interface{}) bool {
|
||||
switch src := src.(type) {
|
||||
case *User:
|
||||
@@ -383,6 +401,15 @@ func Clone(dst, src interface{}) bool {
|
||||
*dst = src.Clone()
|
||||
return true
|
||||
}
|
||||
case *DNSResolver:
|
||||
switch dst := dst.(type) {
|
||||
case *DNSResolver:
|
||||
*dst = *src.Clone()
|
||||
return true
|
||||
case **DNSResolver:
|
||||
*dst = src.Clone()
|
||||
return true
|
||||
}
|
||||
case *RegisterResponse:
|
||||
switch dst := dst.(type) {
|
||||
case *RegisterResponse:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build (windows && 386) || (windows && arm)
|
||||
// +build windows,386 windows,arm
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build (windows && amd64) || (windows && arm64)
|
||||
// +build windows,amd64 windows,arm64
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/* SPDX-License-Identifier: MIT
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
_ "flag"
|
||||
_ "fmt"
|
||||
_ "github.com/go-multierror/multierror"
|
||||
_ "inet.af/netaddr"
|
||||
_ "io"
|
||||
_ "io/ioutil"
|
||||
_ "log"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
// Command udp_tester exists because all of these distros being tested don't
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package vms
|
||||
|
||||
@@ -95,21 +95,16 @@ func (t Time) String() string {
|
||||
return fmt.Sprintf("mono.Time(ns=%d, estimated wall=%v)", int64(t), baseWall.Add(t.Sub(baseMono)).Truncate(0))
|
||||
}
|
||||
|
||||
// WallTime returns an approximate wall time that corresponded to t.
|
||||
func (t Time) WallTime() time.Time {
|
||||
if !t.IsZero() {
|
||||
return baseWall.Add(t.Sub(baseMono)).Truncate(0)
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// MarshalJSON formats t for JSON as if it were a time.Time.
|
||||
// We format Time this way for backwards-compatibility.
|
||||
// This is best-effort only. Time does not survive a MarshalJSON/UnmarshalJSON round trip unchanged.
|
||||
// Since t is a monotonic time, it can vary from the actual wall clock by arbitrary amounts.
|
||||
// Even in the best of circumstances, it may vary by a few milliseconds.
|
||||
func (t Time) MarshalJSON() ([]byte, error) {
|
||||
tt := t.WallTime()
|
||||
var tt time.Time
|
||||
if !t.IsZero() {
|
||||
tt = baseWall.Add(t.Sub(baseMono)).Truncate(0)
|
||||
}
|
||||
return tt.MarshalJSON()
|
||||
}
|
||||
|
||||
@@ -121,10 +116,6 @@ func (t *Time) UnmarshalJSON(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tt.IsZero() {
|
||||
*t = 0
|
||||
return nil
|
||||
}
|
||||
*t = Now().Add(-time.Since(tt))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
package mono
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -18,22 +17,6 @@ func TestNow(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalZero(t *testing.T) {
|
||||
var tt time.Time
|
||||
buf, err := json.Marshal(tt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var m Time
|
||||
err = json.Unmarshal(buf, &m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !m.IsZero() {
|
||||
t.Errorf("expected unmarshal of zero time to be 0, got %d (~=%v)", m, m)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMonoNow(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Now()
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
// 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 dnstype defines types for working with DNS.
|
||||
package dnstype
|
||||
|
||||
//go:generate go run tailscale.com/cmd/cloner --type=Resolver --clonefunc=true --output=dnstype_clone.go
|
||||
|
||||
import "inet.af/netaddr"
|
||||
|
||||
// Resolver is the configuration for one DNS resolver.
|
||||
type Resolver struct {
|
||||
// Addr is the address of the DNS resolver, one of:
|
||||
// - A plain IP address for a "classic" UDP+TCP DNS resolver
|
||||
// - [TODO] "tls://resolver.com" for DNS over TCP+TLS
|
||||
// - [TODO] "https://resolver.com/query-tmpl" for DNS over HTTPS
|
||||
Addr string `json:",omitempty"`
|
||||
|
||||
// BootstrapResolution is an optional suggested resolution for the
|
||||
// DoT/DoH resolver, if the resolver URL does not reference an IP
|
||||
// address directly.
|
||||
// BootstrapResolution may be empty, in which case clients should
|
||||
// look up the DoT/DoH server using their local "classic" DNS
|
||||
// resolver.
|
||||
BootstrapResolution []netaddr.IP `json:",omitempty"`
|
||||
}
|
||||
@@ -1,48 +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.
|
||||
|
||||
// Code generated by tailscale.com/cmd/cloner -type Resolver; DO NOT EDIT.
|
||||
|
||||
package dnstype
|
||||
|
||||
import (
|
||||
"inet.af/netaddr"
|
||||
)
|
||||
|
||||
// Clone makes a deep copy of Resolver.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *Resolver) Clone() *Resolver {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(Resolver)
|
||||
*dst = *src
|
||||
dst.BootstrapResolution = append(src.BootstrapResolution[:0:0], src.BootstrapResolution...)
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with command:
|
||||
// tailscale.com/cmd/cloner -type Resolver
|
||||
var _ResolverNeedsRegeneration = Resolver(struct {
|
||||
Addr string
|
||||
BootstrapResolution []netaddr.IP
|
||||
}{})
|
||||
|
||||
// Clone duplicates src into dst and reports whether it succeeded.
|
||||
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
|
||||
// where T is one of Resolver.
|
||||
func Clone(dst, src interface{}) bool {
|
||||
switch src := src.(type) {
|
||||
case *Resolver:
|
||||
switch dst := dst.(type) {
|
||||
case *Resolver:
|
||||
*dst = *src.Clone()
|
||||
return true
|
||||
case **Resolver:
|
||||
*dst = src.Clone()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package logger
|
||||
|
||||
@@ -8,15 +8,8 @@
|
||||
// The hash is sufficiently strong and unique such that
|
||||
// Hash(x) == Hash(y) is an appropriate replacement for x == y.
|
||||
//
|
||||
// The definition of equality is identical to reflect.DeepEqual except:
|
||||
// * Floating-point values are compared based on the raw bits,
|
||||
// which means that NaNs (with the same bit pattern) are treated as equal.
|
||||
// * Types which implement interface { AppendTo([]byte) []byte } use
|
||||
// the AppendTo method to produce a textual representation of the value.
|
||||
// Thus, two values are equal if AppendTo produces the same bytes.
|
||||
//
|
||||
// WARNING: This package, like most of the tailscale.com Go module,
|
||||
// should be considered Tailscale-internal; we make no API promises.
|
||||
// This package, like most of the tailscale.com Go module, should be
|
||||
// considered Tailscale-internal; we make no API promises.
|
||||
package deephash
|
||||
|
||||
import (
|
||||
@@ -33,33 +26,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// There is much overlap between the theory of serialization and hashing.
|
||||
// A hash (useful for determing equality) can be produced by printing a value
|
||||
// and hashing the output. The format must:
|
||||
// * be deterministic such that the same value hashes to the same output, and
|
||||
// * be parsable such that the same value can be reproduced by the output.
|
||||
//
|
||||
// The logic below hashes a value by printing it to a hash.Hash.
|
||||
// To be parsable, it assumes that we know the Go type of each value:
|
||||
// * scalar types (e.g., bool or int32) are printed as fixed-width fields.
|
||||
// * list types (e.g., strings, slices, and AppendTo buffers) are prefixed
|
||||
// by a fixed-width length field, followed by the contents of the list.
|
||||
// * slices, arrays, and structs print each element/field consecutively.
|
||||
// * interfaces print with a 1-byte prefix indicating whether it is nil.
|
||||
// If non-nil, it is followed by a fixed-width field of the type index,
|
||||
// followed by the format of the underlying value.
|
||||
// * pointers print with a 1-byte prefix indicating whether the pointer is
|
||||
// 1) nil, 2) previously seen, or 3) newly seen. Previously seen pointers are
|
||||
// followed by a fixed-width field with the index of the previous pointer.
|
||||
// Newly seen pointers are followed by the format of the underlying value.
|
||||
// * maps print with a 1-byte prefix indicating whether the map pointer is
|
||||
// 1) nil, 2) previously seen, or 3) newly seen. Previously seen pointers
|
||||
// are followed by a fixed-width field of the index of the previous pointer.
|
||||
// Newly seen maps are printed as a fixed-width field with the XOR of the
|
||||
// hash of every map entry. With a sufficiently strong hash, this value is
|
||||
// theoretically "parsable" by looking up the hash in a magical map that
|
||||
// returns the set of entries for that given hash.
|
||||
|
||||
const scratchSize = 128
|
||||
|
||||
// hasher is reusable state for hashing a value.
|
||||
@@ -208,7 +174,10 @@ func (h *hasher) hashValue(v reflect.Value) {
|
||||
h.hashUint8(1) // indicates visiting a pointer
|
||||
h.hashValue(v.Elem())
|
||||
case reflect.Struct:
|
||||
w.WriteString("struct")
|
||||
h.hashUint64(uint64(v.NumField()))
|
||||
for i, n := 0, v.NumField(); i < n; i++ {
|
||||
h.hashUint64(uint64(i))
|
||||
h.hashValue(v.Field(i))
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
@@ -233,6 +202,7 @@ func (h *hasher) hashValue(v reflect.Value) {
|
||||
// TODO(dsnet): Perform cycle detection for slices,
|
||||
// which is functionally a list of pointers.
|
||||
// See https://github.com/google/go-cmp/blob/402949e8139bb890c71a707b6faf6dd05c92f4e5/cmp/compare.go#L438-L450
|
||||
h.hashUint64(uint64(i))
|
||||
h.hashValue(v.Index(i))
|
||||
}
|
||||
case reflect.Interface:
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
|
||||
"inet.af/netaddr"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/dnstype"
|
||||
"tailscale.com/types/ipproto"
|
||||
"tailscale.com/util/dnsname"
|
||||
"tailscale.com/version"
|
||||
@@ -190,7 +189,7 @@ func getVal() []interface{} {
|
||||
},
|
||||
},
|
||||
DNSConfig: &tailcfg.DNSConfig{
|
||||
Resolvers: []dnstype.Resolver{
|
||||
Resolvers: []tailcfg.DNSResolver{
|
||||
{Addr: "10.0.0.1"},
|
||||
},
|
||||
},
|
||||
@@ -270,7 +269,11 @@ func TestPrintArray(t *testing.T) {
|
||||
h := &hasher{bw: bw}
|
||||
h.hashValue(reflect.ValueOf(x))
|
||||
bw.Flush()
|
||||
const want = "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f"
|
||||
const want = "struct" +
|
||||
"\x01\x00\x00\x00\x00\x00\x00\x00" + // 1 field
|
||||
"\x00\x00\x00\x00\x00\x00\x00\x00" + // 0th field
|
||||
// the 32 bytes:
|
||||
"\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f"
|
||||
if got := got.Bytes(); string(got) != want {
|
||||
t.Errorf("wrong:\n got: %q\nwant: %q\n", got, want)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user