diff --git a/health/health.go b/health/health.go index 48beec3df..eab7477ea 100644 --- a/health/health.go +++ b/health/health.go @@ -9,6 +9,7 @@ package health import ( "errors" "fmt" + "net/http" "os" "runtime" "sort" @@ -28,6 +29,8 @@ var ( watchers = map[*watchHandle]func(Subsystem, error){} // opt func to run if error state changes timer *time.Timer + debugHandler = map[string]http.Handler{} + inMapPoll bool inMapPollSince time.Time lastMapPollEndedAt time.Time @@ -116,6 +119,18 @@ func SetNetworkCategoryHealth(err error) { set(SysNetworkCategory, err) } func NetworkCategoryHealth() error { return get(SysNetworkCategory) } +func RegisterDebugHandler(typ string, h http.Handler) { + mu.Lock() + defer mu.Unlock() + debugHandler[typ] = h +} + +func DebugHandler(typ string) http.Handler { + mu.Lock() + defer mu.Unlock() + return debugHandler[typ] +} + func get(key Subsystem) error { mu.Lock() defer mu.Unlock() diff --git a/ipn/ipnlocal/peerapi.go b/ipn/ipnlocal/peerapi.go index 35b44d6f6..19aeb2240 100644 --- a/ipn/ipnlocal/peerapi.go +++ b/ipn/ipnlocal/peerapi.go @@ -32,6 +32,7 @@ import ( "golang.org/x/net/dns/dnsmessage" "inet.af/netaddr" "tailscale.com/client/tailscale/apitype" + "tailscale.com/health" "tailscale.com/hostinfo" "tailscale.com/ipn" "tailscale.com/logtail/backoff" @@ -556,6 +557,9 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { case "/v0/magicsock": h.handleServeMagicsock(w, r) return + case "/v0/dnsfwd": + h.handleServeDNSFwd(w, r) + return } who := h.peerUser.DisplayName fmt.Fprintf(w, ` @@ -808,6 +812,19 @@ func (h *peerAPIHandler) handleServeMetrics(w http.ResponseWriter, r *http.Reque clientmetric.WritePrometheusExpositionFormat(w) } +func (h *peerAPIHandler) handleServeDNSFwd(w http.ResponseWriter, r *http.Request) { + if !h.isSelf { + http.Error(w, "not owner", http.StatusForbidden) + return + } + dh := health.DebugHandler("dnsfwd") + if dh == nil { + http.Error(w, "not wired up", 500) + return + } + dh.ServeHTTP(w, r) +} + func (h *peerAPIHandler) replyToDNSQueries() bool { if h.isSelf { // If the peer is owned by the same user, just allow it diff --git a/net/dns/resolver/debug.go b/net/dns/resolver/debug.go new file mode 100644 index 000000000..cbe415bda --- /dev/null +++ b/net/dns/resolver/debug.go @@ -0,0 +1,78 @@ +// Copyright (c) 2020 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 resolver + +import ( + "fmt" + "html" + "net/http" + "strconv" + "sync" + "sync/atomic" + "time" + + "tailscale.com/health" +) + +func init() { + health.RegisterDebugHandler("dnsfwd", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + n, _ := strconv.Atoi(r.FormValue("n")) + if n <= 0 { + n = 100 + } else if n > 10000 { + n = 10000 + } + fl, ok := fwdLogAtomic.Load().(*fwdLog) + if !ok || n != len(fl.ent) { + fl = &fwdLog{ent: make([]fwdLogEntry, n)} + fwdLogAtomic.Store(fl) + } + fl.ServeHTTP(w, r) + })) +} + +var fwdLogAtomic atomic.Value // of *fwdLog + +type fwdLog struct { + mu sync.Mutex + pos int // ent[pos] is next entry + ent []fwdLogEntry +} + +type fwdLogEntry struct { + Domain string + Time time.Time +} + +func (fl *fwdLog) addName(name string) { + if fl == nil { + return + } + fl.mu.Lock() + defer fl.mu.Unlock() + if len(fl.ent) == 0 { + return + } + fl.ent[fl.pos] = fwdLogEntry{Domain: name, Time: time.Now()} + fl.pos++ + if fl.pos == len(fl.ent) { + fl.pos = 0 + } +} + +func (fl *fwdLog) ServeHTTP(w http.ResponseWriter, r *http.Request) { + fl.mu.Lock() + defer fl.mu.Unlock() + + fmt.Fprintf(w, "

DNS forwards

") + now := time.Now() + for i := 0; i < len(fl.ent); i++ { + ent := fl.ent[(i+fl.pos)%len(fl.ent)] + if ent.Domain == "" { + continue + } + fmt.Fprintf(w, "%v ago: %v
\n", now.Sub(ent.Time).Round(time.Second), html.EscapeString(ent.Domain)) + } +} diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index d84400e70..2d626688a 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -611,6 +611,10 @@ func (f *forwarder) forwardWithDestChan(ctx context.Context, query packet, respo } } + if fl, ok := fwdLogAtomic.Load().(*fwdLog); ok { + fl.addName(string(domain)) + } + clampEDNSSize(query.bs, maxResponseBytes) if len(resolvers) == 0 {