mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-23 05:52:09 -04:00
Add a debug facility that reports, per peer, the active dataplane endpoint state as seen by both magicsock and wireguard-go: magicsock's bestAddr (including relay VNI), whether it is currently trusted, the DERP fallback addr/home region, last send/recv (any and WireGuard-only) activity, full-ping time, WireGuard-only/expired flags, and peer map epAddr counts; plus whether a (lazily-created) wireguard-go peer currently exists, the endpoint it currently holds (string form plus a classification by concrete type: magicsock-managed vs other), and last handshake time and rx/tx byte counters. wireguard-go can end up holding a stale magicsock lazy endpoint as a peer's TX endpoint, silently black-holing traffic (#20082). Debugging that class of issue previously required manual log spelunking. This surfaces the relevant state via a LocalAPI debug endpoint (debug-active-endpoints), a c2n endpoint (/debug/active-endpoints), and a CLI command (tailscale debug active-endpoints), so both humans and tests can assert a client is in the expected dataplane state. The wireguard-go side is read via device.LookupActivePeer and wgint (which gains a mutex-guarded accessor for the peer's current conn.Endpoint, layout-checked like its existing stats accessors) rather than the UAPI text protocol, which we deliberately moved off of. Endpoint classification is a type assertion via a new magicsock.IsMagicsockEndpoint helper instead of string comparison. Updates #20082 Change-Id: Id39cf6fa8255584d0316b44e0a18bf26dc2769a5 Signed-off-by: James Tucker <james@tailscale.com>
57 lines
1.7 KiB
Go
57 lines
1.7 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package wgengine
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"tailscale.com/ipn/ipnstate"
|
|
"tailscale.com/wgengine/magicsock"
|
|
"tailscale.com/wgengine/wgint"
|
|
)
|
|
|
|
// DebugActiveEndpoints implements [Engine].
|
|
//
|
|
// The set of peers is enumerated from magicsock's peer map (which mirrors the
|
|
// netmap); for each, the corresponding wireguard-go peer is looked up without
|
|
// triggering wireguard-go's lazy on-demand peer creation. wgcfg.ReconfigDevice
|
|
// keeps the device's peers a subset of the configured (netmap) peers, so this
|
|
// covers every wireguard-go peer that can exist.
|
|
func (e *userspaceEngine) DebugActiveEndpoints() (*ipnstate.DebugActiveEndpoints, error) {
|
|
peers := e.magicConn.DebugActiveEndpoints()
|
|
|
|
e.wgLock.Lock()
|
|
dev := e.wgdev
|
|
e.wgLock.Unlock()
|
|
if dev == nil {
|
|
return nil, errors.New("no wireguard-go device")
|
|
}
|
|
for i := range peers {
|
|
p := &peers[i]
|
|
// Use LookupActivePeer (not LookupPeer) to avoid lazily
|
|
// creating a wireguard-go peer for every netmap peer; absence
|
|
// is itself the signal reported by a nil WireGuard field.
|
|
wgp, ok := dev.LookupActivePeer(p.NodeKey.Raw32())
|
|
if !ok {
|
|
continue
|
|
}
|
|
wp := wgint.PeerOf(wgp)
|
|
st := &ipnstate.WireGuardPeerState{
|
|
LastHandshake: wp.LastHandshake(),
|
|
RxBytes: wp.RxBytes(),
|
|
TxBytes: wp.TxBytes(),
|
|
}
|
|
if ep := wp.Endpoint(); ep != nil {
|
|
st.Endpoint = ep.DstToString()
|
|
if magicsock.IsMagicsockEndpoint(ep) {
|
|
st.EndpointType = ipnstate.WireGuardEndpointTypeMagicsock
|
|
} else {
|
|
st.EndpointType = ipnstate.WireGuardEndpointTypeOther
|
|
}
|
|
}
|
|
p.WireGuard = st
|
|
}
|
|
return &ipnstate.DebugActiveEndpoints{Peers: peers}, nil
|
|
}
|