diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 624316edd..7e3e88fd7 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -194,7 +194,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de tailscale.com/net/flowtrack from tailscale.com/net/packet+ 💣 tailscale.com/net/interfaces from tailscale.com/cmd/tailscaled+ tailscale.com/net/netcheck from tailscale.com/wgengine/magicsock - tailscale.com/net/netknob from tailscale.com/ipn/localapi+ + tailscale.com/net/netknob from tailscale.com/logpolicy+ tailscale.com/net/netns from tailscale.com/cmd/tailscaled+ 💣 tailscale.com/net/netstat from tailscale.com/ipn/ipnserver tailscale.com/net/packet from tailscale.com/net/tstun+ diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index f9447fb01..e0aaa72e8 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -43,7 +43,6 @@ import ( "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" @@ -308,7 +307,8 @@ func run() error { socksListener, httpProxyListener := mustStartProxyListeners(args.socksAddr, args.httpProxyAddr) - e, useNetstack, err := createEngine(logf, linkMon) + dialer := new(tsdial.Dialer) // mutated below (before used) + e, useNetstack, err := createEngine(logf, linkMon, dialer) if err != nil { logf("wgengine.New: %v", err) return err @@ -327,7 +327,6 @@ 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) @@ -337,13 +336,10 @@ func run() error { return ns.DialContextTCP(ctx, dst.String()) } } - e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { - dialer.SetDNSMap(tsdial.DNSMapFromNetworkMap(nm)) - }) if socksListener != nil || httpProxyListener != nil { if httpProxyListener != nil { - hs := &http.Server{Handler: httpProxyHandler(dialer.DialContext)} + hs := &http.Server{Handler: httpProxyHandler(dialer.UserDial)} go func() { log.Fatalf("HTTP proxy exited: %v", hs.Serve(httpProxyListener)) }() @@ -351,7 +347,7 @@ func run() error { if socksListener != nil { ss := &socks5.Server{ Logf: logger.WithPrefix(logf, "socks5: "), - Dialer: dialer.DialContext, + Dialer: dialer.UserDial, } go func() { log.Fatalf("SOCKS5 server exited: %v", ss.Serve(socksListener)) @@ -387,7 +383,7 @@ func run() error { logf("ipnserver.StateStore: %v", err) return err } - srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, nil, opts) + srv, err := ipnserver.New(logf, pol.PublicID.String(), store, e, dialer, nil, opts) if err != nil { logf("ipnserver.New: %v", err) return err @@ -412,14 +408,14 @@ func run() error { return nil } -func createEngine(logf logger.Logf, linkMon *monitor.Mon) (e wgengine.Engine, useNetstack bool, err error) { +func createEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer) (e wgengine.Engine, useNetstack bool, err error) { if args.tunname == "" { return nil, false, errors.New("no --tun value specified") } var errs []error for _, name := range strings.Split(args.tunname, ",") { logf("wgengine.NewUserspaceEngine(tun %q) ...", name) - e, useNetstack, err = tryEngine(logf, linkMon, name) + e, useNetstack, err = tryEngine(logf, linkMon, dialer, name) if err == nil { return e, useNetstack, nil } @@ -451,10 +447,11 @@ func shouldWrapNetstack() bool { return false } -func tryEngine(logf logger.Logf, linkMon *monitor.Mon, name string) (e wgengine.Engine, useNetstack bool, err error) { +func tryEngine(logf logger.Logf, linkMon *monitor.Mon, dialer *tsdial.Dialer, name string) (e wgengine.Engine, useNetstack bool, err error) { conf := wgengine.Config{ ListenPort: args.port, LinkMonitor: linkMon, + Dialer: dialer, } useNetstack = name == "userspace-networking" diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 35cc73123..5c005460e 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -36,6 +36,7 @@ import ( "tailscale.com/net/dns" "tailscale.com/net/interfaces" "tailscale.com/net/tsaddr" + "tailscale.com/net/tsdial" "tailscale.com/paths" "tailscale.com/portlist" "tailscale.com/tailcfg" @@ -88,6 +89,7 @@ type LocalBackend struct { statsLogf logger.Logf // for printing peers stats on change e wgengine.Engine store ipn.StateStore + dialer *tsdial.Dialer // non-nil backendLogID string unregisterLinkMon func() unregisterHealthWatch func() @@ -155,10 +157,18 @@ type clientGen func(controlclient.Options) (controlclient.Client, error) // NewLocalBackend returns a new LocalBackend that is ready to run, // but is not actually running. -func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wgengine.Engine) (*LocalBackend, error) { +// +// If dialer is nil, a new one is made. +func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, dialer *tsdial.Dialer, e wgengine.Engine) (*LocalBackend, error) { if e == nil { - panic("ipn.NewLocalBackend: wgengine must not be nil") + panic("ipn.NewLocalBackend: engine must not be nil") + } + if dialer == nil { + dialer = new(tsdial.Dialer) } + e.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { + dialer.SetDNSMap(tsdial.DNSMapFromNetworkMap(nm)) + }) osshare.SetFileSharingEnabled(false, logf) @@ -176,11 +186,14 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge statsLogf: logger.LogOnChange(logf, 5*time.Minute, time.Now), e: e, store: store, + dialer: dialer, backendLogID: logid, state: ipn.NoState, portpoll: portpoll, gotPortPollRes: make(chan struct{}), } + dialer.SetPeerDialControlFuncGetter(b.peerDialControlFunc) + // Default filter blocks everything and logs nothing, until Start() is called. b.setFilter(filter.NewAllowNone(logf, &netaddr.IPSet{})) @@ -210,6 +223,11 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, e wge return b, nil } +// Dialer returns the backend's dialer. +func (b *LocalBackend) Dialer() *tsdial.Dialer { + return b.dialer +} + // SetDirectFileRoot sets the directory to download files to directly, // without buffering them through an intermediate daemon-owned // tailcfg.UserID-specific directory. @@ -3035,9 +3053,9 @@ func disabledSysctls(sysctls ...string) (disabled []string, err error) { // bind to dial out to other peers. var peerDialControlFunc func(*LocalBackend) func(network, address string, c syscall.RawConn) error -// PeerDialControlFunc returns a net.Dialer.Control func (possibly nil) to use to +// peerDialControlFunc returns a net.Dialer.Control func (possibly nil) to use to // dial other Tailscale peers from the current environment. -func (b *LocalBackend) PeerDialControlFunc() func(network, address string, c syscall.RawConn) error { +func (b *LocalBackend) peerDialControlFunc() func(network, address string, c syscall.RawConn) error { if peerDialControlFunc != nil { return peerDialControlFunc(b) } diff --git a/ipn/ipnlocal/local_test.go b/ipn/ipnlocal/local_test.go index 4967d4093..af0ca0473 100644 --- a/ipn/ipnlocal/local_test.go +++ b/ipn/ipnlocal/local_test.go @@ -445,7 +445,7 @@ func TestLazyMachineKeyGeneration(t *testing.T) { t.Fatalf("NewFakeUserspaceEngine: %v", err) } t.Cleanup(eng.Close) - lb, err := NewLocalBackend(logf, "logid", store, eng) + lb, err := NewLocalBackend(logf, "logid", store, nil, eng) if err != nil { t.Fatalf("NewLocalBackend: %v", err) } diff --git a/ipn/ipnlocal/loglines_test.go b/ipn/ipnlocal/loglines_test.go index e5bcbf42d..56435a2ba 100644 --- a/ipn/ipnlocal/loglines_test.go +++ b/ipn/ipnlocal/loglines_test.go @@ -54,7 +54,7 @@ func TestLocalLogLines(t *testing.T) { } t.Cleanup(e.Close) - lb, err := NewLocalBackend(logf, idA.String(), store, e) + lb, err := NewLocalBackend(logf, idA.String(), store, nil, e) if err != nil { t.Fatal(err) } diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 1ad950f95..fac7e9536 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -284,7 +284,7 @@ func TestStateMachine(t *testing.T) { t.Cleanup(e.Close) cc := newMockControl(t) - b, err := NewLocalBackend(logf, "logid", store, e) + b, err := NewLocalBackend(logf, "logid", store, nil, e) if err != nil { t.Fatalf("NewLocalBackend: %v", err) } @@ -941,7 +941,7 @@ func TestWGEngineStatusRace(t *testing.T) { eng, err := wgengine.NewFakeUserspaceEngine(logf, 0) c.Assert(err, qt.IsNil) t.Cleanup(eng.Close) - b, err := NewLocalBackend(logf, "logid", new(ipn.MemoryStore), eng) + b, err := NewLocalBackend(logf, "logid", new(ipn.MemoryStore), nil, eng) c.Assert(err, qt.IsNil) cc := newMockControl(t) diff --git a/ipn/ipnserver/server.go b/ipn/ipnserver/server.go index 20a2c3b67..1c5d272f4 100644 --- a/ipn/ipnserver/server.go +++ b/ipn/ipnserver/server.go @@ -38,10 +38,12 @@ import ( "tailscale.com/log/filelogger" "tailscale.com/logtail/backoff" "tailscale.com/net/netstat" + "tailscale.com/net/tsdial" "tailscale.com/paths" "tailscale.com/safesocket" "tailscale.com/smallzstd" "tailscale.com/types/logger" + "tailscale.com/types/netmap" "tailscale.com/util/groupmember" "tailscale.com/util/pidowner" "tailscale.com/util/systemd" @@ -735,7 +737,12 @@ func Run(ctx context.Context, logf logger.Logf, ln net.Listener, store ipn.State } } - server, err := New(logf, logid, store, eng, serverModeUser, opts) + dialer := new(tsdial.Dialer) + eng.AddNetworkMapCallback(func(nm *netmap.NetworkMap) { + dialer.SetDNSMap(tsdial.DNSMapFromNetworkMap(nm)) + }) + + server, err := New(logf, logid, store, eng, nil, serverModeUser, opts) if err != nil { return err } @@ -748,8 +755,8 @@ func Run(ctx context.Context, logf logger.Logf, ln net.Listener, store ipn.State // New returns a new Server. // // To start it, use the Server.Run method. -func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engine, serverModeUser *user.User, opts Options) (*Server, error) { - b, err := ipnlocal.NewLocalBackend(logf, logid, store, eng) +func New(logf logger.Logf, logid string, store ipn.StateStore, eng wgengine.Engine, dialer *tsdial.Dialer, serverModeUser *user.User, opts Options) (*Server, error) { + b, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng) if err != nil { return nil, fmt.Errorf("NewLocalBackend: %v", err) } diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index b8651ae74..727386af0 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -12,7 +12,6 @@ import ( "errors" "fmt" "io" - "net" "net/http" "net/http/httputil" "net/url" @@ -20,7 +19,6 @@ import ( "runtime" "strconv" "strings" - "sync" "time" "inet.af/netaddr" @@ -28,7 +26,6 @@ import ( "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnstate" - "tailscale.com/net/netknob" "tailscale.com/tailcfg" "tailscale.com/types/logger" "tailscale.com/util/clientmetric" @@ -442,7 +439,7 @@ func (h *Handler) serveFilePut(w http.ResponseWriter, r *http.Request) { outReq.ContentLength = r.ContentLength rp := httputil.NewSingleHostReverseProxy(dstURL) - rp.Transport = getDialPeerTransport(h.b) + rp.Transport = h.b.Dialer().PeerAPITransport() rp.ServeHTTP(w, outReq) } @@ -476,26 +473,6 @@ func (h *Handler) serveDERPMap(w http.ResponseWriter, r *http.Request) { e.Encode(h.b.DERPMap()) } -var dialPeerTransportOnce struct { - sync.Once - v *http.Transport -} - -func getDialPeerTransport(b *ipnlocal.LocalBackend) *http.Transport { - dialPeerTransportOnce.Do(func() { - t := http.DefaultTransport.(*http.Transport).Clone() - t.Dial = nil - dialer := net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: netknob.PlatformTCPKeepAlive(), - Control: b.PeerDialControlFunc(), - } - t.DialContext = dialer.DialContext - dialPeerTransportOnce.v = t - }) - return dialPeerTransportOnce.v -} - func defBool(a string, def bool) bool { if a == "" { return def diff --git a/net/dns/manager.go b/net/dns/manager.go index 6fd6d8f3a..db0fd6381 100644 --- a/net/dns/manager.go +++ b/net/dns/manager.go @@ -13,6 +13,7 @@ import ( "tailscale.com/health" "tailscale.com/net/dns/resolver" "tailscale.com/net/tsaddr" + "tailscale.com/net/tsdial" "tailscale.com/types/dnstype" "tailscale.com/types/logger" "tailscale.com/util/dnsname" @@ -39,11 +40,14 @@ type Manager struct { } // NewManagers created a new manager from the given config. -func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, linkSel resolver.ForwardLinkSelector) *Manager { +func NewManager(logf logger.Logf, oscfg OSConfigurator, linkMon *monitor.Mon, dialer *tsdial.Dialer, linkSel resolver.ForwardLinkSelector) *Manager { + if dialer == nil { + panic("nil Dialer") + } logf = logger.WithPrefix(logf, "dns: ") m := &Manager{ logf: logf, - resolver: resolver.New(logf, linkMon, linkSel), + resolver: resolver.New(logf, linkMon, linkSel, dialer), os: oscfg, } m.logf("using %T", m.os) @@ -230,7 +234,7 @@ func Cleanup(logf logger.Logf, interfaceName string) { logf("creating dns cleanup: %v", err) return } - dns := NewManager(logf, oscfg, nil, nil) + dns := NewManager(logf, oscfg, nil, new(tsdial.Dialer), nil) if err := dns.Down(); err != nil { logf("dns down: %v", err) } diff --git a/net/dns/manager_test.go b/net/dns/manager_test.go index 05ec7c06f..1c5e1714d 100644 --- a/net/dns/manager_test.go +++ b/net/dns/manager_test.go @@ -13,6 +13,7 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "inet.af/netaddr" "tailscale.com/net/dns/resolver" + "tailscale.com/net/tsdial" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" ) @@ -398,7 +399,7 @@ func TestManager(t *testing.T) { SplitDNS: test.split, BaseConfig: test.bs, } - m := NewManager(t.Logf, &f, nil, nil) + m := NewManager(t.Logf, &f, nil, new(tsdial.Dialer), nil) m.resolver.TestOnlySetHook(f.SetResolver) if err := m.Set(test.in); err != nil { diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 65bd1ce82..2af3d8c8a 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -26,6 +26,7 @@ import ( "inet.af/netaddr" "tailscale.com/hostinfo" "tailscale.com/net/netns" + "tailscale.com/net/tsdial" "tailscale.com/types/dnstype" "tailscale.com/types/logger" "tailscale.com/util/dnsname" @@ -159,7 +160,8 @@ type resolverAndDelay struct { type forwarder struct { logf logger.Logf linkMon *monitor.Mon - linkSel ForwardLinkSelector + linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absords it + dialer *tsdial.Dialer dohSem chan struct{} ctx context.Context // good until Close @@ -205,11 +207,12 @@ func maxDoHInFlight(goos string) int { return 1000 } -func newForwarder(logf logger.Logf, responses chan packet, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *forwarder { +func newForwarder(logf logger.Logf, responses chan packet, linkMon *monitor.Mon, linkSel ForwardLinkSelector, dialer *tsdial.Dialer) *forwarder { f := &forwarder{ logf: logger.WithPrefix(logf, "forward: "), linkMon: linkMon, linkSel: linkSel, + dialer: dialer, responses: responses, dohSem: make(chan struct{}, maxDoHInFlight(runtime.GOOS)), } @@ -423,10 +426,7 @@ func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client, // send expects the reply to have the same txid as txidOut. func (f *forwarder) send(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) ([]byte, error) { if strings.HasPrefix(rr.name.Addr, "http://") { - // TODO(bradfitz): this only work for TUN mode right now; plumb a universal dialer - // that can handle the dozen special cases for modes/platforms/routes. - TODOHTTPClient := http.DefaultClient - return f.sendDoH(ctx, rr.name.Addr, TODOHTTPClient, fq.packet) + return f.sendDoH(ctx, rr.name.Addr, f.dialer.PeerAPIHTTPClient(), fq.packet) } if strings.HasPrefix(rr.name.Addr, "https://") { metricDNSFwdErrorType.Add(1) diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index 7a1ed6075..8ca24bbaf 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -27,6 +27,7 @@ import ( dns "golang.org/x/net/dns/dnsmessage" "inet.af/netaddr" "tailscale.com/net/tsaddr" + "tailscale.com/net/tsdial" "tailscale.com/types/dnstype" "tailscale.com/types/logger" "tailscale.com/util/clientmetric" @@ -192,6 +193,7 @@ func WriteRoutes(w *bufio.Writer, routes map[dnsname.FQDN][]dnstype.Resolver) { type Resolver struct { logf logger.Logf linkMon *monitor.Mon // or nil + dialer *tsdial.Dialer // non-nil saveConfigForTests func(cfg Config) // used in tests to capture resolver config // forwarder forwards requests to upstream nameservers. forwarder *forwarder @@ -223,7 +225,10 @@ type ForwardLinkSelector interface { // New returns a new resolver. // linkMon optionally specifies a link monitor to use for socket rebinding. -func New(logf logger.Logf, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *Resolver { +func New(logf logger.Logf, linkMon *monitor.Mon, linkSel ForwardLinkSelector, dialer *tsdial.Dialer) *Resolver { + if dialer == nil { + panic("nil Dialer") + } r := &Resolver{ logf: logger.WithPrefix(logf, "resolver: "), linkMon: linkMon, @@ -232,8 +237,9 @@ func New(logf logger.Logf, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *R closed: make(chan struct{}), hostToIP: map[dnsname.FQDN][]netaddr.IP{}, ipToHost: map[netaddr.IP]dnsname.FQDN{}, + dialer: dialer, } - r.forwarder = newForwarder(r.logf, r.responses, linkMon, linkSel) + r.forwarder = newForwarder(r.logf, r.responses, linkMon, linkSel, dialer) return r } diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index 06e51cacd..6081c5ea6 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -18,6 +18,7 @@ import ( dns "golang.org/x/net/dns/dnsmessage" "inet.af/netaddr" + "tailscale.com/net/tsdial" "tailscale.com/tstest" "tailscale.com/types/dnstype" "tailscale.com/util/dnsname" @@ -308,7 +309,7 @@ func TestRDNSNameToIPv6(t *testing.T) { } func newResolver(t testing.TB) *Resolver { - return New(t.Logf, nil /* no link monitor */, nil /* no link selector */) + return New(t.Logf, nil /* no link monitor */, nil /* no link selector */, new(tsdial.Dialer)) } func TestResolveLocal(t *testing.T) { @@ -1062,7 +1063,7 @@ func TestForwardLinkSelection(t *testing.T) { return "special" } return "" - })) + }), new(tsdial.Dialer)) // Test non-special IP. if got, err := fwd.packetListener(netaddr.IP{}); err != nil { diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go index 060c945eb..dfd403902 100644 --- a/net/tsdial/tsdial.go +++ b/net/tsdial/tsdial.go @@ -9,9 +9,14 @@ import ( "context" "errors" "net" + "net/http" "sync" + "sync/atomic" + "syscall" + "time" "inet.af/netaddr" + "tailscale.com/net/netknob" ) // Dialer dials out of tailscaled, while taking care of details while @@ -27,11 +32,43 @@ type Dialer struct { // If nil, it's not used. NetstackDialTCP func(context.Context, netaddr.IPPort) (net.Conn, error) + peerDialControlFuncAtomic atomic.Value // of func() func(network, address string, c syscall.RawConn) error + + peerClientOnce sync.Once + peerClient *http.Client + mu sync.Mutex dns DNSMap } +// PeerDialControlFunc returns a function +// that can assigned to net.Dialer.Control to set sockopts or whatnot +// to make a dial escape the current platform's network sandbox. +// +// On many platforms the returned func will be nil. +// +// Notably, this is non-nil on iOS and macOS when run as a Network or +// System Extension (the GUI variants). +func (d *Dialer) PeerDialControlFunc() func(network, address string, c syscall.RawConn) error { + gf, _ := d.peerDialControlFuncAtomic.Load().(func() func(network, address string, c syscall.RawConn) error) + if gf == nil { + return nil + } + return gf() +} + +// SetPeerDialControlFuncGetter sets a function that returns, for the +// current network configuration at the time it's called, a function +// that can assigned to net.Dialer.Control to set sockopts or whatnot +// to make a dial escape the current platform's network sandbox. +func (d *Dialer) SetPeerDialControlFuncGetter(getFunc func() func(network, address string, c syscall.RawConn) error) { + d.peerDialControlFuncAtomic.Store(getFunc) +} + +// SetDNSMap sets the current map of DNS names learned from the netmap. func (d *Dialer) SetDNSMap(m DNSMap) { + // TODO(bradfitz): update this to be aware of DNSConfig + // ExtraNames and CertDomains. d.mu.Lock() defer d.mu.Unlock() d.dns = m @@ -44,7 +81,9 @@ func (d *Dialer) resolve(ctx context.Context, addr string) (netaddr.IPPort, erro return dns.Resolve(ctx, addr) } -func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { +// UserDial connects to the provided network address as if a user were initiating the dial. +// (e.g. from a SOCKS or HTTP outbound proxy) +func (d *Dialer) UserDial(ctx context.Context, network, addr string) (net.Conn, error) { ipp, err := d.resolve(ctx, addr) if err != nil { return nil, err @@ -59,3 +98,31 @@ func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Con var stdDialer net.Dialer return stdDialer.DialContext(ctx, network, ipp.String()) } + +// PeerAPIHTTPClient returns an HTTP Client to call peers' peerapi +// endpoints. // +// The returned Client must not be mutated; it's owned by the Dialer +// and shared by callers. +func (d *Dialer) PeerAPIHTTPClient() *http.Client { + d.peerClientOnce.Do(func() { + t := http.DefaultTransport.(*http.Transport).Clone() + t.Dial = nil + dialer := net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: netknob.PlatformTCPKeepAlive(), + Control: d.PeerDialControlFunc(), + } + t.DialContext = dialer.DialContext + d.peerClient = &http.Client{Transport: t} + }) + return d.peerClient +} + +// PeerAPITransport returns a Transport to call peers' peerapi +// endpoints. +// +// The returned value must not be mutated; it's owned by the Dialer +// and shared by callers. +func (d *Dialer) PeerAPITransport() *http.Transport { + return d.PeerAPIHTTPClient().Transport.(*http.Transport) +} diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index 684ccbd39..dedbc26a6 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -8,6 +8,7 @@ package tsnet import ( + "context" "errors" "fmt" "log" @@ -20,12 +21,14 @@ import ( "sync" "time" + "inet.af/netaddr" "tailscale.com/client/tailscale" "tailscale.com/control/controlclient" "tailscale.com/ipn" "tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/localapi" "tailscale.com/net/nettest" + "tailscale.com/net/tsdial" "tailscale.com/smallzstd" "tailscale.com/types/logger" "tailscale.com/wgengine" @@ -114,9 +117,11 @@ func (s *Server) start() error { return err } + dialer := new(tsdial.Dialer) // mutated below (before used) eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ ListenPort: 0, LinkMonitor: linkMon, + Dialer: dialer, }) if err != nil { return err @@ -136,6 +141,13 @@ func (s *Server) start() error { if err := ns.Start(); err != nil { return fmt.Errorf("failed to start netstack: %w", err) } + dialer.UseNetstackForIP = func(ip netaddr.IP) bool { + _, ok := eng.PeerForIP(ip) + return ok + } + dialer.NetstackDialTCP = func(ctx context.Context, dst netaddr.IPPort) (net.Conn, error) { + return ns.DialContextTCP(ctx, dst.String()) + } statePath := filepath.Join(s.dir, "tailscaled.state") store, err := ipn.NewFileStore(statePath) @@ -144,7 +156,7 @@ func (s *Server) start() error { } logid := "tslib-TODO" - lb, err := ipnlocal.NewLocalBackend(logf, logid, store, eng) + lb, err := ipnlocal.NewLocalBackend(logf, logid, store, dialer, eng) if err != nil { return fmt.Errorf("NewLocalBackend: %v", err) } diff --git a/tstest/integration/tailscaled_deps_test_darwin.go b/tstest/integration/tailscaled_deps_test_darwin.go index c6c7c3c5e..8890ee8e8 100644 --- a/tstest/integration/tailscaled_deps_test_darwin.go +++ b/tstest/integration/tailscaled_deps_test_darwin.go @@ -33,7 +33,6 @@ 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 c6c7c3c5e..8890ee8e8 100644 --- a/tstest/integration/tailscaled_deps_test_freebsd.go +++ b/tstest/integration/tailscaled_deps_test_freebsd.go @@ -33,7 +33,6 @@ 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 c6c7c3c5e..8890ee8e8 100644 --- a/tstest/integration/tailscaled_deps_test_linux.go +++ b/tstest/integration/tailscaled_deps_test_linux.go @@ -33,7 +33,6 @@ 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 c6c7c3c5e..8890ee8e8 100644 --- a/tstest/integration/tailscaled_deps_test_openbsd.go +++ b/tstest/integration/tailscaled_deps_test_openbsd.go @@ -33,7 +33,6 @@ 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 d1ef8256c..6e5254203 100644 --- a/tstest/integration/tailscaled_deps_test_windows.go +++ b/tstest/integration/tailscaled_deps_test_windows.go @@ -37,7 +37,6 @@ 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/userspace.go b/wgengine/userspace.go index ee1994453..3abd798f2 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -33,6 +33,7 @@ import ( "tailscale.com/net/interfaces" "tailscale.com/net/packet" "tailscale.com/net/tsaddr" + "tailscale.com/net/tsdial" "tailscale.com/net/tshttpproxy" "tailscale.com/net/tstun" "tailscale.com/tailcfg" @@ -191,6 +192,10 @@ type Config struct { // If nil, a new link monitor is created. LinkMonitor *monitor.Mon + // Dialer is the dialer to use for outbound connections. + // If nil, a new Dialer is created + Dialer *tsdial.Dialer + // ListenPort is the port on which the engine will listen. // If zero, a port is automatically selected. ListenPort uint16 @@ -268,6 +273,9 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } conf.DNS = d } + if conf.Dialer == nil { + conf.Dialer = new(tsdial.Dialer) + } var tsTUNDev *tstun.Wrapper if conf.IsTAP { @@ -310,7 +318,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error) } tunName, _ := conf.Tun.Name() - e.dns = dns.NewManager(logf, conf.DNS, e.linkMon, fwdDNSLinkSelector{e, tunName}) + e.dns = dns.NewManager(logf, conf.DNS, e.linkMon, conf.Dialer, fwdDNSLinkSelector{e, tunName}) logf("link state: %+v", e.linkMon.InterfaceState())