diff --git a/cmd/cli/dns_proxy.go b/cmd/cli/dns_proxy.go index 6cc36c8..f1aa445 100644 --- a/cmd/cli/dns_proxy.go +++ b/cmd/cli/dns_proxy.go @@ -1400,9 +1400,6 @@ func (p *prog) checkUpstreamOnce(upstream string, uc *ctrld.UpstreamConfig) erro return err } - msg := new(dns.Msg) - msg.SetQuestion(".", dns.TypeNS) - timeout := 1000 * time.Millisecond if uc.Timeout > 0 { timeout = time.Millisecond * time.Duration(uc.Timeout) @@ -1416,6 +1413,7 @@ func (p *prog) checkUpstreamOnce(upstream string, uc *ctrld.UpstreamConfig) erro mainLog.Load().Debug().Msgf("Rebootstrapping resolver for upstream: %s", upstream) start := time.Now() + msg := uc.VerifyMsg() _, err = resolver.Resolve(ctx, msg) duration := time.Since(start) diff --git a/config.go b/config.go index 96f6686..73484d7 100644 --- a/config.go +++ b/config.go @@ -358,6 +358,15 @@ func (uc *UpstreamConfig) Init() { } } +// VerifyMsg creates and returns a new DNS message could be used for testing upstream health. +func (uc *UpstreamConfig) VerifyMsg() *dns.Msg { + msg := new(dns.Msg) + msg.RecursionDesired = true + msg.SetQuestion(".", dns.TypeNS) + msg.SetEdns0(4096, false) // ensure handling of large DNS response + return msg +} + // VerifyDomain returns the domain name that could be resolved by the upstream endpoint. // It returns empty for non-ControlD upstream endpoint. func (uc *UpstreamConfig) VerifyDomain() string { diff --git a/internal/clientinfo/ptr_lookup.go b/internal/clientinfo/ptr_lookup.go index 8e6b3f7..29ed84f 100644 --- a/internal/clientinfo/ptr_lookup.go +++ b/internal/clientinfo/ptr_lookup.go @@ -120,8 +120,7 @@ func (p *ptrDiscover) lookupIPByHostname(name string, v6 bool) string { // is reachable, set p.serverDown to false, so p.lookupHostname can continue working. func (p *ptrDiscover) checkServer() { bo := backoff.NewBackoff("ptrDiscover", func(format string, args ...any) {}, time.Minute*5) - m := new(dns.Msg) - m.SetQuestion(".", dns.TypeNS) + m := (&ctrld.UpstreamConfig{}).VerifyMsg() ping := func() error { ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() diff --git a/resolver_test.go b/resolver_test.go index ebcad16..f030739 100644 --- a/resolver_test.go +++ b/resolver_test.go @@ -282,6 +282,35 @@ func Test_Edns0_CacheReply(t *testing.T) { } } +// https://github.com/Control-D-Inc/ctrld/issues/255 +func Test_legacyResolverWithBigExtraSection(t *testing.T) { + lanPC, err := net.ListenPacket("udp", "127.0.0.1:0") // 127.0.0.1 is considered LAN (loopback) + if err != nil { + t.Fatalf("failed to listen on LAN address: %v", err) + } + lanServer, lanAddr, err := runLocalPacketConnTestServer(t, lanPC, bigExtraSectionHandler()) + if err != nil { + t.Fatalf("failed to run LAN test server: %v", err) + } + defer lanServer.Shutdown() + + uc := &UpstreamConfig{ + Name: "Legacy", + Type: ResolverTypeLegacy, + Endpoint: lanAddr, + } + uc.Init() + r, err := NewResolver(uc) + if err != nil { + t.Fatal(err) + } + + _, err = r.Resolve(context.Background(), uc.VerifyMsg()) + if err != nil { + t.Fatal(err) + } +} + func Test_upstreamTypeFromEndpoint(t *testing.T) { tests := []struct { name string @@ -370,6 +399,68 @@ func countHandler(call *atomic.Int64) dns.HandlerFunc { } } +func mustRR(s string) dns.RR { + r, err := dns.NewRR(s) + if err != nil { + panic(err) + } + return r +} + +func bigExtraSectionHandler() dns.HandlerFunc { + return func(w dns.ResponseWriter, msg *dns.Msg) { + m := &dns.Msg{ + Answer: []dns.RR{ + mustRR(". 7149 IN NS m.root-servers.net."), + mustRR(". 7149 IN NS c.root-servers.net."), + mustRR(". 7149 IN NS e.root-servers.net."), + mustRR(". 7149 IN NS j.root-servers.net."), + mustRR(". 7149 IN NS g.root-servers.net."), + mustRR(". 7149 IN NS k.root-servers.net."), + mustRR(". 7149 IN NS l.root-servers.net."), + mustRR(". 7149 IN NS d.root-servers.net."), + mustRR(". 7149 IN NS h.root-servers.net."), + mustRR(". 7149 IN NS b.root-servers.net."), + mustRR(". 7149 IN NS a.root-servers.net."), + mustRR(". 7149 IN NS f.root-servers.net."), + mustRR(". 7149 IN NS i.root-servers.net."), + }, + Extra: []dns.RR{ + mustRR("m.root-servers.net. 656 IN A 202.12.27.33"), + mustRR("m.root-servers.net. 656 IN AAAA 2001:dc3::35"), + mustRR("c.root-servers.net. 656 IN A 192.33.4.12"), + mustRR("c.root-servers.net. 656 IN AAAA 2001:500:2::c"), + mustRR("e.root-servers.net. 656 IN A 192.203.230.10"), + mustRR("e.root-servers.net. 656 IN AAAA 2001:500:a8::e"), + mustRR("j.root-servers.net. 656 IN A 192.58.128.30"), + mustRR("j.root-servers.net. 656 IN AAAA 2001:503:c27::2:30"), + mustRR("g.root-servers.net. 656 IN A 192.112.36.4"), + mustRR("g.root-servers.net. 656 IN AAAA 2001:500:12::d0d"), + mustRR("k.root-servers.net. 656 IN A 193.0.14.129"), + mustRR("k.root-servers.net. 656 IN AAAA 2001:7fd::1"), + mustRR("l.root-servers.net. 656 IN A 199.7.83.42"), + mustRR("l.root-servers.net. 656 IN AAAA 2001:500:9f::42"), + mustRR("d.root-servers.net. 656 IN A 199.7.91.13"), + mustRR("d.root-servers.net. 656 IN AAAA 2001:500:2d::d"), + mustRR("h.root-servers.net. 656 IN A 198.97.190.53"), + mustRR("h.root-servers.net. 656 IN AAAA 2001:500:1::53"), + mustRR("b.root-servers.net. 656 IN A 170.247.170.2"), + mustRR("b.root-servers.net. 656 IN AAAA 2801:1b8:10::b"), + mustRR("a.root-servers.net. 656 IN A 198.41.0.4"), + mustRR("a.root-servers.net. 656 IN AAAA 2001:503:ba3e::2:30"), + mustRR("f.root-servers.net. 656 IN A 192.5.5.241"), + mustRR("f.root-servers.net. 656 IN AAAA 2001:500:2f::f"), + mustRR("i.root-servers.net. 656 IN A 192.36.148.17"), + mustRR("i.root-servers.net. 656 IN AAAA 2001:7fe::53"), + }, + } + + m.Compress = true + m.SetReply(msg) + w.WriteMsg(m) + } +} + func generateEdns0ClientCookie() string { cookie := make([]byte, 8) if _, err := rand.Read(cookie); err != nil {