diff --git a/wgengine/tsdns/forwarder.go b/wgengine/tsdns/forwarder.go index 161379943..470748c4a 100644 --- a/wgengine/tsdns/forwarder.go +++ b/wgengine/tsdns/forwarder.go @@ -163,6 +163,14 @@ func (f *forwarder) Close() { f.wg.Wait() } +func (f *forwarder) rebindFromNetworkChange() { + for _, c := range f.conns { + c.mu.Lock() + c.reconnectLocked() + c.mu.Unlock() + } +} + func (f *forwarder) setUpstreams(upstreams []net.Addr) { f.mu.Lock() f.upstreams = upstreams diff --git a/wgengine/tsdns/tsdns.go b/wgengine/tsdns/tsdns.go index 23db7bba4..b68b8c04e 100644 --- a/wgengine/tsdns/tsdns.go +++ b/wgengine/tsdns/tsdns.go @@ -16,8 +16,10 @@ import ( dns "golang.org/x/net/dns/dnsmessage" "inet.af/netaddr" + "tailscale.com/net/interfaces" "tailscale.com/types/logger" "tailscale.com/util/dnsname" + "tailscale.com/wgengine/monitor" ) // maxResponseBytes is the maximum size of a response from a Resolver. @@ -58,7 +60,9 @@ type Packet struct { // If it is asked to resolve a domain that is not of that form, // it delegates to upstream nameservers if any are set. type Resolver struct { - logf logger.Logf + logf logger.Logf + linkMon *monitor.Mon // or nil + unregLinkMon func() // or nil // forwarder forwards requests to upstream nameservers. forwarder *forwarder @@ -86,6 +90,10 @@ type ResolverConfig struct { // Forward determines whether the resolver will forward packets to // nameservers set with SetUpstreams if the domain name is not of a Tailscale node. Forward bool + // LinkMonitor optionally provides a link monitor to use to rebind + // connections on link changes. + // If nil, rebinds are not performend. + LinkMonitor *monitor.Mon } // NewResolver constructs a resolver associated with the given root domain. @@ -93,6 +101,7 @@ type ResolverConfig struct { func NewResolver(config ResolverConfig) *Resolver { r := &Resolver{ logf: logger.WithPrefix(config.Logf, "tsdns: "), + linkMon: config.LinkMonitor, queue: make(chan Packet, queueSize), responses: make(chan Packet), errors: make(chan error), @@ -102,6 +111,9 @@ func NewResolver(config ResolverConfig) *Resolver { if config.Forward { r.forwarder = newForwarder(r.logf, r.responses) } + if r.linkMon != nil { + r.unregLinkMon = r.linkMon.RegisterChangeCallback(r.onLinkMonitorChange) + } return r } @@ -130,6 +142,10 @@ func (r *Resolver) Close() { } close(r.closed) + if r.unregLinkMon != nil { + r.unregLinkMon() + } + if r.forwarder != nil { r.forwarder.Close() } @@ -137,6 +153,15 @@ func (r *Resolver) Close() { r.wg.Wait() } +func (r *Resolver) onLinkMonitorChange(changed bool, state *interfaces.State) { + if !changed { + return + } + if r.forwarder != nil { + r.forwarder.rebindFromNetworkChange() + } +} + // SetMap sets the resolver's DNS map, taking ownership of it. func (r *Resolver) SetMap(m *Map) { r.mu.Lock() diff --git a/wgengine/userspace.go b/wgengine/userspace.go index 8d73e9483..af8f823a2 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -219,18 +219,13 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ tsTUNDev := tstun.WrapTUN(logf, rawTUNDev) closePool.add(tsTUNDev) - rconf := tsdns.ResolverConfig{ - Logf: logf, - Forward: true, - } e := &userspaceEngine{ - timeNow: time.Now, - logf: logf, - reqCh: make(chan struct{}, 1), - waitCh: make(chan struct{}), - tundev: tsTUNDev, - resolver: tsdns.NewResolver(rconf), - pingers: make(map[wgkey.Key]*pinger), + timeNow: time.Now, + logf: logf, + reqCh: make(chan struct{}, 1), + waitCh: make(chan struct{}), + tundev: tsTUNDev, + pingers: make(map[wgkey.Key]*pinger), } e.localAddrs.Store(map[netaddr.IP]bool{}) @@ -246,6 +241,12 @@ func newUserspaceEngine(logf logger.Logf, rawTUNDev tun.Device, conf Config) (_ e.linkMonOwned = true } + e.resolver = tsdns.NewResolver(tsdns.ResolverConfig{ + Logf: logf, + Forward: true, + LinkMonitor: e.linkMon, + }) + logf("link state: %+v", e.linkMon.InterfaceState()) unregisterMonWatch := e.linkMon.RegisterChangeCallback(func(changed bool, st *interfaces.State) {