mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-27 20:19:31 +00:00
client, cmd/tailscale/cli, feature/relayserver, net/udprelay: implement tailscale debug peer-relay-sessions (#17239)
Fixes tailscale/corp#30035 Signed-off-by: Dylan Bargatze <dylan@tailscale.com> Signed-off-by: Jordan Whited <jordan@tailscale.com> Co-authored-by: Dylan Bargatze <dylan@tailscale.com>
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
package relayserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -16,8 +19,10 @@ import (
|
||||
"tailscale.com/feature"
|
||||
"tailscale.com/ipn"
|
||||
"tailscale.com/ipn/ipnext"
|
||||
"tailscale.com/ipn/localapi"
|
||||
"tailscale.com/net/udprelay"
|
||||
"tailscale.com/net/udprelay/endpoint"
|
||||
"tailscale.com/net/udprelay/status"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -33,6 +38,32 @@ const featureName = "relayserver"
|
||||
func init() {
|
||||
feature.Register(featureName)
|
||||
ipnext.RegisterExtension(featureName, newExtension)
|
||||
localapi.Register("debug-peer-relay-sessions", servePeerRelayDebugSessions)
|
||||
}
|
||||
|
||||
// servePeerRelayDebugSessions is an HTTP handler for the Local API that
|
||||
// returns debug/status information for peer relay sessions being relayed by
|
||||
// this Tailscale node. It writes a JSON-encoded [status.ServerStatus] into the
|
||||
// HTTP response, or returns an HTTP 405/500 with error text as the body.
|
||||
func servePeerRelayDebugSessions(h *localapi.Handler, w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
http.Error(w, "GET required", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var e *extension
|
||||
if ok := h.LocalBackend().FindMatchingExtension(&e); !ok {
|
||||
http.Error(w, "peer relay server extension unavailable", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
st := e.serverStatus()
|
||||
j, err := json.Marshal(st)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to marshal json: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Write(j)
|
||||
}
|
||||
|
||||
// newExtension is an [ipnext.NewExtensionFn] that creates a new relay server
|
||||
@@ -53,16 +84,18 @@ type extension struct {
|
||||
|
||||
mu sync.Mutex // guards the following fields
|
||||
shutdown bool
|
||||
port *int // ipn.Prefs.RelayServerPort, nil if disabled
|
||||
disconnectFromBusCh chan struct{} // non-nil if consumeEventbusTopics is running, closed to signal it to return
|
||||
busDoneCh chan struct{} // non-nil if consumeEventbusTopics is running, closed when it returns
|
||||
hasNodeAttrDisableRelayServer bool // tailcfg.NodeAttrDisableRelayServer
|
||||
port *int // ipn.Prefs.RelayServerPort, nil if disabled
|
||||
disconnectFromBusCh chan struct{} // non-nil if consumeEventbusTopics is running, closed to signal it to return
|
||||
busDoneCh chan struct{} // non-nil if consumeEventbusTopics is running, closed when it returns
|
||||
debugSessionsCh chan chan []status.ServerSession // non-nil if consumeEventbusTopics is running
|
||||
hasNodeAttrDisableRelayServer bool // tailcfg.NodeAttrDisableRelayServer
|
||||
}
|
||||
|
||||
// relayServer is the interface of [udprelay.Server].
|
||||
type relayServer interface {
|
||||
AllocateEndpoint(discoA key.DiscoPublic, discoB key.DiscoPublic) (endpoint.ServerEndpoint, error)
|
||||
Close() error
|
||||
GetSessions() []status.ServerSession
|
||||
}
|
||||
|
||||
// Name implements [ipnext.Extension].
|
||||
@@ -93,6 +126,7 @@ func (e *extension) handleBusLifetimeLocked() {
|
||||
port := *e.port
|
||||
e.disconnectFromBusCh = make(chan struct{})
|
||||
e.busDoneCh = make(chan struct{})
|
||||
e.debugSessionsCh = make(chan chan []status.ServerSession)
|
||||
go e.consumeEventbusTopics(port)
|
||||
}
|
||||
|
||||
@@ -139,6 +173,11 @@ var overrideAddrs = sync.OnceValue(func() (ret []netip.Addr) {
|
||||
return
|
||||
})
|
||||
|
||||
// consumeEventbusTopics serves endpoint allocation requests over the eventbus.
|
||||
// It also serves [relayServer] debug information on a channel.
|
||||
// consumeEventbusTopics must never acquire [extension.mu], which can be held by
|
||||
// other goroutines while waiting to receive on [extension.busDoneCh] or the
|
||||
// inner [extension.debugSessionsCh] channel.
|
||||
func (e *extension) consumeEventbusTopics(port int) {
|
||||
defer close(e.busDoneCh)
|
||||
|
||||
@@ -159,6 +198,14 @@ func (e *extension) consumeEventbusTopics(port int) {
|
||||
return
|
||||
case <-eventClient.Done():
|
||||
return
|
||||
case respCh := <-e.debugSessionsCh:
|
||||
if rs == nil {
|
||||
// Don't initialize the server simply for a debug request.
|
||||
respCh <- nil
|
||||
continue
|
||||
}
|
||||
sessions := rs.GetSessions()
|
||||
respCh <- sessions
|
||||
case req := <-reqSub.Events():
|
||||
if rs == nil {
|
||||
var err error
|
||||
@@ -199,6 +246,7 @@ func (e *extension) disconnectFromBusLocked() {
|
||||
<-e.busDoneCh
|
||||
e.busDoneCh = nil
|
||||
e.disconnectFromBusCh = nil
|
||||
e.debugSessionsCh = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,3 +258,30 @@ func (e *extension) Shutdown() error {
|
||||
e.shutdown = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// serverStatus gathers and returns current peer relay server status information
|
||||
// for this Tailscale node, and status of each peer relay session this node is
|
||||
// relaying (if any).
|
||||
func (e *extension) serverStatus() status.ServerStatus {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
st := status.ServerStatus{
|
||||
UDPPort: nil,
|
||||
Sessions: nil,
|
||||
}
|
||||
if e.port == nil || e.busDoneCh == nil {
|
||||
return st
|
||||
}
|
||||
st.UDPPort = ptr.To(*e.port)
|
||||
|
||||
ch := make(chan []status.ServerSession)
|
||||
select {
|
||||
case e.debugSessionsCh <- ch:
|
||||
resp := <-ch
|
||||
st.Sessions = resp
|
||||
return st
|
||||
case <-e.busDoneCh:
|
||||
return st
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user