diff --git a/cmd/derper/bootstrap_dns.go b/cmd/derper/bootstrap_dns.go new file mode 100644 index 000000000..2220e72a8 --- /dev/null +++ b/cmd/derper/bootstrap_dns.go @@ -0,0 +1,69 @@ +// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "encoding/json" + "expvar" + "log" + "net" + "net/http" + "strings" + "sync" + "time" +) + +var ( + dnsMu sync.Mutex + dnsCache = map[string][]net.IP{} +) + +var bootstrapDNSRequests = expvar.NewInt("counter_bootstrap_dns_requests") + +func refreshBootstrapDNSLoop() { + if *bootstrapDNS == "" { + return + } + for { + refreshBootstrapDNS() + time.Sleep(10 * time.Minute) + } +} + +func refreshBootstrapDNS() { + if *bootstrapDNS == "" { + return + } + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + names := strings.Split(*bootstrapDNS, ",") + var r net.Resolver + for _, name := range names { + addrs, err := r.LookupIP(ctx, "ip", name) + if err != nil { + log.Printf("bootstrap DNS lookup %q: %v", name, err) + continue + } + dnsMu.Lock() + dnsCache[name] = addrs + dnsMu.Unlock() + } +} + +func handleBootstrapDNS(w http.ResponseWriter, r *http.Request) { + bootstrapDNSRequests.Add(1) + dnsMu.Lock() + j, err := json.MarshalIndent(dnsCache, "", "\t") + dnsMu.Unlock() + if err != nil { + log.Printf("bootstrap DNS JSON: %v", err) + http.Error(w, "JSON marshal error", 500) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(j) +} diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index 41a1c44c5..c726389c0 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -48,6 +48,7 @@ var ( runSTUN = flag.Bool("stun", false, "also run a STUN server") meshPSKFile = flag.String("mesh-psk-file", defaultMeshPSKFile(), "if non-empty, path to file containing the mesh pre-shared key file. It should contain some hex string; whitespace is trimmed.") meshWith = flag.String("mesh-with", "", "optional comma-separated list of hostnames to mesh with; the server's own hostname can be in the list") + bootstrapDNS = flag.String("bootstrap-dns-names", "", "optional comma-separated list of hostnames to make available at /bootstrap-dns") ) type config struct { @@ -145,6 +146,8 @@ func main() { // Create our own mux so we don't expose /debug/ stuff to the world. mux := tsweb.NewMux(debugHandler(s)) mux.Handle("/derp", derphttp.Handler(s)) + go refreshBootstrapDNSLoop() + mux.HandleFunc("/bootstrap-dns", handleBootstrapDNS) mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(200)