multiport: say when lanes are on and what was negotiated

Every multiport log line so far was a reason it turned itself off, so a node
running it looked identical to one that never tried. Add the other half:

- an Info at startup with the lane count and port range
- the negotiated lanes on both "Handshake message received" lines, which
  required allocating the lane set before the log rather than after; it still
  lands before CheckAndComplete/Complete, which was the ordering requirement
- multiport.lanes.up and multiport.lanes.tunnels gauges

The lane field is a slog.Attr so it vanishes on a node without multiport, and
reads zeros when we offered lanes and the peer had none - the case an operator
is actually looking for.

The gauges walk the hostmap instead of keeping a counter at promote/demote: a
tunnel torn down while its lanes are up never demotes them, so a counter would
drift upward forever. Only a multiport node pays for the walk.
This commit is contained in:
Wade Simmons
2026-09-02 16:38:49 -04:00
parent e3efe0c53d
commit a304f370f4
5 changed files with 100 additions and 5 deletions
+12
View File
@@ -706,11 +706,23 @@ func (f *Interface) emitStats(ctx context.Context, i time.Duration) {
certInitiatingVersion := metrics.GetOrRegisterGauge("certificate.initiating_version", nil)
certMaxVersion := metrics.GetOrRegisterGauge("certificate.max_version", nil)
// Registered only when we run multiport, so these don't sit at zero on a node
// that was never going to have a lane and read as a broken feature.
var lanesUpGauge, laneTunnelsGauge metrics.Gauge
if f.multiport && f.laneCount > 1 {
lanesUpGauge = metrics.GetOrRegisterGauge("multiport.lanes.up", nil)
laneTunnelsGauge = metrics.GetOrRegisterGauge("multiport.lanes.tunnels", nil)
}
emit := func() {
f.firewall.EmitStats()
f.handshakeManager.EmitStats()
udpStats()
if lanesUpGauge != nil {
f.emitLaneStats(lanesUpGauge, laneTunnelsGauge)
}
certState := f.pki.getCertState()
defaultCrt := certState.GetDefaultCertificate()
certExpirationGauge.Update(int64(defaultCrt.NotAfter().Sub(time.Now()) / time.Second))