diff --git a/config.go b/config.go index 84e2f40..dfafd91 100644 --- a/config.go +++ b/config.go @@ -169,6 +169,7 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { return "" } + Log(ctx, ProxyLog.Debug(), "Resolving %q using bootstrap DNS %q", uc.Domain, bootstrapDNS) // Find all A, AAAA records of the upstream. for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} { m := new(dns.Msg) @@ -202,7 +203,7 @@ func (uc *UpstreamConfig) SetupBootstrapIP() { uc.nextBootstrapIP.Add(1) // If this is an ipv6, and ipv6 is not available, don't use it as bootstrap ip. - if !ctrldnet.IPv6Available() && ctrldnet.IsIPv6(ip) { + if !ctrldnet.IPv6Available(ctx) && ctrldnet.IsIPv6(ip) { continue } uc.BootstrapIP = ip @@ -217,23 +218,41 @@ func (uc *UpstreamConfig) ReBootstrap() { _, _, _ = uc.g.Do("rebootstrap", func() (any, error) { ProxyLog.Debug().Msg("re-bootstrapping upstream ip") n := uint32(len(uc.bootstrapIPs)) + + timeoutMs := 1000 + if uc.Timeout < timeoutMs { + timeoutMs = uc.Timeout + } + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutMs)*time.Millisecond) + defer cancel() + + hasIPv6 := ctrldnet.IPv6Available(ctx) // Only attempt n times, because if there's no usable ip, // the bootstrap ip will be kept as-is. for i := uint32(0); i < n; i++ { // Select the next ip in bootstrap ip list. next := uc.nextBootstrapIP.Add(1) ip := uc.bootstrapIPs[(next-1)%n] - if !ctrldnet.IPv6Available() && ctrldnet.IsIPv6(ip) { + if !hasIPv6 && ctrldnet.IsIPv6(ip) { continue } uc.BootstrapIP = ip break } - uc.SetupTransport() + uc.setupTransportWithoutPingUpstream() return true, nil }) } +func (uc *UpstreamConfig) setupTransportWithoutPingUpstream() { + switch uc.Type { + case ResolverTypeDOH: + uc.setupDOHTransportWithoutPingUpstream() + case ResolverTypeDOH3: + uc.setupDOH3TransportWithoutPingUpstream() + } +} + // SetupTransport initializes the network transport used to connect to upstream server. // For now, only DoH upstream is supported. func (uc *UpstreamConfig) SetupTransport() { @@ -246,14 +265,24 @@ func (uc *UpstreamConfig) SetupTransport() { } func (uc *UpstreamConfig) setupDOHTransport() { + uc.setupDOHTransportWithoutPingUpstream() + uc.pingUpstream() +} + +func (uc *UpstreamConfig) setupDOHTransportWithoutPingUpstream() { uc.transport = http.DefaultTransport.(*http.Transport).Clone() uc.transport.IdleConnTimeout = 5 * time.Second + + dialerTimeoutMs := 2000 + if uc.Timeout < dialerTimeoutMs { + dialerTimeoutMs = uc.Timeout + } + dialerTimeout := time.Duration(dialerTimeoutMs) * time.Millisecond uc.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { dialer := &net.Dialer{ - Timeout: 2 * time.Second, - KeepAlive: 2 * time.Second, + Timeout: dialerTimeout, + KeepAlive: dialerTimeout, } - Log(ctx, ProxyLog.Debug(), "debug dial context %s - %s - %s", addr, network, bootstrapDNS) // if we have a bootstrap ip set, use it to avoid DNS lookup if uc.BootstrapIP != "" { if _, port, _ := net.SplitHostPort(addr); port != "" { @@ -263,8 +292,6 @@ func (uc *UpstreamConfig) setupDOHTransport() { Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) return dialer.DialContext(ctx, network, addr) } - - uc.pingUpstream() } func (uc *UpstreamConfig) pingUpstream() { diff --git a/config_quic.go b/config_quic.go index 72ce351..253fc4e 100644 --- a/config_quic.go +++ b/config_quic.go @@ -12,6 +12,11 @@ import ( ) func (uc *UpstreamConfig) setupDOH3Transport() { + uc.setupDOH3TransportWithoutPingUpstream() + uc.pingUpstream() +} + +func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() { rt := &http3.RoundTripper{} rt.Dial = func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { host := addr @@ -36,5 +41,4 @@ func (uc *UpstreamConfig) setupDOH3Transport() { } uc.http3RoundTripper = rt - uc.pingUpstream() } diff --git a/config_quic_free.go b/config_quic_free.go index 5f39bc6..3817e51 100644 --- a/config_quic_free.go +++ b/config_quic_free.go @@ -3,3 +3,5 @@ package ctrld func (uc *UpstreamConfig) setupDOH3Transport() {} + +func (uc *UpstreamConfig) setupDOH3TransportWithoutPingUpstream() {} diff --git a/internal/net/net.go b/internal/net/net.go index 6c78586..a155f2c 100644 --- a/internal/net/net.go +++ b/internal/net/net.go @@ -45,8 +45,8 @@ func supportIPv4() bool { return err == nil } -func supportIPv6() bool { - _, err := Dialer.Dial("tcp6", net.JoinHostPort(controldIPv6Test, "80")) +func supportIPv6(ctx context.Context) bool { + _, err := Dialer.DialContext(ctx, "tcp6", net.JoinHostPort(controldIPv6Test, "80")) return err == nil } @@ -69,7 +69,7 @@ func probeStack() { } } ipv4Enabled = supportIPv4() - ipv6Enabled = supportIPv6() + ipv6Enabled = supportIPv6(context.Background()) canListenIPv6Local = supportListenIPv6Local() } @@ -94,8 +94,8 @@ func SupportsIPv6ListenLocal() bool { } // IPv6Available is like SupportsIPv6, but always do the check without caching. -func IPv6Available() bool { - return supportIPv6() +func IPv6Available(ctx context.Context) bool { + return supportIPv6(ctx) } // IsIPv6 checks if the provided IP is v6.