summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cmd/tinyderpclient/tinyderpclient.go39
-rw-r--r--derp/derp.go19
-rw-r--r--derp/derp_server.go15
-rw-r--r--derp/derp_server_default.go2
-rw-r--r--derp/derphttp/derphttp_client.go110
-rw-r--r--derp/derphttp/derphttp_server.go10
-rw-r--r--hostinfo/hostinfo.go2
-rw-r--r--net/dnscache/dnscache.go27
-rw-r--r--net/netmon/state.go11
-rw-r--r--net/netns/netns.go2
-rw-r--r--net/tlsdial/tlsdial.go56
-rw-r--r--syncs/locked.go32
-rw-r--r--types/logger/rusage.go2
-rw-r--r--util/eventbus/debug.go4
-rw-r--r--util/eventbus/debughttp.go240
-rw-r--r--util/eventbus/debughttp_off.go20
16 files changed, 76 insertions, 515 deletions
diff --git a/cmd/tinyderpclient/tinyderpclient.go b/cmd/tinyderpclient/tinyderpclient.go
new file mode 100644
index 000000000..746a216ff
--- /dev/null
+++ b/cmd/tinyderpclient/tinyderpclient.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "log"
+ "maps"
+ "net/http"
+ "slices"
+
+ "tailscale.com/derp/derphttp"
+ "tailscale.com/net/netmon"
+ "tailscale.com/tailcfg"
+ "tailscale.com/types/key"
+)
+
+func main() {
+ dm := &tailcfg.DERPMap{}
+ res, err := http.Get("https://controlplane.tailscale.com/derpmap/default")
+ if err != nil {
+ log.Fatalf("fetching DERPMap: %v", err)
+ }
+ defer res.Body.Close()
+ if err := json.NewDecoder(res.Body).Decode(dm); err != nil {
+ log.Fatalf("decoding DERPMap: %v", err)
+ }
+
+ region := slices.Sorted(maps.Keys(dm.Regions))[0]
+
+ netMon := netmon.NewStatic()
+ rc := derphttp.NewRegionClient(key.NewNode(), log.Printf, netMon, func() *tailcfg.DERPRegion {
+ return dm.Regions[region]
+ })
+ defer rc.Close()
+
+ if err := rc.Connect(context.Background()); err != nil {
+ log.Fatalf("rc.Connect: %v", err)
+ }
+}
diff --git a/derp/derp.go b/derp/derp.go
index 24c1ca65c..f628580ce 100644
--- a/derp/derp.go
+++ b/derp/derp.go
@@ -270,3 +270,22 @@ type Conn interface {
SetReadDeadline(time.Time) error
SetWriteDeadline(time.Time) error
}
+
+type serverInfo struct {
+ Version int `json:"version,omitempty"`
+
+ TokenBucketBytesPerSecond int `json:",omitempty"`
+ TokenBucketBytesBurst int `json:",omitempty"`
+}
+
+// IdealNodeHeader is the HTTP request header sent on DERP HTTP client requests
+// to indicate that they're connecting to their ideal (Region.Nodes[0]) node.
+// The HTTP header value is the name of the node they wish they were connected
+// to. This is an optional header.
+const IdealNodeHeader = "Ideal-Node"
+
+// FastStartHeader is the header (with value "1") that signals to the HTTP
+// server that the DERP HTTP client does not want the HTTP 101 response
+// headers and it will begin writing & reading the DERP protocol immediately
+// following its HTTP request.
+const FastStartHeader = "Derp-Fast-Start"
diff --git a/derp/derp_server.go b/derp/derp_server.go
index bd67e7eec..1c9c10129 100644
--- a/derp/derp_server.go
+++ b/derp/derp_server.go
@@ -1,6 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
+//go:build !ts_omit_derpserver
+
package derp
// TODO(crawshaw): with predefined serverKey in clients and HMAC on packets we could skip TLS
@@ -60,12 +62,6 @@ import (
// verbosely log whenever DERP drops a packet.
var verboseDropKeys = map[key.NodePublic]bool{}
-// IdealNodeHeader is the HTTP request header sent on DERP HTTP client requests
-// to indicate that they're connecting to their ideal (Region.Nodes[0]) node.
-// The HTTP header value is the name of the node they wish they were connected
-// to. This is an optional header.
-const IdealNodeHeader = "Ideal-Node"
-
// IdealNodeContextKey is the context key used to pass the IdealNodeHeader value
// from the HTTP handler to the DERP server's Accept method.
var IdealNodeContextKey = ctxkey.New[string]("ideal-node", "")
@@ -1505,13 +1501,6 @@ func (s *Server) noteClientActivity(c *sclient) {
dup.sendHistory = append(dup.sendHistory, c)
}
-type serverInfo struct {
- Version int `json:"version,omitempty"`
-
- TokenBucketBytesPerSecond int `json:",omitempty"`
- TokenBucketBytesBurst int `json:",omitempty"`
-}
-
func (s *Server) sendServerInfo(bw *lazyBufioWriter, clientKey key.NodePublic) error {
msg, err := json.Marshal(serverInfo{Version: ProtocolVersion})
if err != nil {
diff --git a/derp/derp_server_default.go b/derp/derp_server_default.go
index 014cfffd6..0d517b186 100644
--- a/derp/derp_server_default.go
+++ b/derp/derp_server_default.go
@@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
-//go:build !linux || android
+//go:build !ts_omit_derpserver && (!linux || android)
package derp
diff --git a/derp/derphttp/derphttp_client.go b/derp/derphttp/derphttp_client.go
index 7385f0ad1..1a9fdd8af 100644
--- a/derp/derphttp/derphttp_client.go
+++ b/derp/derphttp/derphttp_client.go
@@ -32,14 +32,10 @@ import (
"tailscale.com/derp"
"tailscale.com/derp/derpconst"
"tailscale.com/envknob"
- "tailscale.com/health"
- "tailscale.com/net/dnscache"
"tailscale.com/net/netmon"
- "tailscale.com/net/netns"
"tailscale.com/net/netx"
"tailscale.com/net/sockstats"
"tailscale.com/net/tlsdial"
- "tailscale.com/net/tshttpproxy"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/tstime"
@@ -54,11 +50,9 @@ import (
// Send/Recv will completely re-establish the connection (unless Close
// has been called).
type Client struct {
- TLSConfig *tls.Config // optional; nil means default
- HealthTracker *health.Tracker // optional; used if non-nil only
- DNSCache *dnscache.Resolver // optional; nil means no caching
- MeshKey key.DERPMesh // optional; for trusted clients
- IsProber bool // optional; for probers to optional declare themselves as such
+ TLSConfig *tls.Config // optional; nil means default
+ MeshKey key.DERPMesh // optional; for trusted clients
+ IsProber bool // optional; for probers to optional declare themselves as such
// WatchConnectionChanges is whether the client wishes to subscribe to
// notifications about clients connecting & disconnecting.
@@ -522,7 +516,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
// just to get routed into the server's HTTP Handler so it
// can Hijack the request, but we signal with a special header
// that we don't want to deal with its HTTP response.
- req.Header.Set(fastStartHeader, "1") // suppresses the server's HTTP response
+ req.Header.Set(derp.FastStartHeader, "1") // suppresses the server's HTTP response
if err := req.Write(brw); err != nil {
return nil, 0, err
}
@@ -599,20 +593,8 @@ func (c *Client) dialURL(ctx context.Context) (net.Conn, error) {
return c.dialer(ctx, "tcp", net.JoinHostPort(host, urlPort(c.url)))
}
hostOrIP := host
- dialer := netns.NewDialer(c.logf, c.netMon)
-
- if c.DNSCache != nil {
- ip, _, _, err := c.DNSCache.LookupIP(ctx, host)
- if err == nil {
- hostOrIP = ip.String()
- }
- if err != nil && netns.IsSOCKSDialer(dialer) {
- // Return an error if we're not using a dial
- // proxy that can do DNS lookups for us.
- return nil, err
- }
- }
+ var dialer net.Dialer
tcpConn, err := dialer.DialContext(ctx, "tcp", net.JoinHostPort(hostOrIP, urlPort(c.url)))
if err != nil {
return nil, fmt.Errorf("dial of %v: %v", host, err)
@@ -647,7 +629,7 @@ func (c *Client) dialRegion(ctx context.Context, reg *tailcfg.DERPRegion) (net.C
}
func (c *Client) tlsClient(nc net.Conn, node *tailcfg.DERPNode) *tls.Conn {
- tlsConf := tlsdial.Config(c.HealthTracker, c.TLSConfig)
+ tlsConf := tlsdial.Config(nil, c.TLSConfig)
if node != nil {
if node.InsecureForTests {
tlsConf.InsecureSkipVerify = true
@@ -699,7 +681,8 @@ func (c *Client) DialRegionTLS(ctx context.Context, reg *tailcfg.DERPRegion) (tl
}
func (c *Client) dialContext(ctx context.Context, proto, addr string) (net.Conn, error) {
- return netns.NewDialer(c.logf, c.netMon).DialContext(ctx, proto, addr)
+ var d net.Dialer
+ return d.DialContext(ctx, proto, addr)
}
// shouldDialProto reports whether an explicitly provided IPv4 or IPv6
@@ -723,18 +706,6 @@ const dialNodeTimeout = 1500 * time.Millisecond
// TODO(bradfitz): longer if no options remain perhaps? ... Or longer
// overall but have dialRegion start overlapping races?
func (c *Client) dialNode(ctx context.Context, n *tailcfg.DERPNode) (net.Conn, error) {
- // First see if we need to use an HTTP proxy.
- proxyReq := &http.Request{
- Method: "GET", // doesn't really matter
- URL: &url.URL{
- Scheme: "https",
- Host: c.tlsServerName(n),
- Path: "/", // unused
- },
- }
- if proxyURL, err := tshttpproxy.ProxyFromEnvironment(proxyReq); err == nil && proxyURL != nil {
- return c.dialNodeUsingProxy(ctx, n, proxyURL)
- }
type res struct {
c net.Conn
@@ -827,71 +798,6 @@ func firstStr(a, b string) string {
return b
}
-// dialNodeUsingProxy connects to n using a CONNECT to the HTTP(s) proxy in proxyURL.
-func (c *Client) dialNodeUsingProxy(ctx context.Context, n *tailcfg.DERPNode, proxyURL *url.URL) (_ net.Conn, err error) {
- pu := proxyURL
- var proxyConn net.Conn
- if pu.Scheme == "https" {
- var d tls.Dialer
- proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "443")))
- } else {
- var d net.Dialer
- proxyConn, err = d.DialContext(ctx, "tcp", net.JoinHostPort(pu.Hostname(), firstStr(pu.Port(), "80")))
- }
- defer func() {
- if err != nil && proxyConn != nil {
- // In a goroutine in case it's a *tls.Conn (that can block on Close)
- // TODO(bradfitz): track the underlying tcp.Conn and just close that instead.
- go proxyConn.Close()
- }
- }()
- if err != nil {
- return nil, err
- }
-
- done := make(chan struct{})
- defer close(done)
- go func() {
- select {
- case <-done:
- return
- case <-ctx.Done():
- proxyConn.Close()
- }
- }()
-
- target := net.JoinHostPort(n.HostName, "443")
-
- var authHeader string
- if v, err := tshttpproxy.GetAuthHeader(pu); err != nil {
- c.logf("derphttp: error getting proxy auth header for %v: %v", proxyURL, err)
- } else if v != "" {
- authHeader = fmt.Sprintf("Proxy-Authorization: %s\r\n", v)
- }
-
- if _, err := fmt.Fprintf(proxyConn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n%s\r\n", target, target, authHeader); err != nil {
- if ctx.Err() != nil {
- return nil, ctx.Err()
- }
- return nil, err
- }
-
- br := bufio.NewReader(proxyConn)
- res, err := http.ReadResponse(br, nil)
- if err != nil {
- if ctx.Err() != nil {
- return nil, ctx.Err()
- }
- c.logf("derphttp: CONNECT dial to %s: %v", target, err)
- return nil, err
- }
- c.logf("derphttp: CONNECT dial to %s: %v", target, res.Status)
- if res.StatusCode != 200 {
- return nil, fmt.Errorf("invalid response status from HTTP proxy %s on CONNECT to %s: %v", pu, target, res.Status)
- }
- return proxyConn, nil
-}
-
func (c *Client) Send(dstKey key.NodePublic, b []byte) error {
client, _, err := c.connect(c.newContext(), "derphttp.Client.Send")
if err != nil {
diff --git a/derp/derphttp/derphttp_server.go b/derp/derphttp/derphttp_server.go
index 50aba774a..ea2b9ea8c 100644
--- a/derp/derphttp/derphttp_server.go
+++ b/derp/derphttp/derphttp_server.go
@@ -1,6 +1,8 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
+//go:build !ts_omit_derpserver
+
package derphttp
import (
@@ -12,12 +14,6 @@ import (
"tailscale.com/derp"
)
-// fastStartHeader is the header (with value "1") that signals to the HTTP
-// server that the DERP HTTP client does not want the HTTP 101 response
-// headers and it will begin writing & reading the DERP protocol immediately
-// following its HTTP request.
-const fastStartHeader = "Derp-Fast-Start"
-
// Handler returns an http.Handler to be mounted at /derp, serving s.
func Handler(s *derp.Server) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -42,7 +38,7 @@ func Handler(s *derp.Server) http.Handler {
return
}
- fastStart := r.Header.Get(fastStartHeader) == "1"
+ fastStart := r.Header.Get(derp.FastStartHeader) == "1"
h, ok := w.(http.Hijacker)
if !ok {
diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go
index 3e8f2f994..aef8c9dcd 100644
--- a/hostinfo/hostinfo.go
+++ b/hostinfo/hostinfo.go
@@ -24,7 +24,6 @@ import (
"tailscale.com/types/lazy"
"tailscale.com/types/opt"
"tailscale.com/types/ptr"
- "tailscale.com/util/cloudenv"
"tailscale.com/util/dnsname"
"tailscale.com/util/lineiter"
"tailscale.com/version"
@@ -63,7 +62,6 @@ func New() *tailcfg.Hostinfo {
GoVersion: runtime.Version(),
Machine: condCall(unameMachine),
DeviceModel: deviceModelCached(),
- Cloud: string(cloudenv.Get()),
NoLogsNoSupport: envknob.NoLogsNoSupport(),
AllowsUpdate: envknob.AllowsRemoteUpdate(),
}
diff --git a/net/dnscache/dnscache.go b/net/dnscache/dnscache.go
index d60e92f0b..3974b1320 100644
--- a/net/dnscache/dnscache.go
+++ b/net/dnscache/dnscache.go
@@ -21,7 +21,6 @@ import (
"tailscale.com/envknob"
"tailscale.com/net/netx"
"tailscale.com/types/logger"
- "tailscale.com/util/cloudenv"
"tailscale.com/util/singleflight"
"tailscale.com/util/slicesx"
"tailscale.com/util/testenv"
@@ -135,26 +134,6 @@ func (r *Resolver) dlogf(format string, args ...any) {
}
}
-// cloudHostResolver returns a Resolver for the current cloud hosting environment.
-// It currently only supports Google Cloud.
-func (r *Resolver) cloudHostResolver() (v *net.Resolver, ok bool) {
- switch runtime.GOOS {
- case "android", "ios", "darwin":
- return nil, false
- }
- ip := cloudenv.Get().ResolverIP()
- if ip == "" {
- return nil, false
- }
- return &net.Resolver{
- PreferGo: true,
- Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
- var d net.Dialer
- return d.DialContext(ctx, network, net.JoinHostPort(ip, "53"))
- },
- }, true
-}
-
func (r *Resolver) ttl() time.Duration {
if r.TTL > 0 {
return r.TTL
@@ -296,12 +275,6 @@ func (r *Resolver) lookupIP(ctx context.Context, host string) (ip, ip6 netip.Add
} else {
ips, err = r.fwd().LookupNetIP(lookupCtx, "ip", host)
}
- if err != nil || len(ips) == 0 {
- if resolver, ok := r.cloudHostResolver(); ok {
- r.dlogf("resolving %q via cloud resolver", host)
- ips, err = resolver.LookupNetIP(lookupCtx, "ip", host)
- }
- }
if (err != nil || len(ips) == 0) && r.LookupIPFallback != nil {
lookupCtx, lookupCancel := context.WithTimeout(ctx, 30*time.Second)
defer lookupCancel()
diff --git a/net/netmon/state.go b/net/netmon/state.go
index bd0960768..82d941fa0 100644
--- a/net/netmon/state.go
+++ b/net/netmon/state.go
@@ -7,7 +7,6 @@ import (
"bytes"
"fmt"
"net"
- "net/http"
"net/netip"
"runtime"
"slices"
@@ -18,7 +17,6 @@ import (
"tailscale.com/hostinfo"
"tailscale.com/net/netaddr"
"tailscale.com/net/tsaddr"
- "tailscale.com/net/tshttpproxy"
"tailscale.com/util/mak"
)
@@ -154,7 +152,7 @@ func (i Interface) Addrs() ([]net.Addr, error) {
if i.AltAddrs != nil {
return i.AltAddrs, nil
}
- return i.Interface.Addrs()
+ return nil, nil
}
// ForeachInterfaceAddress is a wrapper for GetList, then
@@ -502,13 +500,6 @@ func getState(optTSInterfaceName string) (*State, error) {
}
if s.AnyInterfaceUp() {
- req, err := http.NewRequest("GET", LoginEndpointForProxyDetermination, nil)
- if err != nil {
- return nil, err
- }
- if u, err := tshttpproxy.ProxyFromEnvironment(req); err == nil && u != nil {
- s.HTTPProxy = u.String()
- }
if getPAC != nil {
s.PAC = getPAC()
}
diff --git a/net/netns/netns.go b/net/netns/netns.go
index a473506fa..742e9167a 100644
--- a/net/netns/netns.go
+++ b/net/netns/netns.go
@@ -90,7 +90,7 @@ func FromDialer(logf logger.Logf, netMon *netmon.Monitor, d *net.Dialer) Dialer
if disabled.Load() {
return d
}
- d.Control = control(logf, netMon)
+ //d.Control = control(logf, netMon)
if wrapDialer != nil {
return wrapDialer(d)
}
diff --git a/net/tlsdial/tlsdial.go b/net/tlsdial/tlsdial.go
index 80f3bfc06..770900b24 100644
--- a/net/tlsdial/tlsdial.go
+++ b/net/tlsdial/tlsdial.go
@@ -28,10 +28,8 @@ import (
"tailscale.com/derp/derpconst"
"tailscale.com/envknob"
- "tailscale.com/health"
"tailscale.com/hostinfo"
"tailscale.com/net/bakedroots"
- "tailscale.com/net/tlsdial/blockblame"
)
var counterFallbackOK int32 // atomic
@@ -49,16 +47,6 @@ var debug = envknob.RegisterBool("TS_DEBUG_TLS_DIAL")
// Headscale, etc.
var tlsdialWarningPrinted sync.Map // map[string]bool
-var mitmBlockWarnable = health.Register(&health.Warnable{
- Code: "blockblame-mitm-detected",
- Title: "Network may be blocking Tailscale",
- Text: func(args health.Args) string {
- return fmt.Sprintf("Network equipment from %q may be blocking Tailscale traffic on this network. Connect to another network, or contact your network administrator for assistance.", args["manufacturer"])
- },
- Severity: health.SeverityMedium,
- ImpactsConnectivity: true,
-})
-
// Config returns a tls.Config for connecting to a server that
// uses system roots for validation but, if those fail, also tries
// the baked-in LetsEncrypt roots as a fallback validation method.
@@ -66,7 +54,7 @@ var mitmBlockWarnable = health.Register(&health.Warnable{
// If base is non-nil, it's cloned as the base config before
// being configured and returned.
// If ht is non-nil, it's used to report health errors.
-func Config(ht *health.Tracker, base *tls.Config) *tls.Config {
+func Config(ht any, base *tls.Config) *tls.Config {
var conf *tls.Config
if base == nil {
conf = new(tls.Config)
@@ -109,48 +97,6 @@ func Config(ht *health.Tracker, base *tls.Config) *tls.Config {
return nil
}
- // Perform some health checks on this certificate before we do
- // any verification.
- var cert *x509.Certificate
- var selfSignedIssuer string
- if certs := cs.PeerCertificates; len(certs) > 0 {
- cert = certs[0]
- if certIsSelfSigned(cert) {
- selfSignedIssuer = cert.Issuer.String()
- }
- }
- if ht != nil {
- defer func() {
- if retErr != nil && cert != nil {
- // Is it a MITM SSL certificate from a well-known network appliance manufacturer?
- // Show a dedicated warning.
- m, ok := blockblame.VerifyCertificate(cert)
- if ok {
- log.Printf("tlsdial: server cert seen while dialing %q looks like %q equipment (could be blocking Tailscale)", dialedHost, m.Name)
- ht.SetUnhealthy(mitmBlockWarnable, health.Args{"manufacturer": m.Name})
- } else {
- ht.SetHealthy(mitmBlockWarnable)
- }
- } else {
- ht.SetHealthy(mitmBlockWarnable)
- }
- if retErr != nil && selfSignedIssuer != "" {
- // Self-signed certs are never valid.
- //
- // TODO(bradfitz): plumb down the selfSignedIssuer as a
- // structured health warning argument.
- ht.SetTLSConnectionError(cs.ServerName, fmt.Errorf("likely intercepted connection; certificate is self-signed by %v", selfSignedIssuer))
- } else {
- // Ensure we clear any error state for this ServerName.
- ht.SetTLSConnectionError(cs.ServerName, nil)
- if selfSignedIssuer != "" {
- // Log the self-signed issuer, but don't treat it as an error.
- log.Printf("tlsdial: warning: server cert for %q passed x509 validation but is self-signed by %q", dialedHost, selfSignedIssuer)
- }
- }
- }()
- }
-
// First try doing x509 verification with the system's
// root CA pool.
opts := x509.VerifyOptions{
diff --git a/syncs/locked.go b/syncs/locked.go
deleted file mode 100644
index d2048665d..000000000
--- a/syncs/locked.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-package syncs
-
-import (
- "sync"
-)
-
-// AssertLocked panics if m is not locked.
-func AssertLocked(m *sync.Mutex) {
- if m.TryLock() {
- m.Unlock()
- panic("mutex is not locked")
- }
-}
-
-// AssertRLocked panics if rw is not locked for reading or writing.
-func AssertRLocked(rw *sync.RWMutex) {
- if rw.TryLock() {
- rw.Unlock()
- panic("mutex is not locked")
- }
-}
-
-// AssertWLocked panics if rw is not locked for writing.
-func AssertWLocked(rw *sync.RWMutex) {
- if rw.TryRLock() {
- rw.RUnlock()
- panic("mutex is not rlocked")
- }
-}
diff --git a/types/logger/rusage.go b/types/logger/rusage.go
index 3943636d6..7b728ee0b 100644
--- a/types/logger/rusage.go
+++ b/types/logger/rusage.go
@@ -15,7 +15,7 @@ func RusagePrefixLog(logf Logf) Logf {
return func(f string, argv ...any) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
- goMem := float64(m.HeapInuse+m.StackInuse) / (1 << 20)
+ goMem := float64(m.HeapInuse) / (1 << 20)
maxRSS := rusageMaxRSS()
pf := fmt.Sprintf("%.1fM/%.1fM %s", goMem, maxRSS, f)
logf(pf, argv...)
diff --git a/util/eventbus/debug.go b/util/eventbus/debug.go
index b6264f82f..107dd0280 100644
--- a/util/eventbus/debug.go
+++ b/util/eventbus/debug.go
@@ -10,8 +10,6 @@ import (
"slices"
"sync"
"sync/atomic"
-
- "tailscale.com/tsweb"
)
// A Debugger offers access to a bus's privileged introspection and
@@ -137,8 +135,6 @@ func (d *Debugger) SubscribeTypes(client *Client) []reflect.Type {
return client.subscribeTypes()
}
-func (d *Debugger) RegisterHTTP(td *tsweb.DebugHandler) { registerHTTPDebugger(d, td) }
-
// A hook collects hook functions that can be run as a group.
type hook[T any] struct {
sync.Mutex
diff --git a/util/eventbus/debughttp.go b/util/eventbus/debughttp.go
deleted file mode 100644
index a94eaa9cf..000000000
--- a/util/eventbus/debughttp.go
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-//go:build !ios && !android
-
-package eventbus
-
-import (
- "bytes"
- "cmp"
- "embed"
- "fmt"
- "html/template"
- "io"
- "io/fs"
- "log"
- "net/http"
- "path/filepath"
- "reflect"
- "slices"
- "strings"
- "sync"
-
- "github.com/coder/websocket"
- "tailscale.com/tsweb"
-)
-
-type httpDebugger struct {
- *Debugger
-}
-
-func registerHTTPDebugger(d *Debugger, td *tsweb.DebugHandler) {
- dh := httpDebugger{d}
- td.Handle("bus", "Event bus", dh)
- td.HandleSilent("bus/monitor", http.HandlerFunc(dh.serveMonitor))
- td.HandleSilent("bus/style.css", serveStatic("style.css"))
- td.HandleSilent("bus/htmx.min.js", serveStatic("htmx.min.js.gz"))
- td.HandleSilent("bus/htmx-websocket.min.js", serveStatic("htmx-websocket.min.js.gz"))
-}
-
-//go:embed assets/*.html
-var templatesSrc embed.FS
-
-var templates = sync.OnceValue(func() *template.Template {
- d, err := fs.Sub(templatesSrc, "assets")
- if err != nil {
- panic(fmt.Errorf("getting eventbus debughttp templates subdir: %w", err))
- }
- ret := template.New("").Funcs(map[string]any{
- "prettyPrintStruct": prettyPrintStruct,
- })
- return template.Must(ret.ParseFS(d, "*"))
-})
-
-//go:generate go run fetch-htmx.go
-
-//go:embed assets/*.css assets/*.min.js.gz
-var static embed.FS
-
-func serveStatic(name string) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- switch {
- case strings.HasSuffix(name, ".css"):
- w.Header().Set("Content-Type", "text/css")
- case strings.HasSuffix(name, ".min.js.gz"):
- w.Header().Set("Content-Type", "text/javascript")
- w.Header().Set("Content-Encoding", "gzip")
- case strings.HasSuffix(name, ".js"):
- w.Header().Set("Content-Type", "text/javascript")
- default:
- http.Error(w, "not found", http.StatusNotFound)
- return
- }
-
- f, err := static.Open(filepath.Join("assets", name))
- if err != nil {
- http.Error(w, fmt.Sprintf("opening asset: %v", err), http.StatusInternalServerError)
- return
- }
- defer f.Close()
- if _, err := io.Copy(w, f); err != nil {
- http.Error(w, fmt.Sprintf("serving asset: %v", err), http.StatusInternalServerError)
- return
- }
- })
-}
-
-func render(w http.ResponseWriter, name string, data any) {
- err := templates().ExecuteTemplate(w, name+".html", data)
- if err != nil {
- err := fmt.Errorf("rendering template: %v", err)
- log.Print(err)
- http.Error(w, err.Error(), http.StatusInternalServerError)
- }
-}
-
-func (h httpDebugger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- type clientInfo struct {
- *Client
- Publish []reflect.Type
- Subscribe []reflect.Type
- }
- type typeInfo struct {
- reflect.Type
- Publish []*Client
- Subscribe []*Client
- }
- type info struct {
- *Debugger
- Clients map[string]*clientInfo
- Types map[string]*typeInfo
- }
-
- data := info{
- Debugger: h.Debugger,
- Clients: map[string]*clientInfo{},
- Types: map[string]*typeInfo{},
- }
-
- getTypeInfo := func(t reflect.Type) *typeInfo {
- if data.Types[t.Name()] == nil {
- data.Types[t.Name()] = &typeInfo{
- Type: t,
- }
- }
- return data.Types[t.Name()]
- }
-
- for _, c := range h.Clients() {
- ci := &clientInfo{
- Client: c,
- Publish: h.PublishTypes(c),
- Subscribe: h.SubscribeTypes(c),
- }
- slices.SortFunc(ci.Publish, func(a, b reflect.Type) int { return cmp.Compare(a.Name(), b.Name()) })
- slices.SortFunc(ci.Subscribe, func(a, b reflect.Type) int { return cmp.Compare(a.Name(), b.Name()) })
- data.Clients[c.Name()] = ci
-
- for _, t := range ci.Publish {
- ti := getTypeInfo(t)
- ti.Publish = append(ti.Publish, c)
- }
- for _, t := range ci.Subscribe {
- ti := getTypeInfo(t)
- ti.Subscribe = append(ti.Subscribe, c)
- }
- }
-
- render(w, "main", data)
-}
-
-func (h httpDebugger) serveMonitor(w http.ResponseWriter, r *http.Request) {
- if r.Header.Get("Upgrade") == "websocket" {
- h.serveMonitorStream(w, r)
- return
- }
-
- render(w, "monitor", nil)
-}
-
-func (h httpDebugger) serveMonitorStream(w http.ResponseWriter, r *http.Request) {
- conn, err := websocket.Accept(w, r, nil)
- if err != nil {
- return
- }
- defer conn.CloseNow()
- wsCtx := conn.CloseRead(r.Context())
-
- mon := h.WatchBus()
- defer mon.Close()
-
- i := 0
- for {
- select {
- case <-r.Context().Done():
- return
- case <-wsCtx.Done():
- return
- case <-mon.Done():
- return
- case event := <-mon.Events():
- msg, err := conn.Writer(r.Context(), websocket.MessageText)
- if err != nil {
- return
- }
- data := map[string]any{
- "Count": i,
- "Type": reflect.TypeOf(event.Event),
- "Event": event,
- }
- i++
- if err := templates().ExecuteTemplate(msg, "event.html", data); err != nil {
- log.Println(err)
- return
- }
- if err := msg.Close(); err != nil {
- return
- }
- }
- }
-}
-
-func prettyPrintStruct(t reflect.Type) string {
- if t.Kind() != reflect.Struct {
- return t.String()
- }
- var rec func(io.Writer, int, reflect.Type)
- rec = func(out io.Writer, indent int, t reflect.Type) {
- ind := strings.Repeat(" ", indent)
- fmt.Fprintf(out, "%s", t.String())
- fs := collectFields(t)
- if len(fs) > 0 {
- io.WriteString(out, " {\n")
- for _, f := range fs {
- fmt.Fprintf(out, "%s %s ", ind, f.Name)
- if f.Type.Kind() == reflect.Struct {
- rec(out, indent+1, f.Type)
- } else {
- fmt.Fprint(out, f.Type)
- }
- io.WriteString(out, "\n")
- }
- fmt.Fprintf(out, "%s}", ind)
- }
- }
-
- var ret bytes.Buffer
- rec(&ret, 0, t)
- return ret.String()
-}
-
-func collectFields(t reflect.Type) (ret []reflect.StructField) {
- for _, f := range reflect.VisibleFields(t) {
- if !f.IsExported() {
- continue
- }
- ret = append(ret, f)
- }
- return ret
-}
diff --git a/util/eventbus/debughttp_off.go b/util/eventbus/debughttp_off.go
deleted file mode 100644
index 85330579c..000000000
--- a/util/eventbus/debughttp_off.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (c) Tailscale Inc & AUTHORS
-// SPDX-License-Identifier: BSD-3-Clause
-
-//go:build ios || android
-
-package eventbus
-
-import "tailscale.com/tsweb"
-
-func registerHTTPDebugger(d *Debugger, td *tsweb.DebugHandler) {
- // The event bus debugging UI uses html/template, which uses
- // reflection for method lookups. This forces the compiler to
- // retain a lot more code and information to make dynamic method
- // dispatch work, which is unacceptable bloat for the iOS build.
- // We also disable it on Android while we're at it, as nobody
- // is debugging Tailscale internals on Android.
- //
- // TODO: https://github.com/tailscale/tailscale/issues/15297 to
- // bring the debug UI back to iOS somehow.
-}