diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index 56a755ec6..3b7cb2e36 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -89,7 +89,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de LW 💣 github.com/digitalocean/go-smbios/smbios from tailscale.com/posture 💣 github.com/djherbis/times from tailscale.com/drive/driveimpl github.com/fxamacker/cbor/v2 from tailscale.com/tka - github.com/gaissmai/bart from tailscale.com/net/tstun + github.com/gaissmai/bart from tailscale.com/net/tstun+ github.com/go-json-experiment/json/internal from github.com/go-json-experiment/json/internal/jsonflags+ github.com/go-json-experiment/json/internal/jsonflags from github.com/go-json-experiment/json/internal/jsonopts+ github.com/go-json-experiment/json/internal/jsonopts from github.com/go-json-experiment/json/jsontext diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 84f18fe01..6cb5855ce 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -3590,6 +3590,7 @@ func (b *LocalBackend) authReconfig() { nm := b.netMap hasPAC := b.prevIfState.HasPAC() disableSubnetsIfPAC := nm.HasCap(tailcfg.NodeAttrDisableSubnetsIfPAC) + userDialUseRoutes := nm.HasCap(tailcfg.NodeAttrUserDialUseRoutes) dohURL, dohURLOK := exitNodeCanProxyDNS(nm, b.peers, prefs.ExitNodeID()) dcfg := dnsConfigForNetmap(nm, b.peers, prefs, b.logf, version.OS()) // If the current node is an app connector, ensure the app connector machine is started @@ -3647,6 +3648,12 @@ func (b *LocalBackend) authReconfig() { } b.logf("[v1] authReconfig: ra=%v dns=%v 0x%02x: %v", prefs.RouteAll(), prefs.CorpDNS(), flags, err) + if userDialUseRoutes { + b.dialer.SetRoutes(rcfg.Routes, rcfg.LocalRoutes) + } else { + b.dialer.SetRoutes(nil, nil) + } + b.initPeerAPIListener() } diff --git a/net/tsdial/tsdial.go b/net/tsdial/tsdial.go index 80d208f4f..42433b871 100644 --- a/net/tsdial/tsdial.go +++ b/net/tsdial/tsdial.go @@ -14,9 +14,11 @@ "runtime" "strings" "sync" + "sync/atomic" "syscall" "time" + "github.com/gaissmai/bart" "tailscale.com/net/dnscache" "tailscale.com/net/netknob" "tailscale.com/net/netmon" @@ -66,6 +68,8 @@ type Dialer struct { netnsDialerOnce sync.Once netnsDialer netns.Dialer + routes atomic.Pointer[bart.Table[bool]] // or nil if UserDial should not use routes. `true` indicates routes that point into the Tailscale interface + mu sync.Mutex closed bool dns dnsMap @@ -129,6 +133,23 @@ func (d *Dialer) SetExitDNSDoH(doh string) { } } +// SetRoutes configures the dialer to dial the specified routes via Tailscale, +// and the specified localRoutes using the default interface. +func (d *Dialer) SetRoutes(routes, localRoutes []netip.Prefix) { + var rt *bart.Table[bool] + if len(routes) > 0 || len(localRoutes) > 0 { + rt = &bart.Table[bool]{} + for _, r := range routes { + rt.Insert(r, true) + } + for _, r := range localRoutes { + rt.Insert(r, false) + } + } + + d.routes.Store(rt) +} + func (d *Dialer) Close() error { d.mu.Lock() defer d.mu.Unlock() @@ -387,6 +408,15 @@ func (d *Dialer) UserDial(ctx context.Context, network, addr string) (net.Conn, } return d.NetstackDialTCP(ctx, ipp) } + + if routes := d.routes.Load(); routes != nil { + if isTailscaleRoute, _ := routes.Get(ipp.Addr()); isTailscaleRoute { + return d.getPeerDialer().DialContext(ctx, network, ipp.String()) + } + + return d.SystemDial(ctx, network, ipp.String()) + } + // Workaround for macOS for now: dial Tailscale IPs with peer dialer. // TODO(bradfitz): fix dialing subnet routers, public IPs via exit nodes, // etc. This is a temporary partial for macOS. We need to plumb ART tables & @@ -424,7 +454,7 @@ func (d *Dialer) dialPeerAPI(ctx context.Context, network, addr string) (net.Con } // getPeerDialer returns the *net.Dialer to use to dial peers (e.g. for peerapi, -// or "tailscale nc") +// "tailscale nc", or querying internal DNS servers over Tailscale) // // This is not used in netstack mode. // diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index dbe9daa8b..11f67386d 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -132,7 +132,8 @@ // - 89: 2024-03-23: Client no longer respects deleted PeerChange.Capabilities (use CapMap) // - 90: 2024-04-03: Client understands PeerCapabilityTaildrive. // - 91: 2024-04-24: Client understands PeerCapabilityTaildriveSharer. -const CurrentCapabilityVersion CapabilityVersion = 91 +// - 92: 2024-05-06: Client understands NodeAttrUserDialUseRoutes. +const CurrentCapabilityVersion CapabilityVersion = 92 type StableID string @@ -2259,6 +2260,10 @@ type Oauth2Token struct { // NodeAttrSuggestExitNodeUI allows the currently suggested exit node to appear in the client GUI. NodeAttrSuggestExitNodeUI NodeCapability = "suggest-exit-node-ui" + + // NodeAttrUserDialUseRoutes makes UserDial use either the peer dialer or the system dialer, + // depending on the destination address and the configured routes. + NodeAttrUserDialUseRoutes NodeCapability = "user-dial-routes" ) // SetDNSRequest is a request to add a DNS record.