net/dns/resolver, control/controlknobs, tailcfg: use UserDial instead of SystemDial to dial DNS servers

Now that tsdial.Dialer.UserDial has been updated to honor the configured routes
and dial external network addresses without going through Tailscale, while also being
able to dial a node/subnet router on the tailnet, we can start using UserDial to forward
DNS requests. This is primarily needed for DNS over TCP when forwarding requests
to internal DNS servers, but we also update getKnownDoHClientForProvider to use it.

Updates tailscale/corp#18725

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl
2024-05-02 18:33:13 -05:00
committed by Nick Khyl
parent c28f5767bf
commit f62e678df8
3 changed files with 30 additions and 16 deletions

View File

@@ -391,20 +391,8 @@ func (f *forwarder) getKnownDoHClientForProvider(urlBase string) (c *http.Client
if err != nil {
return nil, false
}
// NOTE: use f.dialer.SystemDial so we close connections on a link
// change; on mobile devices when switching between WiFi and cellular,
// we need to ensure we don't retain a connection on the old interface
// or we can block DNS resolution.
//
// NOTE: if we ever support arbitrary user-defined DoH providers, this
// isn't sufficient; we'd need a dialer that dial a DoH server on the
// internet, without going through Tailscale (as SystemDial does), but
// also can dial a node on the tailnet (e.g. a PiHole).
//
// As of the time of writing (2024-02-11), this isn't a problem because
// we only support a restricted set of public DoH providers that aren't
// on a user's tailnet.
dialer := dnscache.Dialer(f.dialer.SystemDial, &dnscache.Resolver{
dialer := dnscache.Dialer(f.getDialerType(), &dnscache.Resolver{
SingleHost: dohURL.Hostname(),
SingleHostStaticResult: allIPs,
Logf: f.logf,
@@ -699,6 +687,23 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
return out, nil
}
func (f *forwarder) getDialerType() dnscache.DialContextFunc {
if f.controlKnobs != nil && f.controlKnobs.UserDialUseRoutes.Load() {
// It is safe to use UserDial as it dials external servers without going through Tailscale
// and closes connections on interface change in the same way as SystemDial does,
// thus preventing DNS resolution issues when switching between WiFi and cellular,
// but can also dial an internal DNS server on the Tailnet or via a subnet router.
//
// TODO(nickkhyl): Update tsdial.Dialer to reuse the bart.Table we create in net/tstun.Wrapper
// to avoid having two bart tables in memory, especially on iOS. Once that's done,
// we can get rid of the nodeAttr/control knob and always use UserDial for DNS.
//
// See https://github.com/tailscale/tailscale/issues/12027.
return f.dialer.UserDial
}
return f.dialer.SystemDial
}
func (f *forwarder) sendTCP(ctx context.Context, fq *forwardQuery, rr resolverAndDelay) (ret []byte, err error) {
ipp, ok := rr.name.IPPort()
if !ok {
@@ -717,7 +722,7 @@ func (f *forwarder) sendTCP(ctx context.Context, fq *forwardQuery, rr resolverAn
ctx, cancel := context.WithTimeout(ctx, tcpQueryTimeout)
defer cancel()
conn, err := f.dialer.SystemDial(ctx, tcpFam, ipp.String())
conn, err := f.getDialerType()(ctx, tcpFam, ipp.String())
if err != nil {
return nil, err
}