|
@@ -23,6 +23,7 @@ import (
|
|
|
dns "golang.org/x/net/dns/dnsmessage"
|
|
dns "golang.org/x/net/dns/dnsmessage"
|
|
|
"tailscale.com/control/controlknobs"
|
|
"tailscale.com/control/controlknobs"
|
|
|
"tailscale.com/envknob"
|
|
"tailscale.com/envknob"
|
|
|
|
|
+ "tailscale.com/health"
|
|
|
"tailscale.com/net/dns/publicdns"
|
|
"tailscale.com/net/dns/publicdns"
|
|
|
"tailscale.com/net/dnscache"
|
|
"tailscale.com/net/dnscache"
|
|
|
"tailscale.com/net/neterror"
|
|
"tailscale.com/net/neterror"
|
|
@@ -164,6 +165,23 @@ func clampEDNSSize(packet []byte, maxSize uint16) {
|
|
|
binary.BigEndian.PutUint16(opt[3:5], maxSize)
|
|
binary.BigEndian.PutUint16(opt[3:5], maxSize)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// dnsForwarderFailing should be raised when the forwarder is unable to reach the
|
|
|
|
|
+// upstream resolvers. This is a high severity warning as it results in "no internet".
|
|
|
|
|
+// This warning must be cleared when the forwarder is working again.
|
|
|
|
|
+//
|
|
|
|
|
+// We allow for 5 second grace period to ensure this is not raised for spurious errors
|
|
|
|
|
+// under the assumption that DNS queries are relatively frequent and a subsequent
|
|
|
|
|
+// successful query will clear any one-off errors.
|
|
|
|
|
+var dnsForwarderFailing = health.Register(&health.Warnable{
|
|
|
|
|
+ Code: "dns-forward-failing",
|
|
|
|
|
+ Title: "DNS unavailable",
|
|
|
|
|
+ Severity: health.SeverityHigh,
|
|
|
|
|
+ DependsOn: []*health.Warnable{health.NetworkStatusWarnable},
|
|
|
|
|
+ Text: health.StaticMessage("Tailscale can't reach the configured DNS servers. Internet connectivity may be affected."),
|
|
|
|
|
+ ImpactsConnectivity: true,
|
|
|
|
|
+ TimeToVisible: 5 * time.Second,
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
type route struct {
|
|
type route struct {
|
|
|
Suffix dnsname.FQDN
|
|
Suffix dnsname.FQDN
|
|
|
Resolvers []resolverAndDelay
|
|
Resolvers []resolverAndDelay
|
|
@@ -188,6 +206,7 @@ type forwarder struct {
|
|
|
netMon *netmon.Monitor // always non-nil
|
|
netMon *netmon.Monitor // always non-nil
|
|
|
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
|
|
linkSel ForwardLinkSelector // TODO(bradfitz): remove this when tsdial.Dialer absorbs it
|
|
|
dialer *tsdial.Dialer
|
|
dialer *tsdial.Dialer
|
|
|
|
|
+ health *health.Tracker // always non-nil
|
|
|
|
|
|
|
|
controlKnobs *controlknobs.Knobs // or nil
|
|
controlKnobs *controlknobs.Knobs // or nil
|
|
|
|
|
|
|
@@ -219,7 +238,7 @@ type forwarder struct {
|
|
|
missingUpstreamRecovery func()
|
|
missingUpstreamRecovery func()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, knobs *controlknobs.Knobs) *forwarder {
|
|
|
|
|
|
|
+func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkSelector, dialer *tsdial.Dialer, health *health.Tracker, knobs *controlknobs.Knobs) *forwarder {
|
|
|
if netMon == nil {
|
|
if netMon == nil {
|
|
|
panic("nil netMon")
|
|
panic("nil netMon")
|
|
|
}
|
|
}
|
|
@@ -228,6 +247,7 @@ func newForwarder(logf logger.Logf, netMon *netmon.Monitor, linkSel ForwardLinkS
|
|
|
netMon: netMon,
|
|
netMon: netMon,
|
|
|
linkSel: linkSel,
|
|
linkSel: linkSel,
|
|
|
dialer: dialer,
|
|
dialer: dialer,
|
|
|
|
|
+ health: health,
|
|
|
controlKnobs: knobs,
|
|
controlKnobs: knobs,
|
|
|
missingUpstreamRecovery: func() {},
|
|
missingUpstreamRecovery: func() {},
|
|
|
}
|
|
}
|
|
@@ -887,6 +907,7 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
|
|
resolvers = f.resolvers(domain)
|
|
resolvers = f.resolvers(domain)
|
|
|
if len(resolvers) == 0 {
|
|
if len(resolvers) == 0 {
|
|
|
metricDNSFwdErrorNoUpstream.Add(1)
|
|
metricDNSFwdErrorNoUpstream.Add(1)
|
|
|
|
|
+ f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: ""})
|
|
|
f.logf("no upstream resolvers set, returning SERVFAIL")
|
|
f.logf("no upstream resolvers set, returning SERVFAIL")
|
|
|
|
|
|
|
|
// Attempt to recompile the DNS configuration
|
|
// Attempt to recompile the DNS configuration
|
|
@@ -909,6 +930,8 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
|
|
case responseChan <- res:
|
|
case responseChan <- res:
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
|
|
+ } else {
|
|
|
|
|
+ f.health.SetHealthy(dnsForwarderFailing)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -960,6 +983,7 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
|
|
return fmt.Errorf("waiting to send response: %w", ctx.Err())
|
|
return fmt.Errorf("waiting to send response: %w", ctx.Err())
|
|
|
case responseChan <- packet{v, query.family, query.addr}:
|
|
case responseChan <- packet{v, query.family, query.addr}:
|
|
|
metricDNSFwdSuccess.Add(1)
|
|
metricDNSFwdSuccess.Add(1)
|
|
|
|
|
+ f.health.SetHealthy(dnsForwarderFailing)
|
|
|
return nil
|
|
return nil
|
|
|
}
|
|
}
|
|
|
case err := <-errc:
|
|
case err := <-errc:
|
|
@@ -979,6 +1003,11 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
|
|
case <-ctx.Done():
|
|
case <-ctx.Done():
|
|
|
metricDNSFwdErrorContext.Add(1)
|
|
metricDNSFwdErrorContext.Add(1)
|
|
|
metricDNSFwdErrorContextGotError.Add(1)
|
|
metricDNSFwdErrorContextGotError.Add(1)
|
|
|
|
|
+ var resolverAddrs []string
|
|
|
|
|
+ for _, rr := range resolvers {
|
|
|
|
|
+ resolverAddrs = append(resolverAddrs, rr.name.Addr)
|
|
|
|
|
+ }
|
|
|
|
|
+ f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")})
|
|
|
case responseChan <- res:
|
|
case responseChan <- res:
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -999,6 +1028,7 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo
|
|
|
for _, rr := range resolvers {
|
|
for _, rr := range resolvers {
|
|
|
resolverAddrs = append(resolverAddrs, rr.name.Addr)
|
|
resolverAddrs = append(resolverAddrs, rr.name.Addr)
|
|
|
}
|
|
}
|
|
|
|
|
+ f.health.SetUnhealthy(dnsForwarderFailing, health.Args{health.ArgDNSServers: strings.Join(resolverAddrs, ",")})
|
|
|
return fmt.Errorf("waiting for response or error from %v: %w", resolverAddrs, ctx.Err())
|
|
return fmt.Errorf("waiting for response or error from %v: %w", resolverAddrs, ctx.Err())
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|