diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index e3dbc26..eecfd6d 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -556,6 +556,10 @@ func (p *prog) proxy(ctx context.Context, req *proxyRequest) *proxyResponse { if errors.As(err, &e) && e.Timeout() { upstreamConfig.ReBootstrap() } + // For network error, turn ipv6 off if enabled. + if ctrld.HasIPv6() && (errUrlNetworkError(err) || errNetworkError(err)) { + ctrld.DisableIPv6() + } } return nil diff --git a/config.go b/config.go index 2e85e76..48736ac 100644 --- a/config.go +++ b/config.go @@ -485,7 +485,7 @@ func (uc *UpstreamConfig) setupDOHTransport() { uc.transport = uc.newDOHTransport(uc.bootstrapIPs6) case IpStackSplit: uc.transport4 = uc.newDOHTransport(uc.bootstrapIPs4) - if hasIPv6() { + if HasIPv6() { uc.transport6 = uc.newDOHTransport(uc.bootstrapIPs6) } else { uc.transport6 = uc.transport4 @@ -655,7 +655,7 @@ func (uc *UpstreamConfig) bootstrapIPForDNSType(dnsType uint16) string { case dns.TypeA: return pick(uc.bootstrapIPs4) default: - if hasIPv6() { + if HasIPv6() { return pick(uc.bootstrapIPs6) } return pick(uc.bootstrapIPs4) @@ -677,7 +677,7 @@ func (uc *UpstreamConfig) netForDNSType(dnsType uint16) (string, string) { case dns.TypeA: return "tcp4-tls", "udp4" default: - if hasIPv6() { + if HasIPv6() { return "tcp6-tls", "udp6" } return "tcp4-tls", "udp4" diff --git a/config_quic.go b/config_quic.go index a46780a..cadcb6b 100644 --- a/config_quic.go +++ b/config_quic.go @@ -24,7 +24,7 @@ func (uc *UpstreamConfig) setupDOH3Transport() { uc.http3RoundTripper = uc.newDOH3Transport(uc.bootstrapIPs6) case IpStackSplit: uc.http3RoundTripper4 = uc.newDOH3Transport(uc.bootstrapIPs4) - if hasIPv6() { + if HasIPv6() { uc.http3RoundTripper6 = uc.newDOH3Transport(uc.bootstrapIPs6) } else { uc.http3RoundTripper6 = uc.http3RoundTripper4 diff --git a/internal/net/net.go b/internal/net/net.go index 2693fbf..d5bd75e 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -17,9 +17,8 @@ import ( ) const ( - controldIPv6Test = "ipv6.controld.io" - v4BootstrapDNS = "76.76.2.22:53" - v6BootstrapDNS = "[2606:1a40::22]:53" + v4BootstrapDNS = "76.76.2.22:53" + v6BootstrapDNS = "[2606:1a40::22]:53" ) var Dialer = &net.Dialer{ diff --git a/net.go b/net.go index 449620d..7bbf54b 100644 --- a/net.go +++ b/net.go @@ -6,6 +6,8 @@ import ( "sync/atomic" "time" + "tailscale.com/net/netmon" + ctrldnet "github.com/Control-D-Inc/ctrld/internal/net" ) @@ -14,38 +16,38 @@ var ( ipv6Available atomic.Bool ) -const ipv6ProbingInterval = 10 * time.Second - -func hasIPv6() bool { +// HasIPv6 reports whether the current network stack has IPv6 available. +func HasIPv6() bool { hasIPv6Once.Do(func() { - Log(context.Background(), ProxyLogger.Load().Debug(), "checking for IPv6 availability once") + ProxyLogger.Load().Debug().Msg("checking for IPv6 availability once") ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() val := ctrldnet.IPv6Available(ctx) ipv6Available.Store(val) - go probingIPv6(context.TODO(), val) + ProxyLogger.Load().Debug().Msgf("ipv6 availability: %v", val) + mon, err := netmon.New(func(format string, args ...any) {}) + if err != nil { + ProxyLogger.Load().Debug().Err(err).Msg("failed to monitor IPv6 state") + return + } + mon.RegisterChangeCallback(func(delta *netmon.ChangeDelta) { + old := ipv6Available.Load() + cur := delta.Monitor.InterfaceState().HaveV6 + if old != cur { + ProxyLogger.Load().Warn().Msgf("ipv6 availability changed, old: %v, new: %v", old, cur) + } else { + ProxyLogger.Load().Debug().Msg("ipv6 availability does not changed") + } + ipv6Available.Store(cur) + }) + mon.Start() }) return ipv6Available.Load() } -// TODO(cuonglm): doing poll check natively for supported platforms. -func probingIPv6(ctx context.Context, old bool) { - ticker := time.NewTicker(ipv6ProbingInterval) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - func() { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - cur := ctrldnet.IPv6Available(ctx) - if ipv6Available.CompareAndSwap(old, cur) { - old = cur - } - Log(ctx, ProxyLogger.Load().Debug(), "IPv6 availability: %v", cur) - }() - } +// DisableIPv6 marks IPv6 as unavailable if enabled. +func DisableIPv6() { + if ipv6Available.CompareAndSwap(true, false) { + ProxyLogger.Load().Debug().Msg("turned off IPv6 availability") } }