net/udprelay: add tailscaled_peer_relay_endpoints gauge (#18265)

New gauge reflects endpoints state via labels:
- open, when both peers are connected and ready to talk, and
- connecting. when at least one peer hasn't connected yet.

Corresponding client metrics are logged as
- udprelay_endpoints_connecting
- udprelay_endpoints_open

Updates tailscale/corp#30820

Change-Id: Idb1baa90a38c97847e14f9b2390093262ad0ea23

Signed-off-by: Alex Valiushko <alexvaliushko@tailscale.com>
This commit is contained in:
Alex Valiushko
2026-01-21 21:55:37 -08:00
committed by GitHub
parent 6dc0bd834c
commit 4b7585df77
4 changed files with 258 additions and 24 deletions
+74
View File
@@ -8,6 +8,7 @@ import (
"crypto/rand"
"net"
"net/netip"
"sync"
"testing"
"time"
@@ -21,6 +22,7 @@ import (
"tailscale.com/tstime/mono"
"tailscale.com/types/key"
"tailscale.com/types/views"
"tailscale.com/util/mak"
"tailscale.com/util/usermetric"
)
@@ -471,3 +473,75 @@ func TestServer_maybeRotateMACSecretLocked(t *testing.T) {
qt.Assert(t, macSecret, qt.Not(qt.Equals), s.macSecrets.At(1))
qt.Assert(t, s.macSecrets.At(0), qt.Not(qt.Equals), s.macSecrets.At(1))
}
func TestServer_endpointGC(t *testing.T) {
for _, tc := range []struct {
name string
addrs [2]netip.AddrPort
lastSeen [2]mono.Time
allocatedAt mono.Time
wantRemoved bool
}{
{
name: "unbound_endpoint_expired",
allocatedAt: mono.Now().Add(-2 * defaultBindLifetime),
wantRemoved: true,
},
{
name: "unbound_endpoint_kept",
allocatedAt: mono.Now(),
wantRemoved: false,
},
{
name: "bound_endpoint_expired_a",
addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")},
lastSeen: [2]mono.Time{mono.Now().Add(-2 * defaultSteadyStateLifetime), mono.Now()},
wantRemoved: true,
},
{
name: "bound_endpoint_expired_b",
addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")},
lastSeen: [2]mono.Time{mono.Now(), mono.Now().Add(-2 * defaultSteadyStateLifetime)},
wantRemoved: true,
},
{
name: "bound_endpoint_kept",
addrs: [2]netip.AddrPort{netip.MustParseAddrPort("192.0.2.1:1"), netip.MustParseAddrPort("192.0.2.2:1")},
lastSeen: [2]mono.Time{mono.Now(), mono.Now()},
wantRemoved: false,
},
} {
t.Run(tc.name, func(t *testing.T) {
disco1 := key.NewDisco()
disco2 := key.NewDisco()
pair := key.NewSortedPairOfDiscoPublic(disco1.Public(), disco2.Public())
ep := &serverEndpoint{
discoPubKeys: pair,
vni: 1,
lastSeen: tc.lastSeen,
boundAddrPorts: tc.addrs,
allocatedAt: tc.allocatedAt,
}
s := &Server{serverEndpointByVNI: sync.Map{}, metrics: &metrics{}}
mak.Set(&s.serverEndpointByDisco, pair, ep)
s.serverEndpointByVNI.Store(ep.vni, ep)
s.endpointGC(defaultBindLifetime, defaultSteadyStateLifetime)
removed := len(s.serverEndpointByDisco) > 0
if tc.wantRemoved {
if removed {
t.Errorf("expected endpoint to be removed from Server")
}
if !ep.closed {
t.Errorf("expected endpoint to be closed")
}
} else {
if !removed {
t.Errorf("expected endpoint to remain in Server")
}
if ep.closed {
t.Errorf("expected endpoint to remain open")
}
}
})
}
}