ipn/ipnlocal: add wireguard session state metrics + publish on IPN bus
Updates #19989 Updates tailscale/corp#42874 Change-Id: I843ed95bc7b0f5cd38ba1467332c6b022901e254 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
ae743642d9
commit
6596d237a3
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/control/controlknobs"
|
||||
"tailscale.com/drive"
|
||||
"tailscale.com/envknob"
|
||||
@@ -726,6 +727,29 @@ func (e *userspaceEngine) SetPeerByIPPacketFunc(fn func(netip.Addr) (_ key.NodeP
|
||||
})
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) SetPeerSessionStateFunc(fn func(key.NodePublic, PeerWireGuardState)) {
|
||||
e.wgdev.SetSessionStateFunc(func(pk device.NoisePublicKey, state device.PeerSessionState) {
|
||||
if fn != nil {
|
||||
fn(key.NodePublicFromRaw32(mem.B(pk[:])), peerWireGuardStateFromDevice(state))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func peerWireGuardStateFromDevice(state device.PeerSessionState) PeerWireGuardState {
|
||||
switch state {
|
||||
case device.PeerSessionNone:
|
||||
return PeerWireGuardStateNone
|
||||
case device.PeerSessionHandshake:
|
||||
return PeerWireGuardStateHandshake
|
||||
case device.PeerSessionEstablished:
|
||||
return PeerWireGuardStateEstablished
|
||||
case device.PeerSessionExpired:
|
||||
return PeerWireGuardStateExpired
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected wireguard-go PeerSessionState %d", state))
|
||||
}
|
||||
}
|
||||
|
||||
// hasOverlap checks if there is a IPPrefix which is common amongst the two
|
||||
// provided slices.
|
||||
func hasOverlap(aips, rips views.Slice[netip.Prefix]) bool {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"go4.org/mem"
|
||||
"tailscale.com/cmd/testwrapper/flakytest"
|
||||
"tailscale.com/control/controlknobs"
|
||||
@@ -35,6 +36,38 @@ import (
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
)
|
||||
|
||||
func TestPeerWireGuardStateValuesMatchWireguardGo(t *testing.T) {
|
||||
const unknownPeerSessionState device.PeerSessionState = 255
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
wg device.PeerSessionState
|
||||
want PeerWireGuardState
|
||||
}{
|
||||
{"none", device.PeerSessionNone, PeerWireGuardStateNone},
|
||||
{"handshake", device.PeerSessionHandshake, PeerWireGuardStateHandshake},
|
||||
{"established", device.PeerSessionEstablished, PeerWireGuardStateEstablished},
|
||||
{"expired", device.PeerSessionExpired, PeerWireGuardStateExpired},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := peerWireGuardStateFromDevice(tt.wg); got != tt.want {
|
||||
t.Fatalf("converted state = %v; want %v", got, tt.want)
|
||||
}
|
||||
if got, want := uint8(tt.wg), uint8(tt.want); got != want {
|
||||
t.Fatalf("wireguard-go const = %v; want %v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if recover() == nil {
|
||||
t.Fatal("expected panic for unknown wireguard-go state")
|
||||
}
|
||||
}()
|
||||
_ = peerWireGuardStateFromDevice(unknownPeerSessionState)
|
||||
}
|
||||
|
||||
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
|
||||
nv := make([]tailcfg.NodeView, len(v))
|
||||
for i, n := range v {
|
||||
|
||||
@@ -41,6 +41,28 @@ type StatusCallback func(*Status, error)
|
||||
// into network map updates.
|
||||
type NetworkMapCallback func(*netmap.NetworkMap)
|
||||
|
||||
// PeerWireGuardState is the current WireGuard session state for a peer.
|
||||
type PeerWireGuardState uint8
|
||||
|
||||
const (
|
||||
// PeerWireGuardStateNone means there is no handshake in progress and no
|
||||
// session key material retained for this peer.
|
||||
PeerWireGuardStateNone PeerWireGuardState = 0
|
||||
|
||||
// PeerWireGuardStateHandshake means a handshake is in progress for this
|
||||
// peer, but there is not currently a usable WireGuard session.
|
||||
PeerWireGuardStateHandshake PeerWireGuardState = 1
|
||||
|
||||
// PeerWireGuardStateEstablished means the peer has a completed WireGuard
|
||||
// session with usable session key material.
|
||||
PeerWireGuardStateEstablished PeerWireGuardState = 2
|
||||
|
||||
// PeerWireGuardStateExpired means the peer's session key material is no
|
||||
// longer considered usable, but final key cleanup or lazy peer removal may
|
||||
// not have happened yet.
|
||||
PeerWireGuardStateExpired PeerWireGuardState = 3
|
||||
)
|
||||
|
||||
// ErrNoChanges is returned by Engine.Reconfig if no changes were made.
|
||||
var ErrNoChanges = errors.New("no changes made to Engine config")
|
||||
|
||||
@@ -141,4 +163,20 @@ type Engine interface {
|
||||
// SetPeerByIPPacketFunc installs a callback used by wireguard-go to
|
||||
// look up which peer should handle an outbound packet by destination IP.
|
||||
SetPeerByIPPacketFunc(func(netip.Addr) (_ key.NodePublic, ok bool))
|
||||
|
||||
// SetPeerSessionStateFunc installs a callback used to observe WireGuard
|
||||
// peer session state transitions.
|
||||
//
|
||||
// Calls are serialized per Engine and delivered in transition order from
|
||||
// wireguard-go, while wireguard-go is holding locks. The callback must be
|
||||
// cheap and must not call back into wireguard-go.
|
||||
//
|
||||
// It does not replay current state. Callers that need a complete view should
|
||||
// set it before peers are started or lazily created, and maintain any
|
||||
// snapshots, sequence numbers, and pubsub state outside wireguard-go.
|
||||
//
|
||||
// In Tailscale, the usual implementation is
|
||||
// ipnlocal.LocalBackend.onPeerWireGuardState, installed early in
|
||||
// LocalBackend construction.
|
||||
SetPeerSessionStateFunc(func(key.NodePublic, PeerWireGuardState))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user