diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 96b0bfec0..624316edd 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -200,11 +200,11 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/packet from tailscale.com/net/tstun+ tailscale.com/net/portmapper from tailscale.com/cmd/tailscaled+ tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled - tailscale.com/net/socks5 from tailscale.com/net/socks5/tssocks - tailscale.com/net/socks5/tssocks from tailscale.com/cmd/tailscaled + tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled tailscale.com/net/stun from tailscale.com/net/netcheck+ tailscale.com/net/tlsdial from tailscale.com/control/controlclient+ tailscale.com/net/tsaddr from tailscale.com/ipn/ipnlocal+ + tailscale.com/net/tsdial from tailscale.com/cmd/tailscaled+ 💣 tailscale.com/net/tshttpproxy from tailscale.com/cmd/tailscaled+ tailscale.com/net/tstun from tailscale.com/cmd/tailscaled+ 💣 tailscale.com/paths from tailscale.com/client/tailscale+ @@ -252,7 +252,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de 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/tailscaled+ - tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled+ + tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+ tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+ tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index c9f2a50eb..f9447fb01 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -28,6 +28,7 @@ import ( "syscall" "time" + "inet.af/netaddr" "tailscale.com/ipn" "tailscale.com/ipn/ipnserver" "tailscale.com/logpolicy" @@ -35,12 +36,14 @@ import ( "tailscale.com/net/dns" "tailscale.com/net/netns" "tailscale.com/net/proxymux" - "tailscale.com/net/socks5/tssocks" + "tailscale.com/net/socks5" + "tailscale.com/net/tsdial" "tailscale.com/net/tstun" "tailscale.com/paths" "tailscale.com/safesocket" "tailscale.com/types/flagtype" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/util/clientmetric" "tailscale.com/util/multierr" "tailscale.com/util/osshare" @@ -324,17 +327,34 @@ func run() error { log.Fatalf("failed to start netstack: %v", err) } + dialer := new(tsdial.Dialer) + if useNetstack { + dialer.UseNetstackForIP = func(ip netaddr.IP) bool { + _, ok := e.PeerForIP(ip) + return ok + } + dialer.NetstackDialTCP = func(ctx context.Context, dst netaddr.IPPort) (net.Conn, error) { + return ns.DialContextTCP(ctx, dst.String()) + } + } + e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { + dialer.SetDNSMap(tsdial.DNSMapFromNetworkMap(nm)) + }) + if socksListener != nil || httpProxyListener != nil { - srv := tssocks.NewServer(logger.WithPrefix(logf, "socks5: "), e, ns) if httpProxyListener != nil { - hs := &http.Server{Handler: httpProxyHandler(srv.Dialer)} + hs := &http.Server{Handler: httpProxyHandler(dialer.DialContext)} go func() { log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpProxyListener)) }() } if socksListener != nil { + ss := &socks5.Server{ + Logf: logger.WithPrefix(logf, "socks5: "), + Dialer: dialer.DialContext, + } go func() { - log.Fatalf("SOCKS5 server exited: %v", srv.Serve(socksListener)) + log.Fatalf("SOCKS5 server exited: %v", ss.Serve(socksListener)) }() } } diff --git a/net/socks5/tssocks/tssocks.go b/net/socks5/tssocks/tssocks.go deleted file mode 100644 index c8d021ebe..000000000 --- a/net/socks5/tssocks/tssocks.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package tssocks is the glue between Tailscale and the net/socks5 package. -package tssocks - -import ( - "context" - "net" - "sync" - - "inet.af/netaddr" - "tailscale.com/net/socks5" - "tailscale.com/types/logger" - "tailscale.com/types/netmap" - "tailscale.com/wgengine" - "tailscale.com/wgengine/netstack" -) - -// NewServer returns a new SOCKS5 server configured to dial out to -// Tailscale addresses. -// -// The returned server is not yet listening. The caller must call -// Serve with a listener. -// -// If ns is non-nil, it is used for dialing when needed. -func NewServer(logf logger.Logf, e wgengine.Engine, ns *netstack.Impl) *socks5.Server { - d := &dialer{ns: ns, eng: e} - e.AddNetworkMapCallback(d.onNewNetmap) - return &socks5.Server{ - Logf: logf, - Dialer: d.DialContext, - } -} - -// dialer is the Tailscale SOCKS5 dialer. -type dialer struct { - ns *netstack.Impl - eng wgengine.Engine - - mu sync.Mutex - dns netstack.DNSMap -} - -func (d *dialer) onNewNetmap(nm *netmap.NetworkMap) { - d.mu.Lock() - defer d.mu.Unlock() - d.dns = netstack.DNSMapFromNetworkMap(nm) -} - -func (d *dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { - d.mu.Lock() - dns := d.dns - d.mu.Unlock() - return dns.Resolve(ctx, addr) -} - -func (d *dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { - ipp, err := d.resolve(ctx, addr) - if err != nil { - return nil, err - } - if d.ns != nil && d.useNetstackForIP(ipp.IP()) { - return d.ns.DialContextTCP(ctx, ipp.String()) - } - var stdDialer net.Dialer - return stdDialer.DialContext(ctx, network, ipp.String()) -} - -func (d *dialer) useNetstackForIP(ip netaddr.IP) bool { - if d.ns == nil || !d.ns.ProcessLocalIPs { - // If netstack isn't used at all (nil), then obviously don't use it. - // - // But the ProcessLocalIPs check is more subtle: it really means - // whether we should use netstack for incoming traffic to ourselves. - // It's only ever true if we're running in full netstack mode (no TUN), - // so we can also use it as a proxy here for whether TUN is available. - // If it's false, there's tun and OS routes to things we need, - // so we don't want to dial with netstack. - return false - } - // Otherwise, we're in netstack mode, so dial via netstack if there's - // any peer handling that IP (including exit nodes). - // - // Otherwise assume it's something else (e.g. dialing - // google.com:443 via SOCKS) that the caller can dial directly. - _, ok := d.eng.PeerForIP(ip) - return ok -} diff --git a/net/tsdial/dnsmap.go b/net/tsdial/dnsmap.go new file mode 100644 index 000000000..8541d795b --- /dev/null +++ b/net/tsdial/dnsmap.go @@ -0,0 +1,114 @@ +// 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 tsdial + +import ( + "context" + "fmt" + "net" + "strconv" + "strings" + + "inet.af/netaddr" + "tailscale.com/types/netmap" + "tailscale.com/util/dnsname" +) + +// DNSMap maps MagicDNS names (both base + FQDN) to their first IP. +// It must not be mutated once created. +// +// Example keys are "foo.domain.tld.beta.tailscale.net" and "foo", +// both without trailing dots. +type DNSMap map[string]netaddr.IP + +func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap { + ret := make(DNSMap) + suffix := nm.MagicDNSSuffix() + have4 := false + if nm.Name != "" && len(nm.Addresses) > 0 { + ip := nm.Addresses[0].IP() + ret[strings.TrimRight(nm.Name, ".")] = ip + if dnsname.HasSuffix(nm.Name, suffix) { + ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip + } + for _, a := range nm.Addresses { + if a.IP().Is4() { + have4 = true + } + } + } + for _, p := range nm.Peers { + if p.Name == "" { + continue + } + for _, a := range p.Addresses { + ip := a.IP() + if ip.Is4() && !have4 { + continue + } + ret[strings.TrimRight(p.Name, ".")] = ip + if dnsname.HasSuffix(p.Name, suffix) { + ret[dnsname.TrimSuffix(p.Name, suffix)] = ip + } + break + } + } + for _, rec := range nm.DNS.ExtraRecords { + if rec.Type != "" { + continue + } + ip, err := netaddr.ParseIP(rec.Value) + if err != nil { + continue + } + ret[strings.TrimRight(rec.Name, ".")] = ip + } + return ret +} + +// Resolve resolves addr into an IP:port using first the MagicDNS contents +// of m, else using the system resolver. +func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { + ipp, pippErr := netaddr.ParseIPPort(addr) + if pippErr == nil { + return ipp, nil + } + host, port, err := net.SplitHostPort(addr) + if err != nil { + // addr is malformed. + return netaddr.IPPort{}, err + } + if _, err := netaddr.ParseIP(host); err == nil { + // The host part of addr was an IP, so the netaddr.ParseIPPort above should've + // passed. Must've been a bad port number. Return the original error. + return netaddr.IPPort{}, pippErr + } + port16, err := strconv.ParseUint(port, 10, 16) + if err != nil { + return netaddr.IPPort{}, fmt.Errorf("invalid port in address %q", addr) + } + + // Host is not an IP, so assume it's a DNS name. + + // Try MagicDNS first, otherwise a real DNS lookup. + ip := m[host] + if !ip.IsZero() { + return netaddr.IPPortFrom(ip, uint16(port16)), nil + } + + // TODO(bradfitz): wire up net/dnscache too. + + // No MagicDNS name so try real DNS. + var r net.Resolver + ips, err := r.LookupIP(ctx, "ip", host) + if err != nil { + return netaddr.IPPort{}, err + } + if len(ips) == 0 { + return netaddr.IPPort{}, fmt.Errorf("DNS lookup returned no results for %q", host) + } + ip, _ = netaddr.FromStdIP(ips[0]) + return netaddr.IPPortFrom(ip, uint16(port16)), nil +} diff --git a/wgengine/netstack/netstack_test.go b/net/tsdial/dnsmap_test.go similarity index 99% rename from wgengine/netstack/netstack_test.go rename to net/tsdial/dnsmap_test.go index 9b4792190..e3cfc5e76 100644 --- a/wgengine/netstack/netstack_test.go +++ b/net/tsdial/dnsmap_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 netstack +package tsdial import ( "reflect" diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go new file mode 100644 index 000000000..060c945eb --- /dev/null +++ b/net/tsdial/tsdial.go @@ -0,0 +1,61 @@ +// 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 tsdial provides a Dialer type that can dial out of tailscaled. +package tsdial + +import ( + "context" + "errors" + "net" + "sync" + + "inet.af/netaddr" +) + +// Dialer dials out of tailscaled, while taking care of details while +// handling the dozens of edge cases depending on the server mode +// (TUN, netstack), the OS network sandboxing style (macOS/iOS +// Extension, none), user-selected route acceptance prefs, etc. +type Dialer struct { + // UseNetstackForIP if non-nil is whether NetstackDialTCP (if + // it's non-nil) should be used to dial the provided IP. + UseNetstackForIP func(netaddr.IP) bool + + // NetstackDialTCP dials the provided IPPort using netstack. + // If nil, it's not used. + NetstackDialTCP func(context.Context, netaddr.IPPort) (net.Conn, error) + + mu sync.Mutex + dns DNSMap +} + +func (d *Dialer) SetDNSMap(m DNSMap) { + d.mu.Lock() + defer d.mu.Unlock() + d.dns = m +} + +func (d *Dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { + d.mu.Lock() + dns := d.dns + d.mu.Unlock() + return dns.Resolve(ctx, addr) +} + +func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { + ipp, err := d.resolve(ctx, addr) + if err != nil { + return nil, err + } + if d.UseNetstackForIP != nil && d.UseNetstackForIP(ipp.IP()) { + if d.NetstackDialTCP == nil { + return nil, errors.New("Dialer not initialized correctly") + } + return d.NetstackDialTCP(ctx, ipp) + } + // TODO(bradfitz): netns, etc + var stdDialer net.Dialer + return stdDialer.DialContext(ctx, network, ipp.String()) +} diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_freebsd.go b/tstest/integration/tailscaled_deps_test_freebsd.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_linux.go b/tstest/integration/tailscaled_deps_test_linux.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_openbsd.go b/tstest/integration/tailscaled_deps_test_openbsd.go index e71463354..c6c7c3c5e 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -23,7 +23,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -32,6 +33,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/tstest/integration/tailscaled_deps_test_windows.go b/tstest/integration/tailscaled_deps_test_windows.go index 442e15e35..d1ef8256c 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -27,7 +27,8 @@ import ( _ "tailscale.com/net/netns" _ "tailscale.com/net/portmapper" _ "tailscale.com/net/proxymux" - _ "tailscale.com/net/socks5/tssocks" + _ "tailscale.com/net/socks5" + _ "tailscale.com/net/tsdial" _ "tailscale.com/net/tshttpproxy" _ "tailscale.com/net/tstun" _ "tailscale.com/paths" @@ -36,6 +37,7 @@ import ( _ "tailscale.com/types/flagtype" _ "tailscale.com/types/key" _ "tailscale.com/types/logger" + _ "tailscale.com/types/netmap" _ "tailscale.com/util/clientmetric" _ "tailscale.com/util/multierr" _ "tailscale.com/util/osshare" diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 8002cf627..86f61de65 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -33,10 +33,10 @@ import ( "inet.af/netstack/waiter" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" + "tailscale.com/net/tsdial" "tailscale.com/net/tstun" "tailscale.com/types/logger" "tailscale.com/types/netmap" - "tailscale.com/util/dnsname" "tailscale.com/wgengine" "tailscale.com/wgengine/filter" "tailscale.com/wgengine/magicsock" @@ -79,7 +79,7 @@ type Impl struct { atomicIsLocalIPFunc atomic.Value // of func(netaddr.IP) bool mu sync.Mutex - dns DNSMap + dns tsdial.DNSMap // connsOpenBySubnetIP keeps track of number of connections open // for each subnet IP temporarily registered on netstack for active // TCP connections, so they can be unregistered when connections are @@ -179,59 +179,10 @@ func (ns *Impl) Start() error { return nil } -// DNSMap maps MagicDNS names (both base + FQDN) to their first IP. -// It should not be mutated once created. -type DNSMap map[string]netaddr.IP - -func DNSMapFromNetworkMap(nm *netmap.NetworkMap) DNSMap { - ret := make(DNSMap) - suffix := nm.MagicDNSSuffix() - have4 := false - if nm.Name != "" && len(nm.Addresses) > 0 { - ip := nm.Addresses[0].IP() - ret[strings.TrimRight(nm.Name, ".")] = ip - if dnsname.HasSuffix(nm.Name, suffix) { - ret[dnsname.TrimSuffix(nm.Name, suffix)] = ip - } - for _, a := range nm.Addresses { - if a.IP().Is4() { - have4 = true - } - } - } - for _, p := range nm.Peers { - if p.Name == "" { - continue - } - for _, a := range p.Addresses { - ip := a.IP() - if ip.Is4() && !have4 { - continue - } - ret[strings.TrimRight(p.Name, ".")] = ip - if dnsname.HasSuffix(p.Name, suffix) { - ret[dnsname.TrimSuffix(p.Name, suffix)] = ip - } - break - } - } - for _, rec := range nm.DNS.ExtraRecords { - if rec.Type != "" { - continue - } - ip, err := netaddr.ParseIP(rec.Value) - if err != nil { - continue - } - ret[strings.TrimRight(rec.Name, ".")] = ip - } - return ret -} - func (ns *Impl) updateDNS(nm *netmap.NetworkMap) { ns.mu.Lock() defer ns.mu.Unlock() - ns.dns = DNSMapFromNetworkMap(nm) + ns.dns = tsdial.DNSMapFromNetworkMap(nm) } func (ns *Impl) addSubnetAddress(ip netaddr.IP) { @@ -347,49 +298,6 @@ func (ns *Impl) updateIPs(nm *netmap.NetworkMap) { } } -// Resolve resolves addr into an IP:port using first the MagicDNS contents -// of m, else using the system resolver. -func (m DNSMap) Resolve(ctx context.Context, addr string) (netaddr.IPPort, error) { - ipp, pippErr := netaddr.ParseIPPort(addr) - if pippErr == nil { - return ipp, nil - } - host, port, err := net.SplitHostPort(addr) - if err != nil { - // addr is malformed. - return netaddr.IPPort{}, err - } - if net.ParseIP(host) != nil { - // The host part of addr was an IP, so the netaddr.ParseIPPort above should've - // passed. Must've been a bad port number. Return the original error. - return netaddr.IPPort{}, pippErr - } - port16, err := strconv.ParseUint(port, 10, 16) - if err != nil { - return netaddr.IPPort{}, fmt.Errorf("invalid port in address %q", addr) - } - - // Host is not an IP, so assume it's a DNS name. - - // Try MagicDNS first, else otherwise a real DNS lookup. - ip := m[host] - if !ip.IsZero() { - return netaddr.IPPortFrom(ip, uint16(port16)), nil - } - - // No MagicDNS name so try real DNS. - var r net.Resolver - ips, err := r.LookupIP(ctx, "ip", host) - if err != nil { - return netaddr.IPPort{}, err - } - if len(ips) == 0 { - return netaddr.IPPort{}, fmt.Errorf("DNS lookup returned no results for %q", host) - } - ip, _ = netaddr.FromStdIP(ips[0]) - return netaddr.IPPortFrom(ip, uint16(port16)), nil -} - func (ns *Impl) DialContextTCP(ctx context.Context, addr string) (*gonet.TCPConn, error) { ns.mu.Lock() dnsMap := ns.dns