From 81466eef8167d80b63e9f579a6fc1e43d34c906c Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 4 Feb 2021 12:20:17 -0500 Subject: [PATCH 01/71] Add an environment variable to enable customizing the log target (#1243) Signed-off-by: Christine Dodrill --- logpolicy/logpolicy.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index 3a07479a4..4f6ab774c 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -17,6 +17,7 @@ import ( "log" "net" "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -387,6 +388,13 @@ func New(collection string) *Policy { HTTPC: &http.Client{Transport: newLogtailTransport(logtail.DefaultHost)}, } + if val, ok := os.LookupEnv("TAILSCALE_LOG_TARGET"); ok { + log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.") + c.BaseURL = val + u, _ := url.Parse(val) + c.HTTPC = &http.Client{Transport: newLogtailTransport(u.Host)} + } + filchBuf, filchErr := filch.New(filepath.Join(dir, cmdName), filch.Options{}) if filchBuf != nil { c.Buffer = filchBuf From f7eed25bb91e11514a0b8a7bc965397481bbf1ac Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 3 Feb 2021 18:15:01 -0800 Subject: [PATCH 02/71] wgengine/magicsock: filter disco packets and packets when stopped from wireguard Fixes #1167 Fixes tailscale/corp#219 Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 55 ++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index d28a07db3..5e3120453 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -304,6 +304,9 @@ type Conn struct { // with IPv4 or IPv6). It's used to suppress log spam and prevent // new connection that'll fail. networkUp syncs.AtomicBool + + // havePrivateKey is whether privateKey is non-zero. + havePrivateKey syncs.AtomicBool } // derpRoute is a route entry for a public key, saying that a certain @@ -1588,6 +1591,9 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { } // receiveIP is the shared bits of ReceiveIPv4 and ReceiveIPv6. +// +// ok is whether this read should be reported up to wireguard-go (our +// caller). func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) { ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone) if !ok { @@ -1600,6 +1606,13 @@ func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep if c.handleDiscoMessage(b, ipp) { return nil, false } + if !c.havePrivateKey.Get() { + // If we have no private key, we're logged out or + // stopped. Don't try to pass these wireguard packets + // up to wireguard-go; it'll just complain (Issue + // 1167). + return nil, false + } if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() { ep = cache.de } else { @@ -1750,8 +1763,8 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD return sent, err } -// handleDiscoMessage reports whether msg was a Tailscale inter-node discovery message -// that was handled. +// handleDiscoMessage handles a discovery message and reports whether +// msg was a Tailscale inter-node discovery message. // // A discovery message has the form: // @@ -1762,11 +1775,18 @@ func (c *Conn) sendDiscoMessage(dst netaddr.IPPort, dstKey tailcfg.NodeKey, dstD // // For messages received over DERP, the addr will be derpMagicIP (with // port being the region) -func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { +func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) (isDiscoMsg bool) { const headerLen = len(disco.Magic) + len(tailcfg.DiscoKey{}) + disco.NonceLen if len(msg) < headerLen || string(msg[:len(disco.Magic)]) != disco.Magic { return false } + + // If the first four parts are the prefix of disco.Magic + // (0x5453f09f) then it's definitely not a valid Wireguard + // packet (which starts with little-endian uint32 1, 2, 3, 4). + // Use naked returns for all following paths. + isDiscoMsg = true + var sender tailcfg.DiscoKey copy(sender[:], msg[len(disco.Magic):]) @@ -1774,20 +1794,21 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { defer c.mu.Unlock() if c.closed { - return true + return } if debugDisco { c.logf("magicsock: disco: got disco-looking frame from %v", sender.ShortString()) } if c.privateKey.IsZero() { // Ignore disco messages when we're stopped. - return false + // Still return true, to not pass it down to wireguard. + return } if c.discoPrivate.IsZero() { if debugDisco { c.logf("magicsock: disco: ignoring disco-looking frame, no local key") } - return false + return } peerNode, ok := c.nodeOfDisco[sender] @@ -1795,9 +1816,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { if debugDisco { c.logf("magicsock: disco: ignoring disco-looking frame, don't know node for %v", sender.ShortString()) } - // Returning false keeps passing it down, to WireGuard. - // WireGuard will almost surely reject it, but give it a chance. - return false + return } needsRecvActivityCall := false @@ -1810,7 +1829,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { c.logf("magicsock: got disco message from idle peer, starting lazy conf for %v, %v", peerNode.Key.ShortString(), sender.ShortString()) if c.noteRecvActivity == nil { c.logf("magicsock: [unexpected] have node without endpoint, without c.noteRecvActivity hook") - return false + return } needsRecvActivityCall = true } else { @@ -1829,7 +1848,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { // Now, recheck invariants that might've changed while we'd // released the lock, which isn't much: if c.closed || c.privateKey.IsZero() { - return true + return } de, ok = c.endpointOfDisco[sender] if !ok { @@ -1838,7 +1857,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { return false } c.logf("magicsock: [unexpected] lazy endpoint not created for %v, %v", peerNode.Key.ShortString(), sender.ShortString()) - return false + return } if !endpointFound0 { c.logf("magicsock: lazy endpoint created via disco message for %v, %v", peerNode.Key.ShortString(), sender.ShortString()) @@ -1865,7 +1884,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { c.logf("magicsock: disco: failed to open naclbox from %v (wrong rcpt?)", sender) } // TODO(bradfitz): add some counter for this that logs rarely - return false + return } dm, err := disco.Parse(payload) @@ -1879,7 +1898,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { // understand. Not even worth logging about, lest it // be too spammy for old clients. // TODO(bradfitz): add some counter for this that logs rarely - return true + return } switch dm := dm.(type) { @@ -1887,14 +1906,14 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { c.handlePingLocked(dm, de, src, sender, peerNode) case *disco.Pong: if de == nil { - return true + return } de.handlePongConnLocked(dm, src) case *disco.CallMeMaybe: if src.IP != derpMagicIPAddr { // CallMeMaybe messages should only come via DERP. c.logf("[unexpected] CallMeMaybe packets should only come via DERP") - return true + return } if de != nil { c.logf("magicsock: disco: %v<-%v (%v, %v) got call-me-maybe, %d endpoints", @@ -1904,8 +1923,7 @@ func (c *Conn) handleDiscoMessage(msg []byte, src netaddr.IPPort) bool { go de.handleCallMeMaybe(dm) } } - - return true + return } func (c *Conn) handlePingLocked(dm *disco.Ping, de *discoEndpoint, src netaddr.IPPort, sender tailcfg.DiscoKey, peerNode *tailcfg.Node) { @@ -2082,6 +2100,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error { return nil } c.privateKey = newKey + c.havePrivateKey.Set(!newKey.IsZero()) if oldKey.IsZero() { c.everHadKey = true From 2f0cb98e500212536ae10f5bdcdb730d3495e147 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Thu, 4 Feb 2021 12:38:30 -0500 Subject: [PATCH 03/71] logpolicy: rename target env var to TS_LOG_TARGET (#1267) Signed-Off-By: Christine Dodrill --- logpolicy/logpolicy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index 4f6ab774c..12032e022 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -388,7 +388,7 @@ func New(collection string) *Policy { HTTPC: &http.Client{Transport: newLogtailTransport(logtail.DefaultHost)}, } - if val, ok := os.LookupEnv("TAILSCALE_LOG_TARGET"); ok { + if val, ok := os.LookupEnv("TS_LOG_TARGET"); ok { log.Println("You have enabled a non-default log target. Doing without being told to by Tailscale staff or your network administrator will make getting support difficult.") c.BaseURL = val u, _ := url.Parse(val) From d37058af728c72a4ef29ccb154da4528a9cb9575 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 2 Feb 2021 15:05:51 -0800 Subject: [PATCH 04/71] net/packet: add some more TSMP packet reject reasons and MaybeBroken bit Unused for now, but I want to backport this commit to 1.4 so 1.6 can start sending these and then at least 1.4 logs will stringify nicely. Signed-off-by: Brad Fitzpatrick --- net/packet/tsmp.go | 80 ++++++++++++++++++++++++++++++++++------- net/packet/tsmp_test.go | 12 +++++++ wgengine/pendopen.go | 28 +++++++++++++-- 3 files changed, 106 insertions(+), 14 deletions(-) diff --git a/net/packet/tsmp.go b/net/packet/tsmp.go index 8b46a6c98..2346c9419 100644 --- a/net/packet/tsmp.go +++ b/net/packet/tsmp.go @@ -23,12 +23,14 @@ import ( // Tailscale node has rejected the connection from another. Unlike a // TCP RST, this includes a reason. // -// On the wire, after the IP header, it's currently 7 bytes: +// On the wire, after the IP header, it's currently 7 or 8 bytes: // * '!' // * IPProto byte (IANA protocol number: TCP or UDP) // * 'A' or 'S' (RejectedDueToACLs, RejectedDueToShieldsUp) // * srcPort big endian uint16 // * dstPort big endian uint16 +// * [optional] byte of flag bits: +// lowest bit (0x1): MaybeBroken // // In the future it might also accept 16 byte IP flow src/dst IPs // after the header, if they're different than the IP-level ones. @@ -39,8 +41,21 @@ type TailscaleRejectedHeader struct { Dst netaddr.IPPort // rejected flow's dst Proto IPProto // proto that was rejected (TCP or UDP) Reason TailscaleRejectReason // why the connection was rejected + + // MaybeBroken is whether the rejection is non-terminal (the + // client should not fail immediately). This is sent by a + // target when it's not sure whether it's totally broken, but + // it might be. For example, the target tailscaled might think + // its host firewall or IP forwarding aren't configured + // properly, but tailscaled might be wrong (not having enough + // visibility into what the OS is doing). When true, the + // message is simply an FYI as a potential reason to use for + // later when the pendopen connection tracking timer expires. + MaybeBroken bool } +const rejectFlagBitMaybeBroken = 0x1 + func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple { return flowtrack.Tuple{Src: rh.Src, Dst: rh.Dst} } @@ -52,14 +67,32 @@ func (rh TailscaleRejectedHeader) String() string { type TSMPType uint8 const ( + // TSMPTypeRejectedConn is the type byte for a TailscaleRejectedHeader. TSMPTypeRejectedConn TSMPType = '!' ) type TailscaleRejectReason byte +// IsZero reports whether r is the zero value, representing no rejection. +func (r TailscaleRejectReason) IsZero() bool { return r == TailscaleRejectReasonNone } + const ( - RejectedDueToACLs TailscaleRejectReason = 'A' + // TailscaleRejectReasonNone is the TailscaleRejectReason zero value. + TailscaleRejectReasonNone TailscaleRejectReason = 0 + + // RejectedDueToACLs means that the host rejected the connection due to ACLs. + RejectedDueToACLs TailscaleRejectReason = 'A' + + // RejectedDueToShieldsUp means that the host rejected the connection due to shields being up. RejectedDueToShieldsUp TailscaleRejectReason = 'S' + + // RejectedDueToIPForwarding means that the relay node's IP + // forwarding is disabled. + RejectedDueToIPForwarding TailscaleRejectReason = 'F' + + // RejectedDueToHostFirewall means that the target host's + // firewall is blocking the traffic. + RejectedDueToHostFirewall TailscaleRejectReason = 'W' ) func (r TailscaleRejectReason) String() string { @@ -68,22 +101,32 @@ func (r TailscaleRejectReason) String() string { return "acl" case RejectedDueToShieldsUp: return "shields" + case RejectedDueToIPForwarding: + return "host-ip-forwarding-unavailable" + case RejectedDueToHostFirewall: + return "host-firewall" } return fmt.Sprintf("0x%02x", byte(r)) } +func (h TailscaleRejectedHeader) hasFlags() bool { + return h.MaybeBroken // the only one currently +} + func (h TailscaleRejectedHeader) Len() int { - var ipHeaderLen int - if h.IPSrc.Is4() { - ipHeaderLen = ip4HeaderLength - } else if h.IPSrc.Is6() { - ipHeaderLen = ip6HeaderLength - } - return ipHeaderLen + - 1 + // TSMPType byte + v := 1 + // TSMPType byte 1 + // IPProto byte 1 + // TailscaleRejectReason byte 2*2 // 2 uint16 ports + if h.IPSrc.Is4() { + v += ip4HeaderLength + } else if h.IPSrc.Is6() { + v += ip6HeaderLength + } + if h.hasFlags() { + v++ + } + return v } func (h TailscaleRejectedHeader) Marshal(buf []byte) error { @@ -117,6 +160,14 @@ func (h TailscaleRejectedHeader) Marshal(buf []byte) error { buf[2] = byte(h.Reason) binary.BigEndian.PutUint16(buf[3:5], h.Src.Port) binary.BigEndian.PutUint16(buf[5:7], h.Dst.Port) + + if h.hasFlags() { + var flags byte + if h.MaybeBroken { + flags |= rejectFlagBitMaybeBroken + } + buf[7] = flags + } return nil } @@ -129,12 +180,17 @@ func (pp *Parsed) AsTailscaleRejectedHeader() (h TailscaleRejectedHeader, ok boo if len(p) < 7 || p[0] != byte(TSMPTypeRejectedConn) { return } - return TailscaleRejectedHeader{ + h = TailscaleRejectedHeader{ Proto: IPProto(p[1]), Reason: TailscaleRejectReason(p[2]), IPSrc: pp.Src.IP, IPDst: pp.Dst.IP, Src: netaddr.IPPort{IP: pp.Dst.IP, Port: binary.BigEndian.Uint16(p[3:5])}, Dst: netaddr.IPPort{IP: pp.Src.IP, Port: binary.BigEndian.Uint16(p[5:7])}, - }, true + } + if len(p) > 7 { + flags := p[7] + h.MaybeBroken = (flags & rejectFlagBitMaybeBroken) != 0 + } + return h, true } diff --git a/net/packet/tsmp_test.go b/net/packet/tsmp_test.go index 71e4f9439..d4a0cf1a0 100644 --- a/net/packet/tsmp_test.go +++ b/net/packet/tsmp_test.go @@ -37,6 +37,18 @@ func TestTailscaleRejectedHeader(t *testing.T) { }, wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: shields", }, + { + h: TailscaleRejectedHeader{ + IPSrc: netaddr.MustParseIP("2::2"), + IPDst: netaddr.MustParseIP("1::1"), + Src: netaddr.MustParseIPPort("[1::1]:567"), + Dst: netaddr.MustParseIPPort("[2::2]:443"), + Proto: UDP, + Reason: RejectedDueToIPForwarding, + MaybeBroken: true, + }, + wantStr: "TSMP-reject-flow{UDP [1::1]:567 > [2::2]:443}: host-ip-forwarding-unavailable", + }, } for i, tt := range tests { gotStr := tt.h.String() diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index a4d0a6f8b..3526fe9bc 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -30,6 +30,12 @@ func debugConnectFailures() bool { type pendingOpenFlow struct { timer *time.Timer // until giving up on the flow + + // guarded by userspaceEngine.mu: + + // problem is non-zero if we got a MaybeBroken (non-terminal) + // TSMP "reject" header. + problem packet.TailscaleRejectReason } func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) { @@ -45,6 +51,17 @@ func (e *userspaceEngine) removeFlow(f flowtrack.Tuple) (removed bool) { return true } +func (e *userspaceEngine) noteFlowProblemFromPeer(f flowtrack.Tuple, problem packet.TailscaleRejectReason) { + e.mu.Lock() + defer e.mu.Unlock() + of, ok := e.pendOpen[f] + if !ok { + // Not a tracked flow (likely already removed) + return + } + of.problem = problem +} + func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) (res filter.Response) { res = filter.Accept // always @@ -54,7 +71,9 @@ func (e *userspaceEngine) trackOpenPreFilterIn(pp *packet.Parsed, t *tstun.TUN) if !ok { return } - if f := rh.Flow(); e.removeFlow(f) { + if rh.MaybeBroken { + e.noteFlowProblemFromPeer(rh.Flow(), rh.Reason) + } else if f := rh.Flow(); e.removeFlow(f) { e.logf("open-conn-track: flow %v %v > %v rejected due to %v", rh.Proto, rh.Src, rh.Dst, rh.Reason) } return @@ -106,7 +125,8 @@ func (e *userspaceEngine) trackOpenPostFilterOut(pp *packet.Parsed, t *tstun.TUN func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { e.mu.Lock() - if _, ok := e.pendOpen[flow]; !ok { + of, ok := e.pendOpen[flow] + if !ok { // Not a tracked flow, or already handled & deleted. e.mu.Unlock() return @@ -114,6 +134,10 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { delete(e.pendOpen, flow) e.mu.Unlock() + if !of.problem.IsZero() { + e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, of.problem) + } + // Diagnose why it might've timed out. n, ok := e.magicConn.PeerForIP(flow.Dst.IP) if !ok { From 70eb05fd4730f5bca42f870961c94f57bf88b33b Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 4 Feb 2021 11:18:32 -0800 Subject: [PATCH 05/71] wgengine: access flow pending problem with lock held Missed review feedback from just-submitted d37058af728c. --- wgengine/pendopen.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index 3526fe9bc..f465f6542 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -132,10 +132,11 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { return } delete(e.pendOpen, flow) + problem := of.problem e.mu.Unlock() - if !of.problem.IsZero() { - e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, of.problem) + if !problem.IsZero() { + e.logf("open-conn-track: timeout opening %v; peer reported problem: %v", flow, problem) } // Diagnose why it might've timed out. From 6254efb9ef43e37f80a6dc3ee3484d61f550a585 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 4 Feb 2021 12:20:07 -0800 Subject: [PATCH 06/71] cmd/tailscale{,d}: move debug subcommand to tailscaled Work on reducing the size of the tailscale binary, which is currently pulling in most of the same code as tailscaled. Updates #1181 --- cmd/tailscale/cli/cli.go | 14 ----------- cmd/tailscale/depaware.txt | 8 +++--- cmd/{tailscale/cli => tailscaled}/debug.go | 29 +++++++++------------- cmd/tailscaled/depaware.txt | 3 ++- cmd/tailscaled/tailscaled.go | 7 ++++++ 5 files changed, 25 insertions(+), 36 deletions(-) rename cmd/{tailscale/cli => tailscaled}/debug.go (86%) diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go index 623b4a3bd..f8d5e0880 100644 --- a/cmd/tailscale/cli/cli.go +++ b/cmd/tailscale/cli/cli.go @@ -68,11 +68,6 @@ change in the future. Exec: func(context.Context, []string) error { return flag.ErrHelp }, } - // Don't advertise the debug command, but it exists. - if strSliceContains(args, "debug") { - rootCmd.Subcommands = append(rootCmd.Subcommands, debugCmd) - } - if err := rootCmd.Parse(args); err != nil { return err } @@ -134,12 +129,3 @@ func pump(ctx context.Context, bc *ipn.BackendClient, conn net.Conn) { bc.GotNotifyMsg(msg) } } - -func strSliceContains(ss []string, s string) bool { - for _, v := range ss { - if v == s { - return true - } - } - return false -} diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 559f214db..659a3c4f3 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -39,7 +39,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale tailscale.com/control/controlclient from tailscale.com/ipn+ tailscale.com/derp from tailscale.com/derp/derphttp+ - tailscale.com/derp/derphttp from tailscale.com/cmd/tailscale/cli+ + tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli tailscale.com/disco from tailscale.com/derp+ tailscale.com/internal/deepprint from tailscale.com/ipn+ @@ -58,7 +58,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/net/stun from tailscale.com/net/netcheck+ tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ tailscale.com/net/tsaddr from tailscale.com/ipn+ - 💣 tailscale.com/net/tshttpproxy from tailscale.com/cmd/tailscale/cli+ + 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ tailscale.com/paths from tailscale.com/cmd/tailscale/cli tailscale.com/portlist from tailscale.com/ipn tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli @@ -67,7 +67,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W tailscale.com/tsconst from tailscale.com/net/interfaces tailscale.com/tstime from tailscale.com/wgengine/magicsock tailscale.com/types/empty from tailscale.com/control/controlclient+ - tailscale.com/types/key from tailscale.com/cmd/tailscale/cli+ + tailscale.com/types/key from tailscale.com/derp+ tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/nettype from tailscale.com/wgengine/magicsock tailscale.com/types/opt from tailscale.com/control/controlclient+ @@ -83,7 +83,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/wgengine from tailscale.com/ipn tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ tailscale.com/wgengine/magicsock from tailscale.com/wgengine - 💣 tailscale.com/wgengine/monitor from tailscale.com/cmd/tailscale/cli+ + 💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine tailscale.com/wgengine/router from tailscale.com/cmd/tailscale/cli+ tailscale.com/wgengine/router/dns from tailscale.com/ipn+ tailscale.com/wgengine/tsdns from tailscale.com/ipn+ diff --git a/cmd/tailscale/cli/debug.go b/cmd/tailscaled/debug.go similarity index 86% rename from cmd/tailscale/cli/debug.go rename to cmd/tailscaled/debug.go index 7873a65de..91ccb070a 100644 --- a/cmd/tailscale/cli/debug.go +++ b/cmd/tailscaled/debug.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package cli +package main import ( "context" @@ -18,7 +18,6 @@ import ( "os" "time" - "github.com/peterbourgon/ff/v2/ffcli" "tailscale.com/derp/derphttp" "tailscale.com/derp/derpmap" "tailscale.com/net/interfaces" @@ -28,28 +27,24 @@ import ( "tailscale.com/wgengine/monitor" ) -var debugCmd = &ffcli.Command{ - Name: "debug", - Exec: runDebug, - FlagSet: (func() *flag.FlagSet { - fs := flag.NewFlagSet("debug", flag.ExitOnError) - fs.BoolVar(&debugArgs.monitor, "monitor", false, "If true, run link monitor forever. 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") - return fs - })(), -} - var debugArgs struct { monitor bool getURL string derpCheck string } -func runDebug(ctx context.Context, args []string) error { - if len(args) > 0 { - return errors.New("unknown arguments") +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.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 { + return err } + if len(fs.Args()) > 0 { + return errors.New("unknown non-flag debug subcommand arguments") + } + ctx := context.Background() if debugArgs.derpCheck != "" { return checkDerp(ctx, debugArgs.derpCheck) } diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 77ef9073c..45f4f3a08 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -71,6 +71,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/control/controlclient from tailscale.com/ipn+ tailscale.com/derp from tailscale.com/derp/derphttp+ tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ + tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled tailscale.com/disco from tailscale.com/derp+ tailscale.com/internal/deepprint from tailscale.com/ipn+ tailscale.com/ipn from tailscale.com/ipn/ipnserver @@ -123,7 +124,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/wgengine from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ tailscale.com/wgengine/magicsock from tailscale.com/cmd/tailscaled+ - 💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine + 💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine+ tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/router/dns from tailscale.com/ipn+ diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index a8acf514c..9e9aaced8 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -93,6 +93,13 @@ func main() { log.Fatalf("fixConsoleOutput: %v", err) } + if len(os.Args) > 1 && os.Args[1] == "debug" { + if err := debugMode(os.Args[2:]); err != nil { + log.Fatal(err) + } + return + } + flag.Parse() if flag.NArg() > 0 { log.Fatalf("tailscaled does not take non-flag arguments: %q", flag.Args()) From d76334d2f0dfbac790dc70ded1cd20a0ce8d2739 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 4 Feb 2021 13:12:42 -0800 Subject: [PATCH 07/71] ipn: split LocalBackend off into new ipn/ipnlocal package And move a couple other types down into leafier packages. Now cmd/tailscale doesn't bring in netlink, magicsock, wgengine, etc. Fixes #1181 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/cli/up.go | 8 +- cmd/tailscale/depaware.txt | 86 ++++++---------- cmd/tailscaled/depaware.txt | 18 ++-- ipn/backend.go | 5 +- ipn/{ => ipnlocal}/local.go | 145 ++++++++++++++------------- ipn/{ => ipnlocal}/local_test.go | 5 +- ipn/{ => ipnlocal}/loglines_test.go | 14 +-- ipn/ipnserver/server.go | 9 +- ipn/ipnstate/ipnstate.go | 6 ++ ipn/prefs.go | 6 +- ipn/prefs_clone.go | 4 +- ipn/prefs_test.go | 10 +- types/preftype/netfiltermode.go | 30 ++++++ wgengine/pendopen.go | 3 +- wgengine/router/router.go | 30 +----- wgengine/router/router_linux.go | 45 +++++---- wgengine/router/router_linux_test.go | 18 ++-- wgengine/userspace.go | 12 +-- wgengine/wgengine.go | 15 +-- 19 files changed, 229 insertions(+), 240 deletions(-) rename ipn/{ => ipnlocal}/local.go (92%) rename ipn/{ => ipnlocal}/local_test.go (99%) rename ipn/{ => ipnlocal}/loglines_test.go (92%) create mode 100644 types/preftype/netfiltermode.go diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index e60801f9b..e4690f021 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -22,9 +22,9 @@ import ( "inet.af/netaddr" "tailscale.com/ipn" "tailscale.com/tailcfg" + "tailscale.com/types/preftype" "tailscale.com/version" "tailscale.com/version/distro" - "tailscale.com/wgengine/router" ) var upCmd = &ffcli.Command{ @@ -202,12 +202,12 @@ func runUp(ctx context.Context, args []string) error { if runtime.GOOS == "linux" { switch upArgs.netfilterMode { case "on": - prefs.NetfilterMode = router.NetfilterOn + prefs.NetfilterMode = preftype.NetfilterOn case "nodivert": - prefs.NetfilterMode = router.NetfilterNoDivert + prefs.NetfilterMode = preftype.NetfilterNoDivert warnf("netfilter=nodivert; add iptables calls to ts-* chains manually.") case "off": - prefs.NetfilterMode = router.NetfilterOff + prefs.NetfilterMode = preftype.NetfilterOff warnf("netfilter=off; configure iptables yourself.") default: fatalf("invalid value --netfilter-mode: %q", upArgs.netfilterMode) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 659a3c4f3..0fab8b1fb 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -4,29 +4,20 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscale W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole - L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router - LW github.com/go-multierror/multierror from tailscale.com/wgengine/router - W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ - W 💣 github.com/go-ole/go-ole/oleutil from tailscale.com/wgengine/winnet - L 💣 github.com/godbus/dbus/v5 from tailscale.com/wgengine/router/dns - L 💣 github.com/jsimonetti/rtnetlink from tailscale.com/wgengine/monitor - L github.com/jsimonetti/rtnetlink/internal/unix from github.com/jsimonetti/rtnetlink - L 💣 github.com/mdlayher/netlink from github.com/jsimonetti/rtnetlink+ - L 💣 github.com/mdlayher/netlink/nlenc from github.com/jsimonetti/rtnetlink+ L github.com/mdlayher/sdnotify from tailscale.com/util/systemd 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/wireguard-go/conn from github.com/tailscale/wireguard-go/device+ - 💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+ + 💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device + 💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine/wgcfg github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device W 💣 github.com/tailscale/wireguard-go/ipc/winpipe from github.com/tailscale/wireguard-go/ipc github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ - github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device+ - 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+ - W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun+ + github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device + 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device + W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli 💣 go4.org/intern from inet.af/netaddr @@ -35,64 +26,51 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+ inet.af/netaddr from tailscale.com/cmd/tailscale/cli+ rsc.io/goversion/version from tailscale.com/version - tailscale.com/atomicfile from tailscale.com/ipn+ + tailscale.com/atomicfile from tailscale.com/ipn tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale - tailscale.com/control/controlclient from tailscale.com/ipn+ - tailscale.com/derp from tailscale.com/derp/derphttp+ - tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ + tailscale.com/control/controlclient from tailscale.com/ipn + tailscale.com/derp from tailscale.com/derp/derphttp + tailscale.com/derp/derphttp from tailscale.com/net/netcheck tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli - tailscale.com/disco from tailscale.com/derp+ - tailscale.com/internal/deepprint from tailscale.com/ipn+ + tailscale.com/disco from tailscale.com/derp tailscale.com/ipn from tailscale.com/cmd/tailscale/cli tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+ - tailscale.com/ipn/policy from tailscale.com/ipn tailscale.com/log/logheap from tailscale.com/control/controlclient - tailscale.com/logtail/backoff from tailscale.com/control/controlclient+ + tailscale.com/logtail/backoff from tailscale.com/control/controlclient tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dnscache from tailscale.com/control/controlclient+ tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+ 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+ - tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli+ + tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli tailscale.com/net/netns from tailscale.com/control/controlclient+ - tailscale.com/net/packet from tailscale.com/wgengine+ - tailscale.com/net/stun from tailscale.com/net/netcheck+ + tailscale.com/net/packet from tailscale.com/wgengine/filter + tailscale.com/net/stun from tailscale.com/net/netcheck tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ - tailscale.com/net/tsaddr from tailscale.com/ipn+ + tailscale.com/net/tsaddr from tailscale.com/net/interfaces 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ tailscale.com/paths from tailscale.com/cmd/tailscale/cli - tailscale.com/portlist from tailscale.com/ipn tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli 💣 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 from tailscale.com/wgengine/magicsock tailscale.com/types/empty from tailscale.com/control/controlclient+ tailscale.com/types/key from tailscale.com/derp+ tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+ - tailscale.com/types/nettype from tailscale.com/wgengine/magicsock tailscale.com/types/opt from tailscale.com/control/controlclient+ + tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ - tailscale.com/types/wgkey from tailscale.com/control/controlclient+ + tailscale.com/types/wgkey from tailscale.com/control/controlclient tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+ - LW tailscale.com/util/endian from tailscale.com/net/netns+ + W tailscale.com/util/endian from tailscale.com/net/netns tailscale.com/util/lineread from tailscale.com/control/controlclient+ - tailscale.com/util/systemd from tailscale.com/control/controlclient+ + tailscale.com/util/systemd from tailscale.com/control/controlclient tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ - tailscale.com/wgengine from tailscale.com/ipn - tailscale.com/wgengine/filter from tailscale.com/control/controlclient+ - tailscale.com/wgengine/magicsock from tailscale.com/wgengine - 💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine - tailscale.com/wgengine/router from tailscale.com/cmd/tailscale/cli+ - tailscale.com/wgengine/router/dns from tailscale.com/ipn+ - tailscale.com/wgengine/tsdns from tailscale.com/ipn+ - tailscale.com/wgengine/tstun from tailscale.com/wgengine - tailscale.com/wgengine/wgcfg from tailscale.com/control/controlclient+ - tailscale.com/wgengine/wglog from tailscale.com/wgengine - W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router + tailscale.com/wgengine/filter from tailscale.com/control/controlclient + tailscale.com/wgengine/wgcfg from tailscale.com/control/controlclient golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box - golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device+ + golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ @@ -103,9 +81,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ - golang.org/x/net/bpf from github.com/mdlayher/netlink+ + golang.org/x/net/bpf from golang.org/x/net/ipv4+ golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal - golang.org/x/net/dns/dnsmessage from net+ + golang.org/x/net/dns/dnsmessage from net golang.org/x/net/http/httpguts from net/http golang.org/x/net/http/httpproxy from net/http golang.org/x/net/http2/hpack from net/http @@ -119,9 +97,9 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/sync/errgroup from tailscale.com/derp golang.org/x/sync/singleflight from tailscale.com/net/dnscache golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ - LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ + LD golang.org/x/sys/unix from github.com/tailscale/wireguard-go/conn+ W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ - W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ + W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ golang.org/x/text/unicode/bidi from golang.org/x/net/idna+ @@ -158,7 +136,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep debug/elf from rsc.io/goversion/version debug/macho from rsc.io/goversion/version debug/pe from rsc.io/goversion/version - encoding from encoding/json+ + encoding from encoding/json encoding/asn1 from crypto/x509+ encoding/base64 from encoding/json+ encoding/binary from compress/gzip+ @@ -172,7 +150,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep hash from compress/zlib+ hash/adler32 from compress/zlib hash/crc32 from compress/gzip+ - hash/fnv from tailscale.com/wgengine/magicsock hash/maphash from go4.org/mem html from tailscale.com/ipn/ipnstate io from bufio+ @@ -181,7 +158,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep math from compress/flate+ math/big from crypto/dsa+ math/bits from compress/flate+ - math/rand from github.com/mdlayher/netlink+ + math/rand from github.com/tailscale/wireguard-go/device+ mime from golang.org/x/oauth2/internal+ mime/multipart from net/http mime/quotedprintable from mime/multipart @@ -192,16 +169,15 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep net/textproto from golang.org/x/net/http/httpguts+ net/url from crypto/x509+ os from crypto/rand+ - os/exec from github.com/coreos/go-iptables/iptables+ + os/exec from github.com/toqueteos/webbrowser+ os/signal from tailscale.com/cmd/tailscale/cli - L os/user from github.com/godbus/dbus/v5 path from debug/dwarf+ path/filepath from crypto/x509+ reflect from crypto/x509+ - regexp from github.com/coreos/go-iptables/iptables+ + regexp from rsc.io/goversion/version regexp/syntax from regexp runtime/debug from golang.org/x/sync/singleflight - runtime/pprof from tailscale.com/log/logheap+ + runtime/pprof from tailscale.com/log/logheap sort from compress/flate+ strconv from compress/flate+ strings from bufio+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 45f4f3a08..ac3eef2a2 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -73,11 +73,12 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled tailscale.com/disco from tailscale.com/derp+ - tailscale.com/internal/deepprint from tailscale.com/ipn+ - tailscale.com/ipn from tailscale.com/ipn/ipnserver + tailscale.com/internal/deepprint from tailscale.com/ipn/ipnlocal+ + tailscale.com/ipn from tailscale.com/ipn/ipnserver+ + tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/ipn+ - tailscale.com/ipn/policy from tailscale.com/ipn + tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver tailscale.com/log/logheap from tailscale.com/control/controlclient tailscale.com/logpolicy from tailscale.com/cmd/tailscaled @@ -87,17 +88,17 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/metrics from tailscale.com/derp tailscale.com/net/dnscache from tailscale.com/control/controlclient+ tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+ - 💣 tailscale.com/net/interfaces from tailscale.com/ipn+ + 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled+ tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock tailscale.com/net/netns from tailscale.com/control/controlclient+ 💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver tailscale.com/net/packet from tailscale.com/wgengine+ tailscale.com/net/stun from tailscale.com/net/netcheck+ tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ - tailscale.com/net/tsaddr from tailscale.com/ipn+ + tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ tailscale.com/paths from tailscale.com/cmd/tailscaled+ - tailscale.com/portlist from tailscale.com/ipn + tailscale.com/portlist from tailscale.com/ipn/ipnlocal tailscale.com/safesocket from tailscale.com/ipn/ipnserver tailscale.com/smallzstd from tailscale.com/ipn/ipnserver+ 💣 tailscale.com/syncs from tailscale.com/net/interfaces+ @@ -110,6 +111,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/logger from tailscale.com/cmd/tailscaled+ tailscale.com/types/nettype from tailscale.com/wgengine/magicsock tailscale.com/types/opt from tailscale.com/control/controlclient+ + tailscale.com/types/preftype from tailscale.com/ipn+ tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ tailscale.com/types/wgkey from tailscale.com/control/controlclient+ @@ -127,8 +129,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 💣 tailscale.com/wgengine/monitor from tailscale.com/wgengine+ tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+ - tailscale.com/wgengine/router/dns from tailscale.com/ipn+ - tailscale.com/wgengine/tsdns from tailscale.com/ipn+ + tailscale.com/wgengine/router/dns from tailscale.com/ipn/ipnlocal+ + tailscale.com/wgengine/tsdns from tailscale.com/ipn/ipnlocal+ tailscale.com/wgengine/tstun from tailscale.com/wgengine+ tailscale.com/wgengine/wgcfg from tailscale.com/control/controlclient+ tailscale.com/wgengine/wglog from tailscale.com/wgengine diff --git a/ipn/backend.go b/ipn/backend.go index 8042b6625..85bb0581f 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -14,7 +14,6 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/empty" "tailscale.com/types/structs" - "tailscale.com/wgengine" ) type State int @@ -46,10 +45,10 @@ func (s State) String() string { // EngineStatus contains WireGuard engine stats. type EngineStatus struct { - RBytes, WBytes wgengine.ByteCount + RBytes, WBytes int64 NumLive int LiveDERPs int // number of active DERP connections - LivePeers map[tailcfg.NodeKey]wgengine.PeerStatus + LivePeers map[tailcfg.NodeKey]ipnstate.PeerStatusLite } // Notify is a communication from a backend (e.g. tailscaled) to a frontend diff --git a/ipn/local.go b/ipn/ipnlocal/local.go similarity index 92% rename from ipn/local.go rename to ipn/ipnlocal/local.go index 0877ad9f7..cb42ce347 100644 --- a/ipn/local.go +++ b/ipn/ipnlocal/local.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ipn +package ipnlocal import ( "bytes" @@ -19,6 +19,7 @@ import ( "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/internal/deepprint" + "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/ipn/policy" "tailscale.com/net/interfaces" @@ -66,7 +67,7 @@ type LocalBackend struct { keyLogf logger.Logf // for printing list of peers on change statsLogf logger.Logf // for printing peers stats on change e wgengine.Engine - store StateStore + store ipn.StateStore backendLogID string portpoll *portlist.Poller // may be nil portpollOnce sync.Once // guards starting readPoller @@ -78,21 +79,21 @@ type LocalBackend struct { // The mutex protects the following elements. mu sync.Mutex - notify func(Notify) + notify func(ipn.Notify) c *controlclient.Client - stateKey StateKey // computed in part from user-provided value - userID string // current controlling user ID (for Windows, primarily) - prefs *Prefs + stateKey ipn.StateKey // computed in part from user-provided value + userID string // current controlling user ID (for Windows, primarily) + prefs *ipn.Prefs inServerMode bool machinePrivKey wgkey.Private - state State + state ipn.State // hostinfo is mutated in-place while mu is held. hostinfo *tailcfg.Hostinfo // netMap is not mutated in-place once set. netMap *controlclient.NetworkMap nodeByAddr map[netaddr.IP]*tailcfg.Node activeLogin string // last logged LoginName from netMap - engineStatus EngineStatus + engineStatus ipn.EngineStatus endpoints []string blocked bool authURL string @@ -107,7 +108,7 @@ type LocalBackend struct { // NewLocalBackend returns a new LocalBackend that is ready to run, // but is not actually running. -func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengine.Engine) (*LocalBackend, error) { +func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wgengine.Engine) (*LocalBackend, error) { if e == nil { panic("ipn.NewLocalBackend: wgengine must not be nil") } @@ -130,7 +131,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store StateStore, e wgengin e: e, store: store, backendLogID: logid, - state: NoState, + state: ipn.NoState, portpoll: portpoll, gotPortPollRes: make(chan struct{}), } @@ -151,7 +152,7 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) { networkUp := ifst.AnyInterfaceUp() if b.c != nil { - go b.c.SetPaused(b.state == Stopped || !networkUp) + go b.c.SetPaused(b.state == ipn.Stopped || !networkUp) } // If the PAC-ness of the network changed, reconfig wireguard+route to @@ -159,7 +160,7 @@ func (b *LocalBackend) linkChange(major bool, ifst *interfaces.State) { if hadPAC != ifst.HasPAC() { b.logf("linkChange: in state %v; PAC changed from %v->%v", b.state, hadPAC, ifst.HasPAC()) switch b.state { - case NoState, Stopped: + case ipn.NoState, ipn.Stopped: // Do nothing. default: go b.authReconfig() @@ -280,7 +281,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { // Auth completed, unblock the engine b.blockEngineUpdates(false) b.authReconfig() - b.send(Notify{LoginFinished: &empty.Message{}}) + b.send(ipn.Notify{LoginFinished: &empty.Message{}}) } prefsChanged := false @@ -311,7 +312,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { if st.URL != "" { b.authURL = st.URL } - if b.state == NeedsLogin { + if b.state == ipn.NeedsLogin { if !b.prefs.WantRunning { prefsChanged = true } @@ -331,7 +332,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { b.logf("Failed to save new controlclient state: %v", err) } } - b.send(Notify{Prefs: prefs}) + b.send(ipn.Notify{Prefs: prefs}) } if st.NetMap != nil { if netMap != nil { @@ -350,7 +351,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { } b.e.SetDERPMap(st.NetMap.DERPMap) - b.send(Notify{NetMap: st.NetMap}) + b.send(ipn.Notify{NetMap: st.NetMap}) } if st.URL != "" { b.logf("Received auth URL: %.20v...", st.URL) @@ -392,7 +393,7 @@ func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) { b.statusChanged.Broadcast() b.statusLock.Unlock() - b.send(Notify{Engine: &es}) + b.send(ipn.Notify{Engine: &es}) } // Start applies the configuration specified in opts, and starts the @@ -405,7 +406,7 @@ func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) { // guarantee that switching from one user's state to another is // actually a supported operation (it should be, but it's very unclear // from the following whether or not that is a safe transition). -func (b *LocalBackend) Start(opts Options) error { +func (b *LocalBackend) Start(opts ipn.Options) error { if opts.Prefs == nil && opts.StateKey == "" { return errors.New("no state key or prefs provided") } @@ -438,7 +439,7 @@ func (b *LocalBackend) Start(opts Options) error { hostinfo.NetInfo = b.hostinfo.NetInfo } b.hostinfo = hostinfo - b.state = NoState + b.state = ipn.NoState if err := b.loadStateLocked(opts.StateKey, opts.Prefs, opts.LegacyConfigPath); err != nil { b.mu.Unlock() @@ -535,8 +536,8 @@ func (b *LocalBackend) Start(opts Options) error { blid := b.backendLogID b.logf("Backend: logs: be:%v fe:%v", blid, opts.FrontendLogID) - b.send(Notify{BackendLogID: &blid}) - b.send(Notify{Prefs: prefs}) + b.send(ipn.Notify{BackendLogID: &blid}) + b.send(ipn.Notify{Prefs: prefs}) cli.Login(nil, controlclient.LoginDefault) return nil @@ -544,7 +545,7 @@ func (b *LocalBackend) Start(opts Options) error { // updateFilter updates the packet filter in wgengine based on the // given netMap and user preferences. -func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *Prefs) { +func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *ipn.Prefs) { // NOTE(danderson): keep change detection as the first thing in // this function. Don't try to optimize by returning early, more // likely than not you'll just end up breaking the change @@ -701,7 +702,7 @@ func (b *LocalBackend) readPoller() { // send delivers n to the connected frontend. If no frontend is // connected, the notification is dropped without being delivered. -func (b *LocalBackend) send(n Notify) { +func (b *LocalBackend) send(n ipn.Notify) { b.mu.Lock() notify := b.notify b.mu.Unlock() @@ -727,9 +728,9 @@ func (b *LocalBackend) popBrowserAuthNow() { b.blockEngineUpdates(true) b.stopEngineAndWait() - b.send(Notify{BrowseToURL: &url}) - if b.State() == Running { - b.enterState(Starting) + b.send(ipn.Notify{BrowseToURL: &url}) + if b.State() == ipn.Running { + b.enterState(ipn.Starting) } } @@ -760,21 +761,21 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) { legacyMachineKey = b.prefs.Persist.LegacyFrontendPrivateMachineKey } - keyText, err := b.store.ReadState(MachineKeyStateKey) + keyText, err := b.store.ReadState(ipn.MachineKeyStateKey) if err == nil { if err := b.machinePrivKey.UnmarshalText(keyText); err != nil { - return fmt.Errorf("invalid key in %s key of %v: %w", MachineKeyStateKey, b.store, err) + return fmt.Errorf("invalid key in %s key of %v: %w", ipn.MachineKeyStateKey, b.store, err) } if b.machinePrivKey.IsZero() { - return fmt.Errorf("invalid zero key stored in %v key of %v", MachineKeyStateKey, b.store) + return fmt.Errorf("invalid zero key stored in %v key of %v", ipn.MachineKeyStateKey, b.store) } if !legacyMachineKey.IsZero() && !bytes.Equal(legacyMachineKey[:], b.machinePrivKey[:]) { b.logf("frontend-provided legacy machine key ignored; used value from server state") } return nil } - if err != ErrStateNotExist { - return fmt.Errorf("error reading %v key of %v: %w", MachineKeyStateKey, b.store, err) + if err != ipn.ErrStateNotExist { + return fmt.Errorf("error reading %v key of %v: %w", ipn.MachineKeyStateKey, b.store, err) } // If we didn't find one already on disk and the prefs already @@ -797,7 +798,7 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) { } keyText, _ = b.machinePrivKey.MarshalText() - if err := b.store.WriteState(MachineKeyStateKey, keyText); err != nil { + if err := b.store.WriteState(ipn.MachineKeyStateKey, keyText); err != nil { b.logf("error writing machine key to store: %v", err) return err } @@ -810,14 +811,14 @@ func (b *LocalBackend) initMachineKeyLocked() (err error) { // user and prefs. If userID is blank or prefs is blank, no work is done. // // b.mu may either be held or not. -func (b *LocalBackend) writeServerModeStartState(userID string, prefs *Prefs) { +func (b *LocalBackend) writeServerModeStartState(userID string, prefs *ipn.Prefs) { if userID == "" || prefs == nil { return } if prefs.ForceDaemon { - stateKey := StateKey("user-" + userID) - if err := b.store.WriteState(ServerModeStartKey, []byte(stateKey)); err != nil { + stateKey := ipn.StateKey("user-" + userID) + if err := b.store.WriteState(ipn.ServerModeStartKey, []byte(stateKey)); err != nil { b.logf("WriteState error: %v", err) } // It's important we do this here too, even if it looks @@ -829,7 +830,7 @@ func (b *LocalBackend) writeServerModeStartState(userID string, prefs *Prefs) { b.logf("WriteState error: %v", err) } } else { - if err := b.store.WriteState(ServerModeStartKey, nil); err != nil { + if err := b.store.WriteState(ipn.ServerModeStartKey, nil); err != nil { b.logf("WriteState error: %v", err) } } @@ -838,7 +839,7 @@ func (b *LocalBackend) writeServerModeStartState(userID string, prefs *Prefs) { // loadStateLocked sets b.prefs and b.stateKey based on a complex // combination of key, prefs, and legacyPath. b.mu must be held when // calling. -func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath string) (err error) { +func (b *LocalBackend) loadStateLocked(key ipn.StateKey, prefs *ipn.Prefs, legacyPath string) (err error) { if prefs == nil && key == "" { panic("state key and prefs are both unset") } @@ -880,19 +881,19 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st b.logf("using backend prefs") bs, err := b.store.ReadState(key) if err != nil { - if errors.Is(err, ErrStateNotExist) { + if errors.Is(err, ipn.ErrStateNotExist) { if legacyPath != "" { - b.prefs, err = LoadPrefs(legacyPath) + b.prefs, err = ipn.LoadPrefs(legacyPath) if err != nil { if !errors.Is(err, os.ErrNotExist) { b.logf("failed to load legacy prefs: %v", err) } - b.prefs = NewPrefs() + b.prefs = ipn.NewPrefs() } else { b.logf("imported prefs from relaynode for %q: %v", key, b.prefs.Pretty()) } } else { - b.prefs = NewPrefs() + b.prefs = ipn.NewPrefs() b.logf("created empty state for %q: %s", key, b.prefs.Pretty()) } if err := b.initMachineKeyLocked(); err != nil { @@ -902,7 +903,7 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st } return fmt.Errorf("store.ReadState(%q): %v", key, err) } - b.prefs, err = PrefsFromBytes(bs, false) + b.prefs, err = ipn.PrefsFromBytes(bs, false) if err != nil { return fmt.Errorf("PrefsFromBytes: %v", err) } @@ -914,7 +915,7 @@ func (b *LocalBackend) loadStateLocked(key StateKey, prefs *Prefs, legacyPath st } // State returns the backend state machine's current state. -func (b *LocalBackend) State() State { +func (b *LocalBackend) State() ipn.State { b.mu.Lock() defer b.mu.Unlock() @@ -930,7 +931,7 @@ func (b *LocalBackend) InServerMode() bool { // getEngineStatus returns a copy of b.engineStatus. // // TODO(bradfitz): remove this and use Status() throughout. -func (b *LocalBackend) getEngineStatus() EngineStatus { +func (b *LocalBackend) getEngineStatus() ipn.EngineStatus { b.mu.Lock() defer b.mu.Unlock() @@ -986,7 +987,7 @@ func (b *LocalBackend) FakeExpireAfter(x time.Duration) { mapCopy.Expiry = time.Now().Add(x) } b.setNetMapLocked(&mapCopy) - b.send(Notify{NetMap: b.netMap}) + b.send(ipn.Notify{NetMap: b.netMap}) } func (b *LocalBackend) Ping(ipStr string) { @@ -996,7 +997,7 @@ func (b *LocalBackend) Ping(ipStr string) { return } b.e.Ping(ip, func(pr *ipnstate.PingResult) { - b.send(Notify{PingResult: pr}) + b.send(ipn.Notify{PingResult: pr}) }) } @@ -1005,11 +1006,11 @@ func (b *LocalBackend) Ping(ipStr string) { // b.mu must be held; mostly because the caller is about to anyway, and doing so // gives us slightly better guarantees about the two peers stats lines not // being intermixed if there are concurrent calls to our caller. -func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret EngineStatus) { +func (b *LocalBackend) parseWgStatusLocked(s *wgengine.Status) (ret ipn.EngineStatus) { var peerStats, peerKeys strings.Builder ret.LiveDERPs = s.DERPs - ret.LivePeers = map[tailcfg.NodeKey]wgengine.PeerStatus{} + ret.LivePeers = map[tailcfg.NodeKey]ipnstate.PeerStatusLite{} for _, p := range s.Peers { if !p.LastHandshake.IsZero() { fmt.Fprintf(&peerStats, "%d/%d ", p.RxBytes, p.TxBytes) @@ -1065,7 +1066,7 @@ func (b *LocalBackend) SetWantRunning(wantRunning bool) { // SetPrefs saves new user preferences and propagates them throughout // the system. Implements Backend. -func (b *LocalBackend) SetPrefs(newp *Prefs) { +func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) { if newp == nil { panic("SetPrefs got nil prefs") } @@ -1132,7 +1133,7 @@ func (b *LocalBackend) SetPrefs(newp *Prefs) { b.authReconfig() } - b.send(Notify{Prefs: newp}) + b.send(ipn.Notify{Prefs: newp}) } // doSetHostinfoFilterServices calls SetHostinfo on the controlclient, @@ -1256,7 +1257,7 @@ func magicDNSRootDomains(nm *controlclient.NetworkMap) []string { } // routerConfig produces a router.Config from a wireguard config and IPN prefs. -func routerConfig(cfg *wgcfg.Config, prefs *Prefs) *router.Config { +func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { rs := &router.Config{ LocalAddrs: unmapIPPrefixes(cfg.Addresses), SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes), @@ -1285,7 +1286,7 @@ func unmapIPPrefixes(ippsList ...[]netaddr.IPPrefix) (ret []netaddr.IPPrefix) { return ret } -func applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *Prefs) { +func applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *ipn.Prefs) { if h := prefs.Hostname; h != "" { hi.Hostname = h } @@ -1305,7 +1306,7 @@ func applyPrefsToHostinfo(hi *tailcfg.Hostinfo, prefs *Prefs) { // places twiddle IPN internal state without going through here, so // really this is more "one of several places in which random things // happen". -func (b *LocalBackend) enterState(newState State) { +func (b *LocalBackend) enterState(newState ipn.State) { b.mu.Lock() state := b.state b.state = newState @@ -1323,19 +1324,19 @@ func (b *LocalBackend) enterState(newState State) { b.logf("Switching ipn state %v -> %v (WantRunning=%v)", state, newState, prefs.WantRunning) if notify != nil { - b.send(Notify{State: &newState}) + b.send(ipn.Notify{State: &newState}) } if bc != nil { - bc.SetPaused(newState == Stopped || !networkUp) + bc.SetPaused(newState == ipn.Stopped || !networkUp) } switch newState { - case NeedsLogin: + case ipn.NeedsLogin: systemd.Status("Needs login: %s", authURL) b.blockEngineUpdates(true) fallthrough - case Stopped: + case ipn.Stopped: err := b.e.Reconfig(&wgcfg.Config{}, &router.Config{}) if err != nil { b.logf("Reconfig(down): %v", err) @@ -1344,11 +1345,11 @@ func (b *LocalBackend) enterState(newState State) { if authURL == "" { systemd.Status("Stopped; run 'tailscale up' to log in") } - case Starting, NeedsMachineAuth: + case ipn.Starting, ipn.NeedsMachineAuth: b.authReconfig() // Needed so that UpdateEndpoints can run b.e.RequestStatus() - case Running: + case ipn.Running: var addrs []string for _, addr := range b.netMap.Addresses { addrs = append(addrs, addr.IP.String()) @@ -1362,7 +1363,7 @@ func (b *LocalBackend) enterState(newState State) { // nextState returns the state the backend seems to be in, based on // its internal state. -func (b *LocalBackend) nextState() State { +func (b *LocalBackend) nextState() ipn.State { b.mu.Lock() b.assertClientLocked() var ( @@ -1378,31 +1379,31 @@ func (b *LocalBackend) nextState() State { if c.AuthCantContinue() { // Auth was interrupted or waiting for URL visit, // so it won't proceed without human help. - return NeedsLogin + return ipn.NeedsLogin } else { // Auth or map request needs to finish return state } case !wantRunning: - return Stopped + return ipn.Stopped case !netMap.Expiry.IsZero() && time.Until(netMap.Expiry) <= 0: - return NeedsLogin + return ipn.NeedsLogin case netMap.MachineStatus != tailcfg.MachineAuthorized: // TODO(crawshaw): handle tailcfg.MachineInvalid - return NeedsMachineAuth - case state == NeedsMachineAuth: + return ipn.NeedsMachineAuth + case state == ipn.NeedsMachineAuth: // (if we get here, we know MachineAuthorized == true) - return Starting - case state == Starting: + return ipn.Starting + case state == ipn.Starting: if st := b.getEngineStatus(); st.NumLive > 0 || st.LiveDERPs > 0 { - return Running + return ipn.Running } else { return state } - case state == Running: - return Running + case state == ipn.Running: + return ipn.Running default: - return Starting + return ipn.Starting } } @@ -1414,7 +1415,7 @@ func (b *LocalBackend) RequestEngineStatus() { // RequestStatus implements Backend. func (b *LocalBackend) RequestStatus() { st := b.Status() - b.send(Notify{Status: st}) + b.send(ipn.Notify{Status: st}) } // stateMachine updates the state machine state based on other things diff --git a/ipn/local_test.go b/ipn/ipnlocal/local_test.go similarity index 99% rename from ipn/local_test.go rename to ipn/ipnlocal/local_test.go index 547262f61..ec9400952 100644 --- a/ipn/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -2,13 +2,14 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ipn +package ipnlocal import ( + "testing" + "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/tailcfg" - "testing" ) func TestNetworkMapCompare(t *testing.T) { diff --git a/ipn/loglines_test.go b/ipn/ipnlocal/loglines_test.go similarity index 92% rename from ipn/loglines_test.go rename to ipn/ipnlocal/loglines_test.go index fb056e8be..656758afe 100644 --- a/ipn/loglines_test.go +++ b/ipn/ipnlocal/loglines_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package ipn +package ipnlocal import ( "reflect" @@ -10,6 +10,8 @@ import ( "time" "tailscale.com/control/controlclient" + "tailscale.com/ipn" + "tailscale.com/ipn/ipnstate" "tailscale.com/logtail" "tailscale.com/tailcfg" "tailscale.com/tstest" @@ -38,9 +40,7 @@ func TestLocalLogLines(t *testing.T) { idA := logid(0xaa) // set up a LocalBackend, super bare bones. No functional data. - store := &MemoryStore{ - cache: make(map[StateKey][]byte), - } + store := &ipn.MemoryStore{} e, err := wgengine.NewFakeUserspaceEngine(logListen.Logf, 0, nil) if err != nil { t.Fatal(err) @@ -53,7 +53,7 @@ func TestLocalLogLines(t *testing.T) { defer lb.Shutdown() // custom adjustments for required non-nil fields - lb.prefs = NewPrefs() + lb.prefs = ipn.NewPrefs() lb.hostinfo = &tailcfg.Hostinfo{} // hacky manual override of the usual log-on-change behaviour of keylogf lb.keyLogf = logListen.Logf @@ -68,7 +68,7 @@ func TestLocalLogLines(t *testing.T) { // log prefs line persist := &controlclient.Persist{} - prefs := NewPrefs() + prefs := ipn.NewPrefs() prefs.Persist = persist lb.SetPrefs(prefs) @@ -76,7 +76,7 @@ func TestLocalLogLines(t *testing.T) { // log peers, peer keys status := &wgengine.Status{ - Peers: []wgengine.PeerStatus{wgengine.PeerStatus{ + Peers: []ipnstate.PeerStatusLite{{ TxBytes: 10, RxBytes: 10, LastHandshake: time.Now(), diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index dd8a39ab6..bc120ca60 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -28,6 +28,7 @@ import ( "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/ipn" + "tailscale.com/ipn/ipnlocal" "tailscale.com/log/filelogger" "tailscale.com/logtail/backoff" "tailscale.com/net/netstat" @@ -93,7 +94,7 @@ type Options struct { // server is an IPN backend and its set of 0 or more active connections // talking to an IPN backend. type server struct { - b *ipn.LocalBackend + b *ipnlocal.LocalBackend logf logger.Logf // resetOnZero is whether to call bs.Reset on transition from // 1->0 connections. That is, this is whether the backend is @@ -612,7 +613,7 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( } } - b, err := ipn.NewLocalBackend(logf, logid, store, eng) + b, err := ipnlocal.NewLocalBackend(logf, logid, store, eng) if err != nil { return fmt.Errorf("NewLocalBackend: %v", err) } @@ -878,7 +879,7 @@ func (s *server) localhostHandler(ci connIdentity) http.Handler { }) } -func serveHTMLStatus(w http.ResponseWriter, b *ipn.LocalBackend) { +func serveHTMLStatus(w http.ResponseWriter, b *ipnlocal.LocalBackend) { w.Header().Set("Content-Type", "text/html; charset=utf-8") st := b.Status() // TODO(bradfitz): add LogID and opts to st? @@ -896,7 +897,7 @@ func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int { // whoIsHandler is the debug server's /debug?ip=$IP HTTP handler. type whoIsHandler struct { - b *ipn.LocalBackend + b *ipnlocal.LocalBackend } func (h whoIsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index fbddbc46b..3f2d359f7 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -50,6 +50,12 @@ func (s *Status) Peers() []key.Public { return kk } +type PeerStatusLite struct { + TxBytes, RxBytes int64 + LastHandshake time.Time + NodeKey tailcfg.NodeKey +} + type PeerStatus struct { PublicKey key.Public HostName string // HostInfo's Hostname (not a DNS name or necessarily unique) diff --git a/ipn/prefs.go b/ipn/prefs.go index f8256454b..cb6bf2869 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -18,7 +18,7 @@ import ( "inet.af/netaddr" "tailscale.com/atomicfile" "tailscale.com/control/controlclient" - "tailscale.com/wgengine/router" + "tailscale.com/types/preftype" ) //go:generate go run tailscale.com/cmd/cloner -type=Prefs -output=prefs_clone.go @@ -116,7 +116,7 @@ type Prefs struct { // NetfilterMode specifies how much to manage netfilter rules for // Tailscale, if at all. - NetfilterMode router.NetfilterMode + NetfilterMode preftype.NetfilterMode // The Persist field is named 'Config' in the file for backward // compatibility with earlier versions. @@ -240,7 +240,7 @@ func NewPrefs() *Prefs { AllowSingleHosts: true, CorpDNS: true, WantRunning: true, - NetfilterMode: router.NetfilterOn, + NetfilterMode: preftype.NetfilterOn, } } diff --git a/ipn/prefs_clone.go b/ipn/prefs_clone.go index c0e11f8f8..950e34145 100644 --- a/ipn/prefs_clone.go +++ b/ipn/prefs_clone.go @@ -9,7 +9,7 @@ package ipn import ( "inet.af/netaddr" "tailscale.com/control/controlclient" - "tailscale.com/wgengine/router" + "tailscale.com/types/preftype" ) // Clone makes a deep copy of Prefs. @@ -46,6 +46,6 @@ var _PrefsNeedsRegeneration = Prefs(struct { ForceDaemon bool AdvertiseRoutes []netaddr.IPPrefix NoSNAT bool - NetfilterMode router.NetfilterMode + NetfilterMode preftype.NetfilterMode Persist *controlclient.Persist }{}) diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 9715d7c89..77278446c 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -16,8 +16,8 @@ import ( "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/tstest" + "tailscale.com/types/preftype" "tailscale.com/types/wgkey" - "tailscale.com/wgengine/router" ) func fieldsOf(t reflect.Type) (fields []string) { @@ -192,13 +192,13 @@ func TestPrefsEqual(t *testing.T) { }, { - &Prefs{NetfilterMode: router.NetfilterOff}, - &Prefs{NetfilterMode: router.NetfilterOn}, + &Prefs{NetfilterMode: preftype.NetfilterOff}, + &Prefs{NetfilterMode: preftype.NetfilterOn}, false, }, { - &Prefs{NetfilterMode: router.NetfilterOn}, - &Prefs{NetfilterMode: router.NetfilterOn}, + &Prefs{NetfilterMode: preftype.NetfilterOn}, + &Prefs{NetfilterMode: preftype.NetfilterOn}, true, }, diff --git a/types/preftype/netfiltermode.go b/types/preftype/netfiltermode.go new file mode 100644 index 000000000..7e8dec9dd --- /dev/null +++ b/types/preftype/netfiltermode.go @@ -0,0 +1,30 @@ +// 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 preftype is a leaf package containing types for various +// preferences. +package preftype + +// NetfilterMode is the firewall management mode to use when +// programming the Linux network stack. +type NetfilterMode int + +const ( + NetfilterOff NetfilterMode = iota // remove all tailscale netfilter state + NetfilterNoDivert // manage tailscale chains, but don't call them + NetfilterOn // manage tailscale chains and call them from main chains +) + +func (m NetfilterMode) String() string { + switch m { + case NetfilterOff: + return "off" + case NetfilterNoDivert: + return "nodivert" + case NetfilterOn: + return "on" + default: + return "???" + } +} diff --git a/wgengine/pendopen.go b/wgengine/pendopen.go index f465f6542..2ede0429a 100644 --- a/wgengine/pendopen.go +++ b/wgengine/pendopen.go @@ -9,6 +9,7 @@ import ( "strconv" "time" + "tailscale.com/ipn/ipnstate" "tailscale.com/net/flowtrack" "tailscale.com/net/packet" "tailscale.com/wgengine/filter" @@ -158,7 +159,7 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) { lastSeen = *n.LastSeen } - var ps *PeerStatus + var ps *ipnstate.PeerStatusLite if st, err := e.getStatus(); err == nil { for _, v := range st.Peers { if v.NodeKey == n.Key { diff --git a/wgengine/router/router.go b/wgengine/router/router.go index c65a0b806..9c3f1003f 100644 --- a/wgengine/router/router.go +++ b/wgengine/router/router.go @@ -11,6 +11,7 @@ import ( "github.com/tailscale/wireguard-go/tun" "inet.af/netaddr" "tailscale.com/types/logger" + "tailscale.com/types/preftype" "tailscale.com/wgengine/router/dns" ) @@ -53,29 +54,6 @@ func Cleanup(logf logger.Logf, interfaceName string) { cleanup(logf, interfaceName) } -// NetfilterMode is the firewall management mode to use when -// programming the Linux network stack. -type NetfilterMode int - -const ( - NetfilterOff NetfilterMode = iota // remove all tailscale netfilter state - NetfilterNoDivert // manage tailscale chains, but don't call them - NetfilterOn // manage tailscale chains and call them from main chains -) - -func (m NetfilterMode) String() string { - switch m { - case NetfilterOff: - return "off" - case NetfilterNoDivert: - return "nodivert" - case NetfilterOn: - return "on" - default: - return "???" - } -} - // Config is the subset of Tailscale configuration that is relevant to // the OS's network stack. type Config struct { @@ -86,9 +64,9 @@ type Config struct { // Linux-only things below, ignored on other platforms. - SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes - SNATSubnetRoutes bool // SNAT traffic to local subnets - NetfilterMode NetfilterMode // how much to manage netfilter rules + SubnetRoutes []netaddr.IPPrefix // subnets being advertised to other Tailscale nodes + SNATSubnetRoutes bool // SNAT traffic to local subnets + NetfilterMode preftype.NetfilterMode // how much to manage netfilter rules } // shutdownConfig is a routing configuration that removes all router diff --git a/wgengine/router/router_linux.go b/wgengine/router/router_linux.go index d6e10dac1..c3724d77f 100644 --- a/wgengine/router/router_linux.go +++ b/wgengine/router/router_linux.go @@ -21,10 +21,17 @@ import ( "inet.af/netaddr" "tailscale.com/net/tsaddr" "tailscale.com/types/logger" + "tailscale.com/types/preftype" "tailscale.com/version/distro" "tailscale.com/wgengine/router/dns" ) +const ( + netfilterOff = preftype.NetfilterOff + netfilterNoDivert = preftype.NetfilterNoDivert + netfilterOn = preftype.NetfilterOn +) + // The following bits are added to packet marks for Tailscale use. // // We tried to pick bits sufficiently out of the way that it's @@ -89,7 +96,7 @@ type linuxRouter struct { addrs map[netaddr.IPPrefix]bool routes map[netaddr.IPPrefix]bool snatSubnetRoutes bool - netfilterMode NetfilterMode + netfilterMode preftype.NetfilterMode // Various feature checks for the network stack. ipRuleAvailable bool @@ -148,7 +155,7 @@ func newUserspaceRouterAdvanced(logf logger.Logf, tunname string, netfilter4, ne return &linuxRouter{ logf: logf, tunname: tunname, - netfilterMode: NetfilterOff, + netfilterMode: netfilterOff, ipRuleAvailable: ipRuleAvailable, v6Available: supportsV6, @@ -168,7 +175,7 @@ func (r *linuxRouter) Up() error { if err := r.addIPRules(); err != nil { return err } - if err := r.setNetfilterMode(NetfilterOff); err != nil { + if err := r.setNetfilterMode(netfilterOff); err != nil { return err } if err := r.upInterface(); err != nil { @@ -188,7 +195,7 @@ func (r *linuxRouter) Close() error { if err := r.delIPRules(); err != nil { return err } - if err := r.setNetfilterMode(NetfilterOff); err != nil { + if err := r.setNetfilterMode(netfilterOff); err != nil { return err } @@ -246,9 +253,9 @@ func (r *linuxRouter) Set(cfg *Config) error { // mode. Netfilter state is created or deleted appropriately to // reflect the new mode, and r.snatSubnetRoutes is updated to reflect // the current state of subnet SNATing. -func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { +func (r *linuxRouter) setNetfilterMode(mode preftype.NetfilterMode) error { if distro.Get() == distro.Synology { - mode = NetfilterOff + mode = netfilterOff } if r.netfilterMode == mode { return nil @@ -264,9 +271,9 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { reprocess := false switch mode { - case NetfilterOff: + case netfilterOff: switch r.netfilterMode { - case NetfilterNoDivert: + case netfilterNoDivert: if err := r.delNetfilterBase(); err != nil { return err } @@ -276,7 +283,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { // This can happen if someone left a ref to // this table somewhere else. } - case NetfilterOn: + case netfilterOn: if err := r.delNetfilterHooks(); err != nil { return err } @@ -291,9 +298,9 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { } } r.snatSubnetRoutes = false - case NetfilterNoDivert: + case netfilterNoDivert: switch r.netfilterMode { - case NetfilterOff: + case netfilterOff: reprocess = true if err := r.addNetfilterChains(); err != nil { return err @@ -302,12 +309,12 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { return err } r.snatSubnetRoutes = false - case NetfilterOn: + case netfilterOn: if err := r.delNetfilterHooks(); err != nil { return err } } - case NetfilterOn: + case netfilterOn: // Because of bugs in old version of iptables-compat, // we can't add a "-j ts-forward" rule to FORWARD // while ts-forward contains an "-m mark" rule. But @@ -315,7 +322,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { // So we have to delNetFilterBase, then add the hooks, // then re-addNetFilterBase, just in case. switch r.netfilterMode { - case NetfilterOff: + case netfilterOff: reprocess = true if err := r.addNetfilterChains(); err != nil { return err @@ -330,7 +337,7 @@ func (r *linuxRouter) setNetfilterMode(mode NetfilterMode) error { return err } r.snatSubnetRoutes = false - case NetfilterNoDivert: + case netfilterNoDivert: reprocess = true if err := r.delNetfilterBase(); err != nil { return err @@ -397,7 +404,7 @@ func (r *linuxRouter) delAddress(addr netaddr.IPPrefix) error { // addLoopbackRule adds a firewall rule to permit loopback traffic to // a local Tailscale IP. func (r *linuxRouter) addLoopbackRule(addr netaddr.IP) error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } @@ -419,7 +426,7 @@ func (r *linuxRouter) addLoopbackRule(addr netaddr.IP) error { // delLoopbackRule removes the firewall rule permitting loopback // traffic to a Tailscale IP. func (r *linuxRouter) delLoopbackRule(addr netaddr.IP) error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } @@ -903,7 +910,7 @@ func (r *linuxRouter) delNetfilterHooks() error { // addSNATRule adds a netfilter rule to SNAT traffic destined for // local subnets. func (r *linuxRouter) addSNATRule() error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } @@ -922,7 +929,7 @@ func (r *linuxRouter) addSNATRule() error { // delSNATRule removes the netfilter rule to SNAT traffic destined for // local subnets. Fails if the rule does not exist. func (r *linuxRouter) delSNATRule() error { - if r.netfilterMode == NetfilterOff { + if r.netfilterMode == netfilterOff { return nil } diff --git a/wgengine/router/router_linux_test.go b/wgengine/router/router_linux_test.go index bcc93af8f..8298c6d07 100644 --- a/wgengine/router/router_linux_test.go +++ b/wgengine/router/router_linux_test.go @@ -58,7 +58,7 @@ up` + basic, name: "local addr only", in: &Config{ LocalAddrs: mustCIDRs("100.101.102.103/10"), - NetfilterMode: NetfilterOff, + NetfilterMode: netfilterOff, }, want: ` up @@ -70,7 +70,7 @@ ip addr add 100.101.102.103/10 dev tailscale0` + basic, in: &Config{ LocalAddrs: mustCIDRs("100.101.102.103/10"), Routes: mustCIDRs("100.100.100.100/32", "192.168.16.0/24"), - NetfilterMode: NetfilterOff, + NetfilterMode: netfilterOff, }, want: ` up @@ -85,7 +85,7 @@ ip route add 192.168.16.0/24 dev tailscale0 table 52` + basic, LocalAddrs: mustCIDRs("100.101.102.103/10"), Routes: mustCIDRs("100.100.100.100/32", "192.168.16.0/24"), SubnetRoutes: mustCIDRs("200.0.0.0/8"), - NetfilterMode: NetfilterOff, + NetfilterMode: netfilterOff, }, want: ` up @@ -101,7 +101,7 @@ ip route add 192.168.16.0/24 dev tailscale0 table 52` + basic, Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), SubnetRoutes: mustCIDRs("200.0.0.0/8"), SNATSubnetRoutes: true, - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -133,7 +133,7 @@ v6/nat/ts-postrouting -m mark --mark 0x40000 -j MASQUERADE in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -166,7 +166,7 @@ v6/nat/POSTROUTING -j ts-postrouting Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), SubnetRoutes: mustCIDRs("200.0.0.0/8"), SNATSubnetRoutes: false, - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -196,7 +196,7 @@ v6/nat/POSTROUTING -j ts-postrouting in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up @@ -227,7 +227,7 @@ v6/nat/POSTROUTING -j ts-postrouting in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterNoDivert, + NetfilterMode: netfilterNoDivert, }, want: ` up @@ -251,7 +251,7 @@ v6/filter/ts-forward -o tailscale0 -j ACCEPT in: &Config{ LocalAddrs: mustCIDRs("100.101.102.104/10"), Routes: mustCIDRs("100.100.100.100/32", "10.0.0.0/8"), - NetfilterMode: NetfilterOn, + NetfilterMode: netfilterOn, }, want: ` up diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 8638b3d38..03b391bca 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -1082,8 +1082,8 @@ func (e *userspaceEngine) getStatus() (*Status, error) { errc <- err }() - pp := make(map[wgkey.Key]*PeerStatus) - p := &PeerStatus{} + pp := make(map[wgkey.Key]*ipnstate.PeerStatusLite) + p := &ipnstate.PeerStatusLite{} var hst1, hst2, n int64 @@ -1115,20 +1115,20 @@ func (e *userspaceEngine) getStatus() (*Status, error) { if err != nil { return nil, fmt.Errorf("IpcGetOperation: invalid key in line %q", line) } - p = &PeerStatus{} + p = &ipnstate.PeerStatusLite{} pp[wgkey.Key(pk)] = p key := tailcfg.NodeKey(pk) p.NodeKey = key case "rx_bytes": n, err = mem.ParseInt(v, 10, 64) - p.RxBytes = ByteCount(n) + p.RxBytes = n if err != nil { return nil, fmt.Errorf("IpcGetOperation: rx_bytes invalid: %#v", line) } case "tx_bytes": n, err = mem.ParseInt(v, 10, 64) - p.TxBytes = ByteCount(n) + p.TxBytes = n if err != nil { return nil, fmt.Errorf("IpcGetOperation: tx_bytes invalid: %#v", line) } @@ -1154,7 +1154,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) { e.mu.Lock() defer e.mu.Unlock() - var peers []PeerStatus + var peers []ipnstate.PeerStatusLite for _, pk := range e.peerSequence { if p, ok := pp[pk]; ok { // ignore idle ones not in wireguard-go's config peers = append(peers, *p) diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index 563888083..ff3efb6a0 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -6,7 +6,6 @@ package wgengine import ( "errors" - "time" "inet.af/netaddr" "tailscale.com/control/controlclient" @@ -19,23 +18,11 @@ import ( "tailscale.com/wgengine/wgcfg" ) -// ByteCount is the number of bytes that have been sent or received. -// -// TODO: why is this a type? remove? -// TODO: document whether it's payload bytes only or if it includes framing overhead. -type ByteCount int64 - -type PeerStatus struct { - TxBytes, RxBytes ByteCount - LastHandshake time.Time - NodeKey tailcfg.NodeKey -} - // Status is the Engine status. // // TODO(bradfitz): remove this, subset of ipnstate? Need to migrate users. type Status struct { - Peers []PeerStatus + Peers []ipnstate.PeerStatusLite LocalAddrs []string // the set of possible endpoints for the magic conn DERPs int // number of active DERP connections } From aa6856a9eb7cb928811063a96261fd24500f30ef Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 3 Feb 2021 15:24:13 -0800 Subject: [PATCH 08/71] wgengine: adapt to wireguard-go changes Signed-off-by: Josh Bleecher Snyder --- cmd/tailscale/depaware.txt | 1 - cmd/tailscaled/depaware.txt | 1 - go.mod | 8 ++++---- go.sum | 8 ++++++++ wgengine/userspace.go | 20 ++++++++++++-------- wgengine/wglog/wglog.go | 6 ++---- wgengine/wglog/wglog_test.go | 3 +-- 7 files changed, 27 insertions(+), 20 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 0fab8b1fb..2a1fa2833 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -9,7 +9,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli 💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine/wgcfg - github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device W 💣 github.com/tailscale/wireguard-go/ipc/winpipe from github.com/tailscale/wireguard-go/ipc github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index ac3eef2a2..9f318cc86 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -22,7 +22,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de L github.com/mdlayher/sdnotify from tailscale.com/util/systemd 💣 github.com/tailscale/wireguard-go/conn from github.com/tailscale/wireguard-go/device+ 💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine+ - github.com/tailscale/wireguard-go/device/tokenbucket from github.com/tailscale/wireguard-go/device 💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device W 💣 github.com/tailscale/wireguard-go/ipc/winpipe from github.com/tailscale/wireguard-go/ipc github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device diff --git a/go.mod b/go.mod index 4f976363f..c8e1830bb 100644 --- a/go.mod +++ b/go.mod @@ -24,15 +24,15 @@ require ( github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 - github.com/tailscale/wireguard-go v0.0.0-20210201213041-c9817e648365 + github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020 github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 - golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 - golang.org/x/net v0.0.0-20201216054612-986b41b23924 + golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad + golang.org/x/net v0.0.0-20201224014010-6772e930b67b golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 - golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 + golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b golang.org/x/time v0.0.0-20191024005414-555d28b269f0 golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 diff --git a/go.sum b/go.sum index d5becc8b9..fe7dcd431 100644 --- a/go.sum +++ b/go.sum @@ -300,6 +300,8 @@ github.com/tailscale/wireguard-go v0.0.0-20210129202040-ddaf8316eff8 h1:7OWHhbjW github.com/tailscale/wireguard-go v0.0.0-20210129202040-ddaf8316eff8/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8= github.com/tailscale/wireguard-go v0.0.0-20210201213041-c9817e648365 h1:0OC8+fnUCx5ww7uRSlzbcVC6Q/FK0PmVclmimbpWbyk= github.com/tailscale/wireguard-go v0.0.0-20210201213041-c9817e648365/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8= +github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020 h1:DbQtiKont9TyOBIuTHhj1UUpWE75QcsyBiJPxTbqRGQ= +github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= @@ -342,6 +344,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -392,6 +396,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2l golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY= golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -447,6 +453,8 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP3bEwtHcq+0YcBQM2JQ= golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= +golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q= diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 03b391bca..7380b74fa 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -308,16 +308,20 @@ func newUserspaceEngineAdvanced(conf EngineConfig) (_ Engine, reterr error) { // Ping every single-IP that peer routes. // These synthetic packets are used to traverse NATs. var ips []netaddr.IP - allowedIPs := deviceAllowedIPs.EntriesForPeer(peer) - for _, ipNet := range allowedIPs { - if ones, bits := ipNet.Mask.Size(); ones == bits && ones != 0 { - ip, ok := netaddr.FromStdIP(ipNet.IP) - if !ok { - continue - } + var allowedIPs []netaddr.IPPrefix + deviceAllowedIPs.EntriesForPeer(peer, func(stdIP net.IP, cidr uint) bool { + ip, ok := netaddr.FromStdIP(stdIP) + if !ok { + logf("[unexpected] bad IP from deviceAllowedIPs.EntriesForPeer: %v", stdIP) + return true + } + ipp := netaddr.IPPrefix{IP: ip, Bits: uint8(cidr)} + allowedIPs = append(allowedIPs, ipp) + if ipp.IsSingleIP() { ips = append(ips, ip) } - } + return true + }) if len(ips) > 0 { go e.pinger(peerWGKey, ips) } else { diff --git a/wgengine/wglog/wglog.go b/wgengine/wglog/wglog.go index 7786edd82..ed3827b4e 100644 --- a/wgengine/wglog/wglog.go +++ b/wgengine/wglog/wglog.go @@ -59,11 +59,9 @@ func NewLogger(logf logger.Logf) *Logger { // but there's not much we can do about that. logf("%s", new) } - std := logger.StdLogger(wrapper) ret.DeviceLogger = &device.Logger{ - Debug: std, - Info: std, - Error: std, + Verbosef: logger.WithPrefix(wrapper, "[v2] "), + Errorf: wrapper, } return ret } diff --git a/wgengine/wglog/wglog_test.go b/wgengine/wglog/wglog_test.go index 0b93a130a..077981e41 100644 --- a/wgengine/wglog/wglog_test.go +++ b/wgengine/wglog/wglog_test.go @@ -46,12 +46,11 @@ func TestLogger(t *testing.T) { // Then if logf also attempts to write into the channel, it'll fail. c <- "" } - x.DeviceLogger.Info.Println(tt.in) + x.DeviceLogger.Errorf(tt.in) got := <-c if tt.omit { continue } - tt.want += "\n" if got != tt.want { t.Errorf("Println(%q) = %q want %q", tt.in, got, tt.want) } From 7529b7401846813beeb58593f2b201169d0469c8 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 4 Feb 2021 16:23:16 -0800 Subject: [PATCH 09/71] control/controlclient: avoid crash sending map request with zero node key Fixes #1271 Signed-off-by: Brad Fitzpatrick --- control/controlclient/direct.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index e1aa7958b..157840216 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -550,6 +550,9 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw everEndpoints := c.everEndpoints c.mu.Unlock() + if persist.PrivateNodeKey.IsZero() { + return errors.New("privateNodeKey is zero") + } if backendLogID == "" { return errors.New("hostinfo: BackendLogID missing") } From 6099ecf7f47f29d1824e6ac3785a27a7a48391b1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 08:46:12 -0800 Subject: [PATCH 10/71] cmd/tailscaled: run as a service on Windows Updates #1232 --- cmd/tailscaled/tailscaled.go | 10 ++++ cmd/tailscaled/tailscaled_notwindows.go | 13 +++++ cmd/tailscaled/tailscaled_windows.go | 64 +++++++++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 cmd/tailscaled/tailscaled_notwindows.go create mode 100644 cmd/tailscaled/tailscaled_windows.go diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 9e9aaced8..2e4dc05bd 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -132,6 +132,16 @@ func run() error { pol.Shutdown(ctx) }() + if isWindowsService() { + // Run the IPN server from the Windows service manager. + log.Printf("Running service...") + if err := runWindowsService(pol); err != nil { + log.Printf("runservice: %v", err) + } + log.Printf("Service ended.") + return nil + } + var logf logger.Logf = log.Printf if v, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_MEMORY")); v { logf = logger.RusagePrefixLog(logf) diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go new file mode 100644 index 000000000..eb6cd4e9c --- /dev/null +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -0,0 +1,13 @@ +// 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. + +// +build !windows + +package main // import "tailscale.com/cmd/tailscaled" + +import "tailscale.com/logpolicy" + +func isWindowsService() bool { return false } + +func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go new file mode 100644 index 000000000..a7c69eb2c --- /dev/null +++ b/cmd/tailscaled/tailscaled_windows.go @@ -0,0 +1,64 @@ +// 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 main // import "tailscale.com/cmd/tailscaled" + +import ( + "context" + "log" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" + "tailscale.com/ipn/ipnserver" + "tailscale.com/logpolicy" +) + +const serviceName = "Tailscale IPN" + +func isWindowsService() bool { + v, err := svc.IsWindowsService() + if err != nil { + log.Fatalf("svc.IsWindowsService failed: %v", err) + } + return v +} + +func runWindowsService(pol *logpolicy.Policy) error { + return svc.Run(serviceName, &ipnService{Policy: pol}) +} + +type ipnService struct { + Policy *logpolicy.Policy +} + +// Called by Windows to execute the windows service. +func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { + changes <- svc.Status{State: svc.StartPending} + + ctx, cancel := context.WithCancel(context.Background()) + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + args := []string{"/subproc", service.Policy.PublicID.String()} + ipnserver.BabysitProc(ctx, args, log.Printf) + }() + + changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop} + + for ctx.Err() == nil { + select { + case <-doneCh: + case cmd := <-r: + switch cmd.Cmd { + case svc.Stop: + cancel() + case svc.Interrogate: + changes <- cmd.CurrentStatus + } + } + } + + changes <- svc.Status{State: svc.StopPending} + return false, windows.NO_ERROR +} From 6f7974b7f2731aee2245f5bcc51ef42832eb40d7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 08:48:00 -0800 Subject: [PATCH 11/71] cmd/tailscaled: add missing depaware.txt update --- cmd/tailscaled/depaware.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 9f318cc86..982121d28 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -165,6 +165,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ + W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled golang.org/x/term from tailscale.com/logpolicy golang.org/x/text/secure/bidirule from golang.org/x/net/idna golang.org/x/text/transform from golang.org/x/text/secure/bidirule+ From a7562be5e1e2c64e29cbfdb99ff9ee1cffdd52a1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 09:53:54 -0800 Subject: [PATCH 12/71] cmd/tailscaled: move more of the Windows server setup code into tailscaled Updates #1232 --- cmd/tailscaled/depaware.txt | 4 +- cmd/tailscaled/tailscaled.go | 10 +- cmd/tailscaled/tailscaled_notwindows.go | 2 + cmd/tailscaled/tailscaled_windows.go | 116 ++++++++++++++++++++++++ paths/paths.go | 4 + 5 files changed, 127 insertions(+), 9 deletions(-) diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 982121d28..cf4e13c10 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -2,8 +2,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de W 💣 github.com/alexbrainman/sspi from github.com/alexbrainman/sspi/negotiate W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy - github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscaled - W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole L github.com/coreos/go-iptables/iptables from tailscale.com/wgengine/router LW github.com/go-multierror/multierror from tailscale.com/wgengine/router W 💣 github.com/go-ole/go-ole from github.com/go-ole/go-ole/oleutil+ @@ -163,7 +161,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/sync/singleflight from tailscale.com/net/dnscache golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ LD golang.org/x/sys/unix from github.com/jsimonetti/rtnetlink/internal/unix+ - W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ + W golang.org/x/sys/windows from github.com/tailscale/wireguard-go/conn+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg+ W golang.org/x/sys/windows/svc from tailscale.com/cmd/tailscaled golang.org/x/term from tailscale.com/logpolicy diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 2e4dc05bd..64e2723bb 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -24,7 +24,6 @@ import ( "syscall" "time" - "github.com/apenwarr/fixconsole" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" "tailscale.com/paths" @@ -88,11 +87,6 @@ func main() { flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket") flag.BoolVar(&printVersion, "version", false, "print version information and exit") - err := fixconsole.FixConsoleIfNeeded() - if err != nil { - log.Fatalf("fixConsoleOutput: %v", err) - } - if len(os.Args) > 1 && os.Args[1] == "debug" { if err := debugMode(os.Args[2:]); err != nil { log.Fatal(err) @@ -100,6 +94,10 @@ func main() { return } + if beWindowsSubprocess() { + return + } + flag.Parse() if flag.NArg() > 0 { log.Fatalf("tailscaled does not take non-flag arguments: %q", flag.Args()) diff --git a/cmd/tailscaled/tailscaled_notwindows.go b/cmd/tailscaled/tailscaled_notwindows.go index eb6cd4e9c..58221a2ea 100644 --- a/cmd/tailscaled/tailscaled_notwindows.go +++ b/cmd/tailscaled/tailscaled_notwindows.go @@ -11,3 +11,5 @@ import "tailscale.com/logpolicy" func isWindowsService() bool { return false } func runWindowsService(pol *logpolicy.Policy) error { panic("unreachable") } + +func beWindowsSubprocess() bool { return false } diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index a7c69eb2c..bb1554c20 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -4,14 +4,33 @@ package main // import "tailscale.com/cmd/tailscaled" +// TODO: check if administrator, like tswin does. +// +// TODO: try to load wintun.dll early at startup, before wireguard/tun +// does (which panics) and if we'd fail (e.g. due to access +// denied, even if administrator), use 'tasklist /m wintun.dll' +// to see if something else is currently using it and tell user. +// +// TODO: check if Tailscale service is already running, and fail early +// like tswin does. +// +// TODO: on failure, check if on a UNC drive and recommend copying it +// to C:\ to run it, like tswin does. + import ( "context" + "fmt" "log" + "os" + "time" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" + "tailscale.com/types/logger" + "tailscale.com/version" + "tailscale.com/wgengine" ) const serviceName = "Tailscale IPN" @@ -62,3 +81,100 @@ func (service *ipnService) Execute(args []string, r <-chan svc.ChangeRequest, ch changes <- svc.Status{State: svc.StopPending} return false, windows.NO_ERROR } + +func beWindowsSubprocess() bool { + if len(os.Args) != 3 || os.Args[1] != "/subproc" { + return false + } + logid := os.Args[2] + + log.Printf("Program starting: v%v: %#v", version.Long, os.Args) + log.Printf("subproc mode: logid=%v", logid) + + go func() { + b := make([]byte, 16) + for { + _, err := os.Stdin.Read(b) + if err != nil { + log.Fatalf("stdin err (parent process died): %v", err) + } + } + }() + + err := startIPNServer(context.Background(), logid) + if err != nil { + log.Fatalf("ipnserver: %v", err) + } + return true +} + +func startIPNServer(ctx context.Context, logid string) error { + var logf logger.Logf = log.Printf + var eng wgengine.Engine + var err error + + getEngine := func() (wgengine.Engine, error) { + eng, err := wgengine.NewUserspaceEngine(logf, "Tailscale", 41641) + if err != nil { + return nil, err + } + return wgengine.NewWatchdog(eng), nil + } + + if msg := os.Getenv("TS_DEBUG_WIN_FAIL"); msg != "" { + err = fmt.Errorf("pretending to be a service failure: %v", msg) + } else { + // We have a bunch of bug reports of wgengine.NewUserspaceEngine returning a few different errors, + // all intermittently. A few times I (Brad) have also seen sporadic failures that simply + // restarting fixed. So try a few times. + for try := 1; try <= 5; try++ { + if try > 1 { + // Only sleep a bit. Don't do some massive backoff because + // the frontend GUI has a 30 second timeout on connecting to us, + // but even 5 seconds is too long for them to get any results. + // 5 tries * 1 second each seems fine. + time.Sleep(time.Second) + } + eng, err = getEngine() + if err != nil { + logf("wgengine.NewUserspaceEngine: (try %v) %v", try, err) + continue + } + if try > 1 { + logf("wgengine.NewUserspaceEngine: ended up working on try %v", try) + } + break + } + } + if err != nil { + // Log the error, but don't fatalf. We want to + // propagate the error message to the UI frontend. So + // we continue and tell the ipnserver to return that + // in a Notify message. + logf("wgengine.NewUserspaceEngine: %v", err) + } + opts := ipnserver.Options{ + Port: 41112, + SurviveDisconnects: false, + StatePath: args.statepath, + } + if err != nil { + // Return nicer errors to users, annotated with logids, which helps + // when they file bugs. + rawGetEngine := getEngine // raw == without verbose logid-containing error + getEngine = func() (wgengine.Engine, error) { + eng, err := rawGetEngine() + if err != nil { + return nil, fmt.Errorf("wgengine.NewUserspaceEngine: %v\n\nlogid: %v", err, logid) + } + return eng, nil + } + } else { + getEngine = ipnserver.FixedEngine(eng) + } + err = ipnserver.Run(ctx, logf, logid, getEngine, opts) + if err != nil { + logf("ipnserver.Run: %v", err) + } + return err +} diff --git a/paths/paths.go b/paths/paths.go index 9e583713a..0bb028085 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -8,6 +8,7 @@ package paths import ( "os" + "path/filepath" "runtime" ) @@ -42,5 +43,8 @@ func DefaultTailscaledStateFile() string { if f := stateFileFunc; f != nil { return f() } + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("LocalAppData"), "Tailscale", "server-state.conf") + } return "" } From e8d4afedd103f772a02968ef20c6c82ef9796945 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 4 Feb 2021 16:23:16 -0800 Subject: [PATCH 13/71] control/controlclient: don't call lite endpoint update path when logged out This was the other half of the #1271 problem. Signed-off-by: Brad Fitzpatrick --- control/controlclient/auto.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 8bbae5c20..48a02f1b0 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -213,7 +213,7 @@ func (c *Client) sendNewMapRequest() { // If we're not already streaming a netmap, or if we're already stuck // in a lite update, then tear down everything and start a new stream // (which starts by sending a new map request) - if !c.inPollNetMap || c.inLiteMapUpdate { + if !c.inPollNetMap || c.inLiteMapUpdate || !c.loggedIn { c.mu.Unlock() c.cancelMapSafely() return From 98f9e82c62aa999b360c416ff6768bd89a277290 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 10:53:15 -0800 Subject: [PATCH 14/71] logpolicy: on Windows, use tailscale-ipn log name if it already existed For the migration to tailscaled.exe on Windows, don't create a new logid if one existed under the old filename. Updates #1232 --- logpolicy/logpolicy.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index 12032e022..add1874a6 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -339,6 +339,18 @@ func New(collection string) *Policy { tryFixLogStateLocation(dir, cmdName) cfgPath := filepath.Join(dir, fmt.Sprintf("%s.log.conf", cmdName)) + + // The Windows service previously ran as tailscale-ipn.exe, so + // let's keep using that log base name if it exists. + if runtime.GOOS == "windows" && cmdName == "tailscaled" { + const oldCmdName = "tailscale-ipn" + oldPath := filepath.Join(dir, oldCmdName+".log.conf") + if fi, err := os.Stat(oldPath); err == nil && fi.Mode().IsRegular() { + cfgPath = oldPath + cmdName = oldCmdName + } + } + var oldc *Config data, err := ioutil.ReadFile(cfgPath) if err != nil { From fb6b0e247c2efb296fcb150544e3058e71857b65 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 11:13:34 -0800 Subject: [PATCH 15/71] cmd/tailscaled: rename Windows service to just Tailscale Updates #1232 --- cmd/tailscaled/tailscaled_windows.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/tailscaled/tailscaled_windows.go b/cmd/tailscaled/tailscaled_windows.go index bb1554c20..4de9a50e6 100644 --- a/cmd/tailscaled/tailscaled_windows.go +++ b/cmd/tailscaled/tailscaled_windows.go @@ -33,7 +33,7 @@ import ( "tailscale.com/wgengine" ) -const serviceName = "Tailscale IPN" +const serviceName = "Tailscale" func isWindowsService() bool { v, err := svc.IsWindowsService() From b9c2231fdf93d599b453c5e1b6467de8b726963b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Wed, 20 Jan 2021 17:24:16 -0800 Subject: [PATCH 16/71] ipn: program exit node into the data plane according to user pref. Part of #1153, #1154. Fixes #1224. Signed-off-by: David Anderson --- cmd/tailscale/cli/up.go | 15 ++++++ cmd/tailscale/depaware.txt | 2 +- control/controlclient/netmap.go | 17 +++---- ipn/ipnlocal/local.go | 88 +++++++++++++++++++++++++++++++-- ipn/prefs.go | 27 +++++++++- ipn/prefs_clone.go | 3 ++ ipn/prefs_test.go | 24 ++++++++- 7 files changed, 158 insertions(+), 18 deletions(-) diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go index e4690f021..f968a1598 100644 --- a/cmd/tailscale/cli/up.go +++ b/cmd/tailscale/cli/up.go @@ -45,6 +45,7 @@ specify any flags, options are reset to their default. 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") upf.BoolVar(&upArgs.shieldsUp, "shields-up", false, "don't allow incoming connections") upf.BoolVar(&upArgs.forceReauth, "force-reauth", false, "force reauthentication") upf.StringVar(&upArgs.advertiseTags, "advertise-tags", "", "ACL tags to request (comma-separated, e.g. eng,montreal,ssh)") @@ -74,6 +75,7 @@ var upArgs struct { acceptRoutes bool acceptDNS bool singleRoutes bool + exitNodeIP string shieldsUp bool forceReauth bool advertiseRoutes string @@ -138,6 +140,9 @@ func runUp(ctx context.Context, args []string) error { if upArgs.acceptRoutes { return errors.New("--accept-routes is " + notSupported) } + if upArgs.exitNodeIP != "" { + return errors.New("--exit-node is " + notSupported) + } if upArgs.netfilterMode != "off" { return errors.New("--netfilter-mode values besides \"off\" " + notSupported) } @@ -170,6 +175,15 @@ func runUp(ctx context.Context, args []string) error { checkIPForwarding() } + var exitNodeIP netaddr.IP + if upArgs.exitNodeIP != "" { + var err error + exitNodeIP, err = netaddr.ParseIP(upArgs.exitNodeIP) + if err != nil { + fatalf("invalid IP address %q for --exit-node: %v", upArgs.exitNodeIP, err) + } + } + var tags []string if upArgs.advertiseTags != "" { tags = strings.Split(upArgs.advertiseTags, ",") @@ -190,6 +204,7 @@ func runUp(ctx context.Context, args []string) error { prefs.ControlURL = upArgs.server prefs.WantRunning = true prefs.RouteAll = upArgs.acceptRoutes + prefs.ExitNodeIP = exitNodeIP prefs.CorpDNS = upArgs.acceptDNS prefs.AllowSingleHosts = upArgs.singleRoutes prefs.ShieldsUp = upArgs.shieldsUp diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 2a1fa2833..3581fd047 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -45,7 +45,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/net/packet from tailscale.com/wgengine/filter tailscale.com/net/stun from tailscale.com/net/netcheck tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ - tailscale.com/net/tsaddr from tailscale.com/net/interfaces + tailscale.com/net/tsaddr from tailscale.com/net/interfaces+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ tailscale.com/paths from tailscale.com/cmd/tailscale/cli tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli diff --git a/control/controlclient/netmap.go b/control/controlclient/netmap.go index 40041a491..15548cb2c 100644 --- a/control/controlclient/netmap.go +++ b/control/controlclient/netmap.go @@ -14,6 +14,7 @@ import ( "time" "inet.af/netaddr" + "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/types/wgkey" @@ -249,7 +250,6 @@ type WGConfigFlags int const ( AllowSingleHosts WGConfigFlags = 1 << iota AllowSubnetRoutes - AllowDefaultRoute ) // EndpointDiscoSuffix is appended to the hex representation of a peer's discovery key @@ -271,10 +271,6 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi if Debug.OnlyDisco && peer.DiscoKey.IsZero() { continue } - if (flags&AllowSingleHosts) == 0 && len(peer.AllowedIPs) < 2 { - logf("wgcfg: %v skipping a single-host peer.", peer.Key.ShortString()) - continue - } cfg.Peers = append(cfg.Peers, wgcfg.Peer{ PublicKey: wgcfg.Key(peer.Key), }) @@ -298,13 +294,12 @@ func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Confi } } } + for _, allowedIP := range peer.AllowedIPs { - if allowedIP.Bits == 0 { - if (flags & AllowDefaultRoute) == 0 { - logf("[v1] wgcfg: not accepting default route from %q (%v)", - nodeDebugName(peer), peer.Key.ShortString()) - continue - } + if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&AllowSingleHosts) == 0 { + logf("[v1] wgcfg: skipping node IP %v from %q (%v)", + allowedIP.IP, nodeDebugName(peer), peer.Key.ShortString()) + continue } else if cidrIsSubnet(peer, allowedIP) { if (flags & AllowSubnetRoutes) == 0 { logf("[v1] wgcfg: not accepting subnet route %v from %q (%v)", diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index cb42ce347..fd4bac434 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -306,8 +306,10 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { prefsChanged = true } if st.NetMap != nil { + if b.keepOneExitNodeLocked(st.NetMap) { + prefsChanged = true + } b.setNetMapLocked(st.NetMap) - } if st.URL != "" { b.authURL = st.URL @@ -365,6 +367,57 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { b.authReconfig() } +// keepOneExitNodeLocked edits nm to retain only the default +// routes provided by the exit node specified in b.prefs. It returns +// whether prefs was mutated as part of the process, due to an exit +// node IP being converted into a node ID. +func (b *LocalBackend) keepOneExitNodeLocked(nm *controlclient.NetworkMap) (prefsChanged bool) { + if b.prefs.ExitNodeID == "" && b.prefs.ExitNodeIP.IsZero() { + return false + } + + // If we have a desired IP on file, try to find the corresponding + // node. + if !b.prefs.ExitNodeIP.IsZero() { + // IP takes precedence over ID, so if both are set, clear ID. + if b.prefs.ExitNodeID != "" { + b.prefs.ExitNodeID = "" + prefsChanged = true + } + + peerLoop: + for _, peer := range nm.Peers { + for _, addr := range peer.Addresses { + if !addr.IsSingleIP() || addr.IP != b.prefs.ExitNodeIP { + continue + } + // Found the node being referenced, upgrade prefs to + // reference it directly for next time. + b.prefs.ExitNodeID = peer.StableID + b.prefs.ExitNodeIP = netaddr.IP{} + prefsChanged = true + break peerLoop + } + } + } + + // At this point, we have a node ID if the requested node is in + // the netmap. If not, the ID will be empty, and we'll strip out + // all default routes. + for _, peer := range nm.Peers { + out := peer.AllowedIPs[:0] + for _, allowedIP := range peer.AllowedIPs { + if allowedIP.Bits == 0 && peer.StableID != b.prefs.ExitNodeID { + continue + } + out = append(out, allowedIP) + } + peer.AllowedIPs = out + } + + return prefsChanged +} + // setWgengineStatus is the callback by the wireguard engine whenever it posts a new status. // This updates the endpoints both in the backend and in the control client. func (b *LocalBackend) setWgengineStatus(s *wgengine.Status, err error) { @@ -1203,8 +1256,6 @@ func (b *LocalBackend) authReconfig() { var flags controlclient.WGConfigFlags if uc.RouteAll { - flags |= controlclient.AllowDefaultRoute - // TODO(apenwarr): Make subnet routes a different pref? flags |= controlclient.AllowSubnetRoutes } if uc.AllowSingleHosts { @@ -1256,6 +1307,11 @@ func magicDNSRootDomains(nm *controlclient.NetworkMap) []string { return nil } +var ( + ipv4Default = netaddr.MustParseIPPrefix("0.0.0.0/0") + ipv6Default = netaddr.MustParseIPPrefix("::/0") +) + // routerConfig produces a router.Config from a wireguard config and IPN prefs. func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { rs := &router.Config{ @@ -1269,6 +1325,32 @@ func routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router.Config { rs.Routes = append(rs.Routes, unmapIPPrefixes(peer.AllowedIPs)...) } + // Sanity check: we expect the control server to program both a v4 + // and a v6 default route, if default routing is on. Fill in + // blackhole routes appropriately if we're missing some. This is + // likely to break some functionality, but if the user expressed a + // preference for routing remotely, we want to avoid leaking + // traffic at the expense of functionality. + if prefs.ExitNodeID != "" || !prefs.ExitNodeIP.IsZero() { + var default4, default6 bool + for _, route := range rs.Routes { + if route == ipv4Default { + default4 = true + } else if route == ipv6Default { + default6 = true + } + if default4 && default6 { + break + } + } + if !default4 { + rs.Routes = append(rs.Routes, ipv4Default) + } + if !default6 { + rs.Routes = append(rs.Routes, ipv6Default) + } + } + rs.Routes = append(rs.Routes, netaddr.IPPrefix{ IP: tsaddr.TailscaleServiceIP(), Bits: 32, diff --git a/ipn/prefs.go b/ipn/prefs.go index cb6bf2869..c8841f5de 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -18,6 +18,7 @@ import ( "inet.af/netaddr" "tailscale.com/atomicfile" "tailscale.com/control/controlclient" + "tailscale.com/tailcfg" "tailscale.com/types/preftype" ) @@ -28,8 +29,10 @@ type Prefs struct { // ControlURL is the URL of the control server to use. ControlURL string - // RouteAll specifies whether to accept subnet and default routes - // advertised by other nodes on the Tailscale network. + // RouteAll specifies whether to accept subnets advertised by + // other nodes on the Tailscale network. Note that this does not + // include default routes (0.0.0.0/0 and ::/0), those are + // controlled by ExitNodeID/IP below. RouteAll bool // AllowSingleHosts specifies whether to install routes for each @@ -44,6 +47,24 @@ type Prefs struct { // packets stop flowing. What's up with that? AllowSingleHosts bool + // ExitNodeID and ExitNodeIP specify the node that should be used + // as an exit node for internet traffic. At most one of these + // should be non-zero. + // + // The preferred way to express the chosen node is ExitNodeID, but + // in some cases it's not possible to use that ID (e.g. in the + // linux CLI, before tailscaled has a netmap). For those + // situations, we allow specifying the exit node by IP, and + // ipnlocal.LocalBackend will translate the IP into an ID when the + // node is found in the netmap. + // + // If the selected exit node doesn't exist (e.g. it's not part of + // the current tailnet), or it doesn't offer exit node services, a + // blackhole route will be installed on the local system to + // prevent any traffic escaping to the local network. + ExitNodeID tailcfg.StableNodeID + ExitNodeIP netaddr.IP + // CorpDNS specifies whether to install the Tailscale network's // DNS configuration, if it exists. CorpDNS bool @@ -191,6 +212,8 @@ func (p *Prefs) Equals(p2 *Prefs) bool { p.ControlURL == p2.ControlURL && p.RouteAll == p2.RouteAll && p.AllowSingleHosts == p2.AllowSingleHosts && + p.ExitNodeID == p2.ExitNodeID && + p.ExitNodeIP == p2.ExitNodeIP && p.CorpDNS == p2.CorpDNS && p.WantRunning == p2.WantRunning && p.NotepadURLs == p2.NotepadURLs && diff --git a/ipn/prefs_clone.go b/ipn/prefs_clone.go index 950e34145..0691fc4fc 100644 --- a/ipn/prefs_clone.go +++ b/ipn/prefs_clone.go @@ -9,6 +9,7 @@ package ipn import ( "inet.af/netaddr" "tailscale.com/control/controlclient" + "tailscale.com/tailcfg" "tailscale.com/types/preftype" ) @@ -35,6 +36,8 @@ var _PrefsNeedsRegeneration = Prefs(struct { ControlURL string RouteAll bool AllowSingleHosts bool + ExitNodeID tailcfg.StableNodeID + ExitNodeIP netaddr.IP CorpDNS bool WantRunning bool ShieldsUp bool diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index 77278446c..a227b05b3 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -30,7 +30,7 @@ func fieldsOf(t reflect.Type) (fields []string) { func TestPrefsEqual(t *testing.T) { tstest.PanicOnLog() - prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "CorpDNS", "WantRunning", "ShieldsUp", "AdvertiseTags", "Hostname", "OSVersion", "DeviceModel", "NotepadURLs", "ForceDaemon", "AdvertiseRoutes", "NoSNAT", "NetfilterMode", "Persist"} + prefsHandles := []string{"ControlURL", "RouteAll", "AllowSingleHosts", "ExitNodeID", "ExitNodeIP", "CorpDNS", "WantRunning", "ShieldsUp", "AdvertiseTags", "Hostname", "OSVersion", "DeviceModel", "NotepadURLs", "ForceDaemon", "AdvertiseRoutes", "NoSNAT", "NetfilterMode", "Persist"} if have := fieldsOf(reflect.TypeOf(Prefs{})); !reflect.DeepEqual(have, prefsHandles) { t.Errorf("Prefs.Equal check might be out of sync\nfields: %q\nhandled: %q\n", have, prefsHandles) @@ -99,6 +99,28 @@ func TestPrefsEqual(t *testing.T) { true, }, + { + &Prefs{ExitNodeID: "n1234"}, + &Prefs{}, + false, + }, + { + &Prefs{ExitNodeID: "n1234"}, + &Prefs{ExitNodeID: "n1234"}, + true, + }, + + { + &Prefs{ExitNodeIP: netaddr.MustParseIP("1.2.3.4")}, + &Prefs{}, + false, + }, + { + &Prefs{ExitNodeIP: netaddr.MustParseIP("1.2.3.4")}, + &Prefs{ExitNodeIP: netaddr.MustParseIP("1.2.3.4")}, + true, + }, + { &Prefs{CorpDNS: true}, &Prefs{CorpDNS: false}, From ace57d76272387d33025897cafc35ace9430b3a5 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 5 Feb 2021 13:36:05 -0800 Subject: [PATCH 17/71] wgengine/magicsock: set a dummy private key in benchmark. Magicsock started dropping all traffic internally when Tailscale is shut down, to avoid spurious wireguard logspam. This made the benchmark not receive anything. Setting a dummy private key is sufficient to get magicsock to pass traffic for benchmarking purposes. Fixes #1270. Signed-off-by: David Anderson --- wgengine/magicsock/magicsock_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 998b9bb27..094e73359 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1440,6 +1440,7 @@ func BenchmarkReceiveFrom(b *testing.B) { }, }, }) + conn.SetPrivateKey(wgkey.Private{0: 1}) conn.CreateEndpoint([32]byte{1: 1}, "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345") conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String())) From 138055dd7039c31a37486591df700d036acdef2a Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 5 Feb 2021 08:50:30 -0800 Subject: [PATCH 18/71] tstest/natlab: use net.ErrClosed instead of a new error Upstream wireguard-go decided to use errors.Is(err, net.ErrClosed) instead of checking the error string. It also provided an unsafe linknamed version of net.ErrClosed for clients running Go 1.15. Switch to that. This reduces the time required for the wgengine/magicsock tests on my machine from ~35s back to the ~13s it was before 456cf8a3765948d6f1992162993eaf3844371592. Signed-off-by: Josh Bleecher Snyder --- net/stun/stuntest/stuntest.go | 1 + tstest/natlab/natlab.go | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/net/stun/stuntest/stuntest.go b/net/stun/stuntest/stuntest.go index e97accbb8..6015b9066 100644 --- a/net/stun/stuntest/stuntest.go +++ b/net/stun/stuntest/stuntest.go @@ -59,6 +59,7 @@ func runSTUN(t *testing.T, pc net.PacketConn, stats *stunStats, done chan<- stru for { n, addr, err := pc.ReadFrom(buf[:]) if err != nil { + // TODO: when we switch to Go 1.16, replace this with errors.Is(err, net.ErrClosed) if strings.Contains(err.Error(), "closed network connection") { t.Logf("STUN server shutdown") return diff --git a/tstest/natlab/natlab.go b/tstest/natlab/natlab.go index 7d1508afe..df2611be4 100644 --- a/tstest/natlab/natlab.go +++ b/tstest/natlab/natlab.go @@ -26,6 +26,7 @@ import ( "sync" "time" + wgconn "github.com/tailscale/wireguard-go/conn" "inet.af/netaddr" ) @@ -758,7 +759,8 @@ func (c *conn) canRead() error { c.mu.Lock() defer c.mu.Unlock() if c.closed { - return errors.New("closed network connection") // sadface: magic string used by other; don't change + // TODO: when we switch to Go 1.16, replace this with net.ErrClosed + return wgconn.NetErrClosed } if !c.readDeadline.IsZero() && c.readDeadline.Before(time.Now()) { return errors.New("read deadline exceeded") From 6064b6ff479909a3f5a799d72d9c2062ad856aa3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 12:44:43 -0800 Subject: [PATCH 19/71] wgengine/wgcfg/nmcfg: split control/controlclient/netmap.go into own package It couldn't move to ipnlocal due to test dependency cycles. Updates #1278 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 23 +---- cmd/tailscaled/depaware.txt | 3 +- control/controlclient/netmap.go | 117 ------------------------- ipn/ipnlocal/local.go | 3 +- wgengine/magicsock/magicsock.go | 5 +- wgengine/magicsock/magicsock_test.go | 3 +- wgengine/wgcfg/config.go | 5 ++ wgengine/wgcfg/nmcfg/nmcfg.go | 126 +++++++++++++++++++++++++++ 8 files changed, 144 insertions(+), 141 deletions(-) create mode 100644 wgengine/wgcfg/nmcfg/nmcfg.go diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 3581fd047..2113e09ed 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -7,16 +7,6 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep L github.com/mdlayher/sdnotify from tailscale.com/util/systemd 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/wireguard-go/conn from github.com/tailscale/wireguard-go/device - 💣 github.com/tailscale/wireguard-go/device from tailscale.com/wgengine/wgcfg - 💣 github.com/tailscale/wireguard-go/ipc from github.com/tailscale/wireguard-go/device - W 💣 github.com/tailscale/wireguard-go/ipc/winpipe from github.com/tailscale/wireguard-go/ipc - github.com/tailscale/wireguard-go/ratelimiter from github.com/tailscale/wireguard-go/device - github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device - github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+ - github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device - 💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device - W 💣 github.com/tailscale/wireguard-go/tun/wintun from github.com/tailscale/wireguard-go/tun github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli 💣 go4.org/intern from inet.af/netaddr @@ -45,7 +35,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/net/packet from tailscale.com/wgengine/filter tailscale.com/net/stun from tailscale.com/net/netcheck tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ - tailscale.com/net/tsaddr from tailscale.com/net/interfaces+ + tailscale.com/net/tsaddr from tailscale.com/net/interfaces 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ tailscale.com/paths from tailscale.com/cmd/tailscale/cli tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli @@ -67,9 +57,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/version from tailscale.com/cmd/tailscale/cli+ tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ tailscale.com/wgengine/filter from tailscale.com/control/controlclient - tailscale.com/wgengine/wgcfg from tailscale.com/control/controlclient golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box - golang.org/x/crypto/blake2s from github.com/tailscale/wireguard-go/device golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ golang.org/x/crypto/cryptobyte from crypto/ecdsa+ @@ -78,17 +66,14 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/crypto/hkdf from crypto/tls golang.org/x/crypto/nacl/box from tailscale.com/control/controlclient+ golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box - golang.org/x/crypto/poly1305 from github.com/tailscale/wireguard-go/device+ + golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ - golang.org/x/net/bpf from golang.org/x/net/ipv4+ golang.org/x/net/context/ctxhttp from golang.org/x/oauth2/internal golang.org/x/net/dns/dnsmessage from net golang.org/x/net/http/httpguts from net/http golang.org/x/net/http/httpproxy from net/http golang.org/x/net/http2/hpack from net/http golang.org/x/net/idna from golang.org/x/net/http/httpguts+ - golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/device - golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+ golang.org/x/net/proxy from tailscale.com/net/netns D golang.org/x/net/route from net golang.org/x/oauth2 from tailscale.com/control/controlclient+ @@ -96,7 +81,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/sync/errgroup from tailscale.com/derp golang.org/x/sync/singleflight from tailscale.com/net/dnscache golang.org/x/sys/cpu from golang.org/x/crypto/blake2b+ - LD golang.org/x/sys/unix from github.com/tailscale/wireguard-go/conn+ + LD golang.org/x/sys/unix from tailscale.com/net/netns+ W golang.org/x/sys/windows from github.com/apenwarr/fixconsole+ W golang.org/x/sys/windows/registry from golang.zx2c4.com/wireguard/windows/tunnel/winipcfg golang.org/x/text/secure/bidirule from golang.org/x/net/idna @@ -157,7 +142,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep math from compress/flate+ math/big from crypto/dsa+ math/bits from compress/flate+ - math/rand from github.com/tailscale/wireguard-go/device+ + math/rand from math/big+ mime from golang.org/x/oauth2/internal+ mime/multipart from net/http mime/quotedprintable from mime/multipart diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index cf4e13c10..e65e8bfa2 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -129,7 +129,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/wgengine/router/dns from tailscale.com/ipn/ipnlocal+ tailscale.com/wgengine/tsdns from tailscale.com/ipn/ipnlocal+ tailscale.com/wgengine/tstun from tailscale.com/wgengine+ - tailscale.com/wgengine/wgcfg from tailscale.com/control/controlclient+ + tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+ + tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal tailscale.com/wgengine/wglog from tailscale.com/wgengine W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box diff --git a/control/controlclient/netmap.go b/control/controlclient/netmap.go index 15548cb2c..6589bb369 100644 --- a/control/controlclient/netmap.go +++ b/control/controlclient/netmap.go @@ -7,19 +7,14 @@ package controlclient import ( "encoding/json" "fmt" - "net" "reflect" - "strconv" "strings" "time" "inet.af/netaddr" - "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" - "tailscale.com/types/logger" "tailscale.com/types/wgkey" "tailscale.com/wgengine/filter" - "tailscale.com/wgengine/wgcfg" ) type NetworkMap struct { @@ -252,118 +247,6 @@ const ( AllowSubnetRoutes ) -// EndpointDiscoSuffix is appended to the hex representation of a peer's discovery key -// and is then the sole wireguard endpoint for peers with a non-zero discovery key. -// This form is then recognize by magicsock's CreateEndpoint. -const EndpointDiscoSuffix = ".disco.tailscale:12345" - -// WGCfg returns the NetworkMaps's Wireguard configuration. -func (nm *NetworkMap) WGCfg(logf logger.Logf, flags WGConfigFlags) (*wgcfg.Config, error) { - cfg := &wgcfg.Config{ - Name: "tailscale", - PrivateKey: wgcfg.PrivateKey(nm.PrivateKey), - Addresses: nm.Addresses, - ListenPort: nm.LocalPort, - Peers: make([]wgcfg.Peer, 0, len(nm.Peers)), - } - - for _, peer := range nm.Peers { - if Debug.OnlyDisco && peer.DiscoKey.IsZero() { - continue - } - cfg.Peers = append(cfg.Peers, wgcfg.Peer{ - PublicKey: wgcfg.Key(peer.Key), - }) - cpeer := &cfg.Peers[len(cfg.Peers)-1] - if peer.KeepAlive { - cpeer.PersistentKeepalive = 25 // seconds - } - - if !peer.DiscoKey.IsZero() { - if err := appendEndpoint(cpeer, fmt.Sprintf("%x%s", peer.DiscoKey[:], EndpointDiscoSuffix)); err != nil { - return nil, err - } - cpeer.Endpoints = fmt.Sprintf("%x.disco.tailscale:12345", peer.DiscoKey[:]) - } else { - if err := appendEndpoint(cpeer, peer.DERP); err != nil { - return nil, err - } - for _, ep := range peer.Endpoints { - if err := appendEndpoint(cpeer, ep); err != nil { - return nil, err - } - } - } - - for _, allowedIP := range peer.AllowedIPs { - if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&AllowSingleHosts) == 0 { - logf("[v1] wgcfg: skipping node IP %v from %q (%v)", - allowedIP.IP, nodeDebugName(peer), peer.Key.ShortString()) - continue - } else if cidrIsSubnet(peer, allowedIP) { - if (flags & AllowSubnetRoutes) == 0 { - logf("[v1] wgcfg: not accepting subnet route %v from %q (%v)", - allowedIP, nodeDebugName(peer), peer.Key.ShortString()) - continue - } - } - cpeer.AllowedIPs = append(cpeer.AllowedIPs, allowedIP) - } - } - - return cfg, nil -} - -func nodeDebugName(n *tailcfg.Node) string { - name := n.Name - if name == "" { - name = n.Hostinfo.Hostname - } - if i := strings.Index(name, "."); i != -1 { - name = name[:i] - } - if name == "" && len(n.Addresses) != 0 { - return n.Addresses[0].String() - } - return name -} - -// cidrIsSubnet reports whether cidr is a non-default-route subnet -// exported by node that is not one of its own self addresses. -func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool { - if cidr.Bits == 0 { - return false - } - if !cidr.IsSingleIP() { - return true - } - for _, selfCIDR := range node.Addresses { - if cidr == selfCIDR { - return false - } - } - return true -} - -func appendEndpoint(peer *wgcfg.Peer, epStr string) error { - if epStr == "" { - return nil - } - _, port, err := net.SplitHostPort(epStr) - if err != nil { - return fmt.Errorf("malformed endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) - } - _, err = strconv.ParseUint(port, 10, 16) - if err != nil { - return fmt.Errorf("invalid port in endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) - } - if peer.Endpoints != "" { - peer.Endpoints += "," - } - peer.Endpoints += epStr - return nil -} - // eqStringsIgnoreNil reports whether a and b have the same length and // contents, but ignore whether a or b are nil. func eqStringsIgnoreNil(a, b []string) bool { diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index fd4bac434..1bd974ad3 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -38,6 +38,7 @@ import ( "tailscale.com/wgengine/router/dns" "tailscale.com/wgengine/tsdns" "tailscale.com/wgengine/wgcfg" + "tailscale.com/wgengine/wgcfg/nmcfg" ) var controlDebugFlags = getControlDebugFlags() @@ -1268,7 +1269,7 @@ func (b *LocalBackend) authReconfig() { } } - cfg, err := nm.WGCfg(b.logf, flags) + cfg, err := nmcfg.WGCfg(nm, b.logf, flags) if err != nil { b.logf("wgcfg: %v", err) return diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 5e3120453..3147167bb 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -51,6 +51,7 @@ import ( "tailscale.com/types/nettype" "tailscale.com/types/wgkey" "tailscale.com/version" + "tailscale.com/wgengine/wgcfg" ) // Various debugging and experimental tweakables, set by environment @@ -2643,11 +2644,11 @@ func (c *Conn) CreateEndpoint(pubKey [32]byte, addrs string) (conn.Endpoint, err pk := key.Public(pubKey) c.logf("magicsock: CreateEndpoint: key=%s: %s", pk.ShortString(), derpStr(addrs)) - if !strings.HasSuffix(addrs, controlclient.EndpointDiscoSuffix) { + if !strings.HasSuffix(addrs, wgcfg.EndpointDiscoSuffix) { return c.createLegacyEndpointLocked(pk, addrs) } - discoHex := strings.TrimSuffix(addrs, controlclient.EndpointDiscoSuffix) + discoHex := strings.TrimSuffix(addrs, wgcfg.EndpointDiscoSuffix) discoKey, err := key.NewPublicFromHexMem(mem.S(discoHex)) if err != nil { return nil, fmt.Errorf("magicsock: invalid discokey endpoint %q for %v: %w", addrs, pk.ShortString(), err) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 094e73359..90a78b9b0 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -46,6 +46,7 @@ import ( "tailscale.com/wgengine/filter" "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" + "tailscale.com/wgengine/wgcfg/nmcfg" "tailscale.com/wgengine/wglog" ) @@ -293,7 +294,7 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) { peerSet[key.Public(peer.Key)] = struct{}{} } m.conn.UpdatePeers(peerSet) - wg, err := netmap.WGCfg(logf, controlclient.AllowSingleHosts) + wg, err := nmcfg.WGCfg(netmap, logf, controlclient.AllowSingleHosts) if err != nil { // We're too far from the *testing.T to be graceful, // blow up. Shouldn't happen anyway. diff --git a/wgengine/wgcfg/config.go b/wgengine/wgcfg/config.go index af86b36d6..2928e47d2 100644 --- a/wgengine/wgcfg/config.go +++ b/wgengine/wgcfg/config.go @@ -9,6 +9,11 @@ import ( "inet.af/netaddr" ) +// EndpointDiscoSuffix is appended to the hex representation of a peer's discovery key +// and is then the sole wireguard endpoint for peers with a non-zero discovery key. +// This form is then recognize by magicsock's CreateEndpoint. +const EndpointDiscoSuffix = ".disco.tailscale:12345" + // Config is a WireGuard configuration. // It only supports the set of things Tailscale uses. type Config struct { diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go new file mode 100644 index 000000000..ce8408b06 --- /dev/null +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -0,0 +1,126 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package nmcfg converts a controlclient.NetMap into a wgcfg config. +package nmcfg + +import ( + "fmt" + "net" + "strconv" + "strings" + + "inet.af/netaddr" + "tailscale.com/control/controlclient" + "tailscale.com/net/tsaddr" + "tailscale.com/tailcfg" + "tailscale.com/types/logger" + "tailscale.com/wgengine/wgcfg" +) + +func nodeDebugName(n *tailcfg.Node) string { + name := n.Name + if name == "" { + name = n.Hostinfo.Hostname + } + if i := strings.Index(name, "."); i != -1 { + name = name[:i] + } + if name == "" && len(n.Addresses) != 0 { + return n.Addresses[0].String() + } + return name +} + +// cidrIsSubnet reports whether cidr is a non-default-route subnet +// exported by node that is not one of its own self addresses. +func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool { + if cidr.Bits == 0 { + return false + } + if !cidr.IsSingleIP() { + return true + } + for _, selfCIDR := range node.Addresses { + if cidr == selfCIDR { + return false + } + } + return true +} + +// WGCfg returns the NetworkMaps's Wireguard configuration. +func WGCfg(nm *controlclient.NetworkMap, logf logger.Logf, flags controlclient.WGConfigFlags) (*wgcfg.Config, error) { + cfg := &wgcfg.Config{ + Name: "tailscale", + PrivateKey: wgcfg.PrivateKey(nm.PrivateKey), + Addresses: nm.Addresses, + ListenPort: nm.LocalPort, + Peers: make([]wgcfg.Peer, 0, len(nm.Peers)), + } + + for _, peer := range nm.Peers { + if controlclient.Debug.OnlyDisco && peer.DiscoKey.IsZero() { + continue + } + cfg.Peers = append(cfg.Peers, wgcfg.Peer{ + PublicKey: wgcfg.Key(peer.Key), + }) + cpeer := &cfg.Peers[len(cfg.Peers)-1] + if peer.KeepAlive { + cpeer.PersistentKeepalive = 25 // seconds + } + + if !peer.DiscoKey.IsZero() { + if err := appendEndpoint(cpeer, fmt.Sprintf("%x%s", peer.DiscoKey[:], wgcfg.EndpointDiscoSuffix)); err != nil { + return nil, err + } + cpeer.Endpoints = fmt.Sprintf("%x.disco.tailscale:12345", peer.DiscoKey[:]) + } else { + if err := appendEndpoint(cpeer, peer.DERP); err != nil { + return nil, err + } + for _, ep := range peer.Endpoints { + if err := appendEndpoint(cpeer, ep); err != nil { + return nil, err + } + } + } + for _, allowedIP := range peer.AllowedIPs { + if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&controlclient.AllowSingleHosts) == 0 { + logf("[v1] wgcfg: skipping node IP %v from %q (%v)", + allowedIP.IP, nodeDebugName(peer), peer.Key.ShortString()) + continue + } else if cidrIsSubnet(peer, allowedIP) { + if (flags & controlclient.AllowSubnetRoutes) == 0 { + logf("[v1] wgcfg: not accepting subnet route %v from %q (%v)", + allowedIP, nodeDebugName(peer), peer.Key.ShortString()) + continue + } + } + cpeer.AllowedIPs = append(cpeer.AllowedIPs, allowedIP) + } + } + + return cfg, nil +} + +func appendEndpoint(peer *wgcfg.Peer, epStr string) error { + if epStr == "" { + return nil + } + _, port, err := net.SplitHostPort(epStr) + if err != nil { + return fmt.Errorf("malformed endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) + } + _, err = strconv.ParseUint(port, 10, 16) + if err != nil { + return fmt.Errorf("invalid port in endpoint %q for peer %v", epStr, peer.PublicKey.ShortString()) + } + if peer.Endpoints != "" { + peer.Endpoints += "," + } + peer.Endpoints += epStr + return nil +} From a046b48593252dd0956eb1b0b17430df89e9af5b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 5 Feb 2021 13:07:48 -0800 Subject: [PATCH 20/71] cmd/tailscale/cli: display currently active exit node in `tailscale status`. Signed-off-by: David Anderson --- cmd/tailscale/cli/status.go | 7 ++++++- ipn/ipnlocal/local.go | 1 + ipn/ipnstate/ipnstate.go | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index f4f8c78ad..dde138fa2 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -159,13 +159,18 @@ func runStatus(ctx context.Context, args []string) error { relay := ps.Relay anyTraffic := ps.TxBytes != 0 || ps.RxBytes != 0 if !active { - if anyTraffic { + if ps.ExitNode { + f("idle; exit node") + } else if anyTraffic { f("idle") } else { f("-") } } else { f("active; ") + if ps.ExitNode { + f("exit node; ") + } if relay != "" && ps.CurAddr == "" { f("relay %q", relay) } else if ps.CurAddr != "" { diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 1bd974ad3..ffd37ce2a 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -234,6 +234,7 @@ func (b *LocalBackend) UpdateStatus(sb *ipnstate.StatusBuilder) { Created: p.Created, LastSeen: lastSeen, ShareeNode: p.Hostinfo.ShareeNode, + ExitNode: p.StableID != "" && p.StableID == b.prefs.ExitNodeID, }) } } diff --git a/ipn/ipnstate/ipnstate.go b/ipn/ipnstate/ipnstate.go index 3f2d359f7..6d713bc78 100644 --- a/ipn/ipnstate/ipnstate.go +++ b/ipn/ipnstate/ipnstate.go @@ -77,6 +77,7 @@ type PeerStatus struct { LastSeen time.Time // last seen to tailcontrol LastHandshake time.Time // with local wireguard KeepAlive bool + ExitNode bool // true if this is the currently selected exit node. // ShareeNode indicates this node exists in the netmap because // it's owned by a shared-to user and that node might connect @@ -244,6 +245,9 @@ func (sb *StatusBuilder) AddPeer(peer key.Public, st *PeerStatus) { if st.KeepAlive { e.KeepAlive = true } + if st.ExitNode { + e.ExitNode = true + } if st.ShareeNode { e.ShareeNode = true } From ddfcc4326c37672bac9181afb188eead0d4b956e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 15:23:01 -0800 Subject: [PATCH 21/71] types/persist: split controlclient.Persist into a small leaf package This one alone doesn't modify the global dependency map much (depaware.txt if anything looks slightly worse), but it leave controlclient as only containing NetworkMap: bradfitz@tsdev:~/src/tailscale.com/ipn$ grep -F "controlclient." *.go backend.go: NetMap *controlclient.NetworkMap // new netmap received fake_test.go: b.notify(Notify{NetMap: &controlclient.NetworkMap{}}) fake_test.go: b.notify(Notify{NetMap: &controlclient.NetworkMap{}}) handle.go: netmapCache *controlclient.NetworkMap handle.go:func (h *Handle) NetMap() *controlclient.NetworkMap { Once that goes into a leaf package, then ipn doesn't depend on controlclient at all, and then the client gets smaller. Updates #1278 --- cmd/tailscale/depaware.txt | 3 +- cmd/tailscaled/depaware.txt | 1 + control/controlclient/auto.go | 5 +- control/controlclient/direct.go | 68 ++--------------- control/controlclient/direct_clone.go | 20 ----- ipn/ipnlocal/local.go | 9 ++- ipn/ipnlocal/loglines_test.go | 4 +- ipn/prefs.go | 6 +- ipn/prefs_clone.go | 6 +- ipn/prefs_test.go | 16 ++-- types/persist/persist.go | 73 +++++++++++++++++++ types/persist/persist_clone.go | 34 +++++++++ .../persist}/persist_test.go | 11 ++- 13 files changed, 149 insertions(+), 107 deletions(-) delete mode 100644 control/controlclient/direct_clone.go create mode 100644 types/persist/persist.go create mode 100644 types/persist/persist_clone.go rename {control/controlclient => types/persist}/persist_test.go (91%) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 2113e09ed..ba092e496 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -46,10 +46,11 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/types/key from tailscale.com/derp+ tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/opt from tailscale.com/control/controlclient+ + tailscale.com/types/persist from tailscale.com/control/controlclient+ tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ - tailscale.com/types/wgkey from tailscale.com/control/controlclient + tailscale.com/types/wgkey from tailscale.com/control/controlclient+ tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+ W tailscale.com/util/endian from tailscale.com/net/netns tailscale.com/util/lineread from tailscale.com/control/controlclient+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index e65e8bfa2..137a1d686 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -108,6 +108,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/logger from tailscale.com/cmd/tailscaled+ tailscale.com/types/nettype from tailscale.com/wgengine/magicsock tailscale.com/types/opt from tailscale.com/control/controlclient+ + tailscale.com/types/persist from tailscale.com/control/controlclient+ tailscale.com/types/preftype from tailscale.com/ipn+ tailscale.com/types/strbuilder from tailscale.com/net/packet tailscale.com/types/structs from tailscale.com/control/controlclient+ diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index 48a02f1b0..a3a7b02c5 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -22,6 +22,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/empty" "tailscale.com/types/logger" + "tailscale.com/types/persist" "tailscale.com/types/structs" "tailscale.com/types/wgkey" ) @@ -68,7 +69,7 @@ type Status struct { LoginFinished *empty.Message Err string URL string - Persist *Persist // locally persisted configuration + Persist *persist.Persist // locally persisted configuration NetMap *NetworkMap // server-pushed configuration Hostinfo *tailcfg.Hostinfo // current Hostinfo data State State @@ -618,7 +619,7 @@ func (c *Client) sendStatus(who string, err error, url string, nm *NetworkMap) { c.logf("[v1] sendStatus: %s: %v", who, state) - var p *Persist + var p *persist.Persist var fin *empty.Message if state == StateAuthenticated { fin = new(empty.Message) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 157840216..5c603a892 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -4,8 +4,6 @@ package controlclient -//go:generate go run tailscale.com/cmd/cloner -type=Persist -output=direct_clone.go - import ( "bytes" "context" @@ -42,69 +40,13 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/types/opt" - "tailscale.com/types/structs" + "tailscale.com/types/persist" "tailscale.com/types/wgkey" "tailscale.com/util/systemd" "tailscale.com/version" "tailscale.com/wgengine/filter" ) -type Persist struct { - _ structs.Incomparable - - // LegacyFrontendPrivateMachineKey is here temporarily - // (starting 2020-09-28) during migration of Windows users' - // machine keys from frontend storage to the backend. On the - // first LocalBackend.Start call, the backend will initialize - // the real (backend-owned) machine key from the frontend's - // provided value (if non-zero), picking a new random one if - // needed. This field should be considered read-only from GUI - // frontends. The real value should not be written back in - // this field, lest the frontend persist it to disk. - LegacyFrontendPrivateMachineKey wgkey.Private `json:"PrivateMachineKey"` - - PrivateNodeKey wgkey.Private - OldPrivateNodeKey wgkey.Private // needed to request key rotation - Provider string - LoginName string -} - -func (p *Persist) Equals(p2 *Persist) bool { - if p == nil && p2 == nil { - return true - } - if p == nil || p2 == nil { - return false - } - - return p.LegacyFrontendPrivateMachineKey.Equal(p2.LegacyFrontendPrivateMachineKey) && - p.PrivateNodeKey.Equal(p2.PrivateNodeKey) && - p.OldPrivateNodeKey.Equal(p2.OldPrivateNodeKey) && - p.Provider == p2.Provider && - p.LoginName == p2.LoginName -} - -func (p *Persist) Pretty() string { - var mk, ok, nk wgkey.Key - if !p.LegacyFrontendPrivateMachineKey.IsZero() { - mk = p.LegacyFrontendPrivateMachineKey.Public() - } - if !p.OldPrivateNodeKey.IsZero() { - ok = p.OldPrivateNodeKey.Public() - } - if !p.PrivateNodeKey.IsZero() { - nk = p.PrivateNodeKey.Public() - } - ss := func(k wgkey.Key) string { - if k.IsZero() { - return "" - } - return k.ShortString() - } - return fmt.Sprintf("Persist{lm=%v, o=%v, n=%v u=%#v}", - ss(mk), ss(ok), ss(nk), p.LoginName) -} - // Direct is the client that connects to a tailcontrol server for a node. type Direct struct { httpc *http.Client // HTTP client used to talk to tailcontrol @@ -121,7 +63,7 @@ type Direct struct { mu sync.Mutex // mutex guards the following fields serverKey wgkey.Key - persist Persist + persist persist.Persist authKey string tryingNewKey wgkey.Private expiry *time.Time @@ -133,7 +75,7 @@ type Direct struct { } type Options struct { - Persist Persist // initial persistent data + Persist persist.Persist // initial persistent data MachinePrivateKey wgkey.Private // the machine key to use ServerURL string // URL of the tailcontrol server AuthKey string // optional node auth key for auto registration @@ -271,7 +213,7 @@ func (c *Direct) SetNetInfo(ni *tailcfg.NetInfo) bool { return true } -func (c *Direct) GetPersist() Persist { +func (c *Direct) GetPersist() persist.Persist { c.mu.Lock() defer c.mu.Unlock() return c.persist @@ -294,7 +236,7 @@ func (c *Direct) TryLogout(ctx context.Context) error { // immediately invalidated. //if !c.persist.PrivateNodeKey.IsZero() { //} - c.persist = Persist{} + c.persist = persist.Persist{} return nil } diff --git a/control/controlclient/direct_clone.go b/control/controlclient/direct_clone.go deleted file mode 100644 index 9254d82ea..000000000 --- a/control/controlclient/direct_clone.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Code generated by tailscale.com/cmd/cloner -type Persist; DO NOT EDIT. - -package controlclient - -import () - -// Clone makes a deep copy of Persist. -// The result aliases no memory with the original. -func (src *Persist) Clone() *Persist { - if src == nil { - return nil - } - dst := new(Persist) - *dst = *src - return dst -} diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index ffd37ce2a..523292def 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -29,6 +29,7 @@ import ( "tailscale.com/types/empty" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/persist" "tailscale.com/types/wgkey" "tailscale.com/util/systemd" "tailscale.com/version" @@ -512,7 +513,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { b.notify = opts.Notify b.setNetMapLocked(nil) - persist := b.prefs.Persist + persistv := b.prefs.Persist machinePrivKey := b.machinePrivKey b.mu.Unlock() @@ -545,14 +546,14 @@ func (b *LocalBackend) Start(opts ipn.Options) error { } var err error - if persist == nil { + if persistv == nil { // let controlclient initialize it - persist = &controlclient.Persist{} + persistv = &persist.Persist{} } cli, err := controlclient.New(controlclient.Options{ MachinePrivateKey: machinePrivKey, Logf: logger.WithPrefix(b.logf, "control: "), - Persist: *persist, + Persist: *persistv, ServerURL: b.serverURL, AuthKey: opts.AuthKey, Hostinfo: hostinfo, diff --git a/ipn/ipnlocal/loglines_test.go b/ipn/ipnlocal/loglines_test.go index 656758afe..83ae8a309 100644 --- a/ipn/ipnlocal/loglines_test.go +++ b/ipn/ipnlocal/loglines_test.go @@ -9,13 +9,13 @@ import ( "testing" "time" - "tailscale.com/control/controlclient" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" "tailscale.com/logtail" "tailscale.com/tailcfg" "tailscale.com/tstest" "tailscale.com/types/key" + "tailscale.com/types/persist" "tailscale.com/wgengine" ) @@ -67,7 +67,7 @@ func TestLocalLogLines(t *testing.T) { } // log prefs line - persist := &controlclient.Persist{} + persist := &persist.Persist{} prefs := ipn.NewPrefs() prefs.Persist = persist lb.SetPrefs(prefs) diff --git a/ipn/prefs.go b/ipn/prefs.go index c8841f5de..7b4a5562c 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -17,8 +17,8 @@ import ( "inet.af/netaddr" "tailscale.com/atomicfile" - "tailscale.com/control/controlclient" "tailscale.com/tailcfg" + "tailscale.com/types/persist" "tailscale.com/types/preftype" ) @@ -144,7 +144,7 @@ type Prefs struct { // TODO(apenwarr): We should move this out of here, it's not a pref. // We can maybe do that once we're sure which module should persist // it (backend or frontend?) - Persist *controlclient.Persist `json:"Config"` + Persist *persist.Persist `json:"Config"` } // IsEmpty reports whether p is nil or pointing to a Prefs zero value. @@ -275,7 +275,7 @@ func PrefsFromBytes(b []byte, enforceDefaults bool) (*Prefs, error) { if len(b) == 0 { return p, nil } - persist := &controlclient.Persist{} + persist := &persist.Persist{} err := json.Unmarshal(b, persist) if err == nil && (persist.Provider != "" || persist.LoginName != "") { // old-style relaynode config; import it diff --git a/ipn/prefs_clone.go b/ipn/prefs_clone.go index 0691fc4fc..9e426a4fa 100644 --- a/ipn/prefs_clone.go +++ b/ipn/prefs_clone.go @@ -8,8 +8,8 @@ package ipn import ( "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/tailcfg" + "tailscale.com/types/persist" "tailscale.com/types/preftype" ) @@ -24,7 +24,7 @@ func (src *Prefs) Clone() *Prefs { dst.AdvertiseTags = append(src.AdvertiseTags[:0:0], src.AdvertiseTags...) dst.AdvertiseRoutes = append(src.AdvertiseRoutes[:0:0], src.AdvertiseRoutes...) if dst.Persist != nil { - dst.Persist = new(controlclient.Persist) + dst.Persist = new(persist.Persist) *dst.Persist = *src.Persist } return dst @@ -50,5 +50,5 @@ var _PrefsNeedsRegeneration = Prefs(struct { AdvertiseRoutes []netaddr.IPPrefix NoSNAT bool NetfilterMode preftype.NetfilterMode - Persist *controlclient.Persist + Persist *persist.Persist }{}) diff --git a/ipn/prefs_test.go b/ipn/prefs_test.go index a227b05b3..ad8905b03 100644 --- a/ipn/prefs_test.go +++ b/ipn/prefs_test.go @@ -14,8 +14,8 @@ import ( "time" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/tstest" + "tailscale.com/types/persist" "tailscale.com/types/preftype" "tailscale.com/types/wgkey" ) @@ -225,13 +225,13 @@ func TestPrefsEqual(t *testing.T) { }, { - &Prefs{Persist: &controlclient.Persist{}}, - &Prefs{Persist: &controlclient.Persist{LoginName: "dave"}}, + &Prefs{Persist: &persist.Persist{}}, + &Prefs{Persist: &persist.Persist{LoginName: "dave"}}, false, }, { - &Prefs{Persist: &controlclient.Persist{LoginName: "dave"}}, - &Prefs{Persist: &controlclient.Persist{LoginName: "dave"}}, + &Prefs{Persist: &persist.Persist{LoginName: "dave"}}, + &Prefs{Persist: &persist.Persist{LoginName: "dave"}}, true, }, } @@ -296,7 +296,7 @@ func TestBasicPrefs(t *testing.T) { func TestPrefsPersist(t *testing.T) { tstest.PanicOnLog() - c := controlclient.Persist{ + c := persist.Persist{ LoginName: "test@example.com", } p := Prefs{ @@ -362,14 +362,14 @@ func TestPrefsPretty(t *testing.T) { }, { Prefs{ - Persist: &controlclient.Persist{}, + Persist: &persist.Persist{}, }, "linux", `Prefs{ra=false mesh=false dns=false want=false routes=[] nf=off Persist{lm=, o=, n= u=""}}`, }, { Prefs{ - Persist: &controlclient.Persist{ + Persist: &persist.Persist{ PrivateNodeKey: wgkey.Private{1: 1}, }, }, diff --git a/types/persist/persist.go b/types/persist/persist.go new file mode 100644 index 000000000..169288280 --- /dev/null +++ b/types/persist/persist.go @@ -0,0 +1,73 @@ +// 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 persist contains the Persist type. +package persist + +import ( + "fmt" + + "tailscale.com/types/structs" + "tailscale.com/types/wgkey" +) + +//go:generate go run tailscale.com/cmd/cloner -type=Persist -output=persist_clone.go + +// Persist is the JSON type stored on disk on nodes to remember their +// settings between runs. +type Persist struct { + _ structs.Incomparable + + // LegacyFrontendPrivateMachineKey is here temporarily + // (starting 2020-09-28) during migration of Windows users' + // machine keys from frontend storage to the backend. On the + // first LocalBackend.Start call, the backend will initialize + // the real (backend-owned) machine key from the frontend's + // provided value (if non-zero), picking a new random one if + // needed. This field should be considered read-only from GUI + // frontends. The real value should not be written back in + // this field, lest the frontend persist it to disk. + LegacyFrontendPrivateMachineKey wgkey.Private `json:"PrivateMachineKey"` + + PrivateNodeKey wgkey.Private + OldPrivateNodeKey wgkey.Private // needed to request key rotation + Provider string + LoginName string +} + +func (p *Persist) Equals(p2 *Persist) bool { + if p == nil && p2 == nil { + return true + } + if p == nil || p2 == nil { + return false + } + + return p.LegacyFrontendPrivateMachineKey.Equal(p2.LegacyFrontendPrivateMachineKey) && + p.PrivateNodeKey.Equal(p2.PrivateNodeKey) && + p.OldPrivateNodeKey.Equal(p2.OldPrivateNodeKey) && + p.Provider == p2.Provider && + p.LoginName == p2.LoginName +} + +func (p *Persist) Pretty() string { + var mk, ok, nk wgkey.Key + if !p.LegacyFrontendPrivateMachineKey.IsZero() { + mk = p.LegacyFrontendPrivateMachineKey.Public() + } + if !p.OldPrivateNodeKey.IsZero() { + ok = p.OldPrivateNodeKey.Public() + } + if !p.PrivateNodeKey.IsZero() { + nk = p.PrivateNodeKey.Public() + } + ss := func(k wgkey.Key) string { + if k.IsZero() { + return "" + } + return k.ShortString() + } + return fmt.Sprintf("Persist{lm=%v, o=%v, n=%v u=%#v}", + ss(mk), ss(ok), ss(nk), p.LoginName) +} diff --git a/types/persist/persist_clone.go b/types/persist/persist_clone.go new file mode 100644 index 000000000..533e9294d --- /dev/null +++ b/types/persist/persist_clone.go @@ -0,0 +1,34 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Code generated by tailscale.com/cmd/cloner -type Persist; DO NOT EDIT. + +package persist + +import ( + "tailscale.com/types/structs" + "tailscale.com/types/wgkey" +) + +// Clone makes a deep copy of Persist. +// The result aliases no memory with the original. +func (src *Persist) Clone() *Persist { + if src == nil { + return nil + } + dst := new(Persist) + *dst = *src + return dst +} + +// A compilation failure here means this code must be regenerated, with command: +// tailscale.com/cmd/cloner -type Persist +var _PersistNeedsRegeneration = Persist(struct { + _ structs.Incomparable + LegacyFrontendPrivateMachineKey wgkey.Private + PrivateNodeKey wgkey.Private + OldPrivateNodeKey wgkey.Private + Provider string + LoginName string +}{}) diff --git a/control/controlclient/persist_test.go b/types/persist/persist_test.go similarity index 91% rename from control/controlclient/persist_test.go rename to types/persist/persist_test.go index efee06273..04fdb8bc3 100644 --- a/control/controlclient/persist_test.go +++ b/types/persist/persist_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package controlclient +package persist import ( "reflect" @@ -11,6 +11,15 @@ import ( "tailscale.com/types/wgkey" ) +func fieldsOf(t reflect.Type) (fields []string) { + for i := 0; i < t.NumField(); i++ { + if name := t.Field(i).Name; name != "_" { + fields = append(fields, name) + } + } + return +} + func TestPersistEqual(t *testing.T) { persistHandles := []string{"LegacyFrontendPrivateMachineKey", "PrivateNodeKey", "OldPrivateNodeKey", "Provider", "LoginName"} if have := fieldsOf(reflect.TypeOf(Persist{})); !reflect.DeepEqual(have, persistHandles) { From 1e7a35b2250242d4a50a4d55f4568145d9426602 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 5 Feb 2021 15:44:46 -0800 Subject: [PATCH 22/71] types/netmap: split controlclient.NetworkMap off into its own leaf package Updates #1278 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 41 +++++++--------- cmd/tailscaled/depaware.txt | 3 +- control/controlclient/auto.go | 11 +++-- control/controlclient/direct.go | 7 +-- control/controlclient/direct_test.go | 13 +++++ ipn/backend.go | 22 ++++----- ipn/fake_test.go | 6 +-- ipn/handle.go | 6 +-- ipn/ipnlocal/local.go | 27 ++++++----- ipn/ipnlocal/local_test.go | 48 +++++++++---------- .../controlclient => types/netmap}/netmap.go | 3 +- .../netmap}/netmap_test.go | 15 +----- wgengine/magicsock/magicsock.go | 7 +-- wgengine/magicsock/magicsock_test.go | 18 +++---- wgengine/netstack/netstack.go | 4 +- wgengine/userspace.go | 3 +- wgengine/watchdog.go | 4 +- wgengine/wgcfg/nmcfg/nmcfg.go | 7 +-- wgengine/wgengine.go | 6 +-- 19 files changed, 127 insertions(+), 124 deletions(-) rename {control/controlclient => types/netmap}/netmap.go (99%) rename {control/controlclient => types/netmap}/netmap_test.go (96%) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index ba092e496..07ea30a8a 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -4,60 +4,56 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy github.com/apenwarr/fixconsole from tailscale.com/cmd/tailscale W 💣 github.com/apenwarr/w32 from github.com/apenwarr/fixconsole - L github.com/mdlayher/sdnotify from tailscale.com/util/systemd github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli github.com/tcnksm/go-httpstat from tailscale.com/net/netcheck github.com/toqueteos/webbrowser from tailscale.com/cmd/tailscale/cli 💣 go4.org/intern from inet.af/netaddr - 💣 go4.org/mem from tailscale.com/control/controlclient+ + 💣 go4.org/mem from tailscale.com/derp+ go4.org/unsafe/assume-no-moving-gc from go4.org/intern W 💣 golang.zx2c4.com/wireguard/windows/tunnel/winipcfg from tailscale.com/net/interfaces+ inet.af/netaddr from tailscale.com/cmd/tailscale/cli+ rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn tailscale.com/cmd/tailscale/cli from tailscale.com/cmd/tailscale - tailscale.com/control/controlclient from tailscale.com/ipn tailscale.com/derp from tailscale.com/derp/derphttp tailscale.com/derp/derphttp from tailscale.com/net/netcheck tailscale.com/derp/derpmap from tailscale.com/cmd/tailscale/cli tailscale.com/disco from tailscale.com/derp tailscale.com/ipn from tailscale.com/cmd/tailscale/cli tailscale.com/ipn/ipnstate from tailscale.com/cmd/tailscale/cli+ - tailscale.com/log/logheap from tailscale.com/control/controlclient - tailscale.com/logtail/backoff from tailscale.com/control/controlclient tailscale.com/metrics from tailscale.com/derp - tailscale.com/net/dnscache from tailscale.com/control/controlclient+ + tailscale.com/net/dnscache from tailscale.com/derp/derphttp tailscale.com/net/flowtrack from tailscale.com/wgengine/filter+ 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscale/cli+ tailscale.com/net/netcheck from tailscale.com/cmd/tailscale/cli - tailscale.com/net/netns from tailscale.com/control/controlclient+ + tailscale.com/net/netns from tailscale.com/derp/derphttp+ tailscale.com/net/packet from tailscale.com/wgengine/filter tailscale.com/net/stun from tailscale.com/net/netcheck - tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ + tailscale.com/net/tlsdial from tailscale.com/derp/derphttp tailscale.com/net/tsaddr from tailscale.com/net/interfaces - 💣 tailscale.com/net/tshttpproxy from tailscale.com/control/controlclient+ + 💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+ tailscale.com/paths from tailscale.com/cmd/tailscale/cli tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli 💣 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/types/empty from tailscale.com/control/controlclient+ + tailscale.com/types/empty from tailscale.com/ipn tailscale.com/types/key from tailscale.com/derp+ tailscale.com/types/logger from tailscale.com/cmd/tailscale/cli+ - tailscale.com/types/opt from tailscale.com/control/controlclient+ - tailscale.com/types/persist from tailscale.com/control/controlclient+ + tailscale.com/types/netmap from tailscale.com/ipn + tailscale.com/types/opt from tailscale.com/net/netcheck+ + tailscale.com/types/persist from tailscale.com/ipn tailscale.com/types/preftype from tailscale.com/cmd/tailscale/cli+ tailscale.com/types/strbuilder from tailscale.com/net/packet - tailscale.com/types/structs from tailscale.com/control/controlclient+ - tailscale.com/types/wgkey from tailscale.com/control/controlclient+ + tailscale.com/types/structs from tailscale.com/ipn+ + tailscale.com/types/wgkey from tailscale.com/types/netmap+ tailscale.com/util/dnsname from tailscale.com/cmd/tailscale/cli+ W tailscale.com/util/endian from tailscale.com/net/netns - tailscale.com/util/lineread from tailscale.com/control/controlclient+ - tailscale.com/util/systemd from tailscale.com/control/controlclient + tailscale.com/util/lineread from tailscale.com/net/interfaces tailscale.com/version from tailscale.com/cmd/tailscale/cli+ - tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+ - tailscale.com/wgengine/filter from tailscale.com/control/controlclient + tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli + tailscale.com/wgengine/filter from tailscale.com/types/netmap golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305 golang.org/x/crypto/chacha20poly1305 from crypto/tls+ @@ -65,7 +61,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/crypto/cryptobyte/asn1 from crypto/ecdsa+ golang.org/x/crypto/curve25519 from crypto/tls+ golang.org/x/crypto/hkdf from crypto/tls - golang.org/x/crypto/nacl/box from tailscale.com/control/controlclient+ + golang.org/x/crypto/nacl/box from tailscale.com/derp golang.org/x/crypto/nacl/secretbox from golang.org/x/crypto/nacl/box golang.org/x/crypto/poly1305 from golang.org/x/crypto/chacha20poly1305+ golang.org/x/crypto/salsa20/salsa from golang.org/x/crypto/nacl/box+ @@ -77,7 +73,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/net/idna from golang.org/x/net/http/httpguts+ golang.org/x/net/proxy from tailscale.com/net/netns D golang.org/x/net/route from net - golang.org/x/oauth2 from tailscale.com/control/controlclient+ + golang.org/x/oauth2 from tailscale.com/ipn+ golang.org/x/oauth2/internal from golang.org/x/oauth2 golang.org/x/sync/errgroup from tailscale.com/derp golang.org/x/sync/singleflight from tailscale.com/net/dnscache @@ -93,7 +89,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep bufio from compress/flate+ bytes from bufio+ compress/flate from compress/gzip+ - compress/gzip from net/http+ + compress/gzip from net/http compress/zlib from debug/elf+ container/list from crypto/tls+ context from crypto/tls+ @@ -162,14 +158,13 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep regexp from rsc.io/goversion/version regexp/syntax from regexp runtime/debug from golang.org/x/sync/singleflight - runtime/pprof from tailscale.com/log/logheap sort from compress/flate+ strconv from compress/flate+ strings from bufio+ sync from compress/flate+ sync/atomic from context+ syscall from crypto/rand+ - text/tabwriter from github.com/peterbourgon/ff/v2/ffcli+ + text/tabwriter from github.com/peterbourgon/ff/v2/ffcli time from compress/gzip+ unicode from bytes+ unicode/utf16 from encoding/asn1+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 137a1d686..e2fa68aab 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -65,7 +65,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de inet.af/netaddr from tailscale.com/control/controlclient+ rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ - tailscale.com/control/controlclient from tailscale.com/ipn+ + tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+ tailscale.com/derp from tailscale.com/derp/derphttp+ tailscale.com/derp/derphttp from tailscale.com/net/netcheck+ tailscale.com/derp/derpmap from tailscale.com/cmd/tailscaled @@ -106,6 +106,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/types/flagtype from tailscale.com/cmd/tailscaled tailscale.com/types/key from tailscale.com/derp+ tailscale.com/types/logger from tailscale.com/cmd/tailscaled+ + 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/persist from tailscale.com/control/controlclient+ diff --git a/control/controlclient/auto.go b/control/controlclient/auto.go index a3a7b02c5..128ead9a7 100644 --- a/control/controlclient/auto.go +++ b/control/controlclient/auto.go @@ -22,6 +22,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/empty" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/persist" "tailscale.com/types/structs" "tailscale.com/types/wgkey" @@ -69,9 +70,9 @@ type Status struct { LoginFinished *empty.Message Err string URL string - Persist *persist.Persist // locally persisted configuration - NetMap *NetworkMap // server-pushed configuration - Hostinfo *tailcfg.Hostinfo // current Hostinfo data + Persist *persist.Persist // locally persisted configuration + NetMap *netmap.NetworkMap // server-pushed configuration + Hostinfo *tailcfg.Hostinfo // current Hostinfo data State State } @@ -510,7 +511,7 @@ func (c *Client) mapRoutine() { c.inPollNetMap = false c.mu.Unlock() - err := c.direct.PollNetMap(ctx, -1, func(nm *NetworkMap) { + err := c.direct.PollNetMap(ctx, -1, func(nm *netmap.NetworkMap) { c.mu.Lock() select { @@ -607,7 +608,7 @@ func (c *Client) SetNetInfo(ni *tailcfg.NetInfo) { c.sendNewMapRequest() } -func (c *Client) sendStatus(who string, err error, url string, nm *NetworkMap) { +func (c *Client) sendStatus(who string, err error, url string, nm *netmap.NetworkMap) { c.mu.Lock() state := c.state loggedIn := c.loggedIn diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index 5c603a892..ed36dd502 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -39,6 +39,7 @@ import ( "tailscale.com/net/tshttpproxy" "tailscale.com/tailcfg" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/opt" "tailscale.com/types/persist" "tailscale.com/types/wgkey" @@ -468,7 +469,7 @@ func inTest() bool { return flag.Lookup("test.v") != nil } // // maxPolls is how many network maps to download; common values are 1 // or -1 (to keep a long-poll query open to the server). -func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*NetworkMap)) error { +func (c *Direct) PollNetMap(ctx context.Context, maxPolls int, cb func(*netmap.NetworkMap)) error { return c.sendMapRequest(ctx, maxPolls, cb) } @@ -480,7 +481,7 @@ func (c *Direct) SendLiteMapUpdate(ctx context.Context) error { } // cb nil means to omit peers. -func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*NetworkMap)) error { +func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*netmap.NetworkMap)) error { c.mu.Lock() persist := c.persist serverURL := c.serverURL @@ -714,7 +715,7 @@ func (c *Direct) sendMapRequest(ctx context.Context, maxPolls int, cb func(*Netw localPort = c.localPort c.mu.Unlock() - nm := &NetworkMap{ + nm := &netmap.NetworkMap{ SelfNode: resp.Node, NodeKey: tailcfg.NodeKey(persist.PrivateNodeKey.Public()), PrivateKey: persist.PrivateNodeKey, diff --git a/control/controlclient/direct_test.go b/control/controlclient/direct_test.go index bb1637845..3dab4d9ec 100644 --- a/control/controlclient/direct_test.go +++ b/control/controlclient/direct_test.go @@ -5,6 +5,7 @@ package controlclient import ( + "encoding/json" "fmt" "reflect" "strings" @@ -156,3 +157,15 @@ func TestNewDirect(t *testing.T) { t.Errorf("c.newEndpoints(13) want true got %v", changed) } } + +func TestNewHostinfo(t *testing.T) { + hi := NewHostinfo() + if hi == nil { + t.Fatal("no Hostinfo") + } + j, err := json.MarshalIndent(hi, " ", "") + if err != nil { + t.Fatal(err) + } + t.Logf("Got: %s", j) +} diff --git a/ipn/backend.go b/ipn/backend.go index 85bb0581f..9352853b1 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -9,10 +9,10 @@ import ( "time" "golang.org/x/oauth2" - "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" "tailscale.com/tailcfg" "tailscale.com/types/empty" + "tailscale.com/types/netmap" "tailscale.com/types/structs" ) @@ -58,16 +58,16 @@ type EngineStatus struct { // They are JSON-encoded on the wire, despite the lack of struct tags. type Notify struct { _ structs.Incomparable - Version string // version number of IPN backend - ErrMessage *string // critical error message, if any; for InUseOtherUser, the details - LoginFinished *empty.Message // event: non-nil when login process succeeded - State *State // current IPN state has changed - Prefs *Prefs // preferences were changed - NetMap *controlclient.NetworkMap // new netmap received - Engine *EngineStatus // wireguard engine stats - Status *ipnstate.Status // full status - BrowseToURL *string // UI should open a browser right now - BackendLogID *string // public logtail id used by backend + Version string // version number of IPN backend + ErrMessage *string // critical error message, if any; for InUseOtherUser, the details + LoginFinished *empty.Message // event: non-nil when login process succeeded + State *State // current IPN state has changed + Prefs *Prefs // preferences were changed + NetMap *netmap.NetworkMap // new netmap received + Engine *EngineStatus // wireguard engine stats + Status *ipnstate.Status // full status + BrowseToURL *string // UI should open a browser right now + BackendLogID *string // public logtail id used by backend PingResult *ipnstate.PingResult // LocalTCPPort, if non-nil, informs the UI frontend which diff --git a/ipn/fake_test.go b/ipn/fake_test.go index 9b16cceaa..e918f77f0 100644 --- a/ipn/fake_test.go +++ b/ipn/fake_test.go @@ -9,8 +9,8 @@ import ( "time" "golang.org/x/oauth2" - "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" + "tailscale.com/types/netmap" ) type FakeBackend struct { @@ -54,7 +54,7 @@ func (b *FakeBackend) login() { b.newState(NeedsMachineAuth) b.newState(Stopped) // TODO(apenwarr): Fill in a more interesting netmap here. - b.notify(Notify{NetMap: &controlclient.NetworkMap{}}) + b.notify(Notify{NetMap: &netmap.NetworkMap{}}) b.newState(Starting) // TODO(apenwarr): Fill in a more interesting status. b.notify(Notify{Engine: &EngineStatus{}}) @@ -92,7 +92,7 @@ func (b *FakeBackend) RequestStatus() { } func (b *FakeBackend) FakeExpireAfter(x time.Duration) { - b.notify(Notify{NetMap: &controlclient.NetworkMap{}}) + b.notify(Notify{NetMap: &netmap.NetworkMap{}}) } func (b *FakeBackend) Ping(ip string) { diff --git a/ipn/handle.go b/ipn/handle.go index b79eea8e2..91b757f56 100644 --- a/ipn/handle.go +++ b/ipn/handle.go @@ -10,8 +10,8 @@ import ( "golang.org/x/oauth2" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/types/logger" + "tailscale.com/types/netmap" ) type Handle struct { @@ -22,7 +22,7 @@ type Handle struct { // Mutex protects everything below mu sync.Mutex - netmapCache *controlclient.NetworkMap + netmapCache *netmap.NetworkMap engineStatusCache EngineStatus stateCache State prefsCache *Prefs @@ -129,7 +129,7 @@ func (h *Handle) LocalAddrs() []netaddr.IPPrefix { return []netaddr.IPPrefix{} } -func (h *Handle) NetMap() *controlclient.NetworkMap { +func (h *Handle) NetMap() *netmap.NetworkMap { h.mu.Lock() defer h.mu.Unlock() diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 523292def..6ad80ed53 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -29,6 +29,7 @@ import ( "tailscale.com/types/empty" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/persist" "tailscale.com/types/wgkey" "tailscale.com/util/systemd" @@ -92,7 +93,7 @@ type LocalBackend struct { // hostinfo is mutated in-place while mu is held. hostinfo *tailcfg.Hostinfo // netMap is not mutated in-place once set. - netMap *controlclient.NetworkMap + netMap *netmap.NetworkMap nodeByAddr map[netaddr.IP]*tailcfg.Node activeLogin string // last logged LoginName from netMap engineStatus ipn.EngineStatus @@ -374,7 +375,7 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { // routes provided by the exit node specified in b.prefs. It returns // whether prefs was mutated as part of the process, due to an exit // node IP being converted into a node ID. -func (b *LocalBackend) keepOneExitNodeLocked(nm *controlclient.NetworkMap) (prefsChanged bool) { +func (b *LocalBackend) keepOneExitNodeLocked(nm *netmap.NetworkMap) (prefsChanged bool) { if b.prefs.ExitNodeID == "" && b.prefs.ExitNodeIP.IsZero() { return false } @@ -601,7 +602,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error { // updateFilter updates the packet filter in wgengine based on the // given netMap and user preferences. -func (b *LocalBackend) updateFilter(netMap *controlclient.NetworkMap, prefs *ipn.Prefs) { +func (b *LocalBackend) updateFilter(netMap *netmap.NetworkMap, prefs *ipn.Prefs) { // NOTE(danderson): keep change detection as the first thing in // this function. Don't try to optimize by returning early, more // likely than not you'll just end up breaking the change @@ -660,7 +661,7 @@ func dnsCIDRsEqual(newAddr, oldAddr []netaddr.IPPrefix) bool { // dnsMapsEqual determines whether the new and the old network map // induce the same DNS map. It does so without allocating memory, // at the expense of giving false negatives if peers are reordered. -func dnsMapsEqual(new, old *controlclient.NetworkMap) bool { +func dnsMapsEqual(new, old *netmap.NetworkMap) bool { if (old == nil) != (new == nil) { return false } @@ -694,7 +695,7 @@ func dnsMapsEqual(new, old *controlclient.NetworkMap) bool { // updateDNSMap updates the domain map in the DNS resolver in wgengine // based on the given netMap and user preferences. -func (b *LocalBackend) updateDNSMap(netMap *controlclient.NetworkMap) { +func (b *LocalBackend) updateDNSMap(netMap *netmap.NetworkMap) { if netMap == nil { b.logf("dns map: (not ready)") return @@ -1215,7 +1216,7 @@ func (b *LocalBackend) doSetHostinfoFilterServices(hi *tailcfg.Hostinfo) { // NetMap returns the latest cached network map received from // controlclient, or nil if no network map was received yet. -func (b *LocalBackend) NetMap() *controlclient.NetworkMap { +func (b *LocalBackend) NetMap() *netmap.NetworkMap { b.mu.Lock() defer b.mu.Unlock() return b.netMap @@ -1257,17 +1258,17 @@ func (b *LocalBackend) authReconfig() { return } - var flags controlclient.WGConfigFlags + var flags netmap.WGConfigFlags if uc.RouteAll { - flags |= controlclient.AllowSubnetRoutes + flags |= netmap.AllowSubnetRoutes } if uc.AllowSingleHosts { - flags |= controlclient.AllowSingleHosts + flags |= netmap.AllowSingleHosts } if hasPAC && disableSubnetsIfPAC { - if flags&controlclient.AllowSubnetRoutes != 0 { + if flags&netmap.AllowSubnetRoutes != 0 { b.logf("authReconfig: have PAC; disabling subnet routes") - flags &^= controlclient.AllowSubnetRoutes + flags &^= netmap.AllowSubnetRoutes } } @@ -1303,7 +1304,7 @@ func (b *LocalBackend) authReconfig() { // magicDNSRootDomains returns the subset of nm.DNS.Domains that are the search domains for MagicDNS. // Each entry has a trailing period. -func magicDNSRootDomains(nm *controlclient.NetworkMap) []string { +func magicDNSRootDomains(nm *netmap.NetworkMap) []string { if v := nm.MagicDNSSuffix(); v != "" { return []string{strings.Trim(v, ".") + "."} } @@ -1596,7 +1597,7 @@ func (b *LocalBackend) setNetInfo(ni *tailcfg.NetInfo) { c.SetNetInfo(ni) } -func (b *LocalBackend) setNetMapLocked(nm *controlclient.NetworkMap) { +func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { var login string if nm != nil { login = nm.UserProfiles[nm.User].LoginName diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index ec9400952..9f19a08f1 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -8,8 +8,8 @@ import ( "testing" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/tailcfg" + "tailscale.com/types/netmap" ) func TestNetworkMapCompare(t *testing.T) { @@ -27,7 +27,7 @@ func TestNetworkMapCompare(t *testing.T) { tests := []struct { name string - a, b *controlclient.NetworkMap + a, b *netmap.NetworkMap want bool }{ { @@ -38,76 +38,76 @@ func TestNetworkMapCompare(t *testing.T) { }, { "b nil", - &controlclient.NetworkMap{}, + &netmap.NetworkMap{}, nil, false, }, { "a nil", nil, - &controlclient.NetworkMap{}, + &netmap.NetworkMap{}, false, }, { "both default", - &controlclient.NetworkMap{}, - &controlclient.NetworkMap{}, + &netmap.NetworkMap{}, + &netmap.NetworkMap{}, true, }, { "names identical", - &controlclient.NetworkMap{Name: "map1"}, - &controlclient.NetworkMap{Name: "map1"}, + &netmap.NetworkMap{Name: "map1"}, + &netmap.NetworkMap{Name: "map1"}, true, }, { "names differ", - &controlclient.NetworkMap{Name: "map1"}, - &controlclient.NetworkMap{Name: "map2"}, + &netmap.NetworkMap{Name: "map1"}, + &netmap.NetworkMap{Name: "map2"}, false, }, { "Peers identical", - &controlclient.NetworkMap{Peers: []*tailcfg.Node{}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{}}, true, }, { "Peer list length", // length of Peers list differs - &controlclient.NetworkMap{Peers: []*tailcfg.Node{{}}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{{}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{}}, false, }, { "Node names identical", - &controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}}, true, }, { "Node names differ", - &controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "B"}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "A"}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{Name: "B"}}}, false, }, { "Node lists identical", - &controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node1}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node1}}, true, }, { "Node lists differ", - &controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node1}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{node1, node2}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node1}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node2}}, false, }, { "Node Users differ", // User field is not checked. - &controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 0}}}, - &controlclient.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 1}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 0}}}, + &netmap.NetworkMap{Peers: []*tailcfg.Node{&tailcfg.Node{User: 1}}}, true, }, } diff --git a/control/controlclient/netmap.go b/types/netmap/netmap.go similarity index 99% rename from control/controlclient/netmap.go rename to types/netmap/netmap.go index 6589bb369..558c74637 100644 --- a/control/controlclient/netmap.go +++ b/types/netmap/netmap.go @@ -2,7 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package controlclient +// Package netmap contains the netmap.NetworkMap type. +package netmap import ( "encoding/json" diff --git a/control/controlclient/netmap_test.go b/types/netmap/netmap_test.go similarity index 96% rename from control/controlclient/netmap_test.go rename to types/netmap/netmap_test.go index 5bb529ab0..977a64cf0 100644 --- a/control/controlclient/netmap_test.go +++ b/types/netmap/netmap_test.go @@ -2,11 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package controlclient +package netmap import ( "encoding/hex" - "encoding/json" "testing" "inet.af/netaddr" @@ -283,15 +282,3 @@ func TestConciseDiffFrom(t *testing.T) { }) } } - -func TestNewHostinfo(t *testing.T) { - hi := NewHostinfo() - if hi == nil { - t.Fatal("no Hostinfo") - } - j, err := json.MarshalIndent(hi, " ", "") - if err != nil { - t.Fatal(err) - } - t.Logf("Got: %s", j) -} diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 3147167bb..16fc78493 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -48,6 +48,7 @@ import ( "tailscale.com/tstime" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/nettype" "tailscale.com/types/wgkey" "tailscale.com/version" @@ -273,7 +274,7 @@ type Conn struct { netInfoLast *tailcfg.NetInfo derpMap *tailcfg.DERPMap // nil (or zero regions/nodes) means DERP is disabled - netMap *controlclient.NetworkMap + netMap *netmap.NetworkMap privateKey key.Private // WireGuard private key for this node everHadKey bool // whether we ever had a non-zero private key myDerp int // nearest DERP region ID; 0 means none/unknown @@ -777,7 +778,7 @@ func (c *Conn) SetNetInfoCallback(fn func(*tailcfg.NetInfo)) { // peerForIP returns the Node in nm that's responsible for // handling the given IP address. -func peerForIP(nm *controlclient.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok bool) { +func peerForIP(nm *netmap.NetworkMap, ip netaddr.IP) (n *tailcfg.Node, ok bool) { if nm == nil { return nil, false } @@ -2198,7 +2199,7 @@ func nodesEqual(x, y []*tailcfg.Node) bool { // // It should not use the DERPMap field of NetworkMap; that's // conditionally sent to SetDERPMap instead. -func (c *Conn) SetNetworkMap(nm *controlclient.NetworkMap) { +func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) { c.mu.Lock() defer c.mu.Unlock() diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 90a78b9b0..86cd88283 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -30,7 +30,6 @@ import ( "github.com/tailscale/wireguard-go/tun/tuntest" "golang.org/x/crypto/nacl/box" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/derp" "tailscale.com/derp/derphttp" "tailscale.com/derp/derpmap" @@ -41,6 +40,7 @@ import ( "tailscale.com/tstest/natlab" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/nettype" "tailscale.com/types/wgkey" "tailscale.com/wgengine/filter" @@ -252,9 +252,9 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) { eps = make([][]string, len(ms)) ) - buildNetmapLocked := func(myIdx int) *controlclient.NetworkMap { + buildNetmapLocked := func(myIdx int) *netmap.NetworkMap { me := ms[myIdx] - nm := &controlclient.NetworkMap{ + nm := &netmap.NetworkMap{ PrivateKey: me.privateKey, NodeKey: tailcfg.NodeKey(me.privateKey.Public()), Addresses: []netaddr.IPPrefix{{IP: netaddr.IPv4(1, 0, 0, byte(myIdx+1)), Bits: 32}}, @@ -287,14 +287,14 @@ func meshStacks(logf logger.Logf, ms []*magicStack) (cleanup func()) { eps[idx] = newEps for i, m := range ms { - netmap := buildNetmapLocked(i) - m.conn.SetNetworkMap(netmap) - peerSet := make(map[key.Public]struct{}, len(netmap.Peers)) - for _, peer := range netmap.Peers { + nm := buildNetmapLocked(i) + m.conn.SetNetworkMap(nm) + peerSet := make(map[key.Public]struct{}, len(nm.Peers)) + for _, peer := range nm.Peers { peerSet[key.Public(peer.Key)] = struct{}{} } m.conn.UpdatePeers(peerSet) - wg, err := nmcfg.WGCfg(netmap, logf, controlclient.AllowSingleHosts) + wg, err := nmcfg.WGCfg(nm, logf, netmap.AllowSingleHosts) if err != nil { // We're too far from the *testing.T to be graceful, // blow up. Shouldn't happen anyway. @@ -1433,7 +1433,7 @@ func BenchmarkReceiveFrom(b *testing.B) { // valid peer and not fall through to the legacy magicsock // codepath. discoKey := tailcfg.DiscoKey{31: 1} - conn.SetNetworkMap(&controlclient.NetworkMap{ + conn.SetNetworkMap(&netmap.NetworkMap{ Peers: []*tailcfg.Node{ { DiscoKey: discoKey, diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 72651bd41..b2b21fcba 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -28,9 +28,9 @@ import ( "gvisor.dev/gvisor/pkg/tcpip/transport/udp" "gvisor.dev/gvisor/pkg/waiter" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/net/packet" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/magicsock" @@ -63,7 +63,7 @@ func Impl(logf logger.Logf, tundev *tstun.TUN, e wgengine.Engine, mc *magicsock. log.Fatal(err) } - e.AddNetworkMapCallback(func(nm *controlclient.NetworkMap) { + e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { oldIPs := make(map[tcpip.Address]bool) for _, ip := range ipstack.AllAddresses()[nicID] { oldIPs[ip.AddressWithPrefix.Address] = true diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 7380b74fa..282b79c7d 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -36,6 +36,7 @@ import ( "tailscale.com/tailcfg" "tailscale.com/types/key" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/types/wgkey" "tailscale.com/version" "tailscale.com/version/distro" @@ -1324,7 +1325,7 @@ func (e *userspaceEngine) SetDERPMap(dm *tailcfg.DERPMap) { e.magicConn.SetDERPMap(dm) } -func (e *userspaceEngine) SetNetworkMap(nm *controlclient.NetworkMap) { +func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) { e.magicConn.SetNetworkMap(nm) e.mu.Lock() callbacks := make([]NetworkMapCallback, 0, 4) diff --git a/wgengine/watchdog.go b/wgengine/watchdog.go index 91b5fe04e..130ce4610 100644 --- a/wgengine/watchdog.go +++ b/wgengine/watchdog.go @@ -13,10 +13,10 @@ import ( "time" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" "tailscale.com/net/interfaces" "tailscale.com/tailcfg" + "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/tsdns" @@ -107,7 +107,7 @@ func (e *watchdogEngine) SetLinkChangeCallback(cb func(major bool, newState *int func (e *watchdogEngine) SetDERPMap(m *tailcfg.DERPMap) { e.watchdog("SetDERPMap", func() { e.wrap.SetDERPMap(m) }) } -func (e *watchdogEngine) SetNetworkMap(nm *controlclient.NetworkMap) { +func (e *watchdogEngine) SetNetworkMap(nm *netmap.NetworkMap) { e.watchdog("SetNetworkMap", func() { e.wrap.SetNetworkMap(nm) }) } func (e *watchdogEngine) AddNetworkMapCallback(callback NetworkMapCallback) func() { diff --git a/wgengine/wgcfg/nmcfg/nmcfg.go b/wgengine/wgcfg/nmcfg/nmcfg.go index ce8408b06..36dc065c8 100644 --- a/wgengine/wgcfg/nmcfg/nmcfg.go +++ b/wgengine/wgcfg/nmcfg/nmcfg.go @@ -16,6 +16,7 @@ import ( "tailscale.com/net/tsaddr" "tailscale.com/tailcfg" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/wgengine/wgcfg" ) @@ -51,7 +52,7 @@ func cidrIsSubnet(node *tailcfg.Node, cidr netaddr.IPPrefix) bool { } // WGCfg returns the NetworkMaps's Wireguard configuration. -func WGCfg(nm *controlclient.NetworkMap, logf logger.Logf, flags controlclient.WGConfigFlags) (*wgcfg.Config, error) { +func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags) (*wgcfg.Config, error) { cfg := &wgcfg.Config{ Name: "tailscale", PrivateKey: wgcfg.PrivateKey(nm.PrivateKey), @@ -88,12 +89,12 @@ func WGCfg(nm *controlclient.NetworkMap, logf logger.Logf, flags controlclient.W } } for _, allowedIP := range peer.AllowedIPs { - if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&controlclient.AllowSingleHosts) == 0 { + if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.IP) && (flags&netmap.AllowSingleHosts) == 0 { logf("[v1] wgcfg: skipping node IP %v from %q (%v)", allowedIP.IP, nodeDebugName(peer), peer.Key.ShortString()) continue } else if cidrIsSubnet(peer, allowedIP) { - if (flags & controlclient.AllowSubnetRoutes) == 0 { + if (flags & netmap.AllowSubnetRoutes) == 0 { logf("[v1] wgcfg: not accepting subnet route %v from %q (%v)", allowedIP, nodeDebugName(peer), peer.Key.ShortString()) continue diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index ff3efb6a0..257d59f26 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -8,10 +8,10 @@ import ( "errors" "inet.af/netaddr" - "tailscale.com/control/controlclient" "tailscale.com/ipn/ipnstate" "tailscale.com/net/interfaces" "tailscale.com/tailcfg" + "tailscale.com/types/netmap" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/router" "tailscale.com/wgengine/tsdns" @@ -38,7 +38,7 @@ type NetInfoCallback func(*tailcfg.NetInfo) // NetworkMapCallback is the type used by callbacks that hook // into network map updates. -type NetworkMapCallback func(*controlclient.NetworkMap) +type NetworkMapCallback func(*netmap.NetworkMap) // someHandle is allocated so its pointer address acts as a unique // map key handle. (It needs to have non-zero size for Go to guarantee @@ -108,7 +108,7 @@ type Engine interface { // ignored as as it might be disabled; get it from SetDERPMap // instead. // The network map should only be read from. - SetNetworkMap(*controlclient.NetworkMap) + SetNetworkMap(*netmap.NetworkMap) // AddNetworkMapCallback adds a function to a list of callbacks // that are called when the network map updates. It returns a From e86b39b73fd96a894d3a73e7ac5068f5933c7043 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Fri, 5 Feb 2021 20:30:32 -0800 Subject: [PATCH 23/71] ipn/ipnlocal: don't short-circuit default route filtering. If no exit node is specified, the filter must still run to remove offered default routes from all peers. Signed-off-by: David Anderson --- ipn/ipnlocal/local.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 6ad80ed53..071761434 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -376,10 +376,6 @@ func (b *LocalBackend) setClientStatus(st controlclient.Status) { // whether prefs was mutated as part of the process, due to an exit // node IP being converted into a node ID. func (b *LocalBackend) keepOneExitNodeLocked(nm *netmap.NetworkMap) (prefsChanged bool) { - if b.prefs.ExitNodeID == "" && b.prefs.ExitNodeIP.IsZero() { - return false - } - // If we have a desired IP on file, try to find the corresponding // node. if !b.prefs.ExitNodeIP.IsZero() { From 6d2b8df06df1d20d4ae92793a066340bb26b6e25 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 6 Feb 2021 21:27:02 -0800 Subject: [PATCH 24/71] wgengine/magicsock: add disabled failing (deadlocking) test for #1282 The fix can make this test run unconditionally. This moves code from 5c619882bc4911a2c9e7d0bb491b9e50d27afcd7 for testability but doesn't fix it yet. The #1282 problem remains (when I wrote its wake-up mechanism, I forgot there were N DERP readers funneling into 1 UDP reader, and the code just isn't correct at all for that case). Also factor out some test helper code from BenchmarkReceiveFrom. The refactoring in magicsock.go for testability should have no behavior change. --- wgengine/magicsock/magicsock.go | 40 +++++---- wgengine/magicsock/magicsock_test.go | 121 ++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 34 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 16fc78493..1909a2d23 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1445,28 +1445,40 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d continue } - // Before we wake up ReceiveIPv4 with SetReadDeadline, - // note that a DERP packet has arrived. ReceiveIPv4 - // will read this field to note that its UDP read - // error is due to us. - atomic.AddInt64(&c.derpRecvCountAtomic, 1) - // Cancel the pconn read goroutine. - c.pconn4.SetReadDeadline(aLongTimeAgo) + if !c.sendDerpReadResult(ctx, res) { + return + } select { case <-ctx.Done(): return - case c.derpRecvCh <- res: - select { - case <-ctx.Done(): - return - case <-didCopy: - continue - } + case <-didCopy: + continue } } } +// sendDerpReadResult sends res to c.derpRecvCh and reports whether it +// was sent. (It reports false if ctx was done first.) +// +// This includes doing the whole wake-up dance to interrupt +// ReceiveIPv4's blocking UDP read. +func (c *Conn) sendDerpReadResult(ctx context.Context, res derpReadResult) (sent bool) { + // Before we wake up ReceiveIPv4 with SetReadDeadline, + // note that a DERP packet has arrived. ReceiveIPv4 + // will read this field to note that its UDP read + // error is due to us. + atomic.AddInt64(&c.derpRecvCountAtomic, 1) + // Cancel the pconn read goroutine. + c.pconn4.SetReadDeadline(aLongTimeAgo) + select { + case <-ctx.Done(): + return false + case c.derpRecvCh <- res: + return true + } +} + type derpWriteRequest struct { addr netaddr.IPPort pubKey key.Public diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 86cd88283..bcc3ee7c9 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -11,12 +11,14 @@ import ( "crypto/tls" "encoding/binary" "encoding/json" + "flag" "fmt" "io/ioutil" "net" "net/http" "net/http/httptest" "os" + "runtime" "strconv" "strings" "sync" @@ -1408,19 +1410,112 @@ func Test32bitAlignment(t *testing.T) { atomic.AddInt64(&c.derpRecvCountAtomic, 1) } -func BenchmarkReceiveFrom(b *testing.B) { - port := pickPort(b) +// newNonLegacyTestConn returns a new Conn with DisableLegacyNetworking set true. +func newNonLegacyTestConn(t testing.TB) *Conn { + t.Helper() + port := pickPort(t) conn, err := NewConn(Options{ - Logf: b.Logf, + Logf: t.Logf, Port: port, EndpointsFunc: func(eps []string) { - b.Logf("endpoints: %q", eps) + t.Logf("endpoints: %q", eps) }, DisableLegacyNetworking: true, }) if err != nil { - b.Fatal(err) + t.Fatal(err) } + return conn +} + +var testIssue1282 = flag.Bool("test-issue-1282", false, "run test for https://github.com/tailscale/tailscale/issues/1282 on all CPUs") + +// Tests concurrent DERP readers pushing DERP data into ReceiveIPv4 +// (which should blend all DERP reads into UDP reads). +func TestDerpReceiveFromIPv4(t *testing.T) { + conn := newNonLegacyTestConn(t) + defer conn.Close() + + sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer sendConn.Close() + nodeKey, _ := addTestEndpoint(conn, sendConn) + + var sends int = 500 + senders := runtime.NumCPU() + if !*testIssue1282 { + t.Logf("--test-issue-1282 was not specified; so doing single-threaded test (instead of NumCPU=%d) to work around https://github.com/tailscale/tailscale/issues/1282", senders) + senders = 1 + } + sends -= (sends % senders) + var wg sync.WaitGroup + defer wg.Wait() + t.Logf("doing %v sends over %d senders", sends, senders) + ctx := context.Background() + + for i := 0; i < senders; i++ { + wg.Add(1) + regionID := i + 1 + go func() { + defer wg.Done() + ch := make(chan bool, 1) + for i := 0; i < sends/senders; i++ { + if !conn.sendDerpReadResult(ctx, derpReadResult{ + regionID: regionID, + n: 123, + src: key.Public(nodeKey), + copyBuf: func(dst []byte) int { + ch <- true + return 123 + }, + }) { + t.Error("unexpected false") + return + } + <-ch + } + }() + } + + buf := make([]byte, 1500) + for i := 0; i < sends; i++ { + n, ep, err := conn.ReceiveIPv4(buf) + if err != nil { + t.Fatal(err) + } + _ = n + _ = ep + } +} + +// addTestEndpoint sets conn's network map to a single peer expected +// to receive packets from sendConn (or DERP), and returns that peer's +// nodekey and discokey. +func addTestEndpoint(conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tailcfg.DiscoKey) { + // Give conn just enough state that it'll recognize sendConn as a + // valid peer and not fall through to the legacy magicsock + // codepath. + discoKey := tailcfg.DiscoKey{31: 1} + nodeKey := tailcfg.NodeKey{0: 'N', 1: 'K'} + conn.SetNetworkMap(&netmap.NetworkMap{ + Peers: []*tailcfg.Node{ + { + Key: nodeKey, + DiscoKey: discoKey, + Endpoints: []string{sendConn.LocalAddr().String()}, + }, + }, + }) + conn.SetPrivateKey(wgkey.Private{0: 1}) + conn.CreateEndpoint([32]byte(nodeKey), "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345") + conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String())) + return nodeKey, discoKey +} + +func BenchmarkReceiveFrom(b *testing.B) { + conn := newNonLegacyTestConn(b) defer conn.Close() sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") @@ -1429,21 +1524,7 @@ func BenchmarkReceiveFrom(b *testing.B) { } defer sendConn.Close() - // Give conn just enough state that it'll recognize sendConn as a - // valid peer and not fall through to the legacy magicsock - // codepath. - discoKey := tailcfg.DiscoKey{31: 1} - conn.SetNetworkMap(&netmap.NetworkMap{ - Peers: []*tailcfg.Node{ - { - DiscoKey: discoKey, - Endpoints: []string{sendConn.LocalAddr().String()}, - }, - }, - }) - conn.SetPrivateKey(wgkey.Private{0: 1}) - conn.CreateEndpoint([32]byte{1: 1}, "0000000000000000000000000000000000000000000000000000000000000001.disco.tailscale:12345") - conn.addValidDiscoPathForTest(discoKey, netaddr.MustParseIPPort(sendConn.LocalAddr().String())) + addTestEndpoint(conn, sendConn) var dstAddr net.Addr = conn.pconn4.LocalAddr() sendBuf := make([]byte, 1<<10) From e1f773ebba87971d7da49c20fbb7f1e73b2b5ee7 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 8 Feb 2021 12:27:35 -0800 Subject: [PATCH 25/71] wgengine/magicsock: allow more time for pings to transit We removed the "fast retry" code from our wireguard-go fork. As a result, pings can take longer to transit when retries are required. Allow that. Fixes #1277 Signed-off-by: Josh Bleecher Snyder --- wgengine/magicsock/magicsock_test.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index bcc3ee7c9..5ae40ab4c 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -928,6 +928,13 @@ func testTwoDevicePing(t *testing.T, d *devices) { t.Fatal(err) } + // In the normal case, pings succeed immediately. + // However, in the case of a handshake race, we need to retry. + // Typical retries take 5s. With very bad luck, we can need to retry + // multiple times. Give ourselves enough time for three retries + // plus a bit of processing time. + const pingTimeout = 16 * time.Second + ping1 := func(t *testing.T) { msg2to1 := tuntest.Ping(net.ParseIP("1.0.0.1"), net.ParseIP("1.0.0.2")) m2.tun.Outbound <- msg2to1 @@ -937,7 +944,7 @@ func testTwoDevicePing(t *testing.T, d *devices) { if !bytes.Equal(msg2to1, msgRecv) { t.Error("ping did not transit correctly") } - case <-time.After(3 * time.Second): + case <-time.After(pingTimeout): t.Error("ping did not transit") } } @@ -950,7 +957,7 @@ func testTwoDevicePing(t *testing.T, d *devices) { if !bytes.Equal(msg1to2, msgRecv) { t.Error("return ping did not transit correctly") } - case <-time.After(3 * time.Second): + case <-time.After(pingTimeout): t.Error("return ping did not transit") } } @@ -981,7 +988,7 @@ func testTwoDevicePing(t *testing.T, d *devices) { if !bytes.Equal(msg1to2, msgRecv) { t.Error("return ping did not transit correctly") } - case <-time.After(3 * time.Second): + case <-time.After(pingTimeout): t.Error("return ping did not transit") } }) @@ -1044,7 +1051,7 @@ func testTwoDevicePing(t *testing.T, d *devices) { t.Errorf("return ping %d did not transit correctly: %s", i, cmp.Diff(b, msgRecv)) } } - case <-time.After(3 * time.Second): + case <-time.After(pingTimeout): if strict { t.Errorf("return ping %d did not transit", i) } From 6b365b02396b54536cb40dbbe235433493874935 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 6 Feb 2021 22:39:58 -0800 Subject: [PATCH 26/71] wgengine/magicsock: fix DERP reader hang regression during concurrent reads Fixes #1282 Signed-off-by: Brad Fitzpatrick --- wgengine/magicsock/magicsock.go | 72 ++++++++++++++++++++-------- wgengine/magicsock/magicsock_test.go | 57 +++++++++++++++------- 2 files changed, 92 insertions(+), 37 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 1909a2d23..7c481e087 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -12,6 +12,7 @@ import ( crand "crypto/rand" "encoding/binary" "errors" + "expvar" "fmt" "hash/fnv" "math" @@ -155,13 +156,10 @@ type Conn struct { // derpRecvCh is used by ReceiveIPv4 to read DERP messages. derpRecvCh chan derpReadResult - // derpRecvCountAtomic is atomically incremented by runDerpReader whenever - // a DERP message arrives. It's incremented before runDerpReader is interrupted. + // derpRecvCountAtomic is how many derpRecvCh sends are pending. + // It's incremented by runDerpReader whenever a DERP message + // arrives and decremented when they're read. derpRecvCountAtomic int64 - // derpRecvCountLast is used by ReceiveIPv4 to compare against - // its last read value of derpRecvCountAtomic to determine - // whether a DERP channel read should be done. - derpRecvCountLast int64 // owned by ReceiveIPv4 // ippEndpoint4 and ippEndpoint6 are owned by ReceiveIPv4 and // ReceiveIPv6, respectively, to cache an IPPort->endpoint for @@ -1358,6 +1356,8 @@ type derpReadResult struct { // copyBuf is called to copy the data to dst. It returns how // much data was copied, which will be n if dst is large // enough. copyBuf can only be called once. + // If copyBuf is nil, that's a signal from the sender to ignore + // this message. copyBuf func(dst []byte) int } @@ -1458,6 +1458,11 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d } } +var ( + testCounterZeroDerpReadResultSend expvar.Int + testCounterZeroDerpReadResultRecv expvar.Int +) + // sendDerpReadResult sends res to c.derpRecvCh and reports whether it // was sent. (It reports false if ctx was done first.) // @@ -1465,7 +1470,7 @@ func (c *Conn) runDerpReader(ctx context.Context, derpFakeAddr netaddr.IPPort, d // ReceiveIPv4's blocking UDP read. func (c *Conn) sendDerpReadResult(ctx context.Context, res derpReadResult) (sent bool) { // Before we wake up ReceiveIPv4 with SetReadDeadline, - // note that a DERP packet has arrived. ReceiveIPv4 + // note that a DERP packet has arrived. ReceiveIPv4 // will read this field to note that its UDP read // error is due to us. atomic.AddInt64(&c.derpRecvCountAtomic, 1) @@ -1473,6 +1478,23 @@ func (c *Conn) sendDerpReadResult(ctx context.Context, res derpReadResult) (sent c.pconn4.SetReadDeadline(aLongTimeAgo) select { case <-ctx.Done(): + select { + case <-c.donec: + // The whole Conn shut down. The reader of + // c.derpRecvCh also selects on c.donec, so it's + // safe to abort now. + case c.derpRecvCh <- (derpReadResult{}): + // Just this DERP reader is closing (perhaps + // the user is logging out, or the DERP + // connection is too idle for sends). Since we + // already incremented c.derpRecvCountAtomic, + // we need to send on the channel (unless the + // conn is going down). + // The receiver treats a derpReadResult zero value + // message as a skip. + testCounterZeroDerpReadResultSend.Add(1) + + } return false case c.derpRecvCh <- res: return true @@ -1568,20 +1590,20 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) { } func (c *Conn) derpPacketArrived() bool { - rc := atomic.LoadInt64(&c.derpRecvCountAtomic) - if rc != c.derpRecvCountLast { - c.derpRecvCountLast = rc - return true - } - return false + return atomic.LoadInt64(&c.derpRecvCountAtomic) > 0 } // ReceiveIPv4 is called by wireguard-go to receive an IPv4 packet. // In Tailscale's case, that packet might also arrive via DERP. A DERP packet arrival // aborts the pconn4 read deadline to make it fail. func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { + var pAddr net.Addr for { - n, pAddr, err := c.pconn4.ReadFrom(b) + // Drain DERP queues before reading new UDP packets. + if c.derpPacketArrived() { + goto ReadDERP + } + n, pAddr, err = c.pconn4.ReadFrom(b) if err != nil { // If the pconn4 read failed, the likely reason is a DERP reader received // a packet and interrupted us. @@ -1589,18 +1611,21 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { // and for there to have also had a DERP packet arrive, but that's fine: // we'll get the same error from ReadFrom later. if c.derpPacketArrived() { - c.pconn4.SetReadDeadline(time.Time{}) // restore - n, ep, err = c.receiveIPv4DERP(b) - if err == errLoopAgain { - continue - } - return n, ep, err + goto ReadDERP } return 0, nil, err } if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok { return n, ep, nil + } else { + continue } + ReadDERP: + n, ep, err = c.receiveIPv4DERP(b) + if err == errLoopAgain { + continue + } + return n, ep, err } } @@ -1668,6 +1693,13 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) { case dm = <-c.derpRecvCh: // Below. } + if atomic.AddInt64(&c.derpRecvCountAtomic, -1) == 0 { + c.pconn4.SetReadDeadline(time.Time{}) + } + if dm.copyBuf == nil { + testCounterZeroDerpReadResultRecv.Add(1) + return 0, nil, errLoopAgain + } var regionID int n, regionID = dm.n, dm.regionID diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 5ae40ab4c..a4e3ed96d 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -11,7 +11,6 @@ import ( "crypto/tls" "encoding/binary" "encoding/json" - "flag" "fmt" "io/ioutil" "net" @@ -1435,8 +1434,6 @@ func newNonLegacyTestConn(t testing.TB) *Conn { return conn } -var testIssue1282 = flag.Bool("test-issue-1282", false, "run test for https://github.com/tailscale/tailscale/issues/1282 on all CPUs") - // Tests concurrent DERP readers pushing DERP data into ReceiveIPv4 // (which should blend all DERP reads into UDP reads). func TestDerpReceiveFromIPv4(t *testing.T) { @@ -1450,42 +1447,54 @@ func TestDerpReceiveFromIPv4(t *testing.T) { defer sendConn.Close() nodeKey, _ := addTestEndpoint(conn, sendConn) - var sends int = 500 - senders := runtime.NumCPU() - if !*testIssue1282 { - t.Logf("--test-issue-1282 was not specified; so doing single-threaded test (instead of NumCPU=%d) to work around https://github.com/tailscale/tailscale/issues/1282", senders) - senders = 1 + var sends int = 250e3 // takes about a second + if testing.Short() { + sends /= 10 } + senders := runtime.NumCPU() sends -= (sends % senders) var wg sync.WaitGroup defer wg.Wait() t.Logf("doing %v sends over %d senders", sends, senders) - ctx := context.Background() + + ctx, cancel := context.WithCancel(context.Background()) + defer conn.Close() + defer cancel() + + doneCtx, cancelDoneCtx := context.WithCancel(context.Background()) + cancelDoneCtx() for i := 0; i < senders; i++ { wg.Add(1) regionID := i + 1 go func() { defer wg.Done() - ch := make(chan bool, 1) for i := 0; i < sends/senders; i++ { - if !conn.sendDerpReadResult(ctx, derpReadResult{ + res := derpReadResult{ regionID: regionID, n: 123, src: key.Public(nodeKey), - copyBuf: func(dst []byte) int { - ch <- true - return 123 - }, - }) { + copyBuf: func(dst []byte) int { return 123 }, + } + // First send with the closed context. ~50% of + // these should end up going through the + // send-a-zero-derpReadResult path, returning + // true, in which case we don't want to send again. + // We test later that we hit the other path. + if conn.sendDerpReadResult(doneCtx, res) { + continue + } + + if !conn.sendDerpReadResult(ctx, res) { t.Error("unexpected false") return } - <-ch } }() } + zeroSendsStart := testCounterZeroDerpReadResultSend.Value() + buf := make([]byte, 1500) for i := 0; i < sends; i++ { n, ep, err := conn.ReceiveIPv4(buf) @@ -1495,6 +1504,20 @@ func TestDerpReceiveFromIPv4(t *testing.T) { _ = n _ = ep } + + t.Logf("did %d ReceiveIPv4 calls", sends) + + zeroSends, zeroRecv := testCounterZeroDerpReadResultSend.Value(), testCounterZeroDerpReadResultRecv.Value() + if zeroSends != zeroRecv { + t.Errorf("did %d zero sends != %d corresponding receives", zeroSends, zeroRecv) + } + zeroSendDelta := zeroSends - zeroSendsStart + if zeroSendDelta == 0 { + t.Errorf("didn't see any sends of derpReadResult zero value") + } + if zeroSendDelta == int64(sends) { + t.Errorf("saw %v sends of the derpReadResult zero value which was unexpectedly high (100%% of our %v sends)", zeroSendDelta, sends) + } } // addTestEndpoint sets conn's network map to a single peer expected From e7caad61fb62108e491a7e757528e2c83fac2b29 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Mon, 8 Feb 2021 13:34:27 -0800 Subject: [PATCH 27/71] wgengine: remove IpcGetOperation filter This was in place because retrieved allowed_ips was very expensive. Upstream changed the data structure to make them cheaper to compute. This commit is an experiment to find out whether they're now cheap enough. Signed-off-by: Josh Bleecher Snyder --- wgengine/userspace.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 282b79c7d..5793232b4 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -1075,12 +1075,7 @@ func (e *userspaceEngine) getStatus() (*Status, error) { defer pw.Close() // TODO(apenwarr): get rid of silly uapi stuff for in-process comms // FIXME: get notified of status changes instead of polling. - filter := device.IPCGetFilter{ - // The allowed_ips are somewhat expensive to compute and they're - // unused below; request that they not be sent instead. - FilterAllowedIPs: true, - } - err := e.wgdev.IpcGetOperationFiltered(pw, filter) + err := e.wgdev.IpcGetOperation(pw) if err != nil { err = fmt.Errorf("IpcGetOperation: %w", err) } From 07c3df13c69d0a95582ebc6c98b1bed116296287 Mon Sep 17 00:00:00 2001 From: Naman Sood Date: Tue, 26 Jan 2021 16:25:42 -0500 Subject: [PATCH 28/71] wgengine/tstun: inform userspaceEngine about injected outbound packets in tundev Signed-off-by: Naman Sood --- wgengine/tstun/tun.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/wgengine/tstun/tun.go b/wgengine/tstun/tun.go index 8a68f40f0..ef3be6016 100644 --- a/wgengine/tstun/tun.go +++ b/wgengine/tstun/tun.go @@ -259,6 +259,8 @@ func (t *TUN) IdleDuration() time.Duration { func (t *TUN) Read(buf []byte, offset int) (int, error) { var n int + wasInjectedPacket := false + select { case <-t.closed: return 0, io.EOF @@ -273,9 +275,7 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) { t.bufferConsumed <- struct{}{} } else { // If the packet is not from t.buffer, then it is an injected packet. - // In this case, we return early to bypass filtering - t.noteActivity() - return n, nil + wasInjectedPacket = true } } @@ -289,6 +289,12 @@ func (t *TUN) Read(buf []byte, offset int) (int, error) { } } + // For injected packets, we return early to bypass filtering. + if wasInjectedPacket { + t.noteActivity() + return n, nil + } + if !t.disableFilter { response := t.filterOut(p) if response != filter.Accept { From 9b4e50cec01ba56e5a3e33c6dee18c45a66fe4d3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 9 Feb 2021 09:37:24 -0800 Subject: [PATCH 29/71] wgengine/magicsock: fix typo in comment --- wgengine/magicsock/magicsock.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 7c481e087..d571a9a9e 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -358,7 +358,7 @@ type activeDerp struct { writeCh chan<- derpWriteRequest // lastWrite is the time of the last request for its write // channel (currently even if there was no write). - // It is always non-nil and initialized to a non-zero Time[ + // It is always non-nil and initialized to a non-zero Time. lastWrite *time.Time createTime time.Time } From 4a82e364917d274907b10e487151b06dd142b63e Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Tue, 9 Feb 2021 14:20:01 -0800 Subject: [PATCH 30/71] go.mod: bump to latest wireguard-go Stabilization and performance improvements. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index c8e1830bb..79fe3a026 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 - github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020 + github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12 github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 diff --git a/go.sum b/go.sum index fe7dcd431..fcaba0480 100644 --- a/go.sum +++ b/go.sum @@ -302,6 +302,8 @@ github.com/tailscale/wireguard-go v0.0.0-20210201213041-c9817e648365 h1:0OC8+fnU github.com/tailscale/wireguard-go v0.0.0-20210201213041-c9817e648365/go.mod h1:K/wyv4+3PcdVVTV7szyoiEjJ1nVHonM8cJ2mQwG5Fl8= github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020 h1:DbQtiKont9TyOBIuTHhj1UUpWE75QcsyBiJPxTbqRGQ= github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= +github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12 h1:kk8nOHkXmG/yD1a4FQvH7+VOdNEP7GKkQimXFR2iwv8= +github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= From cbd6224ca41d0c9f0ac0db96e5e2854eb6a5212e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 9 Feb 2021 21:08:18 -0800 Subject: [PATCH 31/71] wgengine/winnet: don't build on non-windows It only affects 'go install ./...', etc, and only on darwin/arm64 (M1 Macs) where the go-ole package doesn't compile. No need to build it. Updates #943 --- wgengine/winnet/winnet.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wgengine/winnet/winnet.go b/wgengine/winnet/winnet.go index be76fd9ca..086b07638 100644 --- a/wgengine/winnet/winnet.go +++ b/wgengine/winnet/winnet.go @@ -2,13 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build windows + package winnet import ( "fmt" + "unsafe" + "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" - "unsafe" ) const CLSID_NetworkListManager = "{DCB00C01-570F-4A9B-8D69-199FDBA5723B}" From e101d8396ddb192ad3ce96c21bcfb82430ea044b Mon Sep 17 00:00:00 2001 From: moncho <50428+moncho@users.noreply.github.com> Date: Sat, 19 Dec 2020 11:16:44 +0100 Subject: [PATCH 32/71] portlist, version: update build tags for Go 1.16, Apple M1 Build tags have been updated to build native Apple M1 binaries, existing build tags for ios have been changed from darwin,arm64 to ios,arm64. With this change, running go build cmd/tailscale{,d}/tailscale{,d}.go on an Apple machine with the new processor works and resulting binaries show the expected architecture, e.g. tailscale: Mach-O 64-bit executable arm64. Tested using go version go1.16beta1 darwin/arm64. Updates #943 Signed-off-by: moncho <50428+moncho@users.noreply.github.com> --- portlist/netstat.go | 2 +- portlist/netstat_exec.go | 2 +- portlist/portlist_ios.go | 2 +- portlist/portlist_macos.go | 2 +- version/cmdname.go | 2 +- version/cmdname_ios.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/portlist/netstat.go b/portlist/netstat.go index e0f9345be..02b1a5957 100644 --- a/portlist/netstat.go +++ b/portlist/netstat.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !darwin !arm64 +// +build go1.16,!ios !go1.16,!darwin !go1.16,!arm64 package portlist diff --git a/portlist/netstat_exec.go b/portlist/netstat_exec.go index 2ca3fd574..dd78215ec 100644 --- a/portlist/netstat_exec.go +++ b/portlist/netstat_exec.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build windows freebsd openbsd darwin,amd64 +// +build windows freebsd openbsd darwin,amd64 go1.16,darwin,arm64 package portlist diff --git a/portlist/portlist_ios.go b/portlist/portlist_ios.go index a7a85a54c..19bc2db39 100644 --- a/portlist/portlist_ios.go +++ b/portlist/portlist_ios.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin,!amd64 +// +build go1.16,ios !go1.16,darwin,!amd64 package portlist diff --git a/portlist/portlist_macos.go b/portlist/portlist_macos.go index 66bd558b8..86dd3058e 100644 --- a/portlist/portlist_macos.go +++ b/portlist/portlist_macos.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin,amd64 +// +build darwin,amd64 go1.16,darwin,arm64 package portlist diff --git a/version/cmdname.go b/version/cmdname.go index a7899ed9f..832563532 100644 --- a/version/cmdname.go +++ b/version/cmdname.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !darwin !arm64 +// +build go1.16,!ios !go1.16,!darwin !go1.16,!arm64 package version diff --git a/version/cmdname_ios.go b/version/cmdname_ios.go index 69d71f7db..514da5da0 100644 --- a/version/cmdname_ios.go +++ b/version/cmdname_ios.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin,arm64 +// +build go1.16,ios !go1.16,darwin,arm64 package version From 1f0fa8b814fa09bd5bddc44bd83fed0184a7e8d6 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 10 Feb 2021 08:04:12 -0800 Subject: [PATCH 33/71] go.mod: pull in upstream wireguard-go bug fixes --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 79fe3a026..8f47c2a10 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 - github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12 + github.com/tailscale/wireguard-go v0.0.0-20210210160038-385d6fdeb57a github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 diff --git a/go.sum b/go.sum index fcaba0480..208ffae5d 100644 --- a/go.sum +++ b/go.sum @@ -304,6 +304,8 @@ github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020 h1:DbQtiKon github.com/tailscale/wireguard-go v0.0.0-20210204220812-81c7f3687020/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12 h1:kk8nOHkXmG/yD1a4FQvH7+VOdNEP7GKkQimXFR2iwv8= github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= +github.com/tailscale/wireguard-go v0.0.0-20210210160038-385d6fdeb57a h1:zgMmUGUb2U3E9VerpED4MlIceYjTT0QgpGr3qJKHyBE= +github.com/tailscale/wireguard-go v0.0.0-20210210160038-385d6fdeb57a/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= From 7e201806b10af3d484643b03dc5b5b1dcec85c54 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 10 Feb 2021 10:04:42 -0800 Subject: [PATCH 34/71] wgengine/magicsock: reconnect to DERP home after network comes back up Updates #1310 --- wgengine/magicsock/magicsock.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index d571a9a9e..c3c2c9027 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -963,6 +963,13 @@ func (c *Conn) setNearestDERP(derpNum int) (wantDERP bool) { return true } +// startDerpHomeConnectLocked starts connecting to our DERP home, if any. +// +// c.mu must be held. +func (c *Conn) startDerpHomeConnectLocked() { + c.goDerpConnect(c.myDerp) +} + // goDerpConnect starts a goroutine to start connecting to the given // DERP node. // @@ -2125,7 +2132,9 @@ func (c *Conn) SetNetworkUp(up bool) { c.logf("magicsock: SetNetworkUp(%v)", up) c.networkUp.Set(up) - if !up { + if up { + c.startDerpHomeConnectLocked() + } else { c.closeAllDerpLocked("network-down") } } @@ -2167,7 +2176,7 @@ func (c *Conn) SetPrivateKey(privateKey wgkey.Private) error { // Key changed. Close existing DERP connections and reconnect to home. if c.myDerp != 0 && !newKey.IsZero() { c.logf("magicsock: private key changed, reconnecting to home derp-%d", c.myDerp) - c.goDerpConnect(c.myDerp) + c.startDerpHomeConnectLocked() } if newKey.IsZero() { @@ -2630,12 +2639,11 @@ func (c *Conn) Rebind() { c.mu.Lock() c.closeAllDerpLocked("rebind") - haveKey := !c.privateKey.IsZero() + if !c.privateKey.IsZero() { + c.startDerpHomeConnectLocked() + } c.mu.Unlock() - if haveKey { - c.goDerpConnect(c.myDerp) - } c.resetEndpointStates() } From 1ec64bc94decede7840d50f602cb3bfe90eda3ef Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 10 Feb 2021 12:46:29 -0800 Subject: [PATCH 35/71] wgengine/router: add another Windows firewall rule to allow incoming UDP Based on @sailorfrag's research. Fixes #1312 Signed-off-by: Brad Fitzpatrick --- wgengine/router/router_windows.go | 44 +++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/wgengine/router/router_windows.go b/wgengine/router/router_windows.go index 0194ef0a1..b600709d3 100644 --- a/wgengine/router/router_windows.go +++ b/wgengine/router/router_windows.go @@ -7,6 +7,7 @@ package router import ( "context" "fmt" + "os" "os/exec" "sync" "syscall" @@ -121,11 +122,12 @@ func cleanup(logf logger.Logf, interfaceName string) { type firewallTweaker struct { logf logger.Logf - mu sync.Mutex - running bool // doAsyncSet goroutine is running - known bool // firewall is in known state (in lastVal) - want []string // next value we want, or "" to delete the firewall rule - lastVal []string // last set value, if known + mu sync.Mutex + didProcRule bool + running bool // doAsyncSet goroutine is running + known bool // firewall is in known state (in lastVal) + want []string // next value we want, or "" to delete the firewall rule + lastVal []string // last set value, if known } func (ft *firewallTweaker) clear() { ft.set(nil) } @@ -177,6 +179,7 @@ func (ft *firewallTweaker) doAsyncSet() { return } needClear := !ft.known || len(ft.lastVal) > 0 || len(val) == 0 + needProcRule := !ft.didProcRule ft.mu.Unlock() if needClear { @@ -189,6 +192,37 @@ func (ft *firewallTweaker) doAsyncSet() { d, _ := ft.runFirewall("delete", "rule", "name=Tailscale-In", "dir=in") ft.logf("cleared Tailscale-In firewall rules in %v", d) } + if needProcRule { + ft.logf("deleting any prior Tailscale-Process rule...") + d, err := ft.runFirewall("delete", "rule", "name=Tailscale-Process", "dir=in") // best effort + if err == nil { + ft.logf("removed old Tailscale-Process rule in %v", d) + } + var exe string + exe, err = os.Executable() + if err != nil { + ft.logf("failed to find Executable for Tailscale-Process rule: %v", err) + } else { + ft.logf("adding Tailscale-Process rule to allow UDP for %q ...", exe) + d, err = ft.runFirewall("add", "rule", "name=Tailscale-Process", + "dir=in", + "action=allow", + "edge=yes", + "program="+exe, + "protocol=udp", + "profile=any", + "enable=yes", + ) + if err != nil { + ft.logf("error adding Tailscale-Process rule: %v", err) + } else { + ft.mu.Lock() + ft.didProcRule = true + ft.mu.Unlock() + ft.logf("added Tailscale-Process rule in %v", d) + } + } + } var err error for _, cidr := range val { ft.logf("adding Tailscale-In rule to allow %v ...", cidr) From 635e4c74352b26078ec7a693de779d33105a4264 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 10 Feb 2021 11:49:30 -0800 Subject: [PATCH 36/71] wgengine/magicsock: increase legacy ping timeout again I based my estimation of the required timeout based on locally observed behavior. But CI machines are worse than my local machine. 16s was enough to reduce flakiness but not eliminate it. Bump it up again. Signed-off-by: Josh Bleecher Snyder --- util/cibuild/cibuild.go | 13 +++++++++++++ wgengine/magicsock/magicsock_test.go | 13 +++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 util/cibuild/cibuild.go diff --git a/util/cibuild/cibuild.go b/util/cibuild/cibuild.go new file mode 100644 index 000000000..b2d4af20c --- /dev/null +++ b/util/cibuild/cibuild.go @@ -0,0 +1,13 @@ +// 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 cibuild reports runtime CI information. +package cibuild + +import "os" + +// On reports whether the current binary is executing on a CI system. +func On() bool { + return os.Getenv("GITHUB_ACTIONS") != "" +} diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index a4e3ed96d..520fbe590 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -44,6 +44,7 @@ import ( "tailscale.com/types/netmap" "tailscale.com/types/nettype" "tailscale.com/types/wgkey" + "tailscale.com/util/cibuild" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/tstun" "tailscale.com/wgengine/wgcfg" @@ -929,10 +930,14 @@ func testTwoDevicePing(t *testing.T, d *devices) { // In the normal case, pings succeed immediately. // However, in the case of a handshake race, we need to retry. - // Typical retries take 5s. With very bad luck, we can need to retry - // multiple times. Give ourselves enough time for three retries - // plus a bit of processing time. - const pingTimeout = 16 * time.Second + // With very bad luck, we can need to retry multiple times. + allowedRetries := 3 + if cibuild.On() { + // Allow extra retries on small/flaky/loaded CI machines. + allowedRetries *= 2 + } + // Retries take 5s each. Add 1s for some processing time. + pingTimeout := 5*time.Second*time.Duration(allowedRetries) + time.Second ping1 := func(t *testing.T) { msg2to1 := tuntest.Ping(net.ParseIP("1.0.0.1"), net.ParseIP("1.0.0.2")) From 11bbfbd8bb6f05e1891bf60f533dcc98b7254a77 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 10 Feb 2021 14:14:11 -0800 Subject: [PATCH 37/71] go.mod: update to latest wireguard-go All changes are trivial. --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 8f47c2a10..c0266a0d0 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/pborman/getopt v0.0.0-20190409184431-ee0cd42419d3 github.com/peterbourgon/ff/v2 v2.0.0 github.com/tailscale/depaware v0.0.0-20201214215404-77d1e9757027 - github.com/tailscale/wireguard-go v0.0.0-20210210160038-385d6fdeb57a + github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222 github.com/tcnksm/go-httpstat v0.2.0 github.com/toqueteos/webbrowser v1.2.0 go4.org/mem v0.0.0-20201119185036-c04c5a6ff174 diff --git a/go.sum b/go.sum index 208ffae5d..afa7d830c 100644 --- a/go.sum +++ b/go.sum @@ -306,6 +306,8 @@ github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12 h1:kk8nOHkX github.com/tailscale/wireguard-go v0.0.0-20210209210853-838c6fc0dc12/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= github.com/tailscale/wireguard-go v0.0.0-20210210160038-385d6fdeb57a h1:zgMmUGUb2U3E9VerpED4MlIceYjTT0QgpGr3qJKHyBE= github.com/tailscale/wireguard-go v0.0.0-20210210160038-385d6fdeb57a/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= +github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222 h1:VzTS7LIwCH8jlxwrZguU0TsCLV/MDOunoNIDJdFajyM= +github.com/tailscale/wireguard-go v0.0.0-20210210202228-3cc76ed5f222/go.mod h1:6t0OVdJwFOKFnvaHaVMKG6GznWaHqkmiR2n3kH0t924= github.com/tcnksm/go-httpstat v0.2.0 h1:rP7T5e5U2HfmOBmZzGgGZjBQ5/GluWUylujl0tJ04I0= github.com/tcnksm/go-httpstat v0.2.0/go.mod h1:s3JVJFtQxtBEBC9dwcdTTXS9xFnM3SXAZwPG41aurT8= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= From de3001bc7922fc47bccc03f3ec5f605382c67858 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 10:53:33 -0800 Subject: [PATCH 38/71] cmd/hello: in dev mode, live reload template --- cmd/hello/hello.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index bd4145144..ecb536bda 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -29,6 +29,9 @@ var ( func main() { flag.Parse() + if !devMode() { + tmpl = template.Must(template.New("home").Parse(slurpHTML())) + } http.HandleFunc("/", root) log.Printf("Starting hello server.") @@ -61,7 +64,16 @@ func slurpHTML() string { return string(slurp) } -var tmpl = template.Must(template.New("home").Parse(slurpHTML())) +func devMode() bool { return *httpsAddr == "" && *httpAddr != "" } + +func getTmpl() (*template.Template, error) { + if devMode() { + return template.New("home").Parse(slurpHTML()) + } + return tmpl, nil +} + +var tmpl *template.Template // not used in dev mode, initialized by main after flag parse type tmplData struct { DisplayName string // "Foo Barberson" @@ -88,6 +100,13 @@ func root(w http.ResponseWriter, r *http.Request) { http.Error(w, "no remote addr", 500) return } + tmpl, err := getTmpl() + if err != nil { + w.Header().Set("Content-Type", "text/plain") + http.Error(w, "template error: "+err.Error(), 500) + return + } + who, err := whoIs(ip) if err != nil { log.Printf("whois(%q) error: %v", ip, err) From 34ffd4f7c64bd80eabb8d981dd945dc16430a3da Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 10:57:08 -0800 Subject: [PATCH 39/71] cmd/hello: serve fake data in dev mode on whois failure --- cmd/hello/hello.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index ecb536bda..2aa16f694 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -108,18 +108,30 @@ func root(w http.ResponseWriter, r *http.Request) { } who, err := whoIs(ip) + var data tmplData if err != nil { - log.Printf("whois(%q) error: %v", ip, err) - http.Error(w, "Your Tailscale works, but we failed to look you up.", 500) - return + if devMode() { + data = tmplData{ + DisplayName: "Taily Scalerson", + LoginName: "taily@scaler.son", + MachineName: "scaled", + IP: "100.1.2.3", + } + } else { + log.Printf("whois(%q) error: %v", ip, err) + http.Error(w, "Your Tailscale works, but we failed to look you up.", 500) + return + } + } else { + data = tmplData{ + DisplayName: who.UserProfile.DisplayName, + LoginName: who.UserProfile.LoginName, + MachineName: who.Node.ComputedName, + IP: ip, + } } w.Header().Set("Content-Type", "text/html; charset=utf-8") - tmpl.Execute(w, tmplData{ - DisplayName: who.UserProfile.DisplayName, - LoginName: who.UserProfile.LoginName, - MachineName: who.Node.ComputedName, - IP: ip, - }) + tmpl.Execute(w, data) } // tsSockClient does HTTP requests to the local Tailscale daemon. From 917307a90c90b668cdd66dd64792d973f8ecdfca Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 11:51:59 -0800 Subject: [PATCH 40/71] wgengine/tstun: reply to MagicDNS pings Fixes #849 Signed-off-by: Brad Fitzpatrick --- wgengine/tstun/tun.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/wgengine/tstun/tun.go b/wgengine/tstun/tun.go index ef3be6016..92af1b8b0 100644 --- a/wgengine/tstun/tun.go +++ b/wgengine/tstun/tun.go @@ -215,7 +215,17 @@ func (t *TUN) poll() { } } +var magicDNSIPPort = netaddr.MustParseIPPort("100.100.100.100:0") + func (t *TUN) filterOut(p *packet.Parsed) filter.Response { + // Fake ICMP echo responses to MagicDNS (100.100.100.100). + if p.IsEchoRequest() && p.Dst == magicDNSIPPort { + header := p.ICMP4Header() + header.ToResponse() + outp := packet.Generate(&header, p.Payload()) + t.InjectInboundCopy(outp) + return filter.DropSilently // don't pass on to OS; already handled + } if t.PreFilterOut != nil { if res := t.PreFilterOut(p, t); res.IsDrop() { From 6075135e0add38aa51fbf53d575380a56ceef7f3 Mon Sep 17 00:00:00 2001 From: Ross Zurowski Date: Thu, 11 Feb 2021 17:42:07 -0500 Subject: [PATCH 41/71] cmd/hello: style welcome message (#1325) Signed-off-by: Ross Zurowski --- cmd/hello/hello.go | 30 +-- cmd/hello/hello.tmpl.html | 436 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 438 insertions(+), 28 deletions(-) diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index 2aa16f694..8b0b19dab 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -76,10 +76,12 @@ func getTmpl() (*template.Template, error) { var tmpl *template.Template // not used in dev mode, initialized by main after flag parse type tmplData struct { - DisplayName string // "Foo Barberson" - LoginName string // "foo@bar.com" - MachineName string // "imac5k" - IP string // "100.2.3.4" + DisplayName string // "Foo Barberson" + LoginName string // "foo@bar.com" + ProfilePicURL string // "https://..." + MachineName string // "imac5k" + MachineOS string // "Linux" + IP string // "100.2.3.4" } func root(w http.ResponseWriter, r *http.Request) { @@ -112,10 +114,12 @@ func root(w http.ResponseWriter, r *http.Request) { if err != nil { if devMode() { data = tmplData{ - DisplayName: "Taily Scalerson", - LoginName: "taily@scaler.son", - MachineName: "scaled", - IP: "100.1.2.3", + DisplayName: "Taily Scalerson", + LoginName: "taily@scaler.son", + ProfilePicURL: "https://placekitten.com/200/200", + MachineName: "scaled", + MachineOS: "Linux", + IP: "100.1.2.3", } } else { log.Printf("whois(%q) error: %v", ip, err) @@ -124,10 +128,12 @@ func root(w http.ResponseWriter, r *http.Request) { } } else { data = tmplData{ - DisplayName: who.UserProfile.DisplayName, - LoginName: who.UserProfile.LoginName, - MachineName: who.Node.ComputedName, - IP: ip, + DisplayName: who.UserProfile.DisplayName, + LoginName: who.UserProfile.LoginName, + ProfilePicURL: who.UserProfile.ProfilePicURL, + MachineName: who.Node.ComputedName, + MachineOS: who.Node.Hostinfo.OS, + IP: ip, } } w.Header().Set("Content-Type", "text/html; charset=utf-8") diff --git a/cmd/hello/hello.tmpl.html b/cmd/hello/hello.tmpl.html index bf43b65ac..238a2528b 100644 --- a/cmd/hello/hello.tmpl.html +++ b/cmd/hello/hello.tmpl.html @@ -1,17 +1,421 @@ - - - Hello from Tailscale - - -

Hello!

-

- Hello {{.DisplayName}} ({{.LoginName}}) from {{.MachineName}} ({{.IP}}). -

-

- Your Tailscale is working! -

-

- Welcome to Tailscale. -

- + + + + + + Hello from Tailscale + + + + +
+ +
+

You're connected over Tailscale!

+

This device is signed in as…

+
+
+
+ + + +
+
+
+
+ {{ with .DisplayName }} +

{{.}}

+ {{ end }} +
{{.LoginName}}
+
+
+
+
+ + + + + + +

{{.MachineName}}

+
+
{{.IP}}
+
+
+ +
+ From 537877604301299d439361585688655bcc92e626 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 16:38:26 -0800 Subject: [PATCH 42/71] cmd/hello: chop DNS name at first dot --- cmd/hello/hello.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index 8b0b19dab..e768c1ef2 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -131,7 +131,7 @@ func root(w http.ResponseWriter, r *http.Request) { DisplayName: who.UserProfile.DisplayName, LoginName: who.UserProfile.LoginName, ProfilePicURL: who.UserProfile.ProfilePicURL, - MachineName: who.Node.ComputedName, + MachineName: firstLabel(who.Node.ComputedName), MachineOS: who.Node.Hostinfo.OS, IP: ip, } @@ -140,6 +140,14 @@ func root(w http.ResponseWriter, r *http.Request) { tmpl.Execute(w, data) } +// firstLabel s up until the first period, if any. +func firstLabel(s string) string { + if i := strings.Index(s, "."); i != -1 { + return s[:i] + } + return s +} + // tsSockClient does HTTP requests to the local Tailscale daemon. // The hostname in the HTTP request is ignored. var tsSockClient = &http.Client{ From 25321cbd01b5823ef3279b0638678e8234cd180d Mon Sep 17 00:00:00 2001 From: Ross Zurowski Date: Thu, 11 Feb 2021 20:56:22 -0500 Subject: [PATCH 43/71] cmd/hello: truncate long strings (#1328) Signed-off-by: Ross Zurowski --- cmd/hello/hello.tmpl.html | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/cmd/hello/hello.tmpl.html b/cmd/hello/hello.tmpl.html index 238a2528b..ce64a615f 100644 --- a/cmd/hello/hello.tmpl.html +++ b/cmd/hello/hello.tmpl.html @@ -121,6 +121,10 @@ width: 100%; } + .min-width-0 { + min-width: 0; + } + .rounded-lg { border-radius: 0.5rem; } @@ -222,12 +226,23 @@ font-weight: 400; } + .truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .overflow-hidden { + overflow: hidden; + } + .profile-pic { width: 2.5rem; height: 2.5rem; border-radius: 9999px; background-size: cover; margin-right: 0.5rem; + flex-shrink: 0; } .panel { @@ -388,18 +403,18 @@ -
+
-
+
{{ with .DisplayName }} -

{{.}}

+

{{.}}

{{ end }} -
{{.LoginName}}
+
{{.LoginName}}
-
+
@@ -407,7 +422,7 @@ -

{{.MachineName}}

+

{{.MachineName}}

{{.IP}}
From 88ab0173a7b46b5768fa907c0b3eb655f677c708 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 19:13:03 -0800 Subject: [PATCH 44/71] wgengine/router: fix BSD router to support multiple local addrs, IPv6 Fixes #1201 --- wgengine/router/router_userspace_bsd.go | 111 +++++++++++++++--------- 1 file changed, 68 insertions(+), 43 deletions(-) diff --git a/wgengine/router/router_userspace_bsd.go b/wgengine/router/router_userspace_bsd.go index 33848c8bb..fb81d62fb 100644 --- a/wgengine/router/router_userspace_bsd.go +++ b/wgengine/router/router_userspace_bsd.go @@ -7,7 +7,6 @@ package router import ( - "errors" "fmt" "log" "os/exec" @@ -23,7 +22,7 @@ import ( type userspaceBSDRouter struct { logf logger.Logf tunname string - local netaddr.IPPrefix + local []netaddr.IPPrefix routes map[netaddr.IPPrefix]struct{} dns *dns.Manager @@ -47,6 +46,38 @@ func newUserspaceBSDRouter(logf logger.Logf, _ *device.Device, tundev tun.Device }, nil } +func (r *userspaceBSDRouter) addrsToRemove(newLocalAddrs []netaddr.IPPrefix) (remove []netaddr.IPPrefix) { + for _, cur := range r.local { + found := false + for _, v := range newLocalAddrs { + found = (v == cur) + if found { + break + } + } + if !found { + remove = append(remove, cur) + } + } + return +} + +func (r *userspaceBSDRouter) addrsToAdd(newLocalAddrs []netaddr.IPPrefix) (add []netaddr.IPPrefix) { + for _, cur := range newLocalAddrs { + found := false + for _, v := range r.local { + found = (v == cur) + if found { + break + } + } + if !found { + add = append(add, cur) + } + } + return +} + func cmd(args ...string) *exec.Cmd { if len(args) == 0 { log.Fatalf("exec.Cmd(%#v) invalid; need argv[0]", args) @@ -63,45 +94,40 @@ func (r *userspaceBSDRouter) Up() error { return nil } -func (r *userspaceBSDRouter) Set(cfg *Config) error { +func inet(p netaddr.IPPrefix) string { + if p.IP.Is6() { + return "inet6" + } + return "inet" +} + +func (r *userspaceBSDRouter) Set(cfg *Config) (reterr error) { if cfg == nil { cfg = &shutdownConfig } - if len(cfg.LocalAddrs) == 0 { - return nil - } - // TODO: support configuring multiple local addrs on interface. - if len(cfg.LocalAddrs) != 1 { - return errors.New("freebsd doesn't support setting multiple local addrs yet") - } - localAddr := cfg.LocalAddrs[0] var errq error - - // Update the address. - if localAddr != r.local { - // If the interface is already set, remove it. - if !r.local.IsZero() { - addrdel := []string{"ifconfig", r.tunname, - "inet", r.local.String(), "-alias"} - out, err := cmd(addrdel...).CombinedOutput() - if err != nil { - r.logf("addr del failed: %v: %v\n%s", addrdel, err, out) - if errq == nil { - errq = err - } - } + setErr := func(err error) { + if errq == nil { + errq = err } + } - // Add the interface. - addradd := []string{"ifconfig", r.tunname, - "inet", localAddr.String(), localAddr.IP.String()} - out, err := cmd(addradd...).CombinedOutput() + // Update the addresses. + for _, addr := range r.addrsToRemove(cfg.LocalAddrs) { + arg := []string{"ifconfig", r.tunname, inet(addr), addr.String(), "-alias"} + out, err := cmd(arg...).CombinedOutput() if err != nil { - r.logf("addr add failed: %v: %v\n%s", addradd, err, out) - if errq == nil { - errq = err - } + r.logf("addr del failed: %v => %v\n%s", arg, err, out) + setErr(err) + } + } + for _, addr := range r.addrsToAdd(cfg.LocalAddrs) { + arg := []string{"ifconfig", r.tunname, inet(addr), addr.String(), addr.IP.String()} + out, err := cmd(arg...).CombinedOutput() + if err != nil { + r.logf("addr add failed: %v => %v\n%s", arg, err, out) + setErr(err) } } @@ -120,14 +146,12 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error { del = "delete" } routedel := []string{"route", "-q", "-n", - del, "-inet", nstr, + del, "-" + inet(route), nstr, "-iface", r.tunname} out, err := cmd(routedel...).CombinedOutput() if err != nil { r.logf("route del failed: %v: %v\n%s", routedel, err, out) - if errq == nil { - errq = err - } + setErr(err) } } } @@ -138,24 +162,25 @@ func (r *userspaceBSDRouter) Set(cfg *Config) error { nip := net.IP.Mask(net.Mask) nstr := fmt.Sprintf("%v/%d", nip, route.Bits) routeadd := []string{"route", "-q", "-n", - "add", "-inet", nstr, + "add", "-" + inet(route), nstr, "-iface", r.tunname} out, err := cmd(routeadd...).CombinedOutput() if err != nil { r.logf("addr add failed: %v: %v\n%s", routeadd, err, out) - if errq == nil { - errq = err - } + setErr(err) } } } // Store the interface and routes so we know what to change on an update. - r.local = localAddr + if errq == nil { + r.local = append([]netaddr.IPPrefix{}, cfg.LocalAddrs...) + } r.routes = newRoutes if err := r.dns.Set(cfg.DNS); err != nil { - errq = fmt.Errorf("dns set: %v", err) + r.logf("DNS set: %v", err) + setErr(err) } return errq From 6680976b502c8e1b733eac1591c53257effcc3a5 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 20:10:07 -0800 Subject: [PATCH 45/71] cmd/tailscaled: pick automatic tun device name on darwin --- cmd/tailscaled/tailscaled.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 64e2723bb..4ad437c22 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -52,6 +52,10 @@ func defaultTunName() string { return "tun" case "windows": return "Tailscale" + case "darwin": + // "utun" is recognized by wireguard-go/tun/tun_darwin.go + // as a magic value that uses/creates any free number. + return "utun" } return "tailscale0" } From be906dabd4ed6cce495cceaf24691523d7c5a360 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 11 Feb 2021 20:11:00 -0800 Subject: [PATCH 46/71] version: bump date --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index 5ddd6ff91..9811be774 100644 --- a/version/version.go +++ b/version/version.go @@ -10,7 +10,7 @@ package version // Long is a full version number for this build, of the form // "x.y.z-commithash", or "date.yyyymmdd" if no actual version was // provided. -const Long = "date.20210104" +const Long = "date.20210211" // Short is a short version number for this build, of the form // "x.y.z", or "date.yyyymmdd" if no actual version was provided. From 4cd9218351d3dcfe74a97ffa932c72b931b494cd Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Thu, 11 Feb 2021 11:54:58 -0800 Subject: [PATCH 47/71] wgengine/magicsock: prevent logging while running benchmarks Co-authored-by: Sonia Appasamy Signed-off-by: Josh Bleecher Snyder --- wgengine/magicsock/magicsock_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 520fbe590..274735cd0 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1552,6 +1552,7 @@ func addTestEndpoint(conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tail func BenchmarkReceiveFrom(b *testing.B) { conn := newNonLegacyTestConn(b) defer conn.Close() + conn.logf = logger.Discard sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { From 0c673c1344411b34e760623de80709a48fa8b8cc Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Thu, 11 Feb 2021 12:39:56 -0800 Subject: [PATCH 48/71] wgengine/magicsock: unify on netaddr types in addrSet addrSet maintained duplicate lists of netaddr.IPPorts and net.UDPAddrs. Unify to use the netaddr type only. This makes (*Conn).ReceiveIPvN a bit uglier, but that'll be cleaned up in a subsequent commit. This is preparatory work to remove an allocation from ReceiveIPv4. Co-authored-by: Sonia Appasamy Signed-off-by: Josh Bleecher Snyder --- wgengine/magicsock/legacy.go | 78 ++++++++++------------------ wgengine/magicsock/magicsock.go | 50 ++++++++++-------- wgengine/magicsock/magicsock_test.go | 75 +++++++------------------- 3 files changed, 74 insertions(+), 129 deletions(-) diff --git a/wgengine/magicsock/legacy.go b/wgengine/magicsock/legacy.go index 7620cc1ce..eb4ce9da6 100644 --- a/wgengine/magicsock/legacy.go +++ b/wgengine/magicsock/legacy.go @@ -53,7 +53,6 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.End return nil, fmt.Errorf("bogus address %q", ep) } a.ipPorts = append(a.ipPorts, ipp) - a.addrs = append(a.addrs, *ipp.UDPAddr()) } } @@ -84,14 +83,14 @@ func (c *Conn) createLegacyEndpointLocked(pk key.Public, addrs string) (conn.End return a, nil } -func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint { +func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, packet []byte) conn.Endpoint { if c.disableLegacy { return nil } // Pre-disco: look up their addrSet. if as, ok := c.addrsByUDP[ipp]; ok { - as.updateDst(addr) + as.updateDst(ipp) return as } @@ -100,7 +99,7 @@ func (c *Conn) findLegacyEndpointLocked(ipp netaddr.IPPort, addr *net.UDPAddr, p // know. If this is a handshake packet, we can try to identify the // peer in question. if as := c.peerFromPacketLocked(packet); as != nil { - as.updateDst(addr) + as.updateDst(ipp) return as } @@ -268,14 +267,6 @@ func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP as.lastSend = now - // Some internal invariant checks. - if len(as.addrs) != len(as.ipPorts) { - panic(fmt.Sprintf("lena %d != leni %d", len(as.addrs), len(as.ipPorts))) - } - if n1, n2 := as.roamAddr != nil, as.roamAddrStd != nil; n1 != n2 { - panic(fmt.Sprintf("roamnil %v != roamstdnil %v", n1, n2)) - } - // Spray logic. // // After exchanging a handshake with a peer, we send some outbound @@ -320,8 +311,8 @@ func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP // roamAddr should be special like this. dsts = append(dsts, *as.roamAddr) case as.curAddr != -1: - if as.curAddr >= len(as.addrs) { - as.Logf("[unexpected] magicsock bug: as.curAddr >= len(as.addrs): %d >= %d", as.curAddr, len(as.addrs)) + if as.curAddr >= len(as.ipPorts) { + as.Logf("[unexpected] magicsock bug: as.curAddr >= len(as.ipPorts): %d >= %d", as.curAddr, len(as.ipPorts)) break } // No roaming addr, but we've seen packets from a known peer @@ -352,15 +343,14 @@ func (as *addrSet) appendDests(dsts []netaddr.IPPort, b []byte) (_ []netaddr.IPP type addrSet struct { publicKey key.Public // peer public key used for DERP communication - // addrs is an ordered priority list provided by wgengine, + // ipPorts is an ordered priority list provided by wgengine, // sorted from expensive+slow+reliable at the begnining to // fast+cheap at the end. More concretely, it's typically: // // [DERP fakeip:node, Global IP:port, LAN ip:port] // // But there could be multiple or none of each. - addrs []net.UDPAddr - ipPorts []netaddr.IPPort // same as addrs, in different form + ipPorts []netaddr.IPPort // clock, if non-nil, is used in tests instead of time.Now. clock func() time.Time @@ -376,8 +366,7 @@ type addrSet struct { // this should hopefully never be used (or at least used // rarely) in the case that all the components of Tailscale // are correctly learning/sharing the network map details. - roamAddr *netaddr.IPPort - roamAddrStd *net.UDPAddr + roamAddr *netaddr.IPPort // curAddr is an index into addrs of the highest-priority // address a valid packet has been received from so far. @@ -400,9 +389,9 @@ type addrSet struct { // derpID returns this addrSet's home DERP node, or 0 if none is found. func (as *addrSet) derpID() int { - for _, ua := range as.addrs { - if ua.IP.Equal(derpMagicIP) { - return ua.Port + for _, ua := range as.ipPorts { + if ua.IP == derpMagicIPAddr { + return int(ua.Port) } } return 0 @@ -424,7 +413,7 @@ func (a *addrSet) dst() netaddr.IPPort { if a.roamAddr != nil { return *a.roamAddr } - if len(a.addrs) == 0 { + if len(a.ipPorts) == 0 { return noAddr } i := a.curAddr @@ -439,7 +428,7 @@ func (a *addrSet) DstToBytes() []byte { } func (a *addrSet) DstToString() string { var addrs []string - for _, addr := range a.addrs { + for _, addr := range a.ipPorts { addrs = append(addrs, addr.String()) } @@ -459,8 +448,8 @@ func (a *addrSet) ClearSrc() {} // updateDst records receipt of a packet from new. This is used to // potentially update the transmit address used for this addrSet. -func (a *addrSet) updateDst(new *net.UDPAddr) error { - if new.IP.Equal(derpMagicIP) { +func (a *addrSet) updateDst(new netaddr.IPPort) error { + if new.IP == derpMagicIPAddr { // Never consider DERP addresses as a viable candidate for // either curAddr or roamAddr. It's only ever a last resort // choice, never a preferred choice. @@ -471,25 +460,20 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { a.mu.Lock() defer a.mu.Unlock() - if a.roamAddrStd != nil && equalUDPAddr(new, a.roamAddrStd) { + if a.roamAddr != nil && new == *a.roamAddr { // Packet from the current roaming address, no logging. // This is a hot path for established connections. return nil } - if a.roamAddr == nil && a.curAddr >= 0 && equalUDPAddr(new, &a.addrs[a.curAddr]) { + if a.roamAddr == nil && a.curAddr >= 0 && new == a.ipPorts[a.curAddr] { // Packet from current-priority address, no logging. // This is a hot path for established connections. return nil } - newa, ok := netaddr.FromStdAddr(new.IP, new.Port, new.Zone) - if !ok { - return nil - } - index := -1 - for i := range a.addrs { - if equalUDPAddr(new, &a.addrs[i]) { + for i := range a.ipPorts { + if new == a.ipPorts[i] { index = i break } @@ -499,7 +483,7 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { pk := publicKey.ShortString() old := "" if a.curAddr >= 0 { - old = a.addrs[a.curAddr].String() + old = a.ipPorts[a.curAddr].String() } switch { @@ -509,18 +493,16 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { } else { a.Logf("magicsock: rx %s from roaming address %s, replaces roaming address %s", pk, new, a.roamAddr) } - a.roamAddr = &newa - a.roamAddrStd = new + a.roamAddr = &new case a.roamAddr != nil: a.Logf("magicsock: rx %s from known %s (%d), replaces roaming address %s", pk, new, index, a.roamAddr) a.roamAddr = nil - a.roamAddrStd = nil a.curAddr = index a.loggedLogPriMask = 0 case a.curAddr == -1: - a.Logf("magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.addrs)) + a.Logf("magicsock: rx %s from %s (%d/%d), set as new priority", pk, new, index, len(a.ipPorts)) a.curAddr = index a.loggedLogPriMask = 0 @@ -531,7 +513,7 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { } default: // index > a.curAddr - a.Logf("magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.addrs), old) + a.Logf("magicsock: rx %s from %s (%d/%d), replaces old priority %s", pk, new, index, len(a.ipPorts), old) a.curAddr = index a.loggedLogPriMask = 0 } @@ -539,10 +521,6 @@ func (a *addrSet) updateDst(new *net.UDPAddr) error { return nil } -func equalUDPAddr(x, y *net.UDPAddr) bool { - return x.Port == y.Port && x.IP.Equal(y.IP) -} - func (a *addrSet) String() string { a.mu.Lock() defer a.mu.Unlock() @@ -551,9 +529,9 @@ func (a *addrSet) String() string { buf.WriteByte('[') if a.roamAddr != nil { buf.WriteString("roam:") - sbPrintAddr(buf, *a.roamAddrStd) + sbPrintAddr(buf, *a.roamAddr) } - for i, addr := range a.addrs { + for i, addr := range a.ipPorts { if i > 0 || a.roamAddr != nil { buf.WriteString(", ") } @@ -572,8 +550,8 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) { defer as.mu.Unlock() ps.LastWrite = as.lastSend - for i, ua := range as.addrs { - if ua.IP.Equal(derpMagicIP) { + for i, ua := range as.ipPorts { + if ua.IP == derpMagicIPAddr { continue } uaStr := ua.String() @@ -583,7 +561,7 @@ func (as *addrSet) populatePeerStatus(ps *ipnstate.PeerStatus) { } } if as.roamAddr != nil { - ps.CurAddr = udpAddrDebugString(*as.roamAddrStd) + ps.CurAddr = ippDebugString(*as.roamAddr) } } diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index c3c2c9027..afeeb8323 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -348,8 +348,7 @@ func (c *Conn) addDerpPeerRoute(peer key.Public, derpID int, dc *derphttp.Client // Mnemonic: 3.3.40 are numbers above the keys D, E, R, P. const DerpMagicIP = "127.3.3.40" -var derpMagicIP = net.ParseIP(DerpMagicIP).To4() -var derpMagicIPAddr = netaddr.IPv4(127, 3, 3, 40) +var derpMagicIPAddr = netaddr.MustParseIP(DerpMagicIP) // activeDerp contains fields for an active DERP connection. type activeDerp struct { @@ -1539,7 +1538,6 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan // findEndpoint maps from a UDP address to a WireGuard endpoint, for // ReceiveIPv4/ReceiveIPv6. -// The provided addr and ipp must match. // // TODO(bradfitz): add a fast path that returns nil here for normal // wireguard-go transport packets; wireguard-go only uses this @@ -1547,7 +1545,7 @@ func (c *Conn) runDerpWriter(ctx context.Context, dc *derphttp.Client, ch <-chan // Endpoint to find the UDPAddr to return to wireguard anyway, so no // benefit unless we can, say, always return the same fake UDPAddr for // all packets. -func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte) conn.Endpoint { +func (c *Conn) findEndpoint(ipp netaddr.IPPort, packet []byte) conn.Endpoint { c.mu.Lock() defer c.mu.Unlock() @@ -1559,10 +1557,7 @@ func (c *Conn) findEndpoint(ipp netaddr.IPPort, addr *net.UDPAddr, packet []byte } } - if addr == nil { - addr = ipp.UDPAddr() - } - return c.findLegacyEndpointLocked(ipp, addr, packet) + return c.findLegacyEndpointLocked(ipp, packet) } // aLongTimeAgo is a non-zero time, far in the past, used for @@ -1590,7 +1585,12 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) { if err != nil { return 0, nil, err } - if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint6); ok { + udpAddr := pAddr.(*net.UDPAddr) + ipp, ok := netaddr.FromStdAddr(udpAddr.IP, udpAddr.Port, udpAddr.Zone) + if !ok { + continue + } + if ep, ok := c.receiveIP(b[:n], ipp, &c.ippEndpoint6); ok { return n, ep, nil } } @@ -1604,13 +1604,16 @@ func (c *Conn) derpPacketArrived() bool { // In Tailscale's case, that packet might also arrive via DERP. A DERP packet arrival // aborts the pconn4 read deadline to make it fail. func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { - var pAddr net.Addr + var addr net.Addr + var pAddr *net.UDPAddr + var ipp netaddr.IPPort + var ippOK bool for { // Drain DERP queues before reading new UDP packets. if c.derpPacketArrived() { goto ReadDERP } - n, pAddr, err = c.pconn4.ReadFrom(b) + n, addr, err = c.pconn4.ReadFrom(b) if err != nil { // If the pconn4 read failed, the likely reason is a DERP reader received // a packet and interrupted us. @@ -1622,7 +1625,12 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { } return 0, nil, err } - if ep, ok := c.receiveIP(b[:n], pAddr.(*net.UDPAddr), &c.ippEndpoint4); ok { + pAddr, _ = addr.(*net.UDPAddr) + ipp, ippOK = netaddr.FromStdAddr(pAddr.IP, pAddr.Port, pAddr.Zone) + if !ippOK { + continue + } + if ep, ok := c.receiveIP(b[:n], ipp, &c.ippEndpoint4); ok { return n, ep, nil } else { continue @@ -1640,11 +1648,7 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { // // ok is whether this read should be reported up to wireguard-go (our // caller). -func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) { - ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone) - if !ok { - return nil, false - } +func (c *Conn) receiveIP(b []byte, ipp netaddr.IPPort, cache *ippEndpointCache) (ep conn.Endpoint, ok bool) { if stun.Is(b) { c.stunReceiveFunc.Load().(func([]byte, netaddr.IPPort))(b, ipp) return nil, false @@ -1662,7 +1666,7 @@ func (c *Conn) receiveIP(b []byte, ua *net.UDPAddr, cache *ippEndpointCache) (ep if cache.ipp == ipp && cache.de != nil && cache.gen == cache.de.numStopAndReset() { ep = cache.de } else { - ep = c.findEndpoint(ipp, ua, b) + ep = c.findEndpoint(ipp, b) if ep == nil { return nil, false } @@ -1759,7 +1763,7 @@ func (c *Conn) receiveIPv4DERP(b []byte) (n int, ep conn.Endpoint, err error) { } else { key := wgkey.Key(dm.src) c.logf("magicsock: DERP packet from unknown key: %s", key.ShortString()) - ep = c.findEndpoint(ipp, nil, b[:n]) + ep = c.findEndpoint(ipp, b[:n]) if ep == nil { return 0, nil, errLoopAgain } @@ -2833,8 +2837,8 @@ func peerShort(k key.Public) string { return k2.ShortString() } -func sbPrintAddr(sb *strings.Builder, a net.UDPAddr) { - is6 := a.IP.To4() == nil +func sbPrintAddr(sb *strings.Builder, a netaddr.IPPort) { + is6 := a.IP.Is6() if is6 { sb.WriteByte('[') } @@ -2931,8 +2935,8 @@ func (c *Conn) UpdateStatus(sb *ipnstate.StatusBuilder) { }) } -func udpAddrDebugString(ua net.UDPAddr) string { - if ua.IP.Equal(derpMagicIP) { +func ippDebugString(ua netaddr.IPPort) string { + if ua.IP == derpMagicIPAddr { return fmt.Sprintf("derp-%d", ua.Port) } return ua.String() diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 274735cd0..ea00fe22e 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -398,18 +398,6 @@ func pickPort(t testing.TB) uint16 { return uint16(conn.LocalAddr().(*net.UDPAddr).Port) } -func TestDerpIPConstant(t *testing.T) { - tstest.PanicOnLog() - tstest.ResourceCheck(t) - - if DerpMagicIP != derpMagicIP.String() { - t.Errorf("str %q != IP %v", DerpMagicIP, derpMagicIP) - } - if len(derpMagicIP) != 4 { - t.Errorf("derpMagicIP is len %d; want 4", len(derpMagicIP)) - } -} - func TestPickDERPFallback(t *testing.T) { tstest.PanicOnLog() tstest.ResourceCheck(t) @@ -452,7 +440,7 @@ func TestPickDERPFallback(t *testing.T) { // But move if peers are elsewhere. const otherNode = 789 c.addrsByKey = map[key.Public]*addrSet{ - key.Public{1}: &addrSet{addrs: []net.UDPAddr{{IP: derpMagicIP, Port: otherNode}}}, + key.Public{1}: &addrSet{ipPorts: []netaddr.IPPort{{IP: derpMagicIPAddr, Port: otherNode}}}, } if got := c.pickDERPFallback(); got != otherNode { t.Errorf("didn't join peers: got %v; want %v", got, someNode) @@ -1156,20 +1144,13 @@ func TestAddrSet(t *testing.T) { tstest.ResourceCheck(t) mustIPPortPtr := func(s string) *netaddr.IPPort { - t.Helper() - ipp, err := netaddr.ParseIPPort(s) - if err != nil { - t.Fatal(err) - } + ipp := netaddr.MustParseIPPort(s) return &ipp } - mustUDPAddr := func(s string) *net.UDPAddr { - return mustIPPortPtr(s).UDPAddr() - } - udpAddrs := func(ss ...string) (ret []net.UDPAddr) { + ipps := func(ss ...string) (ret []netaddr.IPPort) { t.Helper() for _, s := range ss { - ret = append(ret, *mustUDPAddr(s)) + ret = append(ret, netaddr.MustParseIPPort(s)) } return ret } @@ -1201,7 +1182,7 @@ func TestAddrSet(t *testing.T) { // updateDst, if set, does an UpdateDst call and // b+want are ignored. - updateDst *net.UDPAddr + updateDst *netaddr.IPPort b []byte want string // comma-separated @@ -1215,7 +1196,7 @@ func TestAddrSet(t *testing.T) { { name: "reg_packet_no_curaddr", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: -1, // unknown roamAddr: nil, }, @@ -1226,7 +1207,7 @@ func TestAddrSet(t *testing.T) { { name: "reg_packet_have_curaddr", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 1, // global IP roamAddr: nil, }, @@ -1237,36 +1218,36 @@ func TestAddrSet(t *testing.T) { { name: "reg_packet_have_roamaddr", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, // should be ignored roamAddr: mustIPPortPtr("5.6.7.8:123"), }, steps: []step{ {b: regPacket, want: "5.6.7.8:123"}, - {updateDst: mustUDPAddr("10.0.0.1:123")}, // no more roaming + {updateDst: mustIPPortPtr("10.0.0.1:123")}, // no more roaming {b: regPacket, want: "10.0.0.1:123"}, }, }, { name: "start_roaming", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, }, steps: []step{ {b: regPacket, want: "10.0.0.1:123"}, - {updateDst: mustUDPAddr("4.5.6.7:123")}, + {updateDst: mustIPPortPtr("4.5.6.7:123")}, {b: regPacket, want: "4.5.6.7:123"}, - {updateDst: mustUDPAddr("5.6.7.8:123")}, + {updateDst: mustIPPortPtr("5.6.7.8:123")}, {b: regPacket, want: "5.6.7.8:123"}, - {updateDst: mustUDPAddr("123.45.67.89:123")}, // end roaming + {updateDst: mustIPPortPtr("123.45.67.89:123")}, // end roaming {b: regPacket, want: "123.45.67.89:123"}, }, }, { name: "spray_packet", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, // should be ignored roamAddr: mustIPPortPtr("5.6.7.8:123"), }, @@ -1275,19 +1256,19 @@ func TestAddrSet(t *testing.T) { {advance: 300 * time.Millisecond, b: regPacket, want: "127.3.3.40:1,123.45.67.89:123,10.0.0.1:123,5.6.7.8:123"}, {advance: 300 * time.Millisecond, b: regPacket, want: "127.3.3.40:1,123.45.67.89:123,10.0.0.1:123,5.6.7.8:123"}, {advance: 3, b: regPacket, want: "5.6.7.8:123"}, - {advance: 2 * time.Millisecond, updateDst: mustUDPAddr("10.0.0.1:123")}, + {advance: 2 * time.Millisecond, updateDst: mustIPPortPtr("10.0.0.1:123")}, {advance: 3, b: regPacket, want: "10.0.0.1:123"}, }, }, { name: "low_pri", as: &addrSet{ - addrs: udpAddrs("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), + ipPorts: ipps("127.3.3.40:1", "123.45.67.89:123", "10.0.0.1:123"), curAddr: 2, }, steps: []step{ - {updateDst: mustUDPAddr("123.45.67.89:123")}, - {updateDst: mustUDPAddr("123.45.67.89:123")}, + {updateDst: mustIPPortPtr("123.45.67.89:123")}, + {updateDst: mustIPPortPtr("123.45.67.89:123")}, }, logCheck: func(t *testing.T, logged []byte) { if n := bytes.Count(logged, []byte(", keeping current ")); n != 1 { @@ -1306,12 +1287,11 @@ func TestAddrSet(t *testing.T) { t.Logf(format, args...) } tt.as.clock = func() time.Time { return faket } - initAddrSet(tt.as) for i, st := range tt.steps { faket = faket.Add(st.advance) if st.updateDst != nil { - if err := tt.as.updateDst(st.updateDst); err != nil { + if err := tt.as.updateDst(*st.updateDst); err != nil { t.Fatal(err) } continue @@ -1328,23 +1308,6 @@ func TestAddrSet(t *testing.T) { } } -// initAddrSet initializes fields in the provided incomplete addrSet -// to satisfying invariants within magicsock. -func initAddrSet(as *addrSet) { - if as.roamAddr != nil && as.roamAddrStd == nil { - as.roamAddrStd = as.roamAddr.UDPAddr() - } - if len(as.ipPorts) == 0 { - for _, ua := range as.addrs { - ipp, ok := netaddr.FromStdAddr(ua.IP, ua.Port, ua.Zone) - if !ok { - panic(fmt.Sprintf("bogus UDPAddr %+v", ua)) - } - as.ipPorts = append(as.ipPorts, ipp) - } - } -} - func TestDiscoMessage(t *testing.T) { c := newConn() c.logf = t.Logf From 88586ec4a43542b758d6f4e15990573970fb4e8a Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Thu, 11 Feb 2021 13:35:06 -0800 Subject: [PATCH 49/71] wgengine/magicsock: remove an alloc from ReceiveIPvN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We modified the standard net package to not allocate a *net.UDPAddr during a call to (*net.UDPConn).ReadFromUDP if the caller's use of the *net.UDPAddr does not cause it to escape. That is https://golang.org/cl/291390. This is the companion change to magicsock. There are two changes required. First, call ReadFromUDP instead of ReadFrom, if possible. ReadFrom returns a net.Addr, which is an interface, which always allocates. Second, reduce the lifetime of the returned *net.UDPAddr. We do this by immediately converting it into a netaddr.IPPort. We left the existing RebindingUDPConn.ReadFrom method in place, as it is required to satisfy the net.PacketConn interface. With the upstream change and both of these fixes in place, we have removed one large allocation per packet received. name old time/op new time/op delta ReceiveFrom-8 16.7µs ± 5% 16.4µs ± 8% ~ (p=0.310 n=5+5) name old alloc/op new alloc/op delta ReceiveFrom-8 112B ± 0% 64B ± 0% -42.86% (p=0.008 n=5+5) name old allocs/op new allocs/op delta ReceiveFrom-8 3.00 ± 0% 2.00 ± 0% -33.33% (p=0.008 n=5+5) Co-authored-by: Sonia Appasamy Signed-off-by: Josh Bleecher Snyder --- wgengine/magicsock/magicsock.go | 70 +++++++++++++++++----- wgengine/magicsock/magicsock_test.go | 86 +++++++++++++++++++++++++--- 2 files changed, 132 insertions(+), 24 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index afeeb8323..45651699f 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -1581,15 +1581,10 @@ func (c *Conn) ReceiveIPv6(b []byte) (int, conn.Endpoint, error) { return 0, nil, syscall.EAFNOSUPPORT } for { - n, pAddr, err := c.pconn6.ReadFrom(b) + n, ipp, err := c.pconn6.ReadFromNetaddr(b) if err != nil { return 0, nil, err } - udpAddr := pAddr.(*net.UDPAddr) - ipp, ok := netaddr.FromStdAddr(udpAddr.IP, udpAddr.Port, udpAddr.Zone) - if !ok { - continue - } if ep, ok := c.receiveIP(b[:n], ipp, &c.ippEndpoint6); ok { return n, ep, nil } @@ -1604,16 +1599,13 @@ func (c *Conn) derpPacketArrived() bool { // In Tailscale's case, that packet might also arrive via DERP. A DERP packet arrival // aborts the pconn4 read deadline to make it fail. func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { - var addr net.Addr - var pAddr *net.UDPAddr var ipp netaddr.IPPort - var ippOK bool for { // Drain DERP queues before reading new UDP packets. if c.derpPacketArrived() { goto ReadDERP } - n, addr, err = c.pconn4.ReadFrom(b) + n, ipp, err = c.pconn4.ReadFromNetaddr(b) if err != nil { // If the pconn4 read failed, the likely reason is a DERP reader received // a packet and interrupted us. @@ -1625,11 +1617,6 @@ func (c *Conn) ReceiveIPv4(b []byte) (n int, ep conn.Endpoint, err error) { } return 0, nil, err } - pAddr, _ = addr.(*net.UDPAddr) - ipp, ippOK = netaddr.FromStdAddr(pAddr.IP, pAddr.Port, pAddr.Zone) - if !ippOK { - continue - } if ep, ok := c.receiveIP(b[:n], ipp, &c.ippEndpoint4); ok { return n, ep, nil } else { @@ -2743,6 +2730,8 @@ func (c *RebindingUDPConn) Reset(pconn net.PacketConn) { } } +// ReadFromNetaddr reads a packet from c into b. +// It returns the number of bytes copied and the source address. func (c *RebindingUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { for { c.mu.Lock() @@ -2763,6 +2752,57 @@ func (c *RebindingUDPConn) ReadFrom(b []byte) (int, net.Addr, error) { } } +// ReadFromNetaddr reads a packet from c into b. +// It returns the number of bytes copied and the return address. +// It is identical to c.ReadFrom, except that it returns a netaddr.IPPort instead of a net.Addr. +// ReadFromNetaddr is designed to work with specific underlying connection types. +// If c's underlying connection returns a non-*net.UPDAddr return address, ReadFromNetaddr will return an error. +// ReadFromNetaddr exists because it removes an allocation per read, +// when c's underlying connection is a net.UDPConn. +func (c *RebindingUDPConn) ReadFromNetaddr(b []byte) (n int, ipp netaddr.IPPort, err error) { + for { + c.mu.Lock() + pconn := c.pconn + c.mu.Unlock() + + // Optimization: Treat *net.UDPConn specially. + // ReadFromUDP gets partially inlined, avoiding allocating a *net.UDPAddr, + // as long as pAddr itself doesn't escape. + // The non-*net.UDPConn case works, but it allocates. + var pAddr *net.UDPAddr + if udpConn, ok := pconn.(*net.UDPConn); ok { + n, pAddr, err = udpConn.ReadFromUDP(b) + } else { + var addr net.Addr + n, addr, err = pconn.ReadFrom(b) + var ok2 bool + pAddr, ok2 = addr.(*net.UDPAddr) + if !ok2 { + return 0, netaddr.IPPort{}, fmt.Errorf("RebindingUDPConn.ReadFromNetaddr: underlying connection returned address of type %T, want *netaddr.UDPAddr", addr) + } + } + + if err != nil { + c.mu.Lock() + pconn2 := c.pconn + c.mu.Unlock() + + if pconn != pconn2 { + continue + } + } else { + // Convert pAddr to a netaddr.IPPort. + // This prevents pAddr from escaping. + var ok bool + ipp, ok = netaddr.FromStdAddr(pAddr.IP, pAddr.Port, pAddr.Zone) + if !ok { + return 0, netaddr.IPPort{}, errors.New("netaddr.FromStdAddr failed") + } + } + return n, ipp, err + } +} + func (c *RebindingUDPConn) LocalAddr() *net.UDPAddr { c.mu.Lock() defer c.mu.Unlock() diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index ea00fe22e..4890ea70a 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -1512,16 +1512,16 @@ func addTestEndpoint(conn *Conn, sendConn net.PacketConn) (tailcfg.NodeKey, tail return nodeKey, discoKey } -func BenchmarkReceiveFrom(b *testing.B) { - conn := newNonLegacyTestConn(b) - defer conn.Close() +func setUpReceiveFrom(tb testing.TB) (roundTrip func()) { + conn := newNonLegacyTestConn(tb) + tb.Cleanup(func() { conn.Close() }) conn.logf = logger.Discard sendConn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { - b.Fatal(err) + tb.Fatal(err) } - defer sendConn.Close() + tb.Cleanup(func() { sendConn.Close() }) addTestEndpoint(conn, sendConn) @@ -1530,21 +1530,89 @@ func BenchmarkReceiveFrom(b *testing.B) { for i := range sendBuf { sendBuf[i] = 'x' } - buf := make([]byte, 2<<10) - for i := 0; i < b.N; i++ { + return func() { if _, err := sendConn.WriteTo(sendBuf, dstAddr); err != nil { - b.Fatalf("WriteTo: %v", err) + tb.Fatalf("WriteTo: %v", err) } n, ep, err := conn.ReceiveIPv4(buf) if err != nil { - b.Fatal(err) + tb.Fatal(err) } _ = n _ = ep } } +// goMajorVersion reports the major Go version and whether it is a Tailscale fork. +// If parsing fails, goMajorVersion returns 0, false. +func goMajorVersion(s string) (version int, isTS bool) { + if !strings.HasPrefix(s, "go1.") { + return 0, false + } + mm := s[len("go1."):] + var major, rest string + for _, sep := range []string{".", "rc", "beta"} { + i := strings.Index(mm, sep) + if i > 0 { + major, rest = mm[:i], mm[i:] + break + } + } + if major == "" { + major = mm + } + n, err := strconv.Atoi(major) + if err != nil { + return 0, false + } + return n, strings.Contains(rest, "ts") +} + +func TestGoMajorVersion(t *testing.T) { + tests := []struct { + version string + wantN int + wantTS bool + }{ + {"go1.15.8", 15, false}, + {"go1.16rc1", 16, false}, + {"go1.16rc1", 16, false}, + {"go1.15.5-ts3bd89195a3", 15, true}, + {"go1.15", 15, false}, + } + + for _, tt := range tests { + n, ts := goMajorVersion(tt.version) + if tt.wantN != n || tt.wantTS != ts { + t.Errorf("goMajorVersion(%s) = %v, %v, want %v, %v", tt.version, n, ts, tt.wantN, tt.wantTS) + } + } +} + +func TestReceiveFromAllocs(t *testing.T) { + // Go 1.16 and before: allow 3 allocs. + // Go Tailscale fork, Go 1.17+: only allow 2 allocs. + major, ts := goMajorVersion(runtime.Version()) + maxAllocs := 3 + if major >= 17 || ts { + maxAllocs = 2 + } + t.Logf("allowing %d allocs for Go version %q", maxAllocs, runtime.Version()) + roundTrip := setUpReceiveFrom(t) + avg := int(testing.AllocsPerRun(100, roundTrip)) + if avg > maxAllocs { + t.Fatalf("expected %d allocs in ReceiveFrom, got %v", maxAllocs, avg) + } +} + +func BenchmarkReceiveFrom(b *testing.B) { + roundTrip := setUpReceiveFrom(b) + for i := 0; i < b.N; i++ { + roundTrip() + } +} + func BenchmarkReceiveFrom_Native(b *testing.B) { recvConn, err := net.ListenPacket("udp4", "127.0.0.1:0") if err != nil { From 1632f9fd6bf5f31011355da724d2f0832b33f11f Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Fri, 12 Feb 2021 10:17:55 -0800 Subject: [PATCH 50/71] wgengine/magicsock: reduce log spam during tests Only do the type assertion to *net.UDPAddr when addr is non-nil. This prevents a bunch of log spam during tests. --- wgengine/magicsock/magicsock.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go index 45651699f..407ca1720 100644 --- a/wgengine/magicsock/magicsock.go +++ b/wgengine/magicsock/magicsock.go @@ -2775,10 +2775,11 @@ func (c *RebindingUDPConn) ReadFromNetaddr(b []byte) (n int, ipp netaddr.IPPort, } else { var addr net.Addr n, addr, err = pconn.ReadFrom(b) - var ok2 bool - pAddr, ok2 = addr.(*net.UDPAddr) - if !ok2 { - return 0, netaddr.IPPort{}, fmt.Errorf("RebindingUDPConn.ReadFromNetaddr: underlying connection returned address of type %T, want *netaddr.UDPAddr", addr) + if addr != nil { + pAddr, ok = addr.(*net.UDPAddr) + if !ok { + return 0, netaddr.IPPort{}, fmt.Errorf("RebindingUDPConn.ReadFromNetaddr: underlying connection returned address of type %T, want *netaddr.UDPAddr", addr) + } } } From 741d654aa3a066a1c85363fcb465b1989f57ded2 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 12 Feb 2021 10:58:43 -0800 Subject: [PATCH 51/71] derp/derphttp: add a context and infoLogger option to RunWatchConnectionLoop --- cmd/derper/mesh.go | 3 ++- derp/derphttp/mesh_client.go | 36 ++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/cmd/derper/mesh.go b/cmd/derper/mesh.go index 670d3f5e8..b442d1b36 100644 --- a/cmd/derper/mesh.go +++ b/cmd/derper/mesh.go @@ -5,6 +5,7 @@ package main import ( + "context" "errors" "fmt" "log" @@ -40,6 +41,6 @@ func startMeshWithHost(s *derp.Server, host string) error { c.MeshKey = s.MeshKey() add := func(k key.Public) { s.AddPacketForwarder(k, c) } remove := func(k key.Public) { s.RemovePacketForwarder(k, c) } - go c.RunWatchConnectionLoop(s.PublicKey(), add, remove) + go c.RunWatchConnectionLoop(context.Background(), s.PublicKey(), logf, add, remove) return nil } diff --git a/derp/derphttp/mesh_client.go b/derp/derphttp/mesh_client.go index 28f54653e..e53e9ec5d 100644 --- a/derp/derphttp/mesh_client.go +++ b/derp/derphttp/mesh_client.go @@ -5,20 +5,32 @@ package derphttp import ( + "context" "sync" "time" "tailscale.com/derp" "tailscale.com/types/key" + "tailscale.com/types/logger" ) -// RunWatchConnectionLoop loops forever, sending WatchConnectionChanges and subscribing to +// RunWatchConnectionLoop loops until ctx is done, sending WatchConnectionChanges and subscribing to // connection changes. // // If the server's public key is ignoreServerKey, RunWatchConnectionLoop returns. // // Otherwise, the add and remove funcs are called as clients come & go. -func (c *Client) RunWatchConnectionLoop(ignoreServerKey key.Public, add, remove func(key.Public)) { +// +// infoLogf, if non-nil, is the logger to write periodic status +// updates about how many peers are on the server. Error log output is +// set to the c's logger, regardless of infoLogf's value. +// +// To force RunWatchConnectionLoop to return quickly, its ctx needs to +// be closed, and c itself needs to be closed. +func (c *Client) RunWatchConnectionLoop(ctx context.Context, ignoreServerKey key.Public, infoLogf logger.Logf, add, remove func(key.Public)) { + if infoLogf == nil { + infoLogf = logger.Discard + } logf := c.logf const retryInterval = 5 * time.Second const statusInterval = 10 * time.Second @@ -45,7 +57,7 @@ func (c *Client) RunWatchConnectionLoop(ignoreServerKey key.Public, add, remove if loggedConnected { return } - logf("connected; %d peers", len(present)) + infoLogf("connected; %d peers", len(present)) loggedConnected = true } @@ -79,12 +91,21 @@ func (c *Client) RunWatchConnectionLoop(ignoreServerKey key.Public, add, remove } } - for { + sleep := func(d time.Duration) { + t := time.NewTimer(d) + select { + case <-ctx.Done(): + t.Stop() + case <-t.C: + } + } + + for ctx.Err() == nil { err := c.WatchConnectionChanges() if err != nil { clear() logf("WatchConnectionChanges: %v", err) - time.Sleep(retryInterval) + sleep(retryInterval) continue } @@ -97,7 +118,7 @@ func (c *Client) RunWatchConnectionLoop(ignoreServerKey key.Public, add, remove if err != nil { clear() logf("Recv: %v", err) - time.Sleep(retryInterval) + sleep(retryInterval) break } if connGen != lastConnGen { @@ -114,9 +135,8 @@ func (c *Client) RunWatchConnectionLoop(ignoreServerKey key.Public, add, remove } if now := time.Now(); now.Sub(lastStatus) > statusInterval { lastStatus = now - logf("%d peers", len(present)) + infoLogf("%d peers", len(present)) } } } - } From ca51529b81cc604bc5b57dfdbf3f67bdf61f7d5a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 12 Feb 2021 12:04:16 -0800 Subject: [PATCH 52/71] derp/derphttp: return nicer errors from Recv on Close --- derp/derphttp/derphttp_client.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go index 87f11eca6..0c523f07e 100644 --- a/derp/derphttp/derphttp_client.go +++ b/derp/derphttp/derphttp_client.go @@ -709,10 +709,19 @@ func (c *Client) RecvDetail() (m derp.ReceivedMessage, connGen int, err error) { m, err = client.Recv() if err != nil { c.closeForReconnect(client) + if c.isClosed() { + err = ErrClientClosed + } } return m, connGen, err } +func (c *Client) isClosed() bool { + c.mu.Lock() + defer c.mu.Unlock() + return c.closed +} + // Close closes the client. It will not automatically reconnect after // being closed. func (c *Client) Close() error { From c7e5ab80944bc490bbc4341b492fcd39b0464101 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 10 Feb 2021 14:47:26 -0800 Subject: [PATCH 53/71] wgengine/magicsock: retry and re-send packets in TestTwoDevicePing When a handshake race occurs, a queued data packet can get lost. TestTwoDevicePing expected that the very first data packet would arrive. This caused occasional flakes. Change TestTwoDevicePing to repeatedly re-send packets and succeed when one of them makes it through. This is acceptable (vs making WireGuard not drop the packets) because this only affects communication with extremely old clients. And those extremely old clients will eventually connect, because the kernel will retry sends on timeout. Signed-off-by: Josh Bleecher Snyder --- wgengine/magicsock/magicsock_test.go | 70 +++++++++++++++++----------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/wgengine/magicsock/magicsock_test.go b/wgengine/magicsock/magicsock_test.go index 4890ea70a..26c157d16 100644 --- a/wgengine/magicsock/magicsock_test.go +++ b/wgengine/magicsock/magicsock_test.go @@ -11,6 +11,7 @@ import ( "crypto/tls" "encoding/binary" "encoding/json" + "errors" "fmt" "io/ioutil" "net" @@ -927,30 +928,45 @@ func testTwoDevicePing(t *testing.T, d *devices) { // Retries take 5s each. Add 1s for some processing time. pingTimeout := 5*time.Second*time.Duration(allowedRetries) + time.Second + // sendWithTimeout sends msg using send, checking that it is received unchanged from in. + // It resends once per second until the send succeeds, or pingTimeout time has elapsed. + sendWithTimeout := func(msg []byte, in chan []byte, send func()) error { + start := time.Now() + for time.Since(start) < pingTimeout { + send() + select { + case recv := <-in: + if !bytes.Equal(msg, recv) { + return errors.New("ping did not transit correctly") + } + return nil + case <-time.After(time.Second): + // try again + } + } + return errors.New("ping timed out") + } + ping1 := func(t *testing.T) { msg2to1 := tuntest.Ping(net.ParseIP("1.0.0.1"), net.ParseIP("1.0.0.2")) - m2.tun.Outbound <- msg2to1 - t.Log("ping1 sent") - select { - case msgRecv := <-m1.tun.Inbound: - if !bytes.Equal(msg2to1, msgRecv) { - t.Error("ping did not transit correctly") - } - case <-time.After(pingTimeout): - t.Error("ping did not transit") + send := func() { + m2.tun.Outbound <- msg2to1 + t.Log("ping1 sent") + } + in := m1.tun.Inbound + if err := sendWithTimeout(msg2to1, in, send); err != nil { + t.Error(err) } } ping2 := func(t *testing.T) { msg1to2 := tuntest.Ping(net.ParseIP("1.0.0.2"), net.ParseIP("1.0.0.1")) - m1.tun.Outbound <- msg1to2 - t.Log("ping2 sent") - select { - case msgRecv := <-m2.tun.Inbound: - if !bytes.Equal(msg1to2, msgRecv) { - t.Error("return ping did not transit correctly") - } - case <-time.After(pingTimeout): - t.Error("return ping did not transit") + send := func() { + m1.tun.Outbound <- msg1to2 + t.Log("ping2 sent") + } + in := m2.tun.Inbound + if err := sendWithTimeout(msg1to2, in, send); err != nil { + t.Error(err) } } @@ -971,17 +987,15 @@ func testTwoDevicePing(t *testing.T, d *devices) { setT(t) defer setT(outerT) msg1to2 := tuntest.Ping(net.ParseIP("1.0.0.2"), net.ParseIP("1.0.0.1")) - if err := m1.tsTun.InjectOutbound(msg1to2); err != nil { - t.Fatal(err) - } - t.Log("SendPacket sent") - select { - case msgRecv := <-m2.tun.Inbound: - if !bytes.Equal(msg1to2, msgRecv) { - t.Error("return ping did not transit correctly") + send := func() { + if err := m1.tsTun.InjectOutbound(msg1to2); err != nil { + t.Fatal(err) } - case <-time.After(pingTimeout): - t.Error("return ping did not transit") + t.Log("SendPacket sent") + } + in := m2.tun.Inbound + if err := sendWithTimeout(msg1to2, in, send); err != nil { + t.Error(err) } }) From 20e66c5b9200f1d0a7d368cb2186b5a6c74ec3d9 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Fri, 12 Feb 2021 18:38:16 -0800 Subject: [PATCH 54/71] net/interfaces: reconcile interface filtering with address printing in logs The interface.State logging tried to only log interfaces which had interesting IPs, but the what-is-interesting checks differed between the code that gathered the interface names to print and the printing of their addresses. --- net/interfaces/interfaces.go | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/net/interfaces/interfaces.go b/net/interfaces/interfaces.go index 725515887..e0d249393 100644 --- a/net/interfaces/interfaces.go +++ b/net/interfaces/interfaces.go @@ -197,10 +197,9 @@ func (s *State) String() string { fmt.Fprintf(&sb, "interfaces.State{defaultRoute=%v ifs={", s.DefaultRouteInterface) ifs := make([]string, 0, len(s.InterfaceUp)) for k := range s.InterfaceUp { - if allLoopbackIPs(s.InterfaceIPs[k]) { - continue + if anyInterestingIP(s.InterfaceIPs[k]) { + ifs = append(ifs, k) } - ifs = append(ifs, k) } sort.Slice(ifs, func(i, j int) bool { upi, upj := s.InterfaceUp[ifs[i]], s.InterfaceUp[ifs[j]] @@ -218,7 +217,7 @@ func (s *State) String() string { fmt.Fprintf(&sb, "%s:[", ifName) needSpace := false for _, ip := range s.InterfaceIPs[ifName] { - if ip.IsLinkLocalUnicast() { + if !isInterestingIP(ip) { continue } if needSpace { @@ -403,14 +402,23 @@ var ( v6Global1 = mustCIDR("2000::/3") ) -func allLoopbackIPs(ips []netaddr.IP) bool { - if len(ips) == 0 { - return false - } +// anyInterestingIP reports ips contains any IP that matches +// isInterestingIP. +func anyInterestingIP(ips []netaddr.IP) bool { for _, ip := range ips { - if !ip.IsLoopback() { - return false + if isInterestingIP(ip) { + return true } } + return false +} + +// isInterestingIP reports whether ip is an interesting IP that we +// should log in interfaces.State logging. We don't need to show +// localhost or link-local addresses. +func isInterestingIP(ip netaddr.IP) bool { + if ip.IsLoopback() || ip.IsLinkLocalUnicast() { + return false + } return true } From 54e108ff4ef08a6f53b06830e0012d812fe17eae Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 13 Feb 2021 12:10:20 -0800 Subject: [PATCH 55/71] paths: update some default paths for darwin --- paths/paths.go | 3 +++ paths/paths_unix.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/paths/paths.go b/paths/paths.go index 0bb028085..b42b2864f 100644 --- a/paths/paths.go +++ b/paths/paths.go @@ -28,6 +28,9 @@ func DefaultTailscaledSocket() string { if runtime.GOOS == "windows" { return "" } + if runtime.GOOS == "darwin" { + return "/var/run/tailscaled.socket" + } if fi, err := os.Stat("/var/run"); err == nil && fi.IsDir() { return "/var/run/tailscale/tailscaled.sock" } diff --git a/paths/paths_unix.go b/paths/paths_unix.go index bde26948e..1633fc32e 100644 --- a/paths/paths_unix.go +++ b/paths/paths_unix.go @@ -23,6 +23,8 @@ func statePath() string { return "/var/lib/tailscale/tailscaled.state" case "freebsd", "openbsd": return "/var/db/tailscale/tailscaled.state" + case "darwin": + return "/Library/Tailscale/tailscaled.state" default: return "" } From 29b028b9c42321621044578daac71b38fd3b9d92 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 13 Feb 2021 12:57:49 -0800 Subject: [PATCH 56/71] cmd/tailscaled: add subcommand on darwin to install+start tailscaled under launchd Tangentially related to #987, #177, #594, #925. --- cmd/tailscaled/install_darwin.go | 104 +++++++++++++++++++++++++++++++ cmd/tailscaled/tailscaled.go | 22 +++++-- 2 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 cmd/tailscaled/install_darwin.go diff --git a/cmd/tailscaled/install_darwin.go b/cmd/tailscaled/install_darwin.go new file mode 100644 index 000000000..34786df93 --- /dev/null +++ b/cmd/tailscaled/install_darwin.go @@ -0,0 +1,104 @@ +// 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 main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" +) + +func init() { + installSystemDaemon = installSystemDaemonDarwin +} + +// darwinLaunchdPlist is the launchd.plist that's written to +// /Library/LaunchDaemons/com.tailscale.tailscaled.plist or (in the +// future) a user-specific location. +// +// See man launchd.plist. +const darwinLaunchdPlist = ` + + + + + + Label + com.tailscale.tailscaled + + ProgramArguments + + /usr/local/bin/tailscaled + + + RunAtLoad + + + + +` + +const sysPlist = "/Library/LaunchDaemons/com.tailscale.tailscaled.plist" +const targetBin = "/usr/local/bin/tailscaled" +const service = "system/com.tailscale.tailscaled" + +func installSystemDaemonDarwin() (err error) { + defer func() { + if err != nil && os.Getuid() != 0 { + err = fmt.Errorf("%w; try running tailscaled with sudo", err) + } + }() + + // Copy ourselves to /usr/local/bin/tailscaled. + exe, err := os.Executable() + if err != nil { + return fmt.Errorf("failed to find our own executable path: %w", err) + } + tmpBin := targetBin + ".tmp" + f, err := os.Create(tmpBin) + if err != nil { + return err + } + self, err := os.Open(exe) + if err != nil { + f.Close() + return err + } + _, err = io.Copy(f, self) + self.Close() + if err != nil { + f.Close() + return err + } + if err := f.Close(); err != nil { + return err + } + if err := os.Chmod(tmpBin, 0755); err != nil { + return err + } + if err := os.Rename(tmpBin, targetBin); err != nil { + return err + } + + // Two best effort commands to stop a previous run. + exec.Command("launchctl", "stop", "system/com.tailscale.tailscaled").Run() + exec.Command("launchctl", "unload", sysPlist).Run() + + if err := ioutil.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil { + return err + } + + if out, err := exec.Command("launchctl", "load", sysPlist).CombinedOutput(); err != nil { + return fmt.Errorf("error running launchctl load %s: %v, %s", sysPlist, err, out) + } + + if out, err := exec.Command("launchctl", "start", service).CombinedOutput(); err != nil { + return fmt.Errorf("error running launchctl start %s: %v, %s", service, err, out) + } + + return nil +} diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 4ad437c22..5782fe3c1 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -71,6 +71,8 @@ var args struct { verbose int } +var installSystemDaemon func() error // non-nil on some platforms + func main() { // We aren't very performance sensitive, and the parts that are // performance sensitive (wireguard) try hard not to do any memory @@ -91,11 +93,23 @@ func main() { flag.StringVar(&args.socketpath, "socket", paths.DefaultTailscaledSocket(), "path of the service unix socket") flag.BoolVar(&printVersion, "version", false, "print version information and exit") - if len(os.Args) > 1 && os.Args[1] == "debug" { - if err := debugMode(os.Args[2:]); err != nil { - log.Fatal(err) + if len(os.Args) > 1 { + switch os.Args[1] { + case "debug": + if err := debugMode(os.Args[2:]); err != nil { + log.Fatal(err) + } + return + case "install-system-daemon": + if f := installSystemDaemon; f == nil { + log.SetFlags(0) + log.Fatalf("install-system-daemon not available on %v", runtime.GOOS) + } else if err := f(); err != nil { + log.SetFlags(0) + log.Fatal(err) + } + return } - return } if beWindowsSubprocess() { From 4f7d60ad42e82adaa4745baef03496a00f2002bd Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sat, 13 Feb 2021 21:06:27 -0800 Subject: [PATCH 57/71] wgengine/monitor: add a darwin implementation for tailscaled mode Tangentially related to #987, #177, #594, #925, #505 Motivated by rebooting a launchd-controlled tailscaled and it going into SetNetworkUp(false) mode immediately because there really is no network up at system boot, but then it got stuck in that paused state forever, without a monitor implementation. --- wgengine/monitor/monitor_darwin_tailscaled.go | 65 +++++++++++++++++++ wgengine/monitor/monitor_unsupported.go | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 wgengine/monitor/monitor_darwin_tailscaled.go diff --git a/wgengine/monitor/monitor_darwin_tailscaled.go b/wgengine/monitor/monitor_darwin_tailscaled.go new file mode 100644 index 000000000..2d53fc77e --- /dev/null +++ b/wgengine/monitor/monitor_darwin_tailscaled.go @@ -0,0 +1,65 @@ +// 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. + +// +build darwin,!redo + +package monitor + +import ( + "bufio" + "os/exec" + + "tailscale.com/types/logger" +) + +// unspecifiedMessage is a minimal message implementation that should not +// be ignored. In general, OS-specific implementations should use better +// types and avoid this if they can. +type unspecifiedMessage struct{} + +func (unspecifiedMessage) ignore() bool { return false } + +func newOSMon(logf logger.Logf) (osMon, error) { + return new(routeMonitorSubProcMon), nil +} + +// routeMonitorSubProcMon is a very simple (temporary? but I know +// better) monitor implementation for darwin in tailscaled-mode where +// we can just shell out to "route -n monitor". It waits for any input +// but doesn't parse it. Then we poll to see if something is different. +type routeMonitorSubProcMon struct { + cmd *exec.Cmd // of "/sbin/route -n monitor" + br *bufio.Reader + buf []byte +} + +func (m *routeMonitorSubProcMon) Close() error { + if m.cmd != nil { + m.cmd.Process.Kill() + m.cmd = nil + } + return nil +} + +func (m *routeMonitorSubProcMon) Receive() (message, error) { + if m.cmd == nil { + cmd := exec.Command("/sbin/route", "-n", "monitor") + outPipe, err := cmd.StdoutPipe() + if err != nil { + return nil, err + } + if err := cmd.Start(); err != nil { + return nil, err + } + m.br = bufio.NewReader(outPipe) + m.cmd = cmd + m.buf = make([]byte, 16<<10) + } + _, err := m.br.Read(m.buf) + if err != nil { + m.Close() + return nil, err + } + return unspecifiedMessage{}, nil +} diff --git a/wgengine/monitor/monitor_unsupported.go b/wgengine/monitor/monitor_unsupported.go index a54990c02..a779536e6 100644 --- a/wgengine/monitor/monitor_unsupported.go +++ b/wgengine/monitor/monitor_unsupported.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!freebsd,!windows android +// +build !linux,!freebsd,!windows,!darwin android darwin,redo package monitor From 52e24aa966ffaae20afbf0b5561bc1d34c102d33 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 14 Feb 2021 07:48:38 -0800 Subject: [PATCH 58/71] net/{interfaces,ns}: add tailscaled-mode darwin routing looping prevention Fixes #1331 Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 2 +- cmd/tailscaled/depaware.txt | 2 +- .../interfaces_darwin_tailscaled.go | 81 +++++++++++++++++++ .../interfaces_defaultrouteif_todo.go | 2 +- net/netns/netns_darwin_tailscaled.go | 52 ++++++++++++ net/netns/netns_default.go | 2 +- 6 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 net/interfaces/interfaces_darwin_tailscaled.go create mode 100644 net/netns/netns_darwin_tailscaled.go diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 07ea30a8a..7bd833cec 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -72,7 +72,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep golang.org/x/net/http2/hpack from net/http golang.org/x/net/idna from golang.org/x/net/http/httpguts+ golang.org/x/net/proxy from tailscale.com/net/netns - D golang.org/x/net/route from net + D golang.org/x/net/route from net+ golang.org/x/oauth2 from tailscale.com/ipn+ golang.org/x/oauth2/internal from golang.org/x/oauth2 golang.org/x/sync/errgroup from tailscale.com/derp diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index e2fa68aab..c19b50860 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -157,7 +157,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de golang.org/x/net/ipv4 from github.com/tailscale/wireguard-go/device golang.org/x/net/ipv6 from github.com/tailscale/wireguard-go/device+ golang.org/x/net/proxy from tailscale.com/net/netns - D golang.org/x/net/route from net + D golang.org/x/net/route from net+ golang.org/x/oauth2 from tailscale.com/control/controlclient+ golang.org/x/oauth2/internal from golang.org/x/oauth2 golang.org/x/sync/errgroup from tailscale.com/derp diff --git a/net/interfaces/interfaces_darwin_tailscaled.go b/net/interfaces/interfaces_darwin_tailscaled.go new file mode 100644 index 000000000..da9eb3dbe --- /dev/null +++ b/net/interfaces/interfaces_darwin_tailscaled.go @@ -0,0 +1,81 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin,!redo,!ios +// (Exclude redo, because we don't want this code in the App Store +// version's sandbox, where it won't work, and also don't want it on +// iOS. This is just for utun-using non-sandboxed cmd/tailscaled on macOS. + +package interfaces + +import ( + "errors" + "fmt" + "net" + "syscall" + + "golang.org/x/net/route" +) + +func DefaultRouteInterface() (string, error) { + idx, err := DefaultRouteInterfaceIndex() + if err != nil { + return "", err + } + iface, err := net.InterfaceByIndex(idx) + if err != nil { + return "", err + } + return iface.Name, nil +} + +func DefaultRouteInterfaceIndex() (int, error) { + // $ netstat -nr + // Routing tables + // Internet: + // Destination Gateway Flags Netif Expire + // default 10.0.0.1 UGSc en0 <-- want this one + // default 10.0.0.1 UGScI en1 + + // From man netstat: + // U RTF_UP Route usable + // G RTF_GATEWAY Destination requires forwarding by intermediary + // S RTF_STATIC Manually added + // c RTF_PRCLONING Protocol-specified generate new routes on use + // I RTF_IFSCOPE Route is associated with an interface scope + + rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) + if err != nil { + return 0, fmt.Errorf("FetchRIB: %w", err) + } + msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) + if err != nil { + return 0, fmt.Errorf("Parse: %w", err) + } + indexSeen := map[int]int{} // index => count + for _, m := range msgs { + rm, ok := m.(*route.RouteMessage) + if !ok { + continue + } + const RTF_GATEWAY = 0x2 + const RTF_IFSCOPE = 0x1000000 + if rm.Flags&RTF_GATEWAY == 0 { + continue + } + if rm.Flags&RTF_IFSCOPE != 0 { + continue + } + indexSeen[rm.Index]++ + } + if len(indexSeen) == 0 { + return 0, errors.New("no gateway index found") + } + if len(indexSeen) == 1 { + for idx := range indexSeen { + return idx, nil + } + } + return 0, fmt.Errorf("ambiguous gateway interfaces found: %v", indexSeen) +} diff --git a/net/interfaces/interfaces_defaultrouteif_todo.go b/net/interfaces/interfaces_defaultrouteif_todo.go index a5067151a..255543336 100644 --- a/net/interfaces/interfaces_defaultrouteif_todo.go +++ b/net/interfaces/interfaces_defaultrouteif_todo.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!windows +// +build !linux,!windows,!darwin darwin,redo package interfaces diff --git a/net/netns/netns_darwin_tailscaled.go b/net/netns/netns_darwin_tailscaled.go new file mode 100644 index 000000000..a5a323fd2 --- /dev/null +++ b/net/netns/netns_darwin_tailscaled.go @@ -0,0 +1,52 @@ +// 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. + +// +build darwin,!redo + +package netns + +import ( + "fmt" + "log" + "strings" + "syscall" + + "golang.org/x/sys/unix" + "tailscale.com/net/interfaces" +) + +// control marks c as necessary to dial in a separate network namespace. +// +// It's intentionally the same signature as net.Dialer.Control +// and net.ListenConfig.Control. +func control(network, address string, c syscall.RawConn) error { + if strings.HasPrefix(address, "127.") || address == "::1" { + // Don't bind to an interface for localhost connections. + return nil + } + idx, err := interfaces.DefaultRouteInterfaceIndex() + if err != nil { + log.Printf("netns: DefaultRouteInterfaceIndex: %v", err) + return nil + } + v6 := strings.Contains(address, "]:") || strings.HasSuffix(network, "6") // hacky test for v6 + proto := unix.IPPROTO_IP + opt := unix.IP_BOUND_IF + if v6 { + proto = unix.IPPROTO_IPV6 + opt = unix.IPV6_BOUND_IF + } + + var sockErr error + err = c.Control(func(fd uintptr) { + sockErr = unix.SetsockoptInt(int(fd), proto, opt, idx) + }) + if err != nil { + return fmt.Errorf("RawConn.Control on %T: %w", c, err) + } + if sockErr != nil { + log.Printf("netns: control(%q, %q), v6=%v, index=%v: %v", network, address, v6, idx, sockErr) + } + return sockErr +} diff --git a/net/netns/netns_default.go b/net/netns/netns_default.go index e794fccb7..0a0e0179b 100644 --- a/net/netns/netns_default.go +++ b/net/netns/netns_default.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !linux,!windows +// +build !linux,!windows,!darwin darwin,redo package netns From d7569863b5bdf17b5b174f1cd3f8b7c22474b5d4 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 14 Feb 2021 21:11:06 -0800 Subject: [PATCH 59/71] cmd/tailscaled: fix up install-system-daemon on darwin, add uninstall too Tangentially related to #987, #177, #594, #925, #505 --- cmd/tailscaled/debug.go | 2 ++ cmd/tailscaled/install_darwin.go | 48 ++++++++++++++++++++++++++++---- cmd/tailscaled/tailscaled.go | 27 ++++++++++-------- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/cmd/tailscaled/debug.go b/cmd/tailscaled/debug.go index 91ccb070a..d852ad893 100644 --- a/cmd/tailscaled/debug.go +++ b/cmd/tailscaled/debug.go @@ -33,6 +33,8 @@ var debugArgs struct { derpCheck string } +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.") diff --git a/cmd/tailscaled/install_darwin.go b/cmd/tailscaled/install_darwin.go index 34786df93..7c86cd985 100644 --- a/cmd/tailscaled/install_darwin.go +++ b/cmd/tailscaled/install_darwin.go @@ -5,6 +5,7 @@ package main import ( + "errors" "fmt" "io" "io/ioutil" @@ -14,6 +15,7 @@ import ( func init() { installSystemDaemon = installSystemDaemonDarwin + uninstallSystemDaemon = uninstallSystemDaemonDarwin } // darwinLaunchdPlist is the launchd.plist that's written to @@ -44,9 +46,46 @@ const darwinLaunchdPlist = ` const sysPlist = "/Library/LaunchDaemons/com.tailscale.tailscaled.plist" const targetBin = "/usr/local/bin/tailscaled" -const service = "system/com.tailscale.tailscaled" +const service = "com.tailscale.tailscaled" -func installSystemDaemonDarwin() (err error) { +func uninstallSystemDaemonDarwin(args []string) (ret error) { + if len(args) > 0 { + return errors.New("uninstall subcommand takes no arguments") + } + + plist, err := exec.Command("launchctl", "list", "com.tailscale.tailscaled").Output() + _ = plist // parse it? https://github.com/DHowett/go-plist if we need something. + running := err == nil + + if running { + out, err := exec.Command("launchctl", "stop", "com.tailscale.tailscaled").CombinedOutput() + if err != nil { + fmt.Printf("launchctl stop com.tailscale.tailscaled: %v, %s\n", err, out) + ret = err + } + out, err = exec.Command("launchctl", "unload", sysPlist).CombinedOutput() + if err != nil { + fmt.Printf("launchctl unload %s: %v, %s\n", sysPlist, err, out) + if ret == nil { + ret = err + } + } + } + + err = os.Remove(sysPlist) + if os.IsNotExist(err) { + err = nil + if ret == nil { + ret = err + } + } + return ret +} + +func installSystemDaemonDarwin(args []string) (err error) { + if len(args) > 0 { + return errors.New("install subcommand takes no arguments") + } defer func() { if err != nil && os.Getuid() != 0 { err = fmt.Errorf("%w; try running tailscaled with sudo", err) @@ -84,9 +123,8 @@ func installSystemDaemonDarwin() (err error) { return err } - // Two best effort commands to stop a previous run. - exec.Command("launchctl", "stop", "system/com.tailscale.tailscaled").Run() - exec.Command("launchctl", "unload", sysPlist).Run() + // Best effort: + uninstallSystemDaemonDarwin(nil) if err := ioutil.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil { return err diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index 5782fe3c1..e9691a5dc 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -71,7 +71,16 @@ var args struct { verbose int } -var installSystemDaemon func() error // non-nil on some platforms +var ( + installSystemDaemon func([]string) error // non-nil on some platforms + uninstallSystemDaemon func([]string) error // non-nil on some platforms +) + +var subCommands = map[string]*func([]string) error{ + "install-system-daemon": &installSystemDaemon, + "uninstall-system-daemon": &uninstallSystemDaemon, + "debug": &debugModeFunc, +} func main() { // We aren't very performance sensitive, and the parts that are @@ -94,17 +103,13 @@ func main() { flag.BoolVar(&printVersion, "version", false, "print version information and exit") if len(os.Args) > 1 { - switch os.Args[1] { - case "debug": - if err := debugMode(os.Args[2:]); err != nil { - log.Fatal(err) - } - return - case "install-system-daemon": - if f := installSystemDaemon; f == nil { + sub := os.Args[1] + if fp, ok := subCommands[sub]; ok { + if *fp == nil { log.SetFlags(0) - log.Fatalf("install-system-daemon not available on %v", runtime.GOOS) - } else if err := f(); err != nil { + log.Fatalf("%s not available on %v", sub, runtime.GOOS) + } + if err := (*fp)(os.Args[2:]); err != nil { log.SetFlags(0) log.Fatal(err) } From e923639feb02712a0974cfd946054ca584edf9fa Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 14 Feb 2021 21:17:12 -0800 Subject: [PATCH 60/71] net/interfaces: fix staticcheck error on darwin --- net/interfaces/interfaces_darwin_tailscaled.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/interfaces/interfaces_darwin_tailscaled.go b/net/interfaces/interfaces_darwin_tailscaled.go index da9eb3dbe..1dd598619 100644 --- a/net/interfaces/interfaces_darwin_tailscaled.go +++ b/net/interfaces/interfaces_darwin_tailscaled.go @@ -47,11 +47,11 @@ func DefaultRouteInterfaceIndex() (int, error) { rib, err := route.FetchRIB(syscall.AF_UNSPEC, syscall.NET_RT_DUMP2, 0) if err != nil { - return 0, fmt.Errorf("FetchRIB: %w", err) + return 0, fmt.Errorf("route.FetchRIB: %w", err) } msgs, err := route.ParseRIB(syscall.NET_RT_IFLIST2, rib) if err != nil { - return 0, fmt.Errorf("Parse: %w", err) + return 0, fmt.Errorf("route.ParseRIB: %w", err) } indexSeen := map[int]int{} // index => count for _, m := range msgs { From f4ae745b0b0f0d32d665a9fcf54837633cd06b4f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Sun, 14 Feb 2021 21:09:18 -0800 Subject: [PATCH 61/71] net/{interfaces,netns}: add some new tests, missed from prior commit I meant for these to be part of 52e24aa966ffa. --- .../interfaces_default_route_test.go | 17 ++++++++ net/netns/netns_test.go | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 net/interfaces/interfaces_default_route_test.go create mode 100644 net/netns/netns_test.go diff --git a/net/interfaces/interfaces_default_route_test.go b/net/interfaces/interfaces_default_route_test.go new file mode 100644 index 000000000..d88bdf685 --- /dev/null +++ b/net/interfaces/interfaces_default_route_test.go @@ -0,0 +1,17 @@ +// 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. + +// +build linux darwin,!redo + +package interfaces + +import "testing" + +func TestDefaultRouteInterface(t *testing.T) { + v, err := DefaultRouteInterface() + if err != nil { + t.Fatal(err) + } + t.Logf("got %q", v) +} diff --git a/net/netns/netns_test.go b/net/netns/netns_test.go new file mode 100644 index 000000000..0e3eb963f --- /dev/null +++ b/net/netns/netns_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package netns contains the common code for using the Go net package +// in a logical "network namespace" to avoid routing loops where +// Tailscale-created packets would otherwise loop back through +// Tailscale routes. +// +// Despite the name netns, the exact mechanism used differs by +// operating system, and perhaps even by version of the OS. +// +// The netns package also handles connecting via SOCKS proxies when +// configured by the environment. +package netns + +import ( + "flag" + "testing" +) + +var extNetwork = flag.Bool("use-external-network", false, "use the external network in tests") + +func TestDial(t *testing.T) { + if !*extNetwork { + t.Skip("skipping test without --use-external-network") + } + d := NewDialer() + c, err := d.Dial("tcp", "google.com:80") + if err != nil { + t.Fatal(err) + } + defer c.Close() + t.Logf("got addr %v", c.RemoteAddr()) + + c, err = d.Dial("tcp4", "google.com:80") + if err != nil { + t.Fatal(err) + } + defer c.Close() + t.Logf("got addr %v", c.RemoteAddr()) +} From bbb4631e04c99726f9ca1eacfc54ffd9f7e1618f Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 08:40:52 -0800 Subject: [PATCH 62/71] safesocket, wgengine: add some darwin failure diagnostic hints --- safesocket/unixsocket.go | 14 ++++++++++++++ wgengine/userspace.go | 27 +++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/safesocket/unixsocket.go b/safesocket/unixsocket.go index fd2a58852..8128bef57 100644 --- a/safesocket/unixsocket.go +++ b/safesocket/unixsocket.go @@ -13,6 +13,7 @@ import ( "log" "net" "os" + "os/exec" "path/filepath" "runtime" "strconv" @@ -54,6 +55,9 @@ func listen(path string, port uint16) (ln net.Listener, _ uint16, err error) { c, err := net.Dial("unix", path) if err == nil { c.Close() + if tailscaledRunningUnderLaunchd() { + return nil, 0, fmt.Errorf("%v: address already in use; tailscaled already running under launchd (to stop, run: $ sudo launchctl stop com.tailscale.tailscaled)", path) + } return nil, 0, fmt.Errorf("%v: address already in use", path) } _ = os.Remove(path) @@ -86,6 +90,16 @@ func listen(path string, port uint16) (ln net.Listener, _ uint16, err error) { return pipe, 0, err } +func tailscaledRunningUnderLaunchd() bool { + if runtime.GOOS != "darwin" { + return false + } + plist, err := exec.Command("launchctl", "list", "com.tailscale.tailscaled").Output() + _ = plist // parse it? https://github.com/DHowett/go-plist if we need something. + running := err == nil + return running +} + // socketPermissionsForOS returns the permissions to use for the // tailscaled.sock. func socketPermissionsForOS() os.FileMode { diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 5793232b4..f3ce131c9 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -171,16 +171,16 @@ func NewFakeUserspaceEngine(logf logger.Logf, listenPort uint16, impl FakeImplFu // NewUserspaceEngine creates the named tun device and returns a // Tailscale Engine running on it. -func NewUserspaceEngine(logf logger.Logf, tunname string, listenPort uint16) (Engine, error) { - if tunname == "" { +func NewUserspaceEngine(logf logger.Logf, tunName string, listenPort uint16) (Engine, error) { + if tunName == "" { return nil, fmt.Errorf("--tun name must not be blank") } - logf("Starting userspace wireguard engine with tun device %q", tunname) + logf("Starting userspace wireguard engine with tun device %q", tunName) - tun, err := tun.CreateTUN(tunname, minimalMTU) + tun, err := tun.CreateTUN(tunName, minimalMTU) if err != nil { - diagnoseTUNFailure(logf) + diagnoseTUNFailure(tunName, logf) logf("CreateTUN: %v", err) return nil, err } @@ -1363,16 +1363,27 @@ func (e *userspaceEngine) Ping(ip netaddr.IP, cb func(*ipnstate.PingResult)) { // the system and log some diagnostic info that might help debug why // TUN failed. Because TUN's already failed and things the program's // about to end, we might as well log a lot. -func diagnoseTUNFailure(logf logger.Logf) { +func diagnoseTUNFailure(tunName string, logf logger.Logf) { switch runtime.GOOS { case "linux": - diagnoseLinuxTUNFailure(logf) + diagnoseLinuxTUNFailure(tunName, logf) + case "darwin": + diagnoseDarwinTUNFailure(tunName, logf) default: logf("no TUN failure diagnostics for OS %q", runtime.GOOS) } } -func diagnoseLinuxTUNFailure(logf logger.Logf) { +func diagnoseDarwinTUNFailure(tunName string, logf logger.Logf) { + if os.Getuid() != 0 { + logf("failed to create TUN device as non-root user; use 'sudo tailscaled', or run under launchd with 'sudo tailscaled install-system-daemon'") + } + if tunName != "utun" { + logf("failed to create TUN device %q; try using tun device \"utun\" instead for automatic selection", tunName) + } +} + +func diagnoseLinuxTUNFailure(tunName string, logf logger.Logf) { kernel, err := exec.Command("uname", "-r").Output() kernel = bytes.TrimSpace(kernel) if err != nil { From 36189e2704fa29d6ad1dc20466374c5ebce9e78a Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 08:59:53 -0800 Subject: [PATCH 63/71] wgengine/monitor: prevent shutdown hang in darwin link monitor --- wgengine/monitor/monitor_darwin_tailscaled.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wgengine/monitor/monitor_darwin_tailscaled.go b/wgengine/monitor/monitor_darwin_tailscaled.go index 2d53fc77e..f7123cf65 100644 --- a/wgengine/monitor/monitor_darwin_tailscaled.go +++ b/wgengine/monitor/monitor_darwin_tailscaled.go @@ -8,8 +8,10 @@ package monitor import ( "bufio" + "errors" "os/exec" + "tailscale.com/syncs" "tailscale.com/types/logger" ) @@ -29,12 +31,14 @@ func newOSMon(logf logger.Logf) (osMon, error) { // we can just shell out to "route -n monitor". It waits for any input // but doesn't parse it. Then we poll to see if something is different. type routeMonitorSubProcMon struct { - cmd *exec.Cmd // of "/sbin/route -n monitor" - br *bufio.Reader - buf []byte + closed syncs.AtomicBool + cmd *exec.Cmd // of "/sbin/route -n monitor" + br *bufio.Reader + buf []byte } func (m *routeMonitorSubProcMon) Close() error { + m.closed.Set(true) if m.cmd != nil { m.cmd.Process.Kill() m.cmd = nil @@ -43,6 +47,9 @@ func (m *routeMonitorSubProcMon) Close() error { } func (m *routeMonitorSubProcMon) Receive() (message, error) { + if m.closed.Get() { + return nil, errors.New("monitor closed") + } if m.cmd == nil { cmd := exec.Command("/sbin/route", "-n", "monitor") outPipe, err := cmd.StdoutPipe() From fdac0387a76dc301e64db234b80821f480ac612c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 10:41:52 -0800 Subject: [PATCH 64/71] ipn/ipnserver, ipn/ipnlocal: move whois handler to new localapi package --- cmd/tailscaled/depaware.txt | 3 +- ipn/ipnserver/server.go | 67 +++++++++----------------- ipn/localapi/localapi.go | 95 +++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 46 deletions(-) create mode 100644 ipn/localapi/localapi.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index c19b50860..f87d3cc91 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -72,9 +72,10 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/disco from tailscale.com/derp+ tailscale.com/internal/deepprint from tailscale.com/ipn/ipnlocal+ tailscale.com/ipn from tailscale.com/ipn/ipnserver+ - tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver + tailscale.com/ipn/ipnlocal from tailscale.com/ipn/ipnserver+ tailscale.com/ipn/ipnserver from tailscale.com/cmd/tailscaled tailscale.com/ipn/ipnstate from tailscale.com/ipn+ + tailscale.com/ipn/localapi from tailscale.com/ipn/ipnserver tailscale.com/ipn/policy from tailscale.com/ipn/ipnlocal tailscale.com/log/filelogger from tailscale.com/ipn/ipnserver tailscale.com/log/logheap from tailscale.com/control/controlclient diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index bc120ca60..681e5263c 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -7,7 +7,6 @@ package ipnserver import ( "bufio" "context" - "encoding/json" "errors" "fmt" "io" @@ -25,16 +24,17 @@ import ( "syscall" "time" + "go4.org/mem" "inet.af/netaddr" "tailscale.com/control/controlclient" "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" + "tailscale.com/ipn/localapi" "tailscale.com/log/filelogger" "tailscale.com/logtail/backoff" "tailscale.com/net/netstat" "tailscale.com/safesocket" "tailscale.com/smallzstd" - "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/util/pidowner" "tailscale.com/util/systemd" @@ -222,13 +222,22 @@ func (s *server) blockWhileInUse(conn io.Reader, ci connIdentity) { } } +// bufferHasHTTPRequest reports whether br looks like it has an HTTP +// request in it, without reading any bytes from it. +func bufferHasHTTPRequest(br *bufio.Reader) bool { + peek, _ := br.Peek(br.Buffered()) + return mem.HasPrefix(mem.B(peek), mem.S("GET ")) || + mem.HasPrefix(mem.B(peek), mem.S("POST ")) || + mem.Contains(mem.B(peek), mem.S(" HTTP/")) +} + func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) { // First see if it's an HTTP request. br := bufio.NewReader(c) c.SetReadDeadline(time.Now().Add(time.Second)) - peek, _ := br.Peek(4) + br.Peek(4) c.SetReadDeadline(time.Time{}) - isHTTPReq := string(peek) == "GET " + isHTTPReq := bufferHasHTTPRequest(br) ci, err := s.addConn(c, isHTTPReq) if err != nil { @@ -255,7 +264,7 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) { s.b.SetCurrentUserID(ci.UserID) if isHTTPReq { - httpServer := http.Server{ + httpServer := &http.Server{ // Localhost connections are cheap; so only do // keep-alives for a short period of time, as these // active connections lock the server into only serving @@ -626,7 +635,9 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() ( opts.DebugMux.HandleFunc("/debug/ipn", func(w http.ResponseWriter, r *http.Request) { serveHTMLStatus(w, b) }) - opts.DebugMux.Handle("/localapi/v0/whois", whoIsHandler{b}) + h := localapi.NewHandler(b) + h.PermitRead = true + opts.DebugMux.Handle("/localapi/", h) } server.b = b @@ -867,8 +878,11 @@ func (psc *protoSwitchConn) Close() error { func (s *server) localhostHandler(ci connIdentity) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if ci.IsUnixSock && r.URL.Path == "/localapi/v0/whois" { - whoIsHandler{s.b}.ServeHTTP(w, r) + if ci.IsUnixSock && strings.HasPrefix(r.URL.Path, "/localapi/") { + h := localapi.NewHandler(s.b) + h.PermitRead = true + h.PermitWrite = false // TODO: flesh out connIdentity on more platforms then set this + h.ServeHTTP(w, r) return } if ci.Unknown { @@ -894,40 +908,3 @@ func peerPid(entries []netstat.Entry, la, ra netaddr.IPPort) int { } return 0 } - -// whoIsHandler is the debug server's /debug?ip=$IP HTTP handler. -type whoIsHandler struct { - b *ipnlocal.LocalBackend -} - -func (h whoIsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - b := h.b - var ip netaddr.IP - if v := r.FormValue("ip"); v != "" { - var err error - ip, err = netaddr.ParseIP(r.FormValue("ip")) - if err != nil { - http.Error(w, "invalid 'ip' parameter", 400) - return - } - } else { - http.Error(w, "missing 'ip' parameter", 400) - return - } - n, u, ok := b.WhoIs(ip) - if !ok { - http.Error(w, "no match for IP", 404) - return - } - res := &tailcfg.WhoIsResponse{ - Node: n, - UserProfile: &u, - } - j, err := json.MarshalIndent(res, "", "\t") - if err != nil { - http.Error(w, "JSON encoding error", 500) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(j) -} diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go new file mode 100644 index 000000000..7b537d404 --- /dev/null +++ b/ipn/localapi/localapi.go @@ -0,0 +1,95 @@ +// 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 localapi contains the HTTP server handlers for tailscaled's API server. +package localapi + +import ( + "encoding/json" + "io" + "net/http" + + "inet.af/netaddr" + "tailscale.com/ipn/ipnlocal" + "tailscale.com/tailcfg" +) + +func NewHandler(b *ipnlocal.LocalBackend) *Handler { + return &Handler{b: b} +} + +type Handler struct { + // RequiredPassword, if non-empty, forces all HTTP + // requests to have HTTP basic auth with this password. + // It's used by the sandboxed macOS sameuserproof GUI auth mechanism. + RequiredPassword string + + // PermitRead is whether read-only HTTP handlers are allowed. + PermitRead bool + + // PermitWrite is whether mutating HTTP handlers are allowed. + PermitWrite bool + + b *ipnlocal.LocalBackend +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if h.b == nil { + http.Error(w, "server has no local backend", http.StatusInternalServerError) + return + } + if h.RequiredPassword != "" { + _, pass, ok := r.BasicAuth() + if !ok { + http.Error(w, "auth required", http.StatusUnauthorized) + return + } + if pass != h.RequiredPassword { + http.Error(w, "bad password", http.StatusForbidden) + return + } + } + switch r.URL.Path { + case "/localapi/v0/whois": + h.serveWhoIs(w, r) + default: + io.WriteString(w, "tailscaled\n") + } +} + +func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) { + if !h.PermitRead { + http.Error(w, "whois access denied", http.StatusForbidden) + return + } + b := h.b + var ip netaddr.IP + if v := r.FormValue("ip"); v != "" { + var err error + ip, err = netaddr.ParseIP(r.FormValue("ip")) + if err != nil { + http.Error(w, "invalid 'ip' parameter", 400) + return + } + } else { + http.Error(w, "missing 'ip' parameter", 400) + return + } + n, u, ok := b.WhoIs(ip) + if !ok { + http.Error(w, "no match for IP", 404) + return + } + res := &tailcfg.WhoIsResponse{ + Node: n, + UserProfile: &u, + } + j, err := json.MarshalIndent(res, "", "\t") + if err != nil { + http.Error(w, "JSON encoding error", 500) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(j) +} From 7e00100a0a3bdcf24243e2c82b4aa3d6cd7bec39 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 11:16:10 -0800 Subject: [PATCH 65/71] cmd/hello: make whois client work on macOS against GUI client Signed-off-by: Brad Fitzpatrick --- cmd/hello/hello.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/cmd/hello/hello.go b/cmd/hello/hello.go index e768c1ef2..c31ccb703 100644 --- a/cmd/hello/hello.go +++ b/cmd/hello/hello.go @@ -16,6 +16,8 @@ import ( "net" "net/http" "net/url" + "os" + "strconv" "strings" "tailscale.com/safesocket" @@ -25,10 +27,21 @@ import ( var ( httpAddr = flag.String("http", ":80", "address to run an HTTP server on, or empty for none") httpsAddr = flag.String("https", ":443", "address to run an HTTPS server on, or empty for none") + testIP = flag.String("test-ip", "", "if non-empty, look up IP and exit before running a server") ) func main() { flag.Parse() + if *testIP != "" { + res, err := whoIs(*testIP) + if err != nil { + log.Fatal(err) + } + e := json.NewEncoder(os.Stdout) + e.SetIndent("", "\t") + e.Encode(res) + return + } if !devMode() { tmpl = template.Must(template.New("home").Parse(slurpHTML())) } @@ -113,6 +126,7 @@ func root(w http.ResponseWriter, r *http.Request) { var data tmplData if err != nil { if devMode() { + log.Printf("warning: using fake data in dev mode due to whois lookup error: %v", err) data = tmplData{ DisplayName: "Taily Scalerson", LoginName: "taily@scaler.son", @@ -153,13 +167,28 @@ func firstLabel(s string) string { var tsSockClient = &http.Client{ Transport: &http.Transport{ DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + // On macOS, when dialing from non-sandboxed program to sandboxed GUI running + // a TCP server on a random port, find the random port. For HTTP connections, + // we don't send the token. It gets added in an HTTP Basic-Auth header. + if port, _, err := safesocket.LocalTCPPortAndToken(); err == nil { + var d net.Dialer + return d.DialContext(ctx, "tcp", "localhost:"+strconv.Itoa(port)) + } return safesocket.ConnectDefault() }, }, } func whoIs(ip string) (*tailcfg.WhoIsResponse, error) { - res, err := tsSockClient.Get("http://local-tailscaled.sock/localapi/v0/whois?ip=" + url.QueryEscape(ip)) + ctx := context.Background() + req, err := http.NewRequestWithContext(ctx, "GET", "http://local-tailscaled.sock/localapi/v0/whois?ip="+url.QueryEscape(ip), nil) + if err != nil { + return nil, err + } + if _, token, err := safesocket.LocalTCPPortAndToken(); err == nil { + req.SetBasicAuth("", token) + } + res, err := tsSockClient.Do(req) if err != nil { return nil, err } From dec01ef22bcce180bbdd70c6b7027900f0fd79d7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 11:19:22 -0800 Subject: [PATCH 66/71] safesocket: make ConnectDefault use paths pkg, fixing tailscaled-on-macOS Signed-off-by: Brad Fitzpatrick --- cmd/tailscale/depaware.txt | 2 +- safesocket/safesocket.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/tailscale/depaware.txt b/cmd/tailscale/depaware.txt index 7bd833cec..4fb8d7d6c 100644 --- a/cmd/tailscale/depaware.txt +++ b/cmd/tailscale/depaware.txt @@ -33,7 +33,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep tailscale.com/net/tlsdial from tailscale.com/derp/derphttp tailscale.com/net/tsaddr from tailscale.com/net/interfaces 💣 tailscale.com/net/tshttpproxy from tailscale.com/derp/derphttp+ - tailscale.com/paths from tailscale.com/cmd/tailscale/cli + tailscale.com/paths from tailscale.com/cmd/tailscale/cli+ tailscale.com/safesocket from tailscale.com/cmd/tailscale/cli 💣 tailscale.com/syncs from tailscale.com/net/interfaces+ tailscale.com/tailcfg from tailscale.com/cmd/tailscale/cli+ diff --git a/safesocket/safesocket.go b/safesocket/safesocket.go index 19e183463..5cbf73770 100644 --- a/safesocket/safesocket.go +++ b/safesocket/safesocket.go @@ -10,6 +10,8 @@ import ( "errors" "net" "runtime" + + "tailscale.com/paths" ) type closeable interface { @@ -31,7 +33,7 @@ func ConnCloseWrite(c net.Conn) error { // ConnectDefault connects to the local Tailscale daemon. func ConnectDefault() (net.Conn, error) { - return Connect("/var/run/tailscale/tailscaled.sock", 41112) + return Connect(paths.DefaultTailscaledSocket(), 41112) } // Connect connects to either path (on Unix) or the provided localhost port (on Windows). From 73552eb32e6514db5baea0b3ddcf604fd80d42f1 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 12:58:56 -0800 Subject: [PATCH 67/71] tailcfg: add Hostinfo.Package Updates tailscale/corp#440 --- tailcfg/tailcfg.go | 1 + tailcfg/tailcfg_clone.go | 1 + tailcfg/tailcfg_test.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index bcb7ec205..f687c9b04 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -406,6 +406,7 @@ type Hostinfo struct { BackendLogID string `json:",omitempty"` // logtail ID of backend instance OS string // operating system the client runs on (a version.OS value) OSVersion string `json:",omitempty"` // operating system version, with optional distro prefix ("Debian 10.4", "Windows 10 Pro 10.0.19041") + Package string `json:",omitempty"` // Tailscale package to disambiguate ("choco", "appstore", etc; "" for unknown) DeviceModel string `json:",omitempty"` // mobile phone model ("Pixel 3a", "iPhone 11 Pro") Hostname string // name of the host the client runs on ShieldsUp bool `json:",omitempty"` // indicates whether the host is blocking incoming connections diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 87bbb2484..ee57f8113 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -107,6 +107,7 @@ var _HostinfoNeedsRegeneration = Hostinfo(struct { BackendLogID string OS string OSVersion string + Package string DeviceModel string Hostname string ShieldsUp bool diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index a6c843db5..5fa579b0d 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -25,7 +25,7 @@ func fieldsOf(t reflect.Type) (fields []string) { func TestHostinfoEqual(t *testing.T) { hiHandles := []string{ "IPNVersion", "FrontendLogID", "BackendLogID", - "OS", "OSVersion", "DeviceModel", "Hostname", + "OS", "OSVersion", "Package", "DeviceModel", "Hostname", "ShieldsUp", "ShareeNode", "GoArch", "RoutableIPs", "RequestTags", From 4ec01323c135c44f4f85acd8e2330a8e8542de71 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 13:23:11 -0800 Subject: [PATCH 68/71] control/controlclient: note package type in Hostinfo Fixes tailscale/corp#440 Signed-off-by: Brad Fitzpatrick --- control/controlclient/direct.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/control/controlclient/direct.go b/control/controlclient/direct.go index ed36dd502..1328af09e 100644 --- a/control/controlclient/direct.go +++ b/control/controlclient/direct.go @@ -20,6 +20,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "reflect" "runtime" "sort" @@ -172,10 +173,25 @@ func NewHostinfo() *tailcfg.Hostinfo { Hostname: hostname, OS: version.OS(), OSVersion: osv, + Package: packageType(), GoArch: runtime.GOARCH, } } +func packageType() string { + switch runtime.GOOS { + case "windows": + if _, err := os.Stat(`C:\ProgramData\chocolatey\lib\tailscale`); err == nil { + return "choco" + } + case "darwin": + // Using tailscaled or IPNExtension? + exe, _ := os.Executable() + return filepath.Base(exe) + } + return "" +} + // SetHostinfo clones the provided Hostinfo and remembers it for the // next update. It reports whether the Hostinfo has changed. func (c *Direct) SetHostinfo(hi *tailcfg.Hostinfo) bool { From 65815cc1ac615371243da9ad8f56cecb7bfda721 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 16 Feb 2021 10:20:24 -0800 Subject: [PATCH 69/71] wgengine/tsdns: skip test that requires local IPv6 when IPv6 unavailable Fixes #1292 Signed-off-by: Brad Fitzpatrick --- wgengine/tsdns/tsdns_server_test.go | 11 +++++++++-- wgengine/tsdns/tsdns_test.go | 23 ++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/wgengine/tsdns/tsdns_server_test.go b/wgengine/tsdns/tsdns_server_test.go index bffb8b869..df9047fc6 100644 --- a/wgengine/tsdns/tsdns_server_test.go +++ b/wgengine/tsdns/tsdns_server_test.go @@ -5,6 +5,9 @@ package tsdns import ( + "log" + "testing" + "github.com/miekg/dns" "inet.af/netaddr" ) @@ -71,7 +74,7 @@ func resolveToNXDOMAIN(w dns.ResponseWriter, req *dns.Msg) { w.WriteMsg(m) } -func serveDNS(addr string) (*dns.Server, chan error) { +func serveDNS(tb testing.TB, addr string) (*dns.Server, chan error) { server := &dns.Server{Addr: addr, Net: "udp"} waitch := make(chan struct{}) @@ -79,7 +82,11 @@ func serveDNS(addr string) (*dns.Server, chan error) { errch := make(chan error, 1) go func() { - errch <- server.ListenAndServe() + err := server.ListenAndServe() + if err != nil { + log.Printf("ListenAndServe(%q): %v", addr, err) + } + errch <- err close(errch) }() diff --git a/wgengine/tsdns/tsdns_test.go b/wgengine/tsdns/tsdns_test.go index 95d32dfbb..a2f56a168 100644 --- a/wgengine/tsdns/tsdns_test.go +++ b/wgengine/tsdns/tsdns_test.go @@ -274,14 +274,27 @@ func TestResolveReverse(t *testing.T) { } } +func ipv6Works() bool { + c, err := net.Listen("tcp", "[::1]:0") + if err != nil { + return false + } + c.Close() + return true +} + func TestDelegate(t *testing.T) { tstest.ResourceCheck(t) + if !ipv6Works() { + t.Skip("skipping test that requires localhost IPv6") + } + dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) dnsHandleFunc("nxdomain.site.", resolveToNXDOMAIN) - v4server, v4errch := serveDNS("127.0.0.1:0") - v6server, v6errch := serveDNS("[::1]:0") + v4server, v4errch := serveDNS(t, "127.0.0.1:0") + v6server, v6errch := serveDNS(t, "[::1]:0") defer func() { if err := <-v4errch; err != nil { @@ -371,7 +384,7 @@ func TestDelegate(t *testing.T) { func TestDelegateCollision(t *testing.T) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - server, errch := serveDNS("127.0.0.1:0") + server, errch := serveDNS(t, "127.0.0.1:0") defer func() { if err := <-errch; err != nil { t.Errorf("server error: %v", err) @@ -473,7 +486,7 @@ func TestConcurrentSetMap(t *testing.T) { func TestConcurrentSetUpstreams(t *testing.T) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - server, errch := serveDNS("127.0.0.1:0") + server, errch := serveDNS(t, "127.0.0.1:0") defer func() { if err := <-errch; err != nil { t.Errorf("server error: %v", err) @@ -752,7 +765,7 @@ func TestTrimRDNSBonjourPrefix(t *testing.T) { func BenchmarkFull(b *testing.B) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site.")) - server, errch := serveDNS("127.0.0.1:0") + server, errch := serveDNS(b, "127.0.0.1:0") defer func() { if err := <-errch; err != nil { b.Errorf("server error: %v", err) From d3efe8caf636aaa9a8e02b65877878954ea980b7 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Mon, 15 Feb 2021 20:50:20 -0800 Subject: [PATCH 70/71] safesocket, ipn/ipnserver: look up peer creds on Darwin And open up socket permissions like Linux, now that we know who connections are from. This uses the new inet.af/peercred that supports Linux and Darwin at the moment. Fixes #1347 Fixes #1348 Signed-off-by: Brad Fitzpatrick --- cmd/tailscaled/depaware.txt | 1 + go.mod | 3 ++- go.sum | 4 +++ ipn/ipnserver/conn_linux.go | 49 ---------------------------------- ipn/ipnserver/conn_no_ucred.go | 27 ------------------- ipn/ipnserver/server.go | 14 ++++++++++ safesocket/unixsocket.go | 5 ++-- 7 files changed, 24 insertions(+), 79 deletions(-) delete mode 100644 ipn/ipnserver/conn_linux.go delete mode 100644 ipn/ipnserver/conn_no_ucred.go diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index f87d3cc91..414734d43 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -63,6 +63,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de gvisor.dev/gvisor/pkg/tcpip/transport/udp from gvisor.dev/gvisor/pkg/tcpip/adapters/gonet+ gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/tcpip+ inet.af/netaddr from tailscale.com/control/controlclient+ + inet.af/peercred from tailscale.com/ipn/ipnserver rsc.io/goversion/version from tailscale.com/version tailscale.com/atomicfile from tailscale.com/ipn+ tailscale.com/control/controlclient from tailscale.com/ipn/ipnlocal+ diff --git a/go.mod b/go.mod index c0266a0d0..a7f3bcbf3 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( golang.org/x/net v0.0.0-20201224014010-6772e930b67b golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 - golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 + golang.org/x/sys v0.0.0-20210216224549-f992740a1bac golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b golang.org/x/time v0.0.0-20191024005414-555d28b269f0 golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 @@ -40,5 +40,6 @@ require ( gvisor.dev/gvisor v0.0.0-20210111185822-3ff3110fcdd6 honnef.co/go/tools v0.1.0 inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d + inet.af/peercred v0.0.0-20210216231719-993aa01eacaa rsc.io/goversion v1.2.0 ) diff --git a/go.sum b/go.sum index afa7d830c..2663ff004 100644 --- a/go.sum +++ b/go.sum @@ -461,6 +461,8 @@ golang.org/x/sys v0.0.0-20201218084310-7d0127a74742 h1:+CBz4km/0KPU3RGTwARGh/noP golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 h1:/dSxr6gT0FNI1MO5WLJo8mTmItROeOKTkDn+7OwWBos= golang.org/x/sys v0.0.0-20210105210732-16f7687f5001/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210216224549-f992740a1bac h1:9glrpwtNjBYgRpb67AZJKHfzj1stG/8BL5H7In2oTC4= +golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201207232118-ee85cb95a76b h1:a0ErnNnPKmhDyIXQvdZr+Lq8dc8xpMeqkF8y5PgQU4Q= @@ -576,6 +578,8 @@ inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf h1:0eHZ8v6j5wIiOVyoYPd70ueZ/R inet.af/netaddr v0.0.0-20201228234250-33d0a924ebbf/go.mod h1:9NdhtHLglxJliAZB6aC5ws3mfnUArdAzHG/iJq7cB/o= inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d h1:6f0242aW/6x2enQBOSKgDS8KQNw6Tp7IVR8eG3x0Jc8= inet.af/netaddr v0.0.0-20210105212526-648fbc18a69d/go.mod h1:jPZo7Jy4nke2cCgISa4fKJKa5T7+EO8k5fWwWghzneg= +inet.af/peercred v0.0.0-20210216231719-993aa01eacaa h1:6qseJO2iNDHl+MLL2BkO5oURJR4A9pLmRz11Yf7KdGM= +inet.af/peercred v0.0.0-20210216231719-993aa01eacaa/go.mod h1:VZeNdG7cRIUqKl9DWoFX86AHyfYwdb4RextAw1CAEO4= k8s.io/api v0.16.13/go.mod h1:QWu8UWSTiuQZMMeYjwLs6ILu5O74qKSJ0c+4vrchDxs= k8s.io/apimachinery v0.16.13/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= k8s.io/apimachinery v0.16.14-rc.0/go.mod h1:4HMHS3mDHtVttspuuhrJ1GGr/0S9B6iWYWZ57KnnZqQ= diff --git a/ipn/ipnserver/conn_linux.go b/ipn/ipnserver/conn_linux.go deleted file mode 100644 index 1aca57e26..000000000 --- a/ipn/ipnserver/conn_linux.go +++ /dev/null @@ -1,49 +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. - -// +build linux - -package ipnserver - -import ( - "net" - - "golang.org/x/sys/unix" - "tailscale.com/types/logger" -) - -func isReadonlyConn(c net.Conn, logf logger.Logf) (ro bool) { - ro = true // conservative default for naked returns below - uc, ok := c.(*net.UnixConn) - if !ok { - logf("unexpected connection type %T", c) - return - } - raw, err := uc.SyscallConn() - if err != nil { - logf("SyscallConn: %v", err) - return - } - - var cred *unix.Ucred - cerr := raw.Control(func(fd uintptr) { - cred, err = unix.GetsockoptUcred(int(fd), - unix.SOL_SOCKET, - unix.SO_PEERCRED) - }) - if cerr != nil { - logf("raw.Control: %v", err) - return - } - if err != nil { - logf("raw.Control: %v", err) - return - } - if cred.Uid == 0 { - // root is not read-only. - return false - } - logf("non-root connection from %v (read-only)", cred.Uid) - return true -} diff --git a/ipn/ipnserver/conn_no_ucred.go b/ipn/ipnserver/conn_no_ucred.go deleted file mode 100644 index c50e4778d..000000000 --- a/ipn/ipnserver/conn_no_ucred.go +++ /dev/null @@ -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. - -// +build !linux - -package ipnserver - -import ( - "net" - - "tailscale.com/types/logger" -) - -func isReadonlyConn(c net.Conn, logf logger.Logf) bool { - // Windows doesn't need/use this mechanism, at least yet. It - // has a different last-user-wins auth model. - - // And on Darwin, we're not using it yet, as the Darwin - // tailscaled port isn't yet done, and unix.Ucred and - // unix.GetsockoptUcred aren't in x/sys/unix. - - // TODO(bradfitz): OpenBSD and FreeBSD should implement this too. - // But their x/sys/unix package is different than Linux, so - // I didn't include it for now. - return false -} diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 681e5263c..1273513ae 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -26,6 +26,7 @@ import ( "go4.org/mem" "inet.af/netaddr" + "inet.af/peercred" "tailscale.com/control/controlclient" "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" @@ -309,6 +310,19 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) { } } +func isReadonlyConn(c net.Conn, logf logger.Logf) bool { + creds, err := peercred.Get(c) + if err != nil { + return true // conservatively + } + uid, ok := creds.UserID() + if !ok { + return true // conservatively + } + logf("connection from userid %v", uid) + return uid != "0" +} + // inUseOtherUserError is the error type for when the server is in use // by a different local user. type inUseOtherUserError struct{ error } diff --git a/safesocket/unixsocket.go b/safesocket/unixsocket.go index 8128bef57..f86d55367 100644 --- a/safesocket/unixsocket.go +++ b/safesocket/unixsocket.go @@ -103,8 +103,9 @@ func tailscaledRunningUnderLaunchd() bool { // socketPermissionsForOS returns the permissions to use for the // tailscaled.sock. func socketPermissionsForOS() os.FileMode { - if runtime.GOOS == "linux" { - // On Linux, the ipn/ipnserver package looks at the Unix peer creds + switch runtime.GOOS { + case "linux", "darwin": + // On Linux and Darwin, the ipn/ipnserver package looks at the Unix peer creds // and only permits read-only actions from non-root users, so we want // this opened up wider. // From 7038c09bc91c7f65ff33afb777187ef9acca214c Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 16 Feb 2021 21:06:10 -0800 Subject: [PATCH 71/71] ipn/ipnserver: on darwin, let users who are admins use CLI without sudo Tangentially related to #987, #177, #594, #925, #505 Signed-off-by: Brad Fitzpatrick --- ipn/ipnserver/server.go | 60 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 1273513ae..f7937d39a 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -21,6 +21,7 @@ import ( "runtime" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -311,16 +312,67 @@ func (s *server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) { } func isReadonlyConn(c net.Conn, logf logger.Logf) bool { + const ro = true + const rw = false creds, err := peercred.Get(c) if err != nil { - return true // conservatively + logf("connection from unknown peer; read-only") + return ro } uid, ok := creds.UserID() if !ok { - return true // conservatively + logf("connection from peer with unknown userid; read-only") + return ro } - logf("connection from userid %v", uid) - return uid != "0" + if uid == "0" { + logf("connection from userid %v; root has access", uid) + return rw + } + var adminGroupID string + switch runtime.GOOS { + case "darwin": + adminGroupID = darwinAdminGroupID() + default: + logf("connection from userid %v; read-only", uid) + return ro + } + if adminGroupID == "" { + logf("connection from userid %v; no system admin group found, read-only", uid) + return ro + } + u, err := user.LookupId(uid) + if err != nil { + logf("connection from userid %v; failed to look up user; read-only", uid) + return ro + } + gids, err := u.GroupIds() + if err != nil { + logf("connection from userid %v; failed to look up groups; read-only", uid) + return ro + } + for _, gid := range gids { + if gid == adminGroupID { + logf("connection from userid %v; is local admin, has access", uid) + return rw + } + } + logf("connection from userid %v; read-only", uid) + return ro +} + +var darwinAdminGroupIDCache atomic.Value // of string + +func darwinAdminGroupID() string { + s, _ := darwinAdminGroupIDCache.Load().(string) + if s != "" { + return s + } + g, err := user.LookupGroup("admin") + if err != nil { + return "" + } + darwinAdminGroupIDCache.Store(g.Gid) + return g.Gid } // inUseOtherUserError is the error type for when the server is in use