diff --git a/cmd/ctrld/net.go b/cmd/ctrld/net.go new file mode 100644 index 0000000..76265d8 --- /dev/null +++ b/cmd/ctrld/net.go @@ -0,0 +1,23 @@ +package main + +import ( + "net" + "sync" +) + +var ( + stackOnce sync.Once + ipv6Enabled bool +) + +func probeStack() { + if ln, err := net.Listen("tcp6", "[::]:0"); err == nil { + ln.Close() + ipv6Enabled = true + } +} + +func supportsIPv6() bool { + stackOnce.Do(probeStack) + return ipv6Enabled +} diff --git a/cmd/ctrld/prog.go b/cmd/ctrld/prog.go index 76af611..c7dce9d 100644 --- a/cmd/ctrld/prog.go +++ b/cmd/ctrld/prog.go @@ -51,23 +51,40 @@ func (p *prog) run() { if uc.BootstrapIP == "" { // resolve it manually and set the bootstrap ip c := new(dns.Client) - m := new(dns.Msg) - m.SetQuestion(uc.Domain+".", dns.TypeA) - m.RecursionDesired = true - r, _, err := c.Exchange(m, net.JoinHostPort(bootstrapDNS, "53")) - if err != nil { - proxyLog.Error().Err(err).Msgf("could not resolve domain %s for upstream.%s", uc.Domain, n) - } else { + for _, dnsType := range []uint16{dns.TypeAAAA, dns.TypeA} { + if !supportsIPv6() && dnsType == dns.TypeAAAA { + continue + } + m := new(dns.Msg) + m.SetQuestion(uc.Domain+".", dnsType) + m.RecursionDesired = true + r, _, err := c.Exchange(m, net.JoinHostPort(bootstrapDNS, "53")) + if err != nil { + proxyLog.Error().Err(err).Msgf("could not resolve domain %s for upstream.%s", uc.Domain, n) + continue + } if r.Rcode != dns.RcodeSuccess { proxyLog.Error().Msgf("could not resolve domain return code: %d, upstream.%s", r.Rcode, n) - } else { - for _, a := range r.Answer { - if ar, ok := a.(*dns.A); ok { - uc.BootstrapIP = ar.A.String() - proxyLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) - } - } + continue } + if len(r.Answer) == 0 { + continue + } + for _, a := range r.Answer { + switch ar := a.(type) { + case *dns.A: + uc.BootstrapIP = ar.A.String() + case *dns.AAAA: + uc.BootstrapIP = ar.AAAA.String() + default: + continue + } + proxyLog.Info().Str("bootstrap_ip", uc.BootstrapIP).Msgf("Setting bootstrap IP for upstream.%s", n) + // Stop if we reached here, because we got the bootstrap IP from r.Answer. + break + } + // If we reached here, uc.BootstrapIP was set, nothing to do anymore. + break } } uc.SetupTransport() diff --git a/config.go b/config.go index faf8720..1d2fd20 100644 --- a/config.go +++ b/config.go @@ -2,7 +2,6 @@ package ctrld import ( "context" - "fmt" "net" "net/http" "net/url" @@ -145,8 +144,10 @@ func (uc *UpstreamConfig) SetupTransport() { } 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 != "" && addr == fmt.Sprintf("%s:443", uc.Domain) { - addr = fmt.Sprintf("%s:443", uc.BootstrapIP) + if uc.BootstrapIP != "" { + if _, port, _ := net.SplitHostPort(addr); port != "" { + addr = net.JoinHostPort(uc.BootstrapIP, port) + } Log(ctx, ProxyLog.Debug(), "sending doh request to: %s", addr) } return dialer.DialContext(ctx, network, addr)