wgengine,wgcfg,feature/netlog: move network flow logging behind a feature hook

wgcfg.Config.NetworkLogging carried the network flow logging identity
inside the WireGuard config, where it was unrelated to WireGuard; it
lived there mainly so that identity changes would defeat Reconfig's
ErrNoChanges check and reach the netlog startup/shutdown logic.

Remove the field and move the whole netlog lifecycle into a new
feature/netlog package, installed on the engine via the new
wgengine.HookNewNetLogger hook, like other feature/* packages. The
logging identity now comes from LocalBackend's current netmap via the
widened NetLogSource interface (replacing Engine.SetNetLogNodeSource),
so nmcfg no longer parses audit log IDs into the config. The engine
still calls the hook before its ErrNoChanges return and before
router.Set (to capture initial packets), and again after router.Set
(to capture final packets), preserving the previous ordering.

Core wgengine no longer imports wgengine/netlog, so minimal builds
drop it entirely. tailscaled keeps netlog via feature/condregister,
and tsnet imports feature/condregister/netlog explicitly to keep
netlog enabled by default in tsnet-based binaries (tsidp,
k8s-operator).

This is pulled out of a future change that removes wgcfg.Config.Peers,
to make that PR smaller.

Updates #12542
Updates #12614

Change-Id: I41ca7dfe43c51e977c41b5f8e934bd1f0e6e6e24
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-07-09 12:56:37 -07:00
committed by Brad Fitzpatrick
parent b7de1753b7
commit 692f84df8d
20 changed files with 329 additions and 129 deletions
+42 -66
View File
@@ -58,7 +58,6 @@ import (
"tailscale.com/version"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/netlog"
"tailscale.com/wgengine/netstack/gro"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg"
@@ -66,10 +65,6 @@ import (
"tailscale.com/wgengine/wglog"
)
// networkLoggerUploadTimeout is the maximum timeout to wait when
// shutting down the network logger as it uploads the last network log messages.
const networkLoggerUploadTimeout = 5 * time.Second
type userspaceEngine struct {
// eventBus will eventually become required, but for now may be nil.
eventBus *eventbus.Bus
@@ -156,13 +151,16 @@ type userspaceEngine struct {
// value of the ICMP identifier and sequence number concatenated.
icmpEchoResponseCallback map[uint32]func()
// networkLogger logs statistics about network connections.
networkLogger netlog.Logger
// netlogger is the network flow logger handle constructed via
// [HookNewNetLogger], or nil if the feature/netlog package is not
// linked into the binary.
netlogger NetLogger
// netLogSource is the [netlog.NodeSource] installed via
// [Engine.SetNetLogNodeSource]; it is read when starting up the network
// logger from inside Reconfig. It may be nil if no source was installed.
netLogSource syncs.AtomicValue[netlog.NodeSource]
// netLogSource is the [NetLogSource] installed via
// [Engine.SetNetLogSource]; the netlogger reads it on each
// Reconfig via [NetLoggerDeps.Source]. It may be nil if no source
// was installed.
netLogSource syncs.AtomicValue[NetLogSource]
// wgPeerLookup is the lookup function installed via
// [Engine.SetWGPeerLookup]; it is consulted by [wgLogger] to translate
@@ -454,6 +452,18 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
closePool.add(e.magicConn)
e.magicConn.SetNetworkUp(e.netMon.InterfaceState().AnyInterfaceUp())
if newNetLogger, ok := HookNewNetLogger.GetOk(); ok {
e.netlogger = newNetLogger(NetLoggerDeps{
Logf: logf,
Tun: e.tundev,
Sock: e.magicConn,
NetMon: e.netMon,
Health: e.health,
Bus: e.eventBus,
Source: e.netLogSource.Load,
})
}
tsTUNDev.SetDiscoKey(e.magicConn.DiscoPublicKey())
if conf.RespondToPing {
@@ -758,9 +768,9 @@ func (e *userspaceEngine) SetPeerSessionStateFunc(fn func(key.NodePublic, PeerWi
})
}
// SetNetLogNodeSource installs the [netlog.NodeSource] used by the engine's
// network logger.
func (e *userspaceEngine) SetNetLogNodeSource(src netlog.NodeSource) {
// SetNetLogSource installs the [NetLogSource] consulted by the engine's
// network flow logger.
func (e *userspaceEngine) SetNetLogSource(src NetLogSource) {
e.netLogSource.Store(src)
}
@@ -864,17 +874,18 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
listenPortChanged := listenPort != e.magicConn.LocalPort()
peerMTUChanged := peerMTUEnable != e.magicConn.PeerMTUEnabled()
if !engineChanged && !routerChanged && !dnsChanged && !listenPortChanged && !isSubnetRouterChanged && !peerMTUChanged {
return ErrNoChanges
// Let the network flow logger react before the early return below,
// so that logging identity changes take effect even when nothing
// else changed, and before the router is configured, so that a
// starting logger captures initial packets.
netlogChanged := false
if e.netlogger != nil {
netlogChanged = e.netlogger.Reconfig(routerCfg, routerChanged)
}
newLogIDs := cfg.NetworkLogging
oldLogIDs := e.lastCfgFull.NetworkLogging
netLogIDsNowValid := !newLogIDs.NodeID.IsZero() && !newLogIDs.DomainID.IsZero()
netLogIDsWasValid := !oldLogIDs.NodeID.IsZero() && !oldLogIDs.DomainID.IsZero()
netLogIDsChanged := netLogIDsNowValid && netLogIDsWasValid && newLogIDs != oldLogIDs
netLogRunning := netLogIDsNowValid && !routerCfg.Equal(&router.Config{})
if !buildfeatures.HasNetLog || envknob.NoLogsNoSupport() {
netLogRunning = false
if !engineChanged && !routerChanged && !dnsChanged && !listenPortChanged && !isSubnetRouterChanged && !peerMTUChanged && !netlogChanged {
return ErrNoChanges
}
// TODO(bradfitz,danderson): maybe delete this isDNSIPOverTailscale
@@ -982,36 +993,8 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
}
}
// Shutdown the network logger because the IDs changed.
// Let it be started back up by subsequent logic.
if buildfeatures.HasNetLog && netLogIDsChanged && e.networkLogger.Running() {
e.logf("wgengine: Reconfig: shutting down network logger")
ctx, cancel := context.WithTimeout(context.Background(), networkLoggerUploadTimeout)
defer cancel()
if err := e.networkLogger.Shutdown(ctx); err != nil {
e.logf("wgengine: Reconfig: error shutting down network logger: %v", err)
}
}
// Startup the network logger.
// Do this before configuring the router so that we capture initial packets.
if buildfeatures.HasNetLog && netLogRunning && !e.networkLogger.Running() {
nid := cfg.NetworkLogging.NodeID
tid := cfg.NetworkLogging.DomainID
logExitFlowEnabled := cfg.NetworkLogging.LogExitFlowEnabled
e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public())
src := e.netLogSource.Load()
if src == nil {
e.logf("wgengine: Reconfig: no NodeSource installed; network logger not started")
} else if err := e.networkLogger.Startup(e.logf, src, nid, tid, e.tundev, e.magicConn, e.netMon, e.health, e.eventBus, logExitFlowEnabled); err != nil {
e.logf("wgengine: Reconfig: error starting up network logger: %v", err)
}
e.networkLogger.ReconfigRoutes(routerCfg)
}
if routerChanged {
e.logf("wgengine: Reconfig: configuring router")
e.networkLogger.ReconfigRoutes(routerCfg)
err := e.router.Set(routerCfg)
e.health.SetRouterHealth(err)
if err != nil {
@@ -1050,16 +1033,11 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
}
}
// Shutdown the network logger.
// Do this after configuring the router so that we capture final packets.
// This attempts to flush out any log messages and may block.
if !netLogRunning && e.networkLogger.Running() {
e.logf("wgengine: Reconfig: shutting down network logger")
ctx, cancel := context.WithTimeout(context.Background(), networkLoggerUploadTimeout)
defer cancel()
if err := e.networkLogger.Shutdown(ctx); err != nil {
e.logf("wgengine: Reconfig: error shutting down network logger: %v", err)
}
// Let the network flow logger finish reacting after the router is
// configured, so that a stopping logger captures final packets.
// This may block to flush pending log messages.
if e.netlogger != nil {
e.netlogger.ReconfigDone()
}
if buildfeatures.HasBird && isSubnetRouterChanged && e.birdClient != nil {
@@ -1252,10 +1230,8 @@ func (e *userspaceEngine) Close() {
}
close(e.waitCh)
ctx, cancel := context.WithTimeout(context.Background(), networkLoggerUploadTimeout)
defer cancel()
if err := e.networkLogger.Shutdown(ctx); err != nil {
e.logf("wgengine: Close: error shutting down network logger: %v", err)
if e.netlogger != nil {
e.netlogger.Shutdown()
}
}