summaryrefslogtreecommitdiffhomepage
path: root/feature/relayserver/relayserver.go
diff options
context:
space:
mode:
Diffstat (limited to 'feature/relayserver/relayserver.go')
-rw-r--r--feature/relayserver/relayserver.go134
1 files changed, 134 insertions, 0 deletions
diff --git a/feature/relayserver/relayserver.go b/feature/relayserver/relayserver.go
index b90a62345..93481080e 100644
--- a/feature/relayserver/relayserver.go
+++ b/feature/relayserver/relayserver.go
@@ -6,14 +6,19 @@
package relayserver
import (
+ "encoding/json"
+ "fmt"
+ "net/http"
"sync"
"tailscale.com/disco"
"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"
@@ -29,6 +34,37 @@ 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, err := e.status()
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to retrieve peer relay server status: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ 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
@@ -59,6 +95,27 @@ type extension struct {
type relayServer interface {
AllocateEndpoint(discoA key.DiscoPublic, discoB key.DiscoPublic) (endpoint.ServerEndpoint, error)
Close() error
+ GetSessions() ([]status.ServerSession, error)
+}
+
+// PeerRelaySessionsReq is an empty event bus message type, used to send an
+// async request for peer relay status information to the peer relay server's
+// event loop. The server should respond with a [PeerRelaySessionsResp] via the
+// event bus.
+type PeerRelaySessionsReq struct{}
+
+// PeerRelaySessionsResp is an event bus message type containing peer relay
+// status information. Sent by the peer relay server in response to a
+// [PeerRelaySessionsReq] message.
+type PeerRelaySessionsResp struct {
+ // Status is the current status/config of the peer relay server and all of
+ // its peer relay sessions (if any). May be the zero value if Error is
+ // populated.
+ Status status.ServerStatus
+ // Error contains any error generated by the peer relay server while trying
+ // to gather status; it may or may not be populated regardless of whether
+ // the Status field is valid.
+ Error error
}
// Name implements [ipnext.Extension].
@@ -119,6 +176,8 @@ func (e *extension) consumeEventbusTopics(port int) {
defer close(e.busDoneCh)
eventClient := e.bus.Client("relayserver.extension")
+ debugReqSub := eventbus.Subscribe[PeerRelaySessionsReq](eventClient)
+ debugRespPub := eventbus.Publish[PeerRelaySessionsResp](eventClient)
reqSub := eventbus.Subscribe[magicsock.UDPRelayAllocReq](eventClient)
respPub := eventbus.Publish[magicsock.UDPRelayAllocResp](eventClient)
defer eventClient.Close()
@@ -137,6 +196,32 @@ func (e *extension) consumeEventbusTopics(port int) {
// If reqSub is done, the eventClient has been closed, which is a
// signal to return.
return
+ case <-debugReqSub.Events():
+ st := status.ServerStatus{
+ State: status.Uninitialized,
+ UDPPort: port,
+ Sessions: nil,
+ }
+ if rs == nil {
+ // Don't initialize the server simply for a debug request;
+ // return the status as-is.
+ resp := PeerRelaySessionsResp{st, nil}
+ debugRespPub.Publish(resp)
+ continue
+ }
+ // We know the server is [status.Running] because rs != nil, which
+ // can only be the case if the port is configured and peer relaying
+ // isn't disabled by node attribute.
+ st.State = status.Running
+ sessions, err := rs.GetSessions()
+ if err != nil {
+ prs_err := fmt.Errorf("error retrieving peer relay sessions: %v", err)
+ e.logf(prs_err.Error())
+ debugRespPub.Publish(PeerRelaySessionsResp{Error: prs_err})
+ continue
+ }
+ st.Sessions = sessions
+ debugRespPub.Publish(PeerRelaySessionsResp{st, nil})
case req := <-reqSub.Events():
if rs == nil {
var err error
@@ -188,3 +273,52 @@ func (e *extension) Shutdown() error {
e.shutdown = true
return nil
}
+
+// status gathers and returns current peer relay server status information for
+// this Tailscale node, including if this node is disabled/not configured as a
+// peer relay server, and status of each peer relay session this node is
+// relaying (if any).
+func (e *extension) status() (status.ServerStatus, error) {
+ st := status.ServerStatus{
+ State: status.Uninitialized,
+ UDPPort: -1,
+ Sessions: nil,
+ }
+
+ e.mu.Lock()
+ running := e.busDoneCh != nil
+ shutdown := e.shutdown
+ port := e.port
+ disabled := e.hasNodeAttrDisableRelayServer
+ e.mu.Unlock()
+
+ if port == nil {
+ st.State = status.NotConfigured
+ return st, nil
+ }
+
+ st.UDPPort = *port
+ if disabled {
+ st.State = status.Disabled
+ return st, nil
+ }
+
+ if shutdown {
+ st.State = status.ShutDown
+ return st, nil
+ }
+
+ if !running {
+ // Leave state as Uninitialized.
+ return st, nil
+ }
+
+ client := e.bus.Client("relayserver.debug-peer-relay-sessions")
+ defer client.Close()
+ debugReqPub := eventbus.Publish[PeerRelaySessionsReq](client)
+ debugRespSub := eventbus.Subscribe[PeerRelaySessionsResp](client)
+
+ debugReqPub.Publish(PeerRelaySessionsReq{})
+ resp := <-debugRespSub.Events()
+ return resp.Status, resp.Error
+}