From 58c0e4f15a1cccd5888966929f585615e4a92753 Mon Sep 17 00:00:00 2001 From: Cuong Manh Le Date: Tue, 11 Mar 2025 01:16:36 +0700 Subject: [PATCH] all: remove ipv6 check polling netmon provides ipv6 availability during network event changes, so use this metadata instead of wasting on polling check. Further, repeated network errors will force marking ipv6 as disable if were being enabled, catching a rare case when ipv6 were disabled from cli or system settings. --- cmd/cli/dns_proxy.go | 4 ++++ config.go | 6 +++--- config_quic.go | 2 +- internal/net/net.go | 5 ++--- net.go | 50 +++++++++++++++++++++++--------------------- 5 files changed, 36 insertions(+), 31 deletions(-) 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") } }