diff --git a/cmd/derper/derper.go b/cmd/derper/derper.go index 7dd84a86d..ffb0e464f 100644 --- a/cmd/derper/derper.go +++ b/cmd/derper/derper.go @@ -7,30 +7,37 @@ package main // import "tailscale.com/cmd/derper" import ( "encoding/json" + "expvar" + _ "expvar" "flag" + "fmt" "io" "io/ioutil" "log" "net" "net/http" + _ "net/http/pprof" "os" "path/filepath" + "time" "github.com/tailscale/wireguard-go/wgcfg" "golang.org/x/crypto/acme/autocert" "tailscale.com/atomicfile" "tailscale.com/derp" "tailscale.com/derp/derphttp" + "tailscale.com/interfaces" "tailscale.com/logpolicy" "tailscale.com/types/key" ) var ( + dev = flag.Bool("dev", false, "run in localhost development mode") addr = flag.String("a", ":443", "server address") configPath = flag.String("c", "", "config file path") certDir = flag.String("certdir", defaultCertDir(), "directory to store LetsEncrypt certs, if addr's port is :443") hostname = flag.String("hostname", "derp.tailscale.com", "LetsEncrypt host name, if addr's port is :443") - bytesPerSec = flag.Int("mbps", 5, "Mbps (mebibit/s) per-client rate limit; 0 means unlimited") + mbps = flag.Int("mbps", 5, "Mbps (mebibit/s) per-client rate limit; 0 means unlimited") logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to") ) @@ -47,6 +54,9 @@ type config struct { } func loadConfig() config { + if *dev { + return config{PrivateKey: mustNewKey()} + } if *configPath == "" { log.Fatalf("derper: -c not specified") } @@ -66,12 +76,16 @@ func loadConfig() config { } } -func writeNewConfig() config { +func mustNewKey() wgcfg.PrivateKey { key, err := wgcfg.NewPrivateKey() if err != nil { log.Fatal(err) } + return key +} +func writeNewConfig() config { + key := mustNewKey() if err := os.MkdirAll(filepath.Dir(*configPath), 0777); err != nil { log.Fatal(err) } @@ -91,6 +105,12 @@ func writeNewConfig() config { func main() { flag.Parse() + if *dev { + *logCollection = "" + *addr = ":3340" // above the keys DERP + log.Printf("Running in dev mode.") + } + var logPol *logpolicy.Policy if *logCollection != "" { logPol = logpolicy.New(*logCollection) @@ -105,16 +125,33 @@ func main() { } s := derp.NewServer(key.Private(cfg.PrivateKey), log.Printf) - if *bytesPerSec != 0 { - s.BytesPerSecond = (*bytesPerSec << 20) / 8 + if *mbps != 0 { + s.BytesPerSecond = (*mbps << 20) / 8 } + expvar.Publish("derp", s.ExpVar()) + expvar.Publish("uptime", uptimeVar{}) + // Create our own mux so we don't expose /debug/ stuff to the world. mux := http.NewServeMux() mux.Handle("/derp", derphttp.Handler(s)) + mux.Handle("/debug/", protected(debugHandler(s))) + mux.Handle("/debug/pprof/", protected(http.DefaultServeMux)) // to net/http/pprof + mux.Handle("/debug/vars", protected(http.DefaultServeMux)) // to expvar mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(200) - io.WriteString(w, "Tailscale DERP server.") + io.WriteString(w, ` +

DERP

+

+ This is a + Tailscale + DERP + server. +

+`) + if allowDebugAccess(r) { + io.WriteString(w, "

Debug info at /debug/.

\n") + } })) httpsrv := &http.Server{ @@ -151,3 +188,55 @@ func main() { log.Fatalf("derper: %v", err) } } + +func protected(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !allowDebugAccess(r) { + http.Error(w, "debug access denied", http.StatusForbidden) + return + } + h.ServeHTTP(w, r) + }) +} + +func allowDebugAccess(r *http.Request) bool { + if r.Header.Get("X-Forwarded-For") != "" { + // TODO if/when needed. For now, conservative: + return false + } + ipStr, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return false + } + ip := net.ParseIP(ipStr) + return interfaces.IsTailscaleIP(ip) || ip.IsLoopback() || ipStr == os.Getenv("ALLOW_DEBUG_IP") +} + +func debugHandler(s *derp.Server) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) } + f(` +

DERP debug

+