mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-29 07:09:33 +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:
@@ -31,6 +31,7 @@ import (
|
||||
"tailscale.com/net/sockopts"
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/net/udprelay/endpoint"
|
||||
"tailscale.com/net/udprelay/status"
|
||||
"tailscale.com/tstime"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/logger"
|
||||
@@ -95,6 +96,8 @@ type serverEndpoint struct {
|
||||
boundAddrPorts [2]netip.AddrPort // or zero value if a handshake has never completed for that relay leg
|
||||
lastSeen [2]time.Time // TODO(jwhited): consider using mono.Time
|
||||
challenge [2][disco.BindUDPRelayChallengeLen]byte
|
||||
packetsRx [2]uint64 // num packets received from/sent by each client after they are bound
|
||||
bytesRx [2]uint64 // num bytes received from/sent by each client after they are bound
|
||||
|
||||
lamportID uint64
|
||||
vni uint32
|
||||
@@ -223,9 +226,13 @@ func (e *serverEndpoint) handlePacket(from netip.AddrPort, gh packet.GeneveHeade
|
||||
switch {
|
||||
case from == e.boundAddrPorts[0]:
|
||||
e.lastSeen[0] = time.Now()
|
||||
e.packetsRx[0]++
|
||||
e.bytesRx[0] += uint64(len(b))
|
||||
return b, e.boundAddrPorts[1]
|
||||
case from == e.boundAddrPorts[1]:
|
||||
e.lastSeen[1] = time.Now()
|
||||
e.packetsRx[1]++
|
||||
e.bytesRx[1] += uint64(len(b))
|
||||
return b, e.boundAddrPorts[0]
|
||||
default:
|
||||
// unrecognized source
|
||||
@@ -782,3 +789,41 @@ func (s *Server) AllocateEndpoint(discoA, discoB key.DiscoPublic) (endpoint.Serv
|
||||
SteadyStateLifetime: tstime.GoDuration{Duration: s.steadyStateLifetime},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// extractClientInfo constructs a [status.ClientInfo] for one of the two peer
|
||||
// relay clients involved in this session.
|
||||
func extractClientInfo(idx int, ep *serverEndpoint) status.ClientInfo {
|
||||
if idx != 0 && idx != 1 {
|
||||
panic(fmt.Sprintf("idx passed to extractClientInfo() must be 0 or 1; got %d", idx))
|
||||
}
|
||||
|
||||
return status.ClientInfo{
|
||||
Endpoint: ep.boundAddrPorts[idx],
|
||||
ShortDisco: ep.discoPubKeys.Get()[idx].ShortString(),
|
||||
PacketsTx: ep.packetsRx[idx],
|
||||
BytesTx: ep.bytesRx[idx],
|
||||
}
|
||||
}
|
||||
|
||||
// GetSessions returns a slice of peer relay session statuses, with each
|
||||
// entry containing detailed info about the server and clients involved in
|
||||
// each session. This information is intended for debugging/status UX, and
|
||||
// should not be relied on for any purpose outside of that.
|
||||
func (s *Server) GetSessions() []status.ServerSession {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.closed {
|
||||
return nil
|
||||
}
|
||||
var sessions = make([]status.ServerSession, 0, len(s.byDisco))
|
||||
for _, se := range s.byDisco {
|
||||
c1 := extractClientInfo(0, se)
|
||||
c2 := extractClientInfo(1, se)
|
||||
sessions = append(sessions, status.ServerSession{
|
||||
VNI: se.vni,
|
||||
Client1: c1,
|
||||
Client2: c2,
|
||||
})
|
||||
}
|
||||
return sessions
|
||||
}
|
||||
|
||||
75
net/udprelay/status/status.go
Normal file
75
net/udprelay/status/status.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Package status contains types relating to the status of peer relay sessions
|
||||
// between peer relay client nodes via a peer relay server.
|
||||
package status
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
// ServerStatus contains the listening UDP port and active sessions (if any) for
|
||||
// this node's peer relay server at a point in time.
|
||||
type ServerStatus struct {
|
||||
// UDPPort is the UDP port number that the peer relay server forwards over,
|
||||
// as configured by the user with 'tailscale set --relay-server-port=<PORT>'.
|
||||
// If the port has not been configured, UDPPort will be nil.
|
||||
UDPPort *int
|
||||
// Sessions is a slice of detailed status information about each peer
|
||||
// relay session that this node's peer relay server is involved with. It
|
||||
// may be empty.
|
||||
Sessions []ServerSession
|
||||
}
|
||||
|
||||
// ClientInfo contains status-related information about a single peer relay
|
||||
// client involved in a single peer relay session.
|
||||
type ClientInfo struct {
|
||||
// Endpoint is the [netip.AddrPort] of this peer relay client's underlay
|
||||
// endpoint participating in the session, or a zero value if the client
|
||||
// has not completed a handshake.
|
||||
Endpoint netip.AddrPort
|
||||
// ShortDisco is a string representation of this peer relay client's disco
|
||||
// public key.
|
||||
//
|
||||
// TODO: disco keys are pretty meaningless to end users, and they are also
|
||||
// ephemeral. We really need node keys (or translation to first ts addr),
|
||||
// but those are not fully plumbed into the [udprelay.Server]. Disco keys
|
||||
// can also be ambiguous to a node key, but we could add node key into a
|
||||
// [disco.AllocateUDPRelayEndpointRequest] in similar fashion to
|
||||
// [disco.Ping]. There's also the problem of netmap trimming, where we
|
||||
// can't verify a node key maps to a disco key.
|
||||
ShortDisco string
|
||||
// PacketsTx is the number of packets this peer relay client has sent to
|
||||
// the other client via the relay server after completing a handshake. This
|
||||
// is identical to the number of packets that the peer relay server has
|
||||
// received from this client.
|
||||
PacketsTx uint64
|
||||
// BytesTx is the total overlay bytes this peer relay client has sent to
|
||||
// the other client via the relay server after completing a handshake. This
|
||||
// is identical to the total overlay bytes that the peer relay server has
|
||||
// received from this client.
|
||||
BytesTx uint64
|
||||
}
|
||||
|
||||
// ServerSession contains status information for a single session between two
|
||||
// peer relay clients, which are relayed via one peer relay server. This is the
|
||||
// status as seen by the peer relay server; each client node may have a
|
||||
// different view of the session's current status based on connectivity and
|
||||
// where the client is in the peer relay endpoint setup (allocation, binding,
|
||||
// pinging, active).
|
||||
type ServerSession struct {
|
||||
// VNI is the Virtual Network Identifier for this peer relay session, which
|
||||
// comes from the Geneve header and is unique to this session.
|
||||
VNI uint32
|
||||
// Client1 contains status information about one of the two peer relay
|
||||
// clients involved in this session. Note that 'Client1' does NOT mean this
|
||||
// was/wasn't the allocating client, or the first client to bind, etc; this
|
||||
// is just one client of two.
|
||||
Client1 ClientInfo
|
||||
// Client2 contains status information about one of the two peer relay
|
||||
// clients involved in this session. Note that 'Client2' does NOT mean this
|
||||
// was/wasn't the allocating client, or the second client to bind, etc; this
|
||||
// is just one client of two.
|
||||
Client2 ClientInfo
|
||||
}
|
||||
Reference in New Issue
Block a user