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 committed 2026-09-02 16:38:49 -04:00
1 parent e3efe0c53d
commit a304f370f4
5 files changed
+100 -5

No files matched your search

+14 -5
View File
@@ -811,6 +811,10 @@ func (hm *HandshakeManager) beginHandshake(via ViaSender, packet []byte, h *head
},
}
// Lanes are allocated before the log line so it can report what was actually
// negotiated, and must in any case be in place before CheckAndComplete below.
hm.maybeAllocLanes(hostinfo, result)
msg := "Handshake message received"
if !anyVpnAddrsInCommon {
msg = "Handshake message received, but no vpnNetworks in common."
@@ -825,6 +829,7 @@ func (hm *HandshakeManager) beginHandshake(via ViaSender, packet []byte, h *head
"initiatorIndex", result.RemoteIndex,
"responderIndex", result.LocalIndex,
"handshake", m{"stage": uint64(machine.MessageIndex()), "style": header.SubTypeName(header.Handshake, machine.Subtype())},
laneLogAttr(hm.config.laneCount, hostinfo.lanes),
)
// packet aliases the listener's incoming buffer, so this copy must stay.
@@ -841,7 +846,6 @@ func (hm *HandshakeManager) beginHandshake(via ViaSender, packet []byte, h *head
hostinfo.SetRemote(via.UdpAddr)
}
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
hm.maybeAllocLanes(hostinfo, result)
existing, err := hm.CheckAndComplete(hostinfo, handshakePacketStage0, f)
if err != nil {
@@ -1000,6 +1004,14 @@ func (hm *HandshakeManager) continueHandshake(via ViaSender, hh *HandshakeHostIn
}
duration := time.Since(hh.startTime).Nanoseconds()
hostinfo.vpnAddrs = vpnAddrs
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
// Lanes are allocated before the log line so it can report what was actually
// negotiated, and must in any case be in place before Complete below.
hm.maybeAllocLanes(hostinfo, result)
msg := "Handshake message received"
if !anyVpnAddrsInCommon {
msg = "Handshake message received, but no vpnNetworks in common."
@@ -1016,12 +1028,9 @@ func (hm *HandshakeManager) continueHandshake(via ViaSender, hh *HandshakeHostIn
"handshake", m{"stage": uint64(machine.MessageIndex()), "style": header.SubTypeName(header.Handshake, machine.Subtype())},
"durationNs", duration,
"sentCachedPackets", len(hh.packetStore),
laneLogAttr(hm.config.laneCount, hostinfo.lanes),
)
hostinfo.vpnAddrs = vpnAddrs
hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
hm.maybeAllocLanes(hostinfo, result)
hm.Complete(hostinfo, f)
if len(hh.packetStore) > 0 {