derp/derpserver: split off derp.Server out of derp into its own package

This exports a number of things from the derp (generic + client) package
to be used by the new derpserver package, as now used by cmd/derper.

And then enough other misc changes to lock in that cmd/tailscaled can
be configured to not bring in tailscale.com/client/local. (The webclient
in particular, even when disabled, was bringing it in, so that's now fixed)

Fixes #17257

Change-Id: I88b6c7958643fb54f386dd900bddf73d2d4d96d5
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2025-09-23 17:07:48 -07:00
committed by Brad Fitzpatrick
parent df747f1c1b
commit 21dc5f4e21
35 changed files with 1442 additions and 1319 deletions
+2 -2
View File
@@ -12,12 +12,12 @@ import (
"net/http"
"strings"
"tailscale.com/derp"
"tailscale.com/derp/derpserver"
"tailscale.com/net/connectproxy"
)
// serveConnect handles a CONNECT request for ACE support.
func serveConnect(s *derp.Server, w http.ResponseWriter, r *http.Request) {
func serveConnect(s *derpserver.Server, w http.ResponseWriter, r *http.Request) {
if !*flagACEEnabled {
http.Error(w, "CONNECT not enabled", http.StatusForbidden)
return
+3 -3
View File
@@ -22,8 +22,8 @@ import (
"testing"
"time"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpserver"
"tailscale.com/net/netmon"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
@@ -131,9 +131,9 @@ func TestPinnedCertRawIP(t *testing.T) {
}
defer ln.Close()
ds := derp.NewServer(key.NewNode(), t.Logf)
ds := derpserver.NewServer(key.NewNode(), t.Logf)
derpHandler := derphttp.Handler(ds)
derpHandler := derpserver.Handler(ds)
mux := http.NewServeMux()
mux.Handle("/derp", derpHandler)
+8 -7
View File
@@ -89,12 +89,13 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
google.golang.org/protobuf/types/known/timestamppb from github.com/prometheus/client_golang/prometheus+
tailscale.com from tailscale.com/version
💣 tailscale.com/atomicfile from tailscale.com/cmd/derper+
tailscale.com/client/local from tailscale.com/derp
tailscale.com/client/local from tailscale.com/derp/derpserver
tailscale.com/client/tailscale/apitype from tailscale.com/client/local
tailscale.com/derp from tailscale.com/cmd/derper+
tailscale.com/derp/derpconst from tailscale.com/derp+
tailscale.com/derp/derpconst from tailscale.com/derp/derphttp+
tailscale.com/derp/derphttp from tailscale.com/cmd/derper
tailscale.com/disco from tailscale.com/derp
tailscale.com/derp/derpserver from tailscale.com/cmd/derper
tailscale.com/disco from tailscale.com/derp/derpserver
tailscale.com/drive from tailscale.com/client/local+
tailscale.com/envknob from tailscale.com/client/local+
tailscale.com/feature from tailscale.com/tsweb
@@ -117,7 +118,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
tailscale.com/net/stun from tailscale.com/net/stunserver
tailscale.com/net/stunserver from tailscale.com/cmd/derper
L tailscale.com/net/tcpinfo from tailscale.com/derp
L tailscale.com/net/tcpinfo from tailscale.com/derp/derpserver
tailscale.com/net/tlsdial from tailscale.com/derp/derphttp
tailscale.com/net/tlsdial/blockblame from tailscale.com/net/tlsdial
tailscale.com/net/tsaddr from tailscale.com/ipn+
@@ -132,7 +133,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
W tailscale.com/tsconst from tailscale.com/net/netmon+
tailscale.com/tstime from tailscale.com/derp+
tailscale.com/tstime/mono from tailscale.com/tstime/rate
tailscale.com/tstime/rate from tailscale.com/derp
tailscale.com/tstime/rate from tailscale.com/derp/derpserver
tailscale.com/tsweb from tailscale.com/cmd/derper+
tailscale.com/tsweb/promvarz from tailscale.com/cmd/derper
tailscale.com/tsweb/varz from tailscale.com/tsweb+
@@ -167,7 +168,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/util/multierr from tailscale.com/health+
tailscale.com/util/nocasemaps from tailscale.com/types/ipproto
tailscale.com/util/rands from tailscale.com/tsweb
tailscale.com/util/set from tailscale.com/derp+
tailscale.com/util/set from tailscale.com/derp/derpserver+
tailscale.com/util/singleflight from tailscale.com/net/dnscache
tailscale.com/util/slicesx from tailscale.com/cmd/derper+
tailscale.com/util/syspolicy/internal from tailscale.com/util/syspolicy/setting
@@ -180,7 +181,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
tailscale.com/util/vizerror from tailscale.com/tailcfg+
W 💣 tailscale.com/util/winutil from tailscale.com/hostinfo+
W 💣 tailscale.com/util/winutil/winenv from tailscale.com/hostinfo+
tailscale.com/version from tailscale.com/derp+
tailscale.com/version from tailscale.com/cmd/derper+
tailscale.com/version/distro from tailscale.com/envknob+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap
golang.org/x/crypto/acme from golang.org/x/crypto/acme/autocert
+8 -9
View File
@@ -40,8 +40,7 @@ import (
"github.com/tailscale/setec/client/setec"
"golang.org/x/time/rate"
"tailscale.com/atomicfile"
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpserver"
"tailscale.com/metrics"
"tailscale.com/net/ktimeout"
"tailscale.com/net/stunserver"
@@ -90,7 +89,7 @@ var (
// tcpUserTimeout is intentionally short, so that hung connections are cleaned up promptly. DERPs should be nearby users.
tcpUserTimeout = flag.Duration("tcp-user-timeout", 15*time.Second, "TCP user timeout")
// tcpWriteTimeout is the timeout for writing to client TCP connections. It does not apply to mesh connections.
tcpWriteTimeout = flag.Duration("tcp-write-timeout", derp.DefaultTCPWiteTimeout, "TCP write timeout; 0 results in no timeout being set on writes")
tcpWriteTimeout = flag.Duration("tcp-write-timeout", derpserver.DefaultTCPWiteTimeout, "TCP write timeout; 0 results in no timeout being set on writes")
// ACE
flagACEEnabled = flag.Bool("ace", false, "whether to enable embedded ACE server [experimental + in-development as of 2025-09-12; not yet documented]")
@@ -189,7 +188,7 @@ func main() {
serveTLS := tsweb.IsProd443(*addr) || *certMode == "manual"
s := derp.NewServer(cfg.PrivateKey, log.Printf)
s := derpserver.NewServer(cfg.PrivateKey, log.Printf)
s.SetVerifyClient(*verifyClients)
s.SetTailscaledSocketPath(*socket)
s.SetVerifyClientURL(*verifyClientURL)
@@ -256,7 +255,7 @@ func main() {
mux := http.NewServeMux()
if *runDERP {
derpHandler := derphttp.Handler(s)
derpHandler := derpserver.Handler(s)
derpHandler = addWebSocketSupport(s, derpHandler)
mux.Handle("/derp", derpHandler)
} else {
@@ -267,8 +266,8 @@ func main() {
// These two endpoints are the same. Different versions of the clients
// have assumes different paths over time so we support both.
mux.HandleFunc("/derp/probe", derphttp.ProbeHandler)
mux.HandleFunc("/derp/latency-check", derphttp.ProbeHandler)
mux.HandleFunc("/derp/probe", derpserver.ProbeHandler)
mux.HandleFunc("/derp/latency-check", derpserver.ProbeHandler)
go refreshBootstrapDNSLoop()
mux.HandleFunc("/bootstrap-dns", tsweb.BrowserHeaderHandlerFunc(handleBootstrapDNS))
@@ -280,7 +279,7 @@ func main() {
tsweb.AddBrowserHeaders(w)
io.WriteString(w, "User-agent: *\nDisallow: /\n")
}))
mux.Handle("/generate_204", http.HandlerFunc(derphttp.ServeNoContent))
mux.Handle("/generate_204", http.HandlerFunc(derpserver.ServeNoContent))
debug := tsweb.Debugger(mux)
debug.KV("TLS hostname", *hostname)
debug.KV("Mesh key", s.HasMeshKey())
@@ -388,7 +387,7 @@ func main() {
if *httpPort > -1 {
go func() {
port80mux := http.NewServeMux()
port80mux.HandleFunc("/generate_204", derphttp.ServeNoContent)
port80mux.HandleFunc("/generate_204", derpserver.ServeNoContent)
port80mux.Handle("/", certManager.HTTPHandler(tsweb.Port80Handler{Main: mux}))
port80srv := &http.Server{
Addr: net.JoinHostPort(listenHost, fmt.Sprintf("%d", *httpPort)),
+5 -5
View File
@@ -11,7 +11,7 @@ import (
"strings"
"testing"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpserver"
"tailscale.com/tstest/deptest"
)
@@ -78,20 +78,20 @@ func TestNoContent(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
req, _ := http.NewRequest("GET", "https://localhost/generate_204", nil)
if tt.input != "" {
req.Header.Set(derphttp.NoContentChallengeHeader, tt.input)
req.Header.Set(derpserver.NoContentChallengeHeader, tt.input)
}
w := httptest.NewRecorder()
derphttp.ServeNoContent(w, req)
derpserver.ServeNoContent(w, req)
resp := w.Result()
if tt.want == "" {
if h, found := resp.Header[derphttp.NoContentResponseHeader]; found {
if h, found := resp.Header[derpserver.NoContentResponseHeader]; found {
t.Errorf("got %+v; expected no response header", h)
}
return
}
if got := resp.Header.Get(derphttp.NoContentResponseHeader); got != tt.want {
if got := resp.Header.Get(derpserver.NoContentResponseHeader); got != tt.want {
t.Errorf("got %q; want %q", got, tt.want)
}
})
+3 -2
View File
@@ -13,11 +13,12 @@ import (
"tailscale.com/derp"
"tailscale.com/derp/derphttp"
"tailscale.com/derp/derpserver"
"tailscale.com/net/netmon"
"tailscale.com/types/logger"
)
func startMesh(s *derp.Server) error {
func startMesh(s *derpserver.Server) error {
if *meshWith == "" {
return nil
}
@@ -32,7 +33,7 @@ func startMesh(s *derp.Server) error {
return nil
}
func startMeshWithHost(s *derp.Server, hostTuple string) error {
func startMeshWithHost(s *derpserver.Server, hostTuple string) error {
var host string
var dialHost string
hostParts := strings.Split(hostTuple, "/")
+2 -2
View File
@@ -11,14 +11,14 @@ import (
"strings"
"github.com/coder/websocket"
"tailscale.com/derp"
"tailscale.com/derp/derpserver"
"tailscale.com/net/wsconn"
)
var counterWebSocketAccepts = expvar.NewInt("derp_websocket_accepts")
// addWebSocketSupport returns a Handle wrapping base that adds WebSocket server support.
func addWebSocketSupport(s *derp.Server, base http.Handler) http.Handler {
func addWebSocketSupport(s *derpserver.Server, base http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
up := strings.ToLower(r.Header.Get("Upgrade"))