From 4e8d1e64f367b1af9deb21fc9fe8fbe6de4ef9cc Mon Sep 17 00:00:00 2001 From: maxpetrusenkoagent Date: Mon, 15 Jun 2026 05:06:40 -0400 Subject: [PATCH] derp: include bootstrap DNS query host --- hscontrol/derp/server/bootstrap_dns_test.go | 46 +++++++++++++++++++++ hscontrol/derp/server/derp_server.go | 36 ++++++++++------ 2 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 hscontrol/derp/server/bootstrap_dns_test.go diff --git a/hscontrol/derp/server/bootstrap_dns_test.go b/hscontrol/derp/server/bootstrap_dns_test.go new file mode 100644 index 000000000..a0c650c13 --- /dev/null +++ b/hscontrol/derp/server/bootstrap_dns_test.go @@ -0,0 +1,46 @@ +package server + +import ( + "encoding/json" + "net" + "net/http" + "net/http/httptest" + "testing" + + "tailscale.com/tailcfg" +) + +func TestDERPBootstrapDNSIncludesControlHostQuery(t *testing.T) { + derpMap := (&tailcfg.DERPMap{ + Regions: map[int]*tailcfg.DERPRegion{ + 999: { + RegionID: 999, + RegionCode: "headscale", + RegionName: "Headscale Embedded DERP", + Nodes: []*tailcfg.DERPNode{{ + Name: "999a", + RegionID: 999, + HostName: "derp.invalid.", + }}, + }, + }, + }).View() + + req := httptest.NewRequest(http.MethodGet, "/bootstrap-dns?q=localhost", nil) + rec := httptest.NewRecorder() + + DERPBootstrapDNSHandler(derpMap)(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("expected status %d, got %d", http.StatusOK, rec.Code) + } + + var entries map[string][]net.IP + if err := json.NewDecoder(rec.Body).Decode(&entries); err != nil { + t.Fatalf("decoding bootstrap DNS response: %v", err) + } + + if len(entries["localhost"]) == 0 { + t.Fatalf("expected bootstrap DNS response to include the control host query, got %#v", entries) + } +} diff --git a/hscontrol/derp/server/derp_server.go b/hscontrol/derp/server/derp_server.go index fdca651a2..edb4aa5e9 100644 --- a/hscontrol/derp/server/derp_server.go +++ b/hscontrol/derp/server/derp_server.go @@ -321,25 +321,37 @@ func DERPBootstrapDNSHandler( resolvCtx, cancel := context.WithTimeout(req.Context(), time.Minute) defer cancel() - var resolver net.Resolver + lookupNames := make([]string, 0) + + if q := strings.TrimSpace(req.URL.Query().Get("q")); q != "" { + lookupNames = append(lookupNames, q) + } for _, region := range derpMap.Regions().All() { //nolint:unqueryvet // not SQLBoiler, tailcfg iterator for _, node := range region.Nodes().All() { //nolint:unqueryvet // not SQLBoiler, tailcfg iterator - addrs, err := resolver.LookupIP(resolvCtx, "ip", node.HostName()) - if err != nil { - log.Trace(). - Caller(). - Err(err). - Msgf("bootstrap DNS lookup failed %q", node.HostName()) - - continue - } - - dnsEntries[node.HostName()] = addrs + lookupNames = append(lookupNames, node.HostName()) } } + for _, name := range lookupNames { + if _, ok := dnsEntries[name]; ok { + continue + } + + addrs, err := resolver.LookupIP(resolvCtx, "ip", name) + if err != nil { + log.Trace(). + Caller(). + Err(err). + Msgf("bootstrap DNS lookup failed %q", name) + + continue + } + + dnsEntries[name] = addrs + } + writer.Header().Set("Content-Type", "application/json") writer.WriteHeader(http.StatusOK)