Compare commits

..

2 Commits

Author SHA1 Message Date
Brad Fitzpatrick
4a8d15971c fix DHCP 2021-07-26 09:41:15 -07:00
Brad Fitzpatrick
2dcc88806b net/tstun: add TAP support on Linux, with DHCP+ARP server
Co-Author: David Crawshaw <crawshaw@tailscale.com>
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2021-07-23 11:38:49 -07:00
168 changed files with 830 additions and 2938 deletions

View File

@@ -4,6 +4,8 @@ on:
pull_request:
paths:
- "tstest/integration/vms/**"
push:
branches: [ main ]
release:
types: [ created ]

View File

@@ -42,7 +42,8 @@ FROM golang:1.16-alpine AS build-env
WORKDIR /go/src/tailscale
COPY go.mod go.sum ./
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .

View File

@@ -1 +1 @@
1.13.0
1.11.0

View File

@@ -9,9 +9,7 @@ import (
"errors"
"fmt"
"log"
"net"
"strings"
"time"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
@@ -41,34 +39,6 @@ func startMeshWithHost(s *derp.Server, host string) error {
return err
}
c.MeshKey = s.MeshKey()
// For meshed peers within a region, connect via VPC addresses.
c.SetURLDialer(func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
var d net.Dialer
var r net.Resolver
if port == "443" && strings.HasSuffix(host, ".tailscale.com") {
base := strings.TrimSuffix(host, ".tailscale.com")
subCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
vpcHost := base + "-vpc.tailscale.com"
ips, _ := r.LookupIP(subCtx, "ip", vpcHost)
if len(ips) > 0 {
vpcAddr := net.JoinHostPort(ips[0].String(), port)
c, err := d.DialContext(subCtx, network, vpcAddr)
if err == nil {
log.Printf("connected to %v (%v) instead of %v", vpcHost, ips[0], base)
return c, nil
}
log.Printf("failed to connect to %v (%v): %v; trying non-VPC route", vpcHost, ips[0], err)
}
}
return d.DialContext(ctx, network, addr)
})
add := func(k key.Public) { s.AddPacketForwarder(k, c) }
remove := func(k key.Public) { s.RemovePacketForwarder(k, c) }
go c.RunWatchConnectionLoop(context.Background(), s.PublicKey(), logf, add, remove)

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -14,6 +14,7 @@ import (
"net/http"
"os"
"strings"
"time"
"github.com/peterbourgon/ff/v2/ffcli"
"github.com/toqueteos/webbrowser"
@@ -61,7 +62,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 +130,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 +139,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 +178,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 +189,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() && time.Since(ps.LastWrite) < 2*time.Minute
}
func dnsOrQuoteHostname(st *ipnstate.Status, ps *ipnstate.PeerStatus) string {
baseName := dnsname.TrimSuffix(ps.DNSName, st.MagicDNSSuffix)
if baseName != "" {

View File

@@ -70,13 +70,13 @@ func newUpFlagSet(goos string, upArgs *upArgsT) *flag.FlagSet {
upf.BoolVar(&upArgs.acceptRoutes, "accept-routes", false, "accept routes advertised by other Tailscale nodes")
upf.BoolVar(&upArgs.acceptDNS, "accept-dns", true, "accept DNS configuration from the admin panel")
upf.BoolVar(&upArgs.singleRoutes, "host-routes", true, "install host routes to other Tailscale nodes")
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic, or empty string to not use an exit node")
upf.StringVar(&upArgs.exitNodeIP, "exit-node", "", "Tailscale IP of the exit node for internet traffic")
upf.BoolVar(&upArgs.exitNodeAllowLANAccess, "exit-node-allow-lan-access", false, "Allow direct access to the local network when routing traffic via an exit node")
upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections")
upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "comma-separated ACL tags to request; each must start with \"tag:\" (e.g. \"tag:eng,tag:montreal,tag:ssh\")")
upf.StringVar(&upArgs.authKey, "authkey", "", "node authorization key")
upf.StringVar(&upArgs.hostname, "hostname", "", "hostname to use instead of the one provided by the OS")
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\") or empty string to not advertise routes")
upf.StringVar(&upArgs.advertiseRoutes, "advertise-routes", "", "routes to advertise to other nodes (comma-separated, e.g. \"10.0.0.0/8,192.168.0.0/24\")")
upf.BoolVar(&upArgs.advertiseDefaultRoute, "advertise-exit-node", false, "offer to be an exit node for internet traffic for the tailnet")
if safesocket.GOOSUsesPeerCreds(goos) {
upf.StringVar(&upArgs.opUser, "operator", "", "Unix username to allow to operate on tailscaled without sudo")

View File

@@ -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,12 @@ 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/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+

View File

@@ -14,23 +14,18 @@ import (
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/http/httptrace"
"net/url"
"os"
"strings"
"time"
"inet.af/netaddr"
"tailscale.com/derp/derphttp"
"tailscale.com/ipn"
"tailscale.com/net/interfaces"
"tailscale.com/net/portmapper"
"tailscale.com/net/tshttpproxy"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/wgengine/monitor"
)
@@ -38,7 +33,6 @@ var debugArgs struct {
monitor bool
getURL string
derpCheck string
portmap bool
}
var debugModeFunc = debugMode // so it can be addressable
@@ -46,7 +40,6 @@ var debugModeFunc = debugMode // so it can be addressable
func debugMode(args []string) error {
fs := flag.NewFlagSet("debug", flag.ExitOnError)
fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. Precludes all other options.")
fs.BoolVar(&debugArgs.portmap, "portmap", false, "If true, run portmap debugging. Precludes all other options.")
fs.StringVar(&debugArgs.getURL, "get-url", "", "If non-empty, fetch provided URL.")
fs.StringVar(&debugArgs.derpCheck, "derp", "", "if non-empty, test a DERP ping via named region code")
if err := fs.Parse(args); err != nil {
@@ -62,9 +55,6 @@ func debugMode(args []string) error {
if debugArgs.monitor {
return runMonitor(ctx)
}
if debugArgs.portmap {
return debugPortmap(ctx)
}
if debugArgs.getURL != "" {
return getURL(ctx, debugArgs.getURL)
}
@@ -201,97 +191,3 @@ func checkDerp(ctx context.Context, derpRegion string) error {
log.Printf("ok")
return err
}
func debugPortmap(ctx context.Context) error {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
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)
var c *portmapper.Client
logf := log.Printf
c = portmapper.NewClient(logger.WithPrefix(logf, "portmapper: "), func() {
logf("portmapping changed.")
logf("have mapping: %v", c.HaveMapping())
if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok {
logf("cb: mapping: %v", ext)
select {
case done <- true:
default:
}
return
}
logf("cb: no mapping")
})
linkMon, err := monitor.New(logger.WithPrefix(logf, "monitor: "))
if err != nil {
return err
}
gatewayAndSelfIP := func() (gw, self netaddr.IP, ok bool) {
if v := os.Getenv("TS_DEBUG_GW_SELF"); strings.Contains(v, "/") {
i := strings.Index(v, "/")
gw = netaddr.MustParseIP(v[:i])
self = netaddr.MustParseIP(v[i+1:])
return gw, self, true
}
return linkMon.GatewayAndSelfIP()
}
c.SetGatewayLookupFunc(gatewayAndSelfIP)
gw, selfIP, ok := gatewayAndSelfIP()
if !ok {
logf("no gateway or self IP; %v", linkMon.InterfaceState())
return nil
}
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)
}
logf("Probe: %+v", res)
if !res.PCP && !res.PMP && !res.UPnP {
logf("no portmapping services available")
return nil
}
if ext, ok := c.GetCachedMappingOrStartCreatingOne(); ok {
logf("mapping: %v", ext)
} else {
logf("no mapping")
}
select {
case <-done:
return nil
case <-ctx.Done():
return ctx.Err()
}
}

View File

@@ -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+
@@ -137,9 +130,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/tailcfg from tailscale.com/control/controlclient+
W tailscale.com/tsconst from tailscale.com/net/interfaces
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 +138,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+

View File

@@ -142,7 +142,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)")
}
@@ -214,9 +214,6 @@ func run() error {
logf = logger.RateLimitedFn(logf, 5*time.Second, 5, 100)
if args.cleanup {
if os.Getenv("TS_PLEASE_PANIC") != "" {
panic("TS_PLEASE_PANIC asked us to panic")
}
dns.Cleanup(logf, args.tunname)
router.Cleanup(logf, args.tunname)
return nil

View File

@@ -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"

View File

@@ -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
@@ -30,8 +29,8 @@ import (
"time"
"unsafe"
"github.com/creack/pty"
"github.com/gliderlabs/ssh"
"github.com/kr/pty"
gossh "golang.org/x/crypto/ssh"
"inet.af/netaddr"
"tailscale.com/net/interfaces"

View File

@@ -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

View File

@@ -220,13 +220,6 @@ func packageType() string {
// Using tailscaled or IPNExtension?
exe, _ := os.Executable()
return filepath.Base(exe)
case "linux":
// Report whether this is in a snap.
// See https://snapcraft.io/docs/environment-variables
// We just look at two somewhat arbitrarily.
if os.Getenv("SNAP_NAME") != "" && os.Getenv("SNAP") != "" {
return "snap"
}
}
return ""
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
@@ -161,7 +167,7 @@ type PacketForwarder interface {
// Conn is the subset of the underlying net.Conn the DERP Server needs.
// It is a defined type so that non-net connections can be used.
type Conn interface {
io.WriteCloser
io.Closer
// The *Deadline methods follow the semantics of net.Conn.
@@ -464,9 +470,8 @@ func (s *Server) addWatcher(c *sclient) {
}
func (s *Server) accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string, connNum int64) error {
br := brw.Reader
br, bw := brw.Reader, brw.Writer
nc.SetDeadline(time.Now().Add(10 * time.Second))
bw := &lazyBufioWriter{w: nc, lbw: brw.Writer}
if err := s.sendServerKey(bw); err != nil {
return fmt.Errorf("send server key: %v", err)
}
@@ -529,7 +534,7 @@ func (s *Server) accept(nc Conn, brw *bufio.ReadWriter, remoteAddr string, connN
}
defer s.unregisterClient(c)
err = s.sendServerInfo(c.bw, clientKey)
err = s.sendServerInfo(bw, clientKey)
if err != nil {
return fmt.Errorf("send server info: %v", err)
}
@@ -732,7 +737,7 @@ func (c *sclient) handleFrameSendPacket(ft frameType, fl uint32) error {
// dropReason is why we dropped a DERP frame.
type dropReason int
//go:generate go run tailscale.com/cmd/addlicense -year 2021 -file dropreason_string.go go run golang.org/x/tools/cmd/stringer -type=dropReason -trimprefix=dropReason
//go:generate go run tailscale.com/cmd/addlicense -year 2021 -file dropreason_string.go stringer -type=dropReason -trimprefix=dropReason
const (
dropReasonUnknownDest dropReason = iota // unknown destination pubkey
@@ -841,20 +846,18 @@ func (s *Server) verifyClient(clientKey key.Public, info *clientInfo) error {
return nil
}
func (s *Server) sendServerKey(lw *lazyBufioWriter) error {
func (s *Server) sendServerKey(bw *bufio.Writer) error {
buf := make([]byte, 0, len(magic)+len(s.publicKey))
buf = append(buf, magic...)
buf = append(buf, s.publicKey[:]...)
err := writeFrame(lw.bw(), frameServerKey, buf)
lw.Flush() // redundant (no-op) flush to release bufio.Writer
return err
return writeFrame(bw, frameServerKey, buf)
}
type serverInfo struct {
Version int `json:"version,omitempty"`
}
func (s *Server) sendServerInfo(bw *lazyBufioWriter, clientKey key.Public) error {
func (s *Server) sendServerInfo(bw *bufio.Writer, clientKey key.Public) error {
var nonce [24]byte
if _, err := crand.Read(nonce[:]); err != nil {
return err
@@ -865,7 +868,7 @@ func (s *Server) sendServerInfo(bw *lazyBufioWriter, clientKey key.Public) error
}
msgbox := box.Seal(nil, msg, &nonce, clientKey.B32(), s.privateKey.B32())
if err := writeFrameHeader(bw.bw(), frameServerInfo, nonceLen+uint32(len(msgbox))); err != nil {
if err := writeFrameHeader(bw, frameServerInfo, nonceLen+uint32(len(msgbox))); err != nil {
return err
}
if _, err := bw.Write(nonce[:]); err != nil {
@@ -999,7 +1002,7 @@ type sclient struct {
preferred bool
// Owned by sender, not thread-safe.
bw *lazyBufioWriter
bw *bufio.Writer
// Guarded by s.mu
//
@@ -1161,14 +1164,14 @@ func (c *sclient) setWriteDeadline() {
// sendKeepAlive sends a keep-alive frame, without flushing.
func (c *sclient) sendKeepAlive() error {
c.setWriteDeadline()
return writeFrameHeader(c.bw.bw(), frameKeepAlive, 0)
return writeFrameHeader(c.bw, frameKeepAlive, 0)
}
// sendPeerGone sends a peerGone frame, without flushing.
func (c *sclient) sendPeerGone(peer key.Public) error {
c.s.peerGoneFrames.Add(1)
c.setWriteDeadline()
if err := writeFrameHeader(c.bw.bw(), framePeerGone, keyLen); err != nil {
if err := writeFrameHeader(c.bw, framePeerGone, keyLen); err != nil {
return err
}
_, err := c.bw.Write(peer[:])
@@ -1178,7 +1181,7 @@ func (c *sclient) sendPeerGone(peer key.Public) error {
// sendPeerPresent sends a peerPresent frame, without flushing.
func (c *sclient) sendPeerPresent(peer key.Public) error {
c.setWriteDeadline()
if err := writeFrameHeader(c.bw.bw(), framePeerPresent, keyLen); err != nil {
if err := writeFrameHeader(c.bw, framePeerPresent, keyLen); err != nil {
return err
}
_, err := c.bw.Write(peer[:])
@@ -1251,11 +1254,11 @@ func (c *sclient) sendPacket(srcKey key.Public, contents []byte) (err error) {
if withKey {
pktLen += len(srcKey)
}
if err = writeFrameHeader(c.bw.bw(), frameRecvPacket, uint32(pktLen)); err != nil {
if err = writeFrameHeader(c.bw, frameRecvPacket, uint32(pktLen)); err != nil {
return err
}
if withKey {
err := writePublicKey(c.bw.bw(), &srcKey)
err := writePublicKey(c.bw, &srcKey)
if err != nil {
return err
}
@@ -1570,45 +1573,3 @@ func (s *Server) ServeDebugTraffic(w http.ResponseWriter, r *http.Request) {
time.Sleep(minTimeBetweenLogs)
}
}
var bufioWriterPool = &sync.Pool{
New: func() interface{} {
return bufio.NewWriterSize(ioutil.Discard, 2<<10)
},
}
// lazyBufioWriter is a bufio.Writer-like wrapping writer that lazily
// allocates its actual bufio.Writer from a sync.Pool, releasing it to
// the pool upon flush.
//
// We do this to reduce memory overhead; most DERP connections are
// idle and the idle bufio.Writers were 30% of overall memory usage.
type lazyBufioWriter struct {
w io.Writer // underlying
lbw *bufio.Writer // lazy; nil means it needs an associated buffer
}
func (w *lazyBufioWriter) bw() *bufio.Writer {
if w.lbw == nil {
w.lbw = bufioWriterPool.Get().(*bufio.Writer)
w.lbw.Reset(w.w)
}
return w.lbw
}
func (w *lazyBufioWriter) Available() int { return w.bw().Available() }
func (w *lazyBufioWriter) Write(p []byte) (int, error) { return w.bw().Write(p) }
func (w *lazyBufioWriter) Flush() error {
if w.lbw == nil {
return nil
}
err := w.lbw.Flush()
w.lbw.Reset(ioutil.Discard)
bufioWriterPool.Put(w.lbw)
w.lbw = nil
return err
}

View File

@@ -54,7 +54,6 @@ type Client struct {
privateKey key.Private
logf logger.Logf
dialer func(ctx context.Context, network, addr string) (net.Conn, error)
// Either url or getRegion is non-nil:
url *url.URL
@@ -364,26 +363,14 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
return c.client, c.connGen, nil
}
// SetURLDialer sets the dialer to use for dialing URLs.
// This dialer is only use for clients created with NewClient, not NewRegionClient.
// If unset or nil, the default dialer is used.
//
// The primary use for this is the derper mesh mode to connect to each
// other over a VPC network.
func (c *Client) SetURLDialer(dialer func(ctx context.Context, network, addr string) (net.Conn, error)) {
c.dialer = dialer
}
func (c *Client) dialURL(ctx context.Context) (net.Conn, error) {
host := c.url.Hostname()
if c.dialer != nil {
return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url)))
}
hostOrIP := host
dialer := netns.NewDialer()
if c.DNSCache != nil {
ip, _, _, err := c.DNSCache.LookupIP(ctx, host)
ip, _, err := c.DNSCache.LookupIP(ctx, host)
if err == nil {
hostOrIP = ip.String()
}

View File

@@ -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

15
go.mod
View File

@@ -7,9 +7,7 @@ require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aws/aws-sdk-go v1.38.52
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/dave/jennifer v1.4.1 // indirect
github.com/frankban/quicktest v1.13.0
github.com/gliderlabs/ssh v0.3.2
github.com/go-multierror/multierror v1.0.2
@@ -19,11 +17,12 @@ require (
github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f
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/iancoleman/strcase v0.2.0 // indirect
github.com/insomniacslk/dhcp v0.0.0-20210621130208-1cac67f12b1e // indirect
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
github.com/kr/pty v1.1.8
github.com/mdlayher/netlink v1.4.1
github.com/mdlayher/sdnotify v0.0.0-20210228150836-ea3ec207d697
github.com/miekg/dns v1.1.42
@@ -33,8 +32,8 @@ require (
github.com/pkg/sftp v1.13.0
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05
github.com/tailscale/hujson v0.0.0-20200924210142-dde312d0d6a2
github.com/tailscale/goupnp v1.0.1-0.20210710010003-1cf2d718bbb2 // indirect
github.com/tailscale/hujson v0.0.0-20200924210142-dde312d0d6a2 // indirect
github.com/tcnksm/go-httpstat v0.2.0
github.com/toqueteos/webbrowser v1.2.0
go4.org/mem v0.0.0-20201119185036-c04c5a6ff174
@@ -48,7 +47,7 @@ require (
golang.zx2c4.com/wireguard v0.0.0-20210624150102-15b24b6179e0
golang.zx2c4.com/wireguard/windows v0.3.16
honnef.co/go/tools v0.1.4
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1
inet.af/netaddr v0.0.0-20210602152128-50f8686885e3
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e
inet.af/peercred v0.0.0-20210318190834-4259e17bb763
inet.af/wf v0.0.0-20210516214145-a5343001b756

19
go.sum
View File

@@ -82,6 +82,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daixiang0/gci v0.2.4/go.mod h1:+AV8KmHTGxxwp/pY84TLQfFKp2vuKXXJVzF3kD/hfR4=
@@ -96,8 +97,6 @@ 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=
@@ -365,6 +364,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
@@ -591,8 +592,12 @@ github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3 h1:fEubocuQkrl
github.com/tailscale/certstore v0.0.0-20210528134328-066c94b793d3/go.mod h1:2P+hpOwd53e7JMX/L4f3VXkv1G+33ES6IWZSrkIeWNs=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 h1:lK99QQdH3yBWY6aGilF+IRlQIdmhzLrsEmF6JgN+Ryw=
github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027/go.mod h1:p9lPsd+cx33L3H9nNoecRRxPssFKUwwI50I3pZ0yT+8=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05 h1:4chzWmimtJPxRs2O36yuGRW3f9SYV+bMTTvMBI0EKio=
github.com/tailscale/goupnp v1.0.1-0.20210804011211-c64d0f06ea05/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/goupnp v1.0.1-0.20210629174436-7df6c9efe30c h1:F+pROyGPs+9wdB7jBPHr9IZEF8SKj9YUCFFShnyLNZM=
github.com/tailscale/goupnp v1.0.1-0.20210629174436-7df6c9efe30c/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/goupnp v1.0.1-0.20210629175715-39c5a55db683 h1:ZXmZQuVebYllEJL/dpttpIDGx723ezC5GJkHIu0YKrM=
github.com/tailscale/goupnp v1.0.1-0.20210629175715-39c5a55db683/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/goupnp v1.0.1-0.20210710010003-1cf2d718bbb2 h1:AIJ8AF9O7jBmCwilP0ydwJMIzW5dw48Us8f3hLJhYBY=
github.com/tailscale/goupnp v1.0.1-0.20210710010003-1cf2d718bbb2/go.mod h1:PdCqy9JzfWMJf1H5UJW2ip33/d4YkoKN0r67yKH1mG8=
github.com/tailscale/hujson v0.0.0-20200924210142-dde312d0d6a2 h1:reREUgl2FG+o7YCsrZB8XLjnuKv5hEIWtnOdAbRAXZI=
github.com/tailscale/hujson v0.0.0-20200924210142-dde312d0d6a2/go.mod h1:STqf+YV0ADdzk4ejtXFsGqDpATP9JoL0OB+hiFQbkdE=
github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0=
@@ -703,6 +708,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -750,6 +756,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
@@ -971,8 +978,8 @@ honnef.co/go/tools v0.0.1-2020.1.6/go.mod h1:pyyisuGw24ruLjrr1ddx39WE0y9OooInRzE
honnef.co/go/tools v0.1.4 h1:SadWOkti5uVN1FAMgxn165+Mw00fuQKyk4Gyn/inxNQ=
honnef.co/go/tools v0.1.4/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
inet.af/netaddr v0.0.0-20210515010201-ad03edc7c841/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1 h1:mxmfTV6kjXTlFqqFETnG9FQZzNFc6AKunZVAgQ3b7WA=
inet.af/netaddr v0.0.0-20210721214506-ce7a8ad02cc1/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netaddr v0.0.0-20210602152128-50f8686885e3 h1:RlarOdsmOUCCvy7Xm1JchJIGuQsuKwD/Lo1bjYmfuQI=
inet.af/netaddr v0.0.0-20210602152128-50f8686885e3/go.mod h1:z0nx+Dh+7N7CC8V5ayHtHGpZpxLQZZxkIaaz6HN65Ls=
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e h1:z11NK94NQcI3DA+a3pUC/2dRYTph1kPX6B0FnCaMDzk=
inet.af/netstack v0.0.0-20210622165351-29b14ebc044e/go.mod h1:fG3G1dekmK8oDX3iVzt8c0zICLMLSN8SjdxbXVt0WjU=
inet.af/peercred v0.0.0-20210318190834-4259e17bb763 h1:gPSJmmVzmdy4kHhlCMx912GdiUz3k/RzJGg0ADqy1dg=

View File

@@ -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"
@@ -985,44 +984,6 @@ var removeFromDefaultRoute = []netaddr.IPPrefix{
tsaddr.TailscaleULARange(),
}
// internalAndExternalInterfaces splits interface routes into "internal"
// and "external" sets. Internal routes are those of virtual ethernet
// network interfaces used by guest VMs and containers, such as WSL and
// Docker.
//
// Given that "internal" routes don't leave the device, we choose to
// trust them more, allowing access to them when an Exit Node is enabled.
func internalAndExternalInterfaces() (internal, external []netaddr.IPPrefix, err error) {
if err := interfaces.ForeachInterfaceAddress(func(iface interfaces.Interface, pfx netaddr.IPPrefix) {
if tsaddr.IsTailscaleIP(pfx.IP()) {
return
}
if pfx.IsSingleIP() {
return
}
if runtime.GOOS == "windows" {
// Windows Hyper-V prefixes all MAC addresses with 00:15:5d.
// https://docs.microsoft.com/en-us/troubleshoot/windows-server/virtualization/default-limit-256-dynamic-mac-addresses
//
// This includes WSL2 vEthernet.
// Importantly: by default WSL2 /etc/resolv.conf points to
// a stub resolver running on the host vEthernet IP.
// So enabling exit nodes with the default tailnet
// configuration breaks WSL2 DNS without this.
mac := iface.Interface.HardwareAddr
if len(mac) == 6 && mac[0] == 0x00 && mac[1] == 0x15 && mac[2] == 0x5d {
internal = append(internal, pfx)
return
}
}
external = append(external, pfx)
}); err != nil {
return nil, nil, err
}
return internal, external, nil
}
func interfaceRoutes() (ips *netaddr.IPSet, hostIPs []netaddr.IP, err error) {
var b netaddr.IPSetBuilder
if err := interfaces.ForeachInterfaceAddress(func(_ interfaces.Interface, pfx netaddr.IPPrefix) {
@@ -1821,7 +1782,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 +1858,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)
@@ -2158,21 +2119,18 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router
if !default6 {
rs.Routes = append(rs.Routes, ipv6Default)
}
internalIPs, externalIPs, err := internalAndExternalInterfaces()
if err != nil {
b.logf("failed to discover interface ips: %v", err)
}
if runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
rs.LocalRoutes = internalIPs // unconditionally allow access to guest VM networks
// Only allow local lan access on linux machines for now.
ips, _, err := interfaceRoutes()
if err != nil {
b.logf("failed to discover interface ips: %v", err)
}
if prefs.ExitNodeAllowLANAccess {
rs.LocalRoutes = append(rs.LocalRoutes, externalIPs...)
if len(externalIPs) != 0 {
b.logf("allowing exit node access to internal IPs: %v", internalIPs)
}
rs.LocalRoutes = ips.Prefixes()
} else {
// Explicitly add routes to the local network so that we do not
// leak any traffic.
rs.Routes = append(rs.Routes, externalIPs...)
rs.Routes = append(rs.Routes, ips.Prefixes()...)
}
}
}
@@ -2201,18 +2159,6 @@ func applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Prefs) {
}
if v := prefs.OSVersion; v != "" {
hi.OSVersion = v
// The Android app annotates when Google Play Services
// aren't available by tacking on a string to the
// OSVersion. Promote that to the Hostinfo.Package
// field instead, rather than adding a new pref, as
// this applyPrefsToHostinfo mechanism is mostly
// abused currently. TODO(bradfitz): instead let
// frontends update Hostinfo, without using Prefs.
if runtime.GOOS == "android" && strings.HasSuffix(v, " [nogoogle]") {
hi.Package = "nogoogle"
hi.OSVersion = strings.TrimSuffix(v, " [nogoogle]")
}
}
if m := prefs.DeviceModel; m != "" {
hi.DeviceModel = m
@@ -2785,18 +2731,17 @@ func (b *LocalBackend) CheckIPForwarding() error {
return nil
}
const suffix = "\nSubnet routes won't work without IP forwarding.\nSee https://tailscale.com/kb/1104/enable-ip-forwarding/"
for _, key := range keys {
bs, err := exec.Command("sysctl", "-n", key).Output()
if err != nil {
return fmt.Errorf("couldn't check %s (%v)%s", key, err, suffix)
return fmt.Errorf("couldn't check %s (%v).\nSubnet routes won't work without IP forwarding.", key, err)
}
on, err := strconv.ParseBool(string(bytes.TrimSpace(bs)))
if err != nil {
return fmt.Errorf("couldn't parse %s (%v)%s.", key, err, suffix)
return fmt.Errorf("couldn't parse %s (%v).\nSubnet routes won't work without IP forwarding.", key, err)
}
if !on {
return fmt.Errorf("%s is disabled.%s", key, suffix)
return fmt.Errorf("%s is disabled. Subnet routes won't work.", key)
}
}
return nil

View File

@@ -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

View File

@@ -96,13 +96,6 @@ type PeerStatus struct {
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 +277,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 {
@@ -387,7 +377,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() && time.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 != "" {

View File

@@ -187,10 +187,6 @@ func runningUnderSystemd() bool {
return false
}
func redirectStderrToLogPanics() bool {
return runningUnderSystemd() || os.Getenv("TS_PLEASE_PANIC") != ""
}
// tryFixLogStateLocation is a temporary fixup for
// https://github.com/tailscale/tailscale/issues/247 . We accidentally
// wrote logging state files to /, and then later to $CACHE_DIRECTORY
@@ -439,14 +435,9 @@ func New(collection string) *Policy {
c.HTTPC = &http.Client{Transport: newLogtailTransport(u.Host)}
}
filchBuf, filchErr := filch.New(filepath.Join(dir, cmdName), filch.Options{
ReplaceStderr: redirectStderrToLogPanics(),
})
filchBuf, filchErr := filch.New(filepath.Join(dir, cmdName), filch.Options{})
if filchBuf != nil {
c.Buffer = filchBuf
if filchBuf.OrigStderr != nil {
c.Stderr = filchBuf.OrigStderr
}
}
lw := logtail.NewLogger(c, log.Printf)
log.SetFlags(0) // other logflags are set on console, not here

View File

@@ -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

View File

@@ -118,10 +118,9 @@ type Logger struct {
bo *backoff.Backoff
zstdEncoder Encoder
uploadCancel func()
explainedRaw bool
shutdownStart chan struct{} // closed when shutdown begins
shutdownDone chan struct{} // closed when shutdown complete
shutdownDone chan struct{} // closd when shutdown complete
}
// SetVerbosityLevel controls the verbosity level that should be
@@ -231,14 +230,6 @@ func (l *Logger) drainPending() (res []byte) {
// outside of the logtail logger. Encode it.
// Do not add a client time, as it could have been
// been written a long time ago.
if !l.explainedRaw {
fmt.Fprintf(l.stderr, "RAW-STDERR: ***\n")
fmt.Fprintf(l.stderr, "RAW-STDERR: *** Lines prefixed with RAW-STDERR below bypassed logtail and probably come from a previous run of the program\n")
fmt.Fprintf(l.stderr, "RAW-STDERR: ***\n")
fmt.Fprintf(l.stderr, "RAW-STDERR:\n")
l.explainedRaw = true
}
fmt.Fprintf(l.stderr, "RAW-STDERR: %s", b)
b = l.encodeText(b, true)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -293,7 +293,7 @@ func (m windowsManager) SetDNS(cfg OSConfig) error {
}
t0 = time.Now()
m.logf("running ipconfig /flushdns ...")
m.logf("running ipconfig /registerdns ...")
cmd = exec.Command("ipconfig", "/flushdns")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
err = cmd.Run()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -44,9 +44,7 @@ func TestDoH(t *testing.T) {
t.Fatal("no known DoH")
}
f := &forwarder{
dohSem: make(chan struct{}, 10),
}
f := new(forwarder)
for ip := range knownDoH {
t.Run(ip.String(), func(t *testing.T) {

View File

@@ -17,7 +17,6 @@ import (
"net"
"net/http"
"runtime"
"sort"
"strings"
"sync"
"time"
@@ -41,11 +40,6 @@ const (
// connections open to DNS-over-HTTPs servers. This is pretty
// arbitrary.
dohTransportTimeout = 30 * time.Second
// wellKnownHostBackupDelay is how long to artificially delay upstream
// DNS queries to the "fallback" DNS server IP for a known provider
// (e.g. how long to wait to query Google's 8.8.4.4 after 8.8.8.8).
wellKnownHostBackupDelay = 200 * time.Millisecond
)
var errNoUpstreams = errors.New("upstream nameservers not set")
@@ -125,7 +119,6 @@ func clampEDNSSize(packet []byte, maxSize uint16) {
return
}
// https://datatracker.ietf.org/doc/html/rfc6891#section-6.1.2
opt := packet[len(packet)-optFixedBytes:]
if opt[0] != 0 {
@@ -142,8 +135,8 @@ func clampEDNSSize(packet []byte, maxSize uint16) {
// Be conservative and don't touch unknown versions.
return
}
// Ignore flags in opt[6:9]
if binary.BigEndian.Uint16(opt[9:11]) != 0 {
// Ignore flags in opt[7:9]
if binary.BigEndian.Uint16(opt[10:12]) != 0 {
// RDLEN must be 0 (no variable length data). We're at the end of the
// packet so this should be 0 anyway)..
return
@@ -159,20 +152,7 @@ func clampEDNSSize(packet []byte, maxSize uint16) {
type route struct {
Suffix dnsname.FQDN
Resolvers []resolverAndDelay
}
// resolverAndDelay is an upstream DNS resolver and a delay for how
// long to wait before querying it.
type resolverAndDelay struct {
// ipp is the upstream resolver.
ipp netaddr.IPPort
// startDelay is an amount to delay this resolver at
// start. It's used when, say, there are four Google or
// Cloudflare DNS IPs (two IPv4 + two IPv6) and we don't want
// to race all four at once.
startDelay time.Duration
Resolvers []netaddr.IPPort
}
// forwarder forwards DNS packets to a number of upstream nameservers.
@@ -224,84 +204,7 @@ func (f *forwarder) Close() error {
return nil
}
// resolversWithDelays maps from a set of DNS server ip:ports (currently
// the port is always 53) to a slice of a type that included a
// startDelay. So if ipps contains e.g. four Google DNS IPs (two IPv4
// + twoIPv6), this function partition adds delays to some.
func resolversWithDelays(ipps []netaddr.IPPort) []resolverAndDelay {
type hostAndFam struct {
host string // some arbitrary string representing DNS host (currently the DoH base)
bits uint8 // either 32 or 128 for IPv4 vs IPv6s address family
}
// Track how many of each known resolver host are in the list,
// per address family.
total := map[hostAndFam]int{}
rr := make([]resolverAndDelay, len(ipps))
for _, ipp := range ipps {
ip := ipp.IP()
if host, ok := knownDoH[ip]; ok {
total[hostAndFam{host, ip.BitLen()}]++
}
}
done := map[hostAndFam]int{}
for i, ipp := range ipps {
ip := ipp.IP()
var startDelay time.Duration
if host, ok := knownDoH[ip]; ok {
key4 := hostAndFam{host, 32}
key6 := hostAndFam{host, 128}
switch {
case ip.Is4():
if done[key4] > 0 {
startDelay += wellKnownHostBackupDelay
}
case ip.Is6():
total4 := total[key4]
if total4 >= 2 {
// If we have two IPv4 IPs of the same provider
// already in the set, delay the IPv6 queries
// until halfway through the timeout (so wait
// 2.5 seconds). Even the network is IPv6-only,
// the DoH dialer will fallback to IPv6
// immediately anyway.
startDelay = responseTimeout / 2
} else if total4 == 1 {
startDelay += wellKnownHostBackupDelay
}
if done[key6] > 0 {
startDelay += wellKnownHostBackupDelay
}
}
done[hostAndFam{host, ip.BitLen()}]++
}
rr[i] = resolverAndDelay{
ipp: ipp,
startDelay: startDelay,
}
}
return rr
}
// setRoutes sets the routes to use for DNS forwarding. It's called by
// Resolver.SetConfig on reconfig.
//
// The memory referenced by routesBySuffix should not be modified.
func (f *forwarder) setRoutes(routesBySuffix map[dnsname.FQDN][]netaddr.IPPort) {
routes := make([]route, 0, len(routesBySuffix))
for suffix, ipps := range routesBySuffix {
routes = append(routes, route{
Suffix: suffix,
Resolvers: resolversWithDelays(ipps),
})
}
// Sort from longest prefix to shortest.
sort.Slice(routes, func(i, j int) bool {
return routes[i].Suffix.NumLabels() > routes[j].Suffix.NumLabels()
})
func (f *forwarder) setRoutes(routes []route) {
f.mu.Lock()
defer f.mu.Unlock()
f.routes = routes
@@ -413,19 +316,22 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client,
//
// send expects the reply to have the same txid as txidOut.
//
func (f *forwarder) send(ctx context.Context, fq *forwardQuery, dst netaddr.IPPort) ([]byte, error) {
ip := dst.IP()
// The provided closeOnCtxDone lets send register values to Close if
// the caller's ctx expires. This avoids send from allocating its own
// waiting goroutine to interrupt the ReadFrom, as memory is tight on
// iOS and we want the number of pending DNS lookups to be bursty
// without too much associated goroutine/memory cost.
func (f *forwarder) send(ctx context.Context, txidOut txid, closeOnCtxDone *closePool, packet []byte, dst netaddr.IPPort) ([]byte, error) {
// Upgrade known DNS IPs to DoH (DNS-over-HTTPs).
if urlBase, dc, ok := f.getDoHClient(ip); ok {
res, err := f.sendDoH(ctx, urlBase, dc, fq.packet)
if urlBase, dc, ok := f.getDoHClient(dst.IP()); ok {
res, err := f.sendDoH(ctx, urlBase, dc, packet)
if err == nil || ctx.Err() != nil {
return res, err
}
f.logf("DoH error from %v: %v", ip, err)
f.logf("DoH error from %v: %v", dst.IP, err)
}
ln, err := f.packetListener(ip)
ln, err := f.packetListener(dst.IP())
if err != nil {
return nil, err
}
@@ -436,10 +342,10 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, dst netaddr.IPPo
}
defer conn.Close()
fq.closeOnCtxDone.Add(conn)
defer fq.closeOnCtxDone.Remove(conn)
closeOnCtxDone.Add(conn)
defer closeOnCtxDone.Remove(conn)
if _, err := conn.WriteTo(fq.packet, dst.UDPAddr()); err != nil {
if _, err := conn.WriteTo(packet, dst.UDPAddr()); err != nil {
if err := ctx.Err(); err != nil {
return nil, err
}
@@ -468,7 +374,7 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, dst netaddr.IPPo
}
out = out[:n]
txid := getTxID(out)
if txid != fq.txid {
if txid != txidOut {
return nil, errors.New("txid doesn't match")
}
@@ -491,7 +397,7 @@ func (f *forwarder) send(ctx context.Context, fq *forwardQuery, dst netaddr.IPPo
}
// resolvers returns the resolvers to use for domain.
func (f *forwarder) resolvers(domain dnsname.FQDN) []resolverAndDelay {
func (f *forwarder) resolvers(domain dnsname.FQDN) []netaddr.IPPort {
f.mu.Lock()
routes := f.routes
f.mu.Unlock()
@@ -503,30 +409,6 @@ func (f *forwarder) resolvers(domain dnsname.FQDN) []resolverAndDelay {
return nil
}
// forwardQuery is information and state about a forwarded DNS query that's
// being sent to 1 or more upstreams.
//
// In the case of racing against multiple equivalent upstreams
// (e.g. Google or CloudFlare's 4 DNS IPs: 2 IPv4 + 2 IPv6), this type
// handles racing them more intelligently than just blasting away 4
// queries at once.
type forwardQuery struct {
txid txid
packet []byte
// closeOnCtxDone lets send register values to Close if the
// caller's ctx expires. This avoids send from allocating its
// own waiting goroutine to interrupt the ReadFrom, as memory
// is tight on iOS and we want the number of pending DNS
// lookups to be bursty without too much associated
// goroutine/memory cost.
closeOnCtxDone *closePool
// TODO(bradfitz): add race delay state:
// mu sync.Mutex
// ...
}
// forward forwards the query to all upstream nameservers and returns the first response.
func (f *forwarder) forward(query packet) error {
domain, err := nameFromQuery(query.bs)
@@ -534,6 +416,7 @@ func (f *forwarder) forward(query packet) error {
return err
}
txid := getTxID(query.bs)
clampEDNSSize(query.bs, maxResponseBytes)
resolvers := f.resolvers(domain)
@@ -541,12 +424,8 @@ func (f *forwarder) forward(query packet) error {
return errNoUpstreams
}
fq := &forwardQuery{
txid: getTxID(query.bs),
packet: query.bs,
closeOnCtxDone: new(closePool),
}
defer fq.closeOnCtxDone.Close()
closeOnCtxDone := new(closePool)
defer closeOnCtxDone.Close()
ctx, cancel := context.WithTimeout(f.ctx, responseTimeout)
defer cancel()
@@ -557,18 +436,9 @@ func (f *forwarder) forward(query packet) error {
firstErr error
)
for _, rr := range resolvers {
go func(rr resolverAndDelay) {
if rr.startDelay > 0 {
timer := time.NewTimer(rr.startDelay)
select {
case <-timer.C:
case <-ctx.Done():
timer.Stop()
return
}
}
resb, err := f.send(ctx, fq, rr.ipp)
for _, ipp := range resolvers {
go func(ipp netaddr.IPPort) {
resb, err := f.send(ctx, txid, closeOnCtxDone, query.bs, ipp)
if err != nil {
mu.Lock()
defer mu.Unlock()
@@ -581,7 +451,7 @@ func (f *forwarder) forward(query packet) error {
case resc <- resb:
default:
}
}(rr)
}(ipp)
}
select {

View File

@@ -1,90 +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 resolver
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
"inet.af/netaddr"
)
func (rr resolverAndDelay) String() string {
return fmt.Sprintf("%v+%v", rr.ipp, rr.startDelay)
}
func TestResolversWithDelays(t *testing.T) {
// query
q := func(ss ...string) (ipps []netaddr.IPPort) {
for _, s := range ss {
ipps = append(ipps, netaddr.MustParseIPPort(s))
}
return
}
// output
o := func(ss ...string) (rr []resolverAndDelay) {
for _, s := range ss {
var d time.Duration
if i := strings.Index(s, "+"); i != -1 {
var err error
d, err = time.ParseDuration(s[i+1:])
if err != nil {
panic(fmt.Sprintf("parsing duration in %q: %v", s, err))
}
s = s[:i]
}
rr = append(rr, resolverAndDelay{
ipp: netaddr.MustParseIPPort(s),
startDelay: d,
})
}
return
}
tests := []struct {
name string
in []netaddr.IPPort
want []resolverAndDelay
}{
{
name: "unknown-no-delays",
in: q("1.2.3.4:53", "2.3.4.5:53"),
want: o("1.2.3.4:53", "2.3.4.5:53"),
},
{
name: "google-all-ipv4",
in: q("8.8.8.8:53", "8.8.4.4:53"),
want: o("8.8.8.8:53", "8.8.4.4:53+200ms"),
},
{
name: "google-only-ipv6",
in: q("[2001:4860:4860::8888]:53", "[2001:4860:4860::8844]:53"),
want: o("[2001:4860:4860::8888]:53", "[2001:4860:4860::8844]:53+200ms"),
},
{
name: "google-all-four",
in: q("8.8.8.8:53", "8.8.4.4:53", "[2001:4860:4860::8888]:53", "[2001:4860:4860::8844]:53"),
want: o("8.8.8.8:53", "8.8.4.4:53+200ms", "[2001:4860:4860::8888]:53+2.5s", "[2001:4860:4860::8844]:53+2.7s"),
},
{
name: "quad9-one-v4-one-v6",
in: q("9.9.9.9:53", "[2620:fe::fe]:53"),
want: o("9.9.9.9:53", "[2620:fe::fe]:53+200ms"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := resolversWithDelays(tt.in)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %v; want %v", got, tt.want)
}
})
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -208,6 +208,7 @@ func (r *Resolver) SetConfig(cfg Config) error {
r.saveConfigForTests(cfg)
}
routes := make([]route, 0, len(cfg.Routes))
reverse := make(map[netaddr.IP]dnsname.FQDN, len(cfg.Hosts))
for host, ips := range cfg.Hosts {
@@ -216,7 +217,18 @@ func (r *Resolver) SetConfig(cfg Config) error {
}
}
r.forwarder.setRoutes(cfg.Routes)
for suffix, ips := range cfg.Routes {
routes = append(routes, route{
Suffix: suffix,
Resolvers: ips,
})
}
// Sort from longest prefix to shortest.
sort.Slice(routes, func(i, j int) bool {
return routes[i].Suffix.NumLabels() > routes[j].Suffix.NumLabels()
})
r.forwarder.setRoutes(routes)
r.mu.Lock()
defer r.mu.Unlock()

View File

@@ -931,11 +931,8 @@ func TestAllocs(t *testing.T) {
query []byte
want int
}{
// Name lowercasing, response slice created by dns.NewBuilder,
// and closure allocation from go call.
// (Closure allocation only happens when using new register ABI,
// which is amd64 with Go 1.17, and probably more platforms later.)
{"forward", dnspacket("test1.ipn.dev.", dns.TypeA, noEdns), 3},
// Name lowercasing and response slice created by dns.NewBuilder.
{"forward", dnspacket("test1.ipn.dev.", dns.TypeA, noEdns), 2},
// 3 extra allocs in rdnsNameToIPv4 and one in marshalPTRRecord (dns.NewName).
{"reverse", dnspacket("4.3.2.1.in-addr.arpa.", dns.TypePTR, noEdns), 5},
}

View File

@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO(bradfitz): update this code to use netaddr more
// Package dnscache contains a minimal DNS cache that makes a bunch of
// assumptions that are only valid for us. Not recommended for general use.
package dnscache
@@ -80,9 +78,8 @@ type Resolver struct {
}
type ipCacheEntry struct {
ip net.IP // either v4 or v6
ip6 net.IP // nil if no v4 or no v6
allIPs []net.IPAddr // 1+ v4 and/or v6
ip net.IP // either v4 or v6
ip6 net.IP // nil if no v4 or no v6
expires time.Time
}
@@ -108,82 +105,81 @@ var debug, _ = strconv.ParseBool(os.Getenv("TS_DEBUG_DNS_CACHE"))
//
// If err is nil, ip will be non-nil. The v6 address may be nil even
// with a nil error.
func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 net.IP, allIPs []net.IPAddr, err error) {
func (r *Resolver) LookupIP(ctx context.Context, host string) (ip, v6 net.IP, err error) {
if ip := net.ParseIP(host); ip != nil {
if ip4 := ip.To4(); ip4 != nil {
return ip4, nil, []net.IPAddr{{IP: ip4}}, nil
return ip4, nil, nil
}
if debug {
log.Printf("dnscache: %q is an IP", host)
}
return ip, nil, []net.IPAddr{{IP: ip}}, nil
return ip, nil, nil
}
if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok {
if ip, ip6, ok := r.lookupIPCache(host); ok {
if debug {
log.Printf("dnscache: %q = %v (cached)", host, ip)
}
return ip, ip6, allIPs, nil
return ip, ip6, nil
}
type ipRes struct {
type ipPair struct {
ip, ip6 net.IP
allIPs []net.IPAddr
}
ch := r.sf.DoChan(host, func() (interface{}, error) {
ip, ip6, allIPs, err := r.lookupIP(host)
ip, ip6, err := r.lookupIP(host)
if err != nil {
return nil, err
}
return ipRes{ip, ip6, allIPs}, nil
return ipPair{ip, ip6}, nil
})
select {
case res := <-ch:
if res.Err != nil {
if r.UseLastGood {
if ip, ip6, allIPs, ok := r.lookupIPCacheExpired(host); ok {
if ip, ip6, ok := r.lookupIPCacheExpired(host); ok {
if debug {
log.Printf("dnscache: %q using %v after error", host, ip)
}
return ip, ip6, allIPs, nil
return ip, ip6, nil
}
}
if debug {
log.Printf("dnscache: error resolving %q: %v", host, res.Err)
}
return nil, nil, nil, res.Err
return nil, nil, res.Err
}
r := res.Val.(ipRes)
return r.ip, r.ip6, r.allIPs, nil
pair := res.Val.(ipPair)
return pair.ip, pair.ip6, nil
case <-ctx.Done():
if debug {
log.Printf("dnscache: context done while resolving %q: %v", host, ctx.Err())
}
return nil, nil, nil, ctx.Err()
return nil, nil, ctx.Err()
}
}
func (r *Resolver) lookupIPCache(host string) (ip, ip6 net.IP, allIPs []net.IPAddr, ok bool) {
func (r *Resolver) lookupIPCache(host string) (ip, ip6 net.IP, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
if ent, ok := r.ipCache[host]; ok && ent.expires.After(time.Now()) {
return ent.ip, ent.ip6, ent.allIPs, true
return ent.ip, ent.ip6, true
}
return nil, nil, nil, false
return nil, nil, false
}
func (r *Resolver) lookupIPCacheExpired(host string) (ip, ip6 net.IP, allIPs []net.IPAddr, ok bool) {
func (r *Resolver) lookupIPCacheExpired(host string) (ip, ip6 net.IP, ok bool) {
r.mu.Lock()
defer r.mu.Unlock()
if ent, ok := r.ipCache[host]; ok {
return ent.ip, ent.ip6, ent.allIPs, true
return ent.ip, ent.ip6, true
}
return nil, nil, nil, false
return nil, nil, false
}
func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
if r.UseLastGood {
if _, _, _, ok := r.lookupIPCacheExpired(host); ok {
if _, _, ok := r.lookupIPCacheExpired(host); ok {
// If we have some previous good value for this host,
// don't give this DNS lookup much time. If we're in a
// situation where the user's DNS server is unreachable
@@ -198,12 +194,12 @@ func (r *Resolver) lookupTimeoutForHost(host string) time.Duration {
return 10 * time.Second
}
func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, allIPs []net.IPAddr, err error) {
if ip, ip6, allIPs, ok := r.lookupIPCache(host); ok {
func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, err error) {
if ip, ip6, ok := r.lookupIPCache(host); ok {
if debug {
log.Printf("dnscache: %q found in cache as %v", host, ip)
}
return ip, ip6, allIPs, nil
return ip, ip6, nil
}
ctx, cancel := context.WithTimeout(context.Background(), r.lookupTimeoutForHost(host))
@@ -222,10 +218,10 @@ func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, allIPs []net.IPAddr, e
}
}
if err != nil {
return nil, nil, nil, err
return nil, nil, err
}
if len(ips) == 0 {
return nil, nil, nil, fmt.Errorf("no IPs for %q found", host)
return nil, nil, fmt.Errorf("no IPs for %q found", host)
}
have4 := false
@@ -244,12 +240,12 @@ func (r *Resolver) lookupIP(host string) (ip, ip6 net.IP, allIPs []net.IPAddr, e
}
}
}
r.addIPCache(host, ip, ip6, ips, r.ttl())
return ip, ip6, ips, nil
r.addIPCache(host, ip, ip6, r.ttl())
return ip, ip6, nil
}
func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, allIPs []net.IPAddr, d time.Duration) {
if naIP, _ := netaddr.FromStdIP(ip); naIP.IsPrivate() {
func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, d time.Duration) {
if isPrivateIP(ip) {
// Don't cache obviously wrong entries from captive portals.
// TODO: use DoH or DoT for the forwarding resolver?
if debug {
@@ -267,14 +263,27 @@ func (r *Resolver) addIPCache(host string, ip, ip6 net.IP, allIPs []net.IPAddr,
if r.ipCache == nil {
r.ipCache = make(map[string]ipCacheEntry)
}
r.ipCache[host] = ipCacheEntry{
ip: ip,
ip6: ip6,
allIPs: allIPs,
expires: time.Now().Add(d),
}
r.ipCache[host] = ipCacheEntry{ip: ip, ip6: ip6, expires: time.Now().Add(d)}
}
func mustCIDR(s string) *net.IPNet {
_, ipNet, err := net.ParseCIDR(s)
if err != nil {
panic(err)
}
return ipNet
}
func isPrivateIP(ip net.IP) bool {
return private1.Contains(ip) || private2.Contains(ip) || private3.Contains(ip)
}
var (
private1 = mustCIDR("10.0.0.0/8")
private2 = mustCIDR("172.16.0.0/12")
private3 = mustCIDR("192.168.0.0/16")
)
type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)
// Dialer returns a wrapped DialContext func that uses the provided dnsCache.
@@ -296,129 +305,39 @@ func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
// Return with original error
return
}
if c, err := raceDial(ctx, fwd, network, ips, port); err == nil {
retConn = c
ret = nil
return
}
}()
ip, ip6, allIPs, err := dnsCache.LookupIP(ctx, host)
if err != nil {
return nil, fmt.Errorf("failed to resolve %q: %w", host, err)
}
i4s := v4addrs(allIPs)
if len(i4s) < 2 {
dst := net.JoinHostPort(ip.String(), port)
if debug {
log.Printf("dnscache: dialing %s, %s for %s", network, dst, address)
}
c, err := fwd(ctx, network, dst)
if err == nil || ctx.Err() != nil || ip6 == nil {
return c, err
}
// Fall back to trying IPv6.
dst = net.JoinHostPort(ip6.String(), port)
return fwd(ctx, network, dst)
}
// Multiple IPv4 candidates, and 0+ IPv6.
ipsToTry := append(i4s, v6addrs(allIPs)...)
return raceDial(ctx, fwd, network, ipsToTry, port)
}
}
// fallbackDelay is how long to wait between trying subsequent
// addresses when multiple options are available.
// 300ms is the same as Go's Happy Eyeballs fallbackDelay value.
const fallbackDelay = 300 * time.Millisecond
// raceDial tries to dial port on each ip in ips, starting a new race
// dial every fallbackDelay apart, returning whichever completes first.
func raceDial(ctx context.Context, fwd DialContextFunc, network string, ips []netaddr.IP, port string) (net.Conn, error) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
type res struct {
c net.Conn
err error
}
resc := make(chan res) // must be unbuffered
failBoost := make(chan struct{}) // best effort send on dial failure
go func() {
for i, ip := range ips {
if i != 0 {
timer := time.NewTimer(fallbackDelay)
select {
case <-timer.C:
case <-failBoost:
timer.Stop()
case <-ctx.Done():
timer.Stop()
for _, ip := range ips {
dst := net.JoinHostPort(ip.String(), port)
if c, err := fwd(ctx, network, dst); err == nil {
retConn = c
ret = nil
return
}
}
go func(ip netaddr.IP) {
c, err := fwd(ctx, network, net.JoinHostPort(ip.String(), port))
if err != nil {
// Best effort wake-up a pending dial.
// e.g. IPv4 dials failing quickly on an IPv6-only system.
// In that case we don't want to wait 300ms per IPv4 before
// we get to the IPv6 addresses.
select {
case failBoost <- struct{}{}:
default:
}
}
select {
case resc <- res{c, err}:
case <-ctx.Done():
if c != nil {
c.Close()
}
}
}(ip)
}
}()
}()
var firstErr error
var fails int
for {
select {
case r := <-resc:
if r.c != nil {
return r.c, nil
}
fails++
if firstErr == nil {
firstErr = r.err
}
if fails == len(ips) {
return nil, firstErr
}
case <-ctx.Done():
return nil, ctx.Err()
ip, ip6, err := dnsCache.LookupIP(ctx, host)
if err != nil {
return nil, fmt.Errorf("failed to resolve %q: %w", host, err)
}
}
}
func v4addrs(aa []net.IPAddr) (ret []netaddr.IP) {
for _, a := range aa {
if ip, ok := netaddr.FromStdIP(a.IP); ok && ip.Is4() {
ret = append(ret, ip)
dst := net.JoinHostPort(ip.String(), port)
if debug {
log.Printf("dnscache: dialing %s, %s for %s", network, dst, address)
}
}
return ret
}
func v6addrs(aa []net.IPAddr) (ret []netaddr.IP) {
for _, a := range aa {
if ip, ok := netaddr.FromStdIP(a.IP); ok && ip.Is6() {
ret = append(ret, ip)
c, err := fwd(ctx, network, dst)
if err == nil || ctx.Err() != nil || ip6 == nil {
return c, err
}
// Fall back to trying IPv6.
// TODO(bradfitz): this is a primarily for IPv6-only
// hosts; it's not supposed to be a real Happy
// Eyeballs implementation. We should use the net
// package's implementation of that by plumbing this
// dnscache impl into net.Dialer.Resolver.Dial and
// unmarshal/marshal DNS queries/responses to the net
// package. This works for v6-only hosts for now.
dst = net.JoinHostPort(ip6.String(), port)
return fwd(ctx, network, dst)
}
return ret
}
var errTLSHandshakeTimeout = errors.New("timeout doing TLS handshake")

View File

@@ -5,29 +5,24 @@
package dnscache
import (
"context"
"flag"
"net"
"testing"
"time"
)
var dialTest = flag.String("dial-test", "", "if non-empty, addr:port to test dial")
func TestIsPrivateIP(t *testing.T) {
tests := []struct {
ip string
want bool
}{
{"10.1.2.3", true},
{"172.16.1.100", true},
{"192.168.1.1", true},
{"1.2.3.4", false},
}
func TestDialer(t *testing.T) {
if *dialTest == "" {
t.Skip("skipping; --dial-test is blank")
for _, test := range tests {
if got := isPrivateIP(net.ParseIP(test.ip)); got != test.want {
t.Errorf("isPrivateIP(%q)=%v, want %v", test.ip, got, test.want)
}
}
r := new(Resolver)
var std net.Dialer
dialer := Dialer(std.DialContext, r)
t0 := time.Now()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
c, err := dialer(ctx, "tcp", *dialTest)
if err != nil {
t.Fatal(err)
}
t.Logf("dialed in %v", time.Since(t0))
c.Close()
}

View File

@@ -90,25 +90,18 @@
"RegionName": "r4",
"Nodes": [
{
"Name": "4c",
"Name": "4a",
"RegionID": 4,
"HostName": "derp4c.tailscale.com",
"IPv4": "134.122.77.138",
"IPv6": "2a03:b0c0:3:d0::1501:6001"
"HostName": "derp4.tailscale.com",
"IPv4": "167.172.182.26",
"IPv6": "2a03:b0c0:3:e0::36e:9001"
},
{
"Name": "4d",
"Name": "4b",
"RegionID": 4,
"HostName": "derp4d.tailscale.com",
"IPv4": "134.122.94.167",
"IPv6": "2a03:b0c0:3:d0::1501:b001"
},
{
"Name": "4e",
"RegionID": 4,
"HostName": "derp4e.tailscale.com",
"IPv4": "134.122.74.153",
"IPv6": "2a03:b0c0:3:d0::29:9001"
"HostName": "derp4b.tailscale.com",
"IPv4": "157.230.25.0",
"IPv6": "2a03:b0c0:3:e0::58f:3001"
}
]
},
@@ -159,26 +152,19 @@
"RegionCode": "r8",
"RegionName": "r8",
"Nodes": [
{
"Name": "8a",
"RegionID": 8,
"HostName": "derp8.tailscale.com",
"IPv4": "167.71.139.179",
"IPv6": "2a03:b0c0:1:e0::3cc:e001"
},
{
"Name": "8b",
"RegionID": 8,
"HostName": "derp8b.tailscale.com",
"IPv4": "46.101.74.201",
"IPv6": "2a03:b0c0:1:d0::ec1:e001"
},
{
"Name": "8c",
"RegionID": 8,
"HostName": "derp8c.tailscale.com",
"IPv4": "206.189.16.32",
"IPv6": "2a03:b0c0:1:d0::e1f:4001"
},
{
"Name": "8d",
"RegionID": 8,
"HostName": "derp8d.tailscale.com",
"IPv4": "178.62.44.132",
"IPv6": "2a03:b0c0:1:d0::e08:e001"
}
]
},

View File

@@ -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

View File

@@ -134,7 +134,7 @@ func LocalAddresses() (regular, loopback []netaddr.IP, err error) {
// but their OS supports IPv6 so they have an fe80::
// address. We don't want to report all of those
// IPv6 LL to Control.
} else if ip.Is6() && ip.IsPrivate() {
} else if ip.Is6() && tsaddr.IsULA(ip) {
// Google Cloud Run uses NAT with IPv6 Unique
// Local Addresses to provide IPv6 connectivity.
ula6 = append(ula6, ip)
@@ -479,7 +479,7 @@ func HTTPOfListener(ln net.Listener) string {
var privateIP string
ForeachInterfaceAddress(func(i Interface, pfx netaddr.IPPrefix) {
ip := pfx.IP()
if ip.IsPrivate() {
if isPrivateIP(ip) {
if privateIP == "" {
privateIP = ip.String()
}
@@ -519,15 +519,21 @@ func LikelyHomeRouterIP() (gateway, myIP netaddr.IP, ok bool) {
if !i.IsUp() || ip.IsZero() || !myIP.IsZero() {
return
}
if gateway.IsPrivate() && ip.IsPrivate() {
myIP = ip
ok = true
return
for _, prefix := range privatev4s {
if prefix.Contains(gateway) && prefix.Contains(ip) {
myIP = ip
ok = true
return
}
}
})
return gateway, myIP, !myIP.IsZero()
}
func isPrivateIP(ip netaddr.IP) bool {
return private1.Contains(ip) || private2.Contains(ip) || private3.Contains(ip)
}
// isUsableV4 reports whether ip is a usable IPv4 address which could
// conceivably be used to get Internet connectivity. Globally routable and
// private IPv4 addresses are always Usable, and link local 169.254.x.x
@@ -548,11 +554,23 @@ func isUsableV4(ip netaddr.IP) bool {
// (fc00::/7) are in some environments used with address translation.
func isUsableV6(ip netaddr.IP) bool {
return v6Global1.Contains(ip) ||
(ip.Is6() && ip.IsPrivate() && !tsaddr.TailscaleULARange().Contains(ip))
(tsaddr.IsULA(ip) && !tsaddr.TailscaleULARange().Contains(ip))
}
func mustCIDR(s string) netaddr.IPPrefix {
prefix, err := netaddr.ParseIPPrefix(s)
if err != nil {
panic(err)
}
return prefix
}
var (
v6Global1 = netaddr.MustParseIPPrefix("2000::/3")
private1 = mustCIDR("10.0.0.0/8")
private2 = mustCIDR("172.16.0.0/12")
private3 = mustCIDR("192.168.0.0/16")
privatev4s = []netaddr.IPPrefix{private1, private2, private3}
v6Global1 = mustCIDR("2000::/3")
)
// anyInterestingIP reports whether pfxs contains any IP that matches

View File

@@ -73,7 +73,7 @@ func likelyHomeRouterIPDarwinExec() (ret netaddr.IP, ok bool) {
return nil
}
ip, err := netaddr.ParseIP(string(mem.Append(nil, ipm)))
if err == nil && ip.IsPrivate() {
if err == nil && isPrivateIP(ip) {
ret = ip
// We've found what we're looking for.
return errStopReadingNetstatTable

View File

@@ -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

View File

@@ -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

View File

@@ -72,7 +72,7 @@ func likelyHomeRouterIPLinux() (ret netaddr.IP, ok bool) {
return nil // ignore error, skip line and keep going
}
ip := netaddr.IPv4(byte(ipu32), byte(ipu32>>8), byte(ipu32>>16), byte(ipu32>>24))
if ip.IsPrivate() {
if isPrivateIP(ip) {
ret = ip
}
return nil

View File

@@ -58,8 +58,6 @@ func TestIsUsableV6(t *testing.T) {
{"zeros", "0000:0000:0000:0000:0000:0000:0000:0000", false},
{"Link Local", "fe80::1", false},
{"Global", "2602::1", true},
{"IPv4 public", "192.0.2.1", false},
{"IPv4 private", "192.168.1.1", false},
}
for _, test := range tests {

View File

@@ -93,7 +93,7 @@ func likelyHomeRouterIPWindows() (ret netaddr.IP, ok bool) {
}
}
if !ret.IsZero() && !ret.IsPrivate() {
if !ret.IsZero() && !isPrivateIP(ret) {
// Default route has a non-private gateway
return netaddr.IP{}, false
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,10 +15,8 @@ import (
type upnpClient interface{}
type uPnPDiscoResponse struct{}
func parseUPnPDiscoResponse([]byte) (uPnPDiscoResponse, error) {
return uPnPDiscoResponse{}, nil
func getUPnPClient(ctx context.Context, gw netaddr.IP) (upnpClient, error) {
return nil, nil
}
func (c *Client) getUPnPPortMapping(

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -3,41 +3,30 @@
// 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"
"io"
"net"
"net/http"
"sync"
"time"
"go4.org/mem"
"inet.af/netaddr"
"tailscale.com/net/interfaces"
"tailscale.com/net/netns"
"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
@@ -73,11 +62,8 @@ type Client struct {
pmpPubIPTime time.Time // time pmpPubIP last verified
pmpLastEpoch uint32
pcpSawTime time.Time // time we last saw PCP was available
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
pcpSawTime time.Time // time we last saw PCP was available
uPnPSawTime time.Time // time we last saw UPnP was available
localPort uint16
@@ -224,7 +210,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 +225,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))
}
@@ -289,7 +270,7 @@ func IsNoMappingError(err error) bool {
var (
ErrNoPortMappingServices = errors.New("no port mapping services were found")
ErrGatewayRange = errors.New("skipping portmap; gateway range likely lacks support")
ErrGatewayNotFound = errors.New("failed to look up gateway address")
)
// GetCachedMappingOrStartCreatingOne quickly returns with our current cached portmapping, if any.
@@ -342,26 +323,24 @@ 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}
return netaddr.IPPort{}, NoMappingError{ErrGatewayNotFound}
}
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 +357,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 +384,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 +418,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 +457,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
@@ -612,7 +540,7 @@ type ProbeResult struct {
func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
gw, myIP, ok := c.gatewayAndSelfIP()
if !ok {
return res, ErrGatewayRange
return res, ErrGatewayNotFound
}
defer func() {
if err == nil {
@@ -632,33 +560,46 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
defer cancel()
defer closeCloserOnContextDone(ctx, uc)()
if c.sawUPnPRecently() {
res.UPnP = true
} else {
hasUPnP := make(chan bool, 1)
defer func() {
res.UPnP = <-hasUPnP
}()
go func() {
client, err := getUPnPClient(ctx, gw)
if err == nil && client != nil {
hasUPnP <- true
c.mu.Lock()
c.uPnPSawTime = time.Now()
c.mu.Unlock()
}
close(hasUPnP)
}()
}
pcpAddr := netaddr.IPPortFrom(gw, pcpPort).UDPAddr()
pmpAddr := netaddr.IPPortFrom(gw, pmpPort).UDPAddr()
upnpAddr := netaddr.IPPortFrom(gw, upnpPort).UDPAddr()
// Don't send probes to services that we recently learned (for
// the same gw/myIP) are available. See
// 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 {
uc.WriteTo(uPnPPacket, upnpAddr)
}
buf := make([]byte, 1500)
pcpHeard := false // true when we get any PCP response
for {
if pcpHeard && res.PMP && res.UPnP {
if pcpHeard && res.PMP {
// Nothing more to discover.
return res, nil
}
@@ -671,21 +612,6 @@ func (c *Client) Probe(ctx context.Context) (res ProbeResult, err error) {
}
port := addr.(*net.UDPAddr).Port
switch port {
case upnpPort:
if mem.Contains(mem.B(buf[:n]), mem.S(":InternetGatewayDevice:")) {
meta, err := parseUPnPDiscoResponse(buf[:n])
if err != nil {
c.logf("unrecognized UPnP discovery response; ignoring")
}
if VerboseLogs {
c.logf("UPnP reply %+v, %q", meta, buf[:n])
}
res.UPnP = true
c.mu.Lock()
c.uPnPSawTime = time.Now()
c.uPnPMeta = meta
c.mu.Unlock()
}
case pcpPort: // same as pmpPort
if pres, ok := parsePCPResponse(buf[:n]); ok {
if pres.OpCode == pcpOpReply|pcpOpAnnounce {
@@ -726,15 +652,75 @@ 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
)
// 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" +
"MAN: \"ssdp:discover\"\r\n" +
"MX: 2\r\n\r\n")
// 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"

View File

@@ -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
}

View File

@@ -2,30 +2,21 @@
// 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
import (
"bufio"
"bytes"
"context"
"fmt"
"math/rand"
"net/http"
"net/url"
"strings"
"time"
"github.com/tailscale/goupnp"
"github.com/tailscale/goupnp/dcps/internetgateway2"
"inet.af/netaddr"
"tailscale.com/control/controlknobs"
"tailscale.com/net/netns"
"tailscale.com/types/logger"
)
// References:
@@ -53,8 +44,7 @@ func (u *upnpMapping) Release(ctx context.Context) {
}
// upnpClient is an interface over the multiple different clients exported by goupnp,
// exposing the functions we need for portmapping. Those clients are auto-generated from XML-specs,
// which is why they're not very idiomatic.
// exposing the functions we need for portmapping. They are auto-generated from XML-specs.
type upnpClient interface {
AddPortMapping(
ctx context.Context,
@@ -87,7 +77,7 @@ type upnpClient interface {
// greater than 0. From the spec, it appears if it is set to 0, it will switch to using
// 604800 seconds, but not sure why this is desired. The recommended time is 3600 seconds.
leaseDurationSec uint32,
) error
) (err error)
DeletePortMapping(ctx context.Context, remoteHost string, externalPort uint16, protocol string) error
GetExternalIPAddress(ctx context.Context) (externalIPAddress string, err error)
@@ -102,8 +92,6 @@ const tsPortMappingDesc = "tailscale-portmap"
// behavior of calling AddPortMapping with port = 0 to specify a wildcard port.
// It returns the new external port (which may not be identical to the external port specified),
// or an error.
//
// TODO(bradfitz): also returned the actual lease duration obtained. and check it regularly.
func addAnyPortMapping(
ctx context.Context,
upnp upnpClient,
@@ -142,89 +130,51 @@ func addAnyPortMapping(
return externalPort, err
}
// getUPnPClient gets a client for interfacing with UPnP, ignoring the underlying protocol for
// getUPnPClients gets a client for interfacing with UPnP, ignoring the underlying protocol for
// now.
// Adapted from https://github.com/huin/goupnp/blob/master/GUIDE.md.
//
// The gw is the detected gateway.
//
// The meta is the most recently parsed UDP discovery packet response
// from the Internet Gateway Device.
//
// 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) (upnpClient, error) {
if controlknobs.DisableUPnP() {
return nil, nil
}
if meta.Location == "" {
return nil, nil
}
if VerboseLogs {
logf("fetching %v", meta.Location)
}
u, err := url.Parse(meta.Location)
if err != nil {
return nil, err
}
ipp, err := netaddr.ParseIPPort(u.Host)
if err != nil {
return nil, fmt.Errorf("unexpected host %q in %q", u.Host, meta.Location)
}
if ipp.IP() != gw {
return nil, fmt.Errorf("UPnP discovered root %q does not match gateway IP %v; ignoring UPnP",
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, 250*time.Millisecond)
defer cancel()
// Attempt to connect over the multiple available connection types concurrently,
// returning the fastest.
// This part does a network fetch.
root, err := goupnp.DeviceByURL(ctx, u)
// TODO(jknodt): this url seems super brittle? maybe discovery is better but this is faster
u, err := url.Parse(fmt.Sprintf("http://%s:5000/rootDesc.xml", gw))
if err != nil {
return nil, err
}
defer func() {
if client == nil {
return
clients := make(chan upnpClient, 3)
go func() {
var err error
ip1Clients, err := internetgateway2.NewWANIPConnection1ClientsByURL(ctx, u)
if err == nil && len(ip1Clients) > 0 {
clients <- ip1Clients[0]
}
}()
go func() {
ip2Clients, err := internetgateway2.NewWANIPConnection2ClientsByURL(ctx, u)
if err == nil && len(ip2Clients) > 0 {
clients <- ip2Clients[0]
}
}()
go func() {
ppp1Clients, err := internetgateway2.NewWANPPPConnection1ClientsByURL(ctx, u)
if err == nil && len(ppp1Clients) > 0 {
clients <- ppp1Clients[0]
}
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 {
return cc[0], nil
select {
case client := <-clients:
return client, nil
case <-ctx.Done():
return nil, ctx.Err()
}
if cc, _ := internetgateway2.NewWANIPConnection1ClientsFromRootDevice(ctx, root, u); len(cc) > 0 {
return cc[0], nil
}
if cc, _ := internetgateway2.NewWANPPPConnection1ClientsFromRootDevice(ctx, root, u); len(cc) > 0 {
return cc[0], nil
}
return nil, nil
}
func (c *Client) upnpHTTPClientLocked() *http.Client {
if c.uPnPHTTPClient == nil {
c.uPnPHTTPClient = &http.Client{
Transport: &http.Transport{
DialContext: netns.NewDialer().DialContext,
IdleConnTimeout: 2 * time.Second, // LAN is cheap
},
}
}
return c.uPnPHTTPClient
}
// getUPnPPortMapping attempts to create a port-mapping over the UPnP protocol. On success,
@@ -236,7 +186,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()
@@ -249,17 +199,11 @@ func (c *Client) getUPnPPortMapping(
var err error
c.mu.Lock()
oldMapping, ok := c.mapping.(*upnpMapping)
meta := c.uPnPMeta
httpClient := c.upnpHTTPClientLocked()
c.mu.Unlock()
if ok && oldMapping != nil {
client = oldMapping.client
} else {
ctx := goupnp.WithHTTPClient(ctx, httpClient)
client, err = getUPnPClient(ctx, c.logf, gw, meta)
if VerboseLogs {
c.logf("getUPnPClient: %T, %v", client, err)
}
client, err = getUPnPClient(ctx, gw)
if err != nil {
return netaddr.IPPort{}, false
}
@@ -277,17 +221,11 @@ func (c *Client) getUPnPPortMapping(
internal.IP().String(),
time.Second*pmpMapLifetimeSec,
)
if VerboseLogs {
c.logf("addAnyPortMapping: %v, %v", newPort, err)
}
if err != nil {
return netaddr.IPPort{}, false
}
// TODO cache this ip somewhere?
extIP, err := client.GetExternalIPAddress(ctx)
if VerboseLogs {
c.logf("client.GetExternalIPAddress: %v, %v", extIP, err)
}
if err != nil {
// TODO this doesn't seem right
return netaddr.IPPort{}, false
@@ -308,18 +246,3 @@ func (c *Client) getUPnPPortMapping(
c.localPort = newPort
return upnp.external, true
}
type uPnPDiscoResponse struct {
Location string
}
// parseUPnPDiscoResponse parses a UPnP HTTP-over-UDP discovery response.
func parseUPnPDiscoResponse(body []byte) (uPnPDiscoResponse, error) {
var r uPnPDiscoResponse
res, err := http.ReadResponse(bufio.NewReaderSize(bytes.NewReader(body), 128), nil)
if err != nil {
return r, err
}
r.Location = res.Header.Get("Location")
return r, nil
}

View File

@@ -1,117 +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"
"context"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"reflect"
"regexp"
"testing"
"inet.af/netaddr"
)
// Google Wifi
const (
googleWifiUPnPDisco = "HTTP/1.1 200 OK\r\nCACHE-CONTROL: max-age=120\r\nST: urn:schemas-upnp-org:device:InternetGatewayDevice:2\r\nUSN: uuid:a9708184-a6c0-413a-bbac-11bcf7e30ece::urn:schemas-upnp-org:device:InternetGatewayDevice:2\r\nEXT:\r\nSERVER: Linux/5.4.0-1034-gcp UPnP/1.1 MiniUPnPd/1.9\r\nLOCATION: http://192.168.86.1:5000/rootDesc.xml\r\nOPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n01-NLS: 1\r\nBOOTID.UPNP.ORG: 1\r\nCONFIGID.UPNP.ORG: 1337\r\n\r\n"
googleWifiRootDescXML = `<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0"><specVersion><major>1</major><minor>0</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:2</deviceType><friendlyName>OnHub</friendlyName><manufacturer>Google</manufacturer><manufacturerURL>http://google.com/</manufacturerURL><modelDescription>Wireless Router</modelDescription><modelName>OnHub</modelName><modelNumber>1</modelNumber><modelURL>https://on.google.com/hub/</modelURL><serialNumber>00000000</serialNumber><UDN>uuid:a9708184-a6c0-413a-bbac-11bcf7e30ece</UDN><serviceList><service><serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType><serviceId>urn:upnp-org:serviceId:Layer3Forwarding1</serviceId><controlURL>/ctl/L3F</controlURL><eventSubURL>/evt/L3F</eventSubURL><SCPDURL>/L3F.xml</SCPDURL></service><service><serviceType>urn:schemas-upnp-org:service:DeviceProtection:1</serviceType><serviceId>urn:upnp-org:serviceId:DeviceProtection1</serviceId><controlURL>/ctl/DP</controlURL><eventSubURL>/evt/DP</eventSubURL><SCPDURL>/DP.xml</SCPDURL></service></serviceList><deviceList><device><deviceType>urn:schemas-upnp-org:device:WANDevice:2</deviceType><friendlyName>WANDevice</friendlyName><manufacturer>MiniUPnP</manufacturer><manufacturerURL>http://miniupnp.free.fr/</manufacturerURL><modelDescription>WAN Device</modelDescription><modelName>WAN Device</modelName><modelNumber>20210414</modelNumber><modelURL>http://miniupnp.free.fr/</modelURL><serialNumber>00000000</serialNumber><UDN>uuid:a9708184-a6c0-413a-bbac-11bcf7e30ecf</UDN><UPC>000000000000</UPC><serviceList><service><serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType><serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId><controlURL>/ctl/CmnIfCfg</controlURL><eventSubURL>/evt/CmnIfCfg</eventSubURL><SCPDURL>/WANCfg.xml</SCPDURL></service></serviceList><deviceList><device><deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:2</deviceType><friendlyName>WANConnectionDevice</friendlyName><manufacturer>MiniUPnP</manufacturer><manufacturerURL>http://miniupnp.free.fr/</manufacturerURL><modelDescription>MiniUPnP daemon</modelDescription><modelName>MiniUPnPd</modelName><modelNumber>20210414</modelNumber><modelURL>http://miniupnp.free.fr/</modelURL><serialNumber>00000000</serialNumber><UDN>uuid:a9708184-a6c0-413a-bbac-11bcf7e30ec0</UDN><UPC>000000000000</UPC><serviceList><service><serviceType>urn:schemas-upnp-org:service:WANIPConnection:2</serviceType><serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId><controlURL>/ctl/IPConn</controlURL><eventSubURL>/evt/IPConn</eventSubURL><SCPDURL>/WANIPCn.xml</SCPDURL></service></serviceList></device></deviceList></device></deviceList><presentationURL>http://testwifi.here/</presentationURL></device></root>`
)
// pfSense 2.5.0-RELEASE / FreeBSD 12.2-STABLE
const (
pfSenseUPnPDisco = "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: FreeBSD/12.2-STABLE UPnP/1.1 MiniUPnPd/2.2.1\r\nLOCATION: http://192.168.1.1:2189/rootDesc.xml\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"
pfSenseRootDescXML = `<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0" configId="1337"><specVersion><major>1</major><minor>1</minor></specVersion><device><deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType><friendlyName>FreeBSD router</friendlyName><manufacturer>FreeBSD</manufacturer><manufacturerURL>http://www.freebsd.org/</manufacturerURL><modelDescription>FreeBSD router</modelDescription><modelName>FreeBSD router</modelName><modelNumber>2.5.0-RELEASE</modelNumber><modelURL>http://www.freebsd.org/</modelURL><serialNumber>BEE7052B</serialNumber><UDN>uuid:bee7052b-49e8-3597-b545-55a1e38ac11</UDN><serviceList><service><serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType><serviceId>urn:upnp-org:serviceId:L3Forwarding1</serviceId><SCPDURL>/L3F.xml</SCPDURL><controlURL>/ctl/L3F</controlURL><eventSubURL>/evt/L3F</eventSubURL></service></serviceList><deviceList><device><deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType><friendlyName>WANDevice</friendlyName><manufacturer>MiniUPnP</manufacturer><manufacturerURL>http://miniupnp.free.fr/</manufacturerURL><modelDescription>WAN Device</modelDescription><modelName>WAN Device</modelName><modelNumber>20210205</modelNumber><modelURL>http://miniupnp.free.fr/</modelURL><serialNumber>BEE7052B</serialNumber><UDN>uuid:bee7052b-49e8-3597-b545-55a1e38ac12</UDN><UPC>000000000000</UPC><serviceList><service><serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType><serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId><SCPDURL>/WANCfg.xml</SCPDURL><controlURL>/ctl/CmnIfCfg</controlURL><eventSubURL>/evt/CmnIfCfg</eventSubURL></service></serviceList><deviceList><device><deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType><friendlyName>WANConnectionDevice</friendlyName><manufacturer>MiniUPnP</manufacturer><manufacturerURL>http://miniupnp.free.fr/</manufacturerURL><modelDescription>MiniUPnP daemon</modelDescription><modelName>MiniUPnPd</modelName><modelNumber>20210205</modelNumber><modelURL>http://miniupnp.free.fr/</modelURL><serialNumber>BEE7052B</serialNumber><UDN>uuid:bee7052b-49e8-3597-b545-55a1e38ac13</UDN><UPC>000000000000</UPC><serviceList><service><serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType><serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId><SCPDURL>/WANIPCn.xml</SCPDURL><controlURL>/ctl/IPConn</controlURL><eventSubURL>/evt/IPConn</eventSubURL></service></serviceList></device></deviceList></device></deviceList><presentationURL>https://192.168.1.1/</presentationURL></device></root>`
)
func TestParseUPnPDiscoResponse(t *testing.T) {
tests := []struct {
name string
headers string
want uPnPDiscoResponse
}{
{"google", googleWifiUPnPDisco, uPnPDiscoResponse{
Location: "http://192.168.86.1:5000/rootDesc.xml",
}},
{"pfsense", pfSenseUPnPDisco, uPnPDiscoResponse{
Location: "http://192.168.1.1:2189/rootDesc.xml",
}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseUPnPDiscoResponse([]byte(tt.headers))
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("unexpected result:\n got: %+v\nwant: %+v\n", got, tt.want)
}
})
}
}
func TestGetUPnPClient(t *testing.T) {
tests := []struct {
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",
},
// TODO(bradfitz): find a PPP one in the wild
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/rootDesc.xml" {
io.WriteString(w, tt.xmlBody)
return
}
http.NotFound(w, r)
}))
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{
Location: ts.URL + "/rootDesc.xml",
})
if err != nil {
t.Fatal(err)
}
got := fmt.Sprintf("%T", c)
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)
}
})
}
}

View File

@@ -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

View File

@@ -105,6 +105,11 @@ func Tailscale4To6(ipv4 netaddr.IP) netaddr.IP {
return netaddr.IPFrom16(ret)
}
func IsULA(ip netaddr.IP) bool {
ulaRange.Do(func() { mustPrefix(&ulaRange.v, "fc00::/7") })
return ulaRange.v.Contains(ip)
}
func mustPrefix(v *netaddr.IPPrefix, prefix string) {
var err error
*v, err = netaddr.ParseIPPrefix(prefix)

View File

@@ -43,6 +43,28 @@ func TestCGNATRange(t *testing.T) {
}
}
func TestIsUla(t *testing.T) {
tests := []struct {
name string
ip string
want bool
}{
{"first ULA", "fc00::1", true},
{"not ULA", "fb00::1", false},
{"Tailscale", "fd7a:115c:a1e0::1", true},
{"Cloud Run", "fddf:3978:feb1:d745::1", true},
{"zeros", "0000:0000:0000:0000:0000:0000:0000:0000", false},
{"Link Local", "fe80::1", false},
{"Global", "2602::1", false},
}
for _, test := range tests {
if got := IsULA(netaddr.MustParseIP(test.ip)); got != test.want {
t.Errorf("IsULA(%s) = %v, want %v", test.name, got, test.want)
}
}
}
func TestNewContainsIPFunc(t *testing.T) {
f := NewContainsIPFunc([]netaddr.IPPrefix{netaddr.MustParseIPPrefix("10.0.0.0/8")})
if f(netaddr.MustParseIP("8.8.8.8")) {

View File

@@ -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

View File

@@ -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

View File

@@ -6,6 +6,7 @@ package tstun
import (
"fmt"
"log"
"net"
"os"
"os/exec"
@@ -13,7 +14,6 @@ import (
"unsafe"
"github.com/insomniacslk/dhcp/dhcpv4"
"golang.zx2c4.com/wireguard/tun"
"inet.af/netaddr"
"inet.af/netstack/tcpip"
"inet.af/netstack/tcpip/buffer"
@@ -24,18 +24,17 @@ import (
"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.
// TODO: this was randomly generated once. do it per process start? or is
// this good enough?
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)
func createTAPLinux(tapName, bridgeName string) (fd int, err error) {
var flags uint16 = syscall.IFF_TAP | syscall.IFF_NO_PI
fd, err = syscall.Open("/dev/net/tun", syscall.O_RDWR, 0)
if err != nil {
return nil, err
return -1, err
}
var ifr struct {
name [16]byte
@@ -43,31 +42,22 @@ func createTAPLinux(tapName, bridgeName string) (dev tun.Device, err error) {
_ [22]byte
}
copy(ifr.name[:], tapName)
ifr.flags = syscall.IFF_TAP | syscall.IFF_NO_PI
ifr.flags = flags
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), syscall.TUNSETIFF, uintptr(unsafe.Pointer(&ifr)))
if errno != 0 {
syscall.Close(fd)
return nil, errno
return -1, errno
}
if err = syscall.SetNonblock(fd, true); err != nil {
syscall.Close(fd)
return nil, err
return -1, err
}
if err := run("ip", "link", "set", "dev", tapName, "up"); err != nil {
return nil, err
}
run("ip", "link", "set", "dev", tapName, "up")
if bridgeName != "" {
if err := run("brctl", "addif", bridgeName, tapName); err != nil {
return nil, err
}
run("brctl", "addif", bridgeName, tapName)
}
dev, _, err = tun.CreateUnmonitoredTUNFromFD(fd) // TODO: MTU
if err != nil {
syscall.Close(fd)
return nil, err
}
return dev, nil
return fd, nil
}
type etherType [2]byte
@@ -80,51 +70,37 @@ var (
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
t.logf("XXX short TAP frame")
return true
}
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
t.logf("XXX ignoring etherType %v", et)
return true // what is this
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
t.logf("XXX ignoring IPv6 %v", et)
return false
case etherTypeIPv4:
if len(ethBuf) < ethernetFrameSize+ipv4HeaderLen {
// Bogus IPv4. Eat.
if tapDebug {
t.logf("tap: short ipv4")
}
return consumePacket
t.logf("XXX short ipv4")
return true
}
return t.handleDHCPRequest(ethBuf)
case etherTypeARP:
arpPacket := header.ARP(ethBuf[ethernetFrameSize:])
if !arpPacket.IsValid() {
// Bogus ARP. Eat.
return consumePacket
return true
}
switch arpPacket.Op() {
case header.ARPRequest:
@@ -161,18 +137,16 @@ func (t *Wrapper) handleTAPFrame(ethBuf []byte) bool {
copy(res.ProtocolAddressTarget(), req.ProtocolAddressSender())
n, err := t.tdev.Write(buf, 0)
if tapDebug {
t.logf("tap: wrote ARP reply %v, %v", n, err)
}
log.Printf("XXX wrote ARP reply %v, %v", n, err)
}
return consumePacket
return true
}
}
// 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
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
@@ -180,19 +154,15 @@ const routerIP = "100.70.145.1" // must be in same netmask (currently hack at
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
t.logf("XXX DHCP short")
return false
}
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
t.logf("XXX dhcp no broadcast")
return false
}
p := parsedPacketPool.Get().(*packet.Parsed)
@@ -201,23 +171,17 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
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
t.logf("XXX DHCP wrong meta")
return false
}
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)
t.logf("XXX DHCP FromBytes bad")
return true
}
log.Printf("XXX DHCP request: %+v", dp)
switch dp.MessageType() {
case dhcpv4.MessageTypeDiscover:
offer, err := dhcpv4.New(
@@ -225,7 +189,8 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
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.WithGatewayIP(net.ParseIP("100.100.100.100").To4()), // why not
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")),
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
dhcpv4.WithYourIP(net.ParseIP(theClientIP)),
dhcpv4.WithLeaseTime(3600), // hour works
@@ -235,26 +200,26 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
)
if err != nil {
t.logf("error building DHCP offer: %v", err)
return consumePacket
return true
}
// Make a layer 2 packet to write out:
// Wrap all the crap back up
pkt := packLayer2UDP(
offer.ToBytes(),
ourMAC, ethSrcMAC,
netaddr.IPPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
//netaddr.IPPortFrom(netaddr.MustParseIP(theClientIP), 68), // dst
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)
}
log.Printf("XXX 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.WithRouter(net.ParseIP(routerIP)), // actually the router
//dhcpv4.WithGatewayIP(net.ParseIP("100.100.100.100").To4()), // why not
dhcpv4.WithServerIP(net.ParseIP("100.100.100.100")), // why not
dhcpv4.WithOption(dhcpv4.OptServerIdentifier(net.ParseIP("100.100.100.100"))),
dhcpv4.WithYourIP(net.ParseIP(theClientIP)), // Hello world
dhcpv4.WithLeaseTime(3600), // hour works
@@ -262,25 +227,22 @@ func (t *Wrapper) handleDHCPRequest(ethBuf []byte) bool {
)
if err != nil {
t.logf("error building DHCP ack: %v", err)
return consumePacket
return true
}
// Make a layer 2 packet to write out:
// Wrap all the crap back up
pkt := packLayer2UDP(
ack.ToBytes(),
ourMAC, ethSrcMAC,
netaddr.IPPortFrom(netaddr.IPv4(100, 100, 100, 100), 67), // src
//netaddr.IPPortFrom(netaddr.MustParseIP(theClientIP), 68),
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)
}
log.Printf("XXX wrote DHCP ACK %v, %v", n, err)
default:
if tapDebug {
t.logf("tap: unknown DHCP type")
}
t.logf("XXX unknown DHCP type")
}
return consumePacket
return true
}
func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst netaddr.IPPort) []byte {
@@ -324,14 +286,13 @@ func packLayer2UDP(payload []byte, srcMAC, dstMAC net.HardwareAddr, src, dst net
return []byte(buf)
}
func run(prog string, args ...string) error {
func run(prog string, args ...string) {
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)
log.Fatalf("Error running %v: %v", cmd, err)
}
return nil
}
func (t *Wrapper) destMAC() [6]byte {
@@ -352,8 +313,6 @@ func (t *Wrapper) tapWrite(buf []byte, offset int) (int, error) {
et = etherTypeIPv6
}
eth[12], eth[13] = et[0], et[1]
if tapDebug {
t.logf("tap: tapWrite off=%v % x", offset, buf)
}
t.logf("XXX tapWrite off=%v % x", offset, buf)
return t.tdev.Write(buf, offset-ethernetFrameSize)
}

View File

@@ -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 tstun

View File

@@ -38,7 +38,7 @@ func init() {
}
// createTAP is non-nil on Linux.
var createTAP func(tapName, bridgeName string) (tun.Device, error)
var createTAP func(tapName, bridgeName string) (fd int, err error)
// New returns a tun.Device for the requested device name, along with
// the OS-dependent name that was allocated to the device.
@@ -59,7 +59,11 @@ func New(logf logger.Logf, tunName string) (tun.Device, string, error) {
default:
return nil, "", errors.New("bogus tap argument")
}
dev, err = createTAP(tapName, bridgeName)
fd, err := createTAP(tapName, bridgeName)
if err != nil {
return nil, "", err
}
dev, _, err = tun.CreateUnmonitoredTUNFromFD(fd) // TODO: MTU
} else {
dev, err = tun.CreateTUN(tunName, tunMTU)
}

View File

@@ -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

View File

@@ -20,10 +20,8 @@ import (
"golang.zx2c4.com/wireguard/tun"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/tstime/mono"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/types/pad32"
"tailscale.com/wgengine/filter"
)
@@ -38,8 +36,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")
@@ -71,8 +67,7 @@ type Wrapper struct {
closeOnce sync.Once
_ pad32.Four
lastActivityAtomic mono.Time // time of last send or receive
lastActivityAtomic int64 // unix seconds of last send or receive
destIPActivity atomic.Value // of map[netaddr.IP]func()
destMACAtomic atomic.Value // of [6]byte
@@ -171,10 +166,9 @@ func wrap(logf logger.Logf, tdev tun.Device, isTAP bool) *Wrapper {
// a goroutine should not block when setting it, even with no listeners.
bufferConsumed: make(chan struct{}, 1),
closed: make(chan struct{}),
// outbound can be unbuffered; the buffer is an optimization.
outbound: make(chan tunReadResult, 1),
eventsUpDown: make(chan tun.Event),
eventsOther: make(chan tun.Event),
outbound: make(chan tunReadResult),
eventsUpDown: make(chan tun.Event),
eventsOther: make(chan tun.Event),
// TODO(dmytro): (highly rate-limited) hexdumps should happen on unknown packets.
filterFlags: filter.LogAccepts | filter.LogDrops,
}
@@ -183,7 +177,6 @@ func wrap(logf logger.Logf, tdev tun.Device, isTAP bool) *Wrapper {
go tun.pumpEvents()
// The buffer starts out consumed.
tun.bufferConsumed <- struct{}{}
tun.noteActivity()
return tun
}
@@ -325,13 +318,11 @@ func (t *Wrapper) poll() {
}
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)
s := fmt.Sprintf("% x", t.buffer[:])
for strings.HasSuffix(s, " 00") {
s = strings.TrimSuffix(s, " 00")
}
t.logf("XXX TAP READ %v, %v: %s", n, err, s)
} else {
n, err = t.tdev.Read(t.buffer[:], PacketStartOffset)
}
@@ -347,9 +338,7 @@ func (t *Wrapper) poll() {
if n >= ethernetFrameSize {
n -= ethernetFrameSize
}
if tapDebug {
t.logf("tap regular frame: %x", t.buffer[PacketStartOffset:PacketStartOffset+n])
}
t.logf("XXX regular frame: %x", t.buffer[PacketStartOffset:PacketStartOffset+n])
}
t.sendOutbound(tunReadResult{data: t.buffer[PacketStartOffset : PacketStartOffset+n], err: err})
}
@@ -412,15 +401,16 @@ func (t *Wrapper) filterOut(p *packet.Parsed) filter.Response {
// noteActivity records that there was a read or write at the current time.
func (t *Wrapper) noteActivity() {
t.lastActivityAtomic.StoreAtomic(mono.Now())
atomic.StoreInt64(&t.lastActivityAtomic, time.Now().Unix())
}
// IdleDuration reports how long it's been since the last read or write to this device.
//
// Its value should only be presumed accurate to roughly 10ms granularity.
// If there's never been activity, the duration is since the wrapper was created.
// Its value is only accurate to roughly second granularity.
// If there's never been activity, the duration is since 1970.
func (t *Wrapper) IdleDuration() time.Duration {
return mono.Since(t.lastActivityAtomic.LoadAtomic())
sec := atomic.LoadInt64(&t.lastActivityAtomic)
return time.Since(time.Unix(sec, 0))
}
func (t *Wrapper) Read(buf []byte, offset int) (int, error) {

View File

@@ -10,13 +10,13 @@ import (
"fmt"
"strconv"
"strings"
"sync/atomic"
"testing"
"unsafe"
"golang.zx2c4.com/wireguard/tun/tuntest"
"inet.af/netaddr"
"tailscale.com/net/packet"
"tailscale.com/tstime/mono"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/wgengine/filter"
@@ -335,9 +335,9 @@ func TestFilter(t *testing.T) {
// data was actually filtered.
// If it stays zero, nothing made it through
// to the wrapped TUN.
tun.lastActivityAtomic.StoreAtomic(0)
atomic.StoreInt64(&tun.lastActivityAtomic, 0)
_, err = tun.Write(tt.data, 0)
filtered = tun.lastActivityAtomic.LoadAtomic() == 0
filtered = atomic.LoadInt64(&tun.lastActivityAtomic) == 0
} else {
chtun.Outbound <- tt.data
n, err = tun.Read(buf[:], 0)
@@ -416,7 +416,7 @@ func TestAtomic64Alignment(t *testing.T) {
}
c := new(Wrapper)
c.lastActivityAtomic.StoreAtomic(mono.Now())
atomic.StoreInt64(&c.lastActivityAtomic, 123)
}
func TestPeerAPIBypass(t *testing.T) {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
;;

View File

@@ -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.

View File

@@ -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

View File

@@ -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"
@@ -165,12 +164,6 @@ type Node struct {
Hostinfo Hostinfo
Created time.Time
// PrimaryRoutes are the routes from AllowedIPs that this node
// is currently the primary subnet router for, as determined
// by the control plane. It does not include the self address
// values from Addresses that are in AllowedIPs.
PrimaryRoutes []netaddr.IPPrefix `json:",omitempty"`
// LastSeen is when the node was last online. It is not
// updated when Online is true. It is nil if the current
// node doesn't have permission to know, or the node
@@ -833,21 +826,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"`
@@ -1132,7 +1142,6 @@ func (n *Node) Equal(n2 *Node) bool {
eqBoolPtr(n.Online, n2.Online) &&
eqCIDRs(n.Addresses, n2.Addresses) &&
eqCIDRs(n.AllowedIPs, n2.AllowedIPs) &&
eqCIDRs(n.PrimaryRoutes, n2.PrimaryRoutes) &&
eqStrings(n.Endpoints, n2.Endpoints) &&
n.DERP == n2.DERP &&
n.Hostinfo.Equal(&n2.Hostinfo) &&

View File

@@ -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
@@ -50,7 +49,6 @@ func (src *Node) Clone() *Node {
dst.AllowedIPs = append(src.AllowedIPs[:0:0], src.AllowedIPs...)
dst.Endpoints = append(src.Endpoints[:0:0], src.Endpoints...)
dst.Hostinfo = *src.Hostinfo.Clone()
dst.PrimaryRoutes = append(src.PrimaryRoutes[:0:0], src.PrimaryRoutes...)
if dst.LastSeen != nil {
dst.LastSeen = new(time.Time)
*dst.LastSeen = *src.LastSeen
@@ -64,7 +62,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
@@ -81,7 +79,6 @@ var _NodeNeedsRegeneration = Node(struct {
DERP string
Hostinfo Hostinfo
Created time.Time
PrimaryRoutes []netaddr.IPPrefix
LastSeen *time.Time
Online *bool
KeepAlive bool
@@ -108,7 +105,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 +142,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 +169,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 +188,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 +210,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 +223,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 +255,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 +280,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 +307,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 +325,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 +342,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 +399,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:

View File

@@ -194,8 +194,7 @@ func TestNodeEqual(t *testing.T) {
"ID", "StableID", "Name", "User", "Sharer",
"Key", "KeyExpiry", "Machine", "DiscoKey",
"Addresses", "AllowedIPs", "Endpoints", "DERP", "Hostinfo",
"Created", "PrimaryRoutes",
"LastSeen", "Online", "KeepAlive", "MachineAuthorized",
"Created", "LastSeen", "Online", "KeepAlive", "MachineAuthorized",
"Capabilities",
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
}

View File

@@ -1,4 +1,3 @@
//go:build windows
// +build windows
/* SPDX-License-Identifier: MIT

View File

@@ -1,4 +1,3 @@
//go:build windows
// +build windows
/* SPDX-License-Identifier: MIT

Some files were not shown because too many files have changed in this diff Show More