wgengine/netlog: stop using netmap.NetworkMap type, use LocalBackend

The Logger previously took a *netmap.NetworkMap at Startup and on every
ReconfigNetworkMap call, denormalizing it into per-IP and self lookup
maps. That denormalization is O(n) over all peers and ran on every
netmap update, contributing to the broader quadratic behavior we want
to eliminate when a single peer is added or removed.

Instead, this makes netlog ask LocalBackend (well, nodeBackend) for
the info it needs, letting us remove the netmap.NetworkMap type
entirely from the netlog package.

This is a dependency to removing the netmap.NetworkMap type from
upstream callers, like wgengine.Engine in general.

Updates #12542

Change-Id: Ib5f2de96e788a667332c0a6f7ac833b3d0053b5c
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2026-06-16 19:31:18 +00:00
committed by Brad Fitzpatrick
parent 994b2c8459
commit 8f210454dd
13 changed files with 180 additions and 83 deletions

View File

@@ -919,7 +919,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netlog from tailscale.com/wgengine+
tailscale.com/wgengine/netstack from tailscale.com/tsnet
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+

View File

@@ -194,7 +194,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netlog from tailscale.com/wgengine+
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+

View File

@@ -215,7 +215,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netlog from tailscale.com/wgengine+
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+

View File

@@ -503,7 +503,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netlog from tailscale.com/wgengine+
tailscale.com/wgengine/netstack from tailscale.com/cmd/tailscaled
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+

View File

@@ -317,7 +317,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netlog from tailscale.com/wgengine+
tailscale.com/wgengine/netstack from tailscale.com/tsnet
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+

View File

@@ -105,6 +105,7 @@
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/netlog"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgcfg/nmcfg"
@@ -645,6 +646,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
e.SetPeerByIPPacketFunc(b.lookupPeerByIP)
e.SetPeerSessionStateFunc(b.onPeerWireGuardState)
e.SetNetLogNodeSource(netLogNodeSource{b})
if sys.InitialConfig != nil {
if err := b.initPrefsFromConfig(sys.InitialConfig); err != nil {
@@ -8046,6 +8048,40 @@ func (n noiseRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
return n.lb.DoNoiseRequest(req)
}
// netLogNodeSource adapts LocalBackend's nodeBackend to [netlog.NodeSource].
// Each method consults [LocalBackend.currentNode] so that profile rotations
// are picked up automatically without re-installing the source.
type netLogNodeSource struct {
b *LocalBackend
}
func (s netLogNodeSource) SelfNode() (tailcfg.NodeView, tailcfg.UserProfileView) {
nb := s.b.currentNode()
self := nb.Self()
if !self.Valid() {
return tailcfg.NodeView{}, tailcfg.UserProfileView{}
}
up, _ := nb.UserByID(self.User())
return self, up
}
func (s netLogNodeSource) NodeByAddr(addr netip.Addr) (_ tailcfg.NodeView, _ tailcfg.UserProfileView, ok bool) {
nb := s.b.currentNode()
nid, ok := nb.NodeByAddr(addr)
if !ok {
return tailcfg.NodeView{}, tailcfg.UserProfileView{}, false
}
nv, ok := nb.NodeByID(nid)
if !ok {
return tailcfg.NodeView{}, tailcfg.UserProfileView{}, false
}
up, _ := nb.UserByID(nv.User())
return nv, up, true
}
// Compile-time assertion that netLogNodeSource implements [netlog.NodeSource].
var _ netlog.NodeSource = netLogNodeSource{}
// ActiveSSHConns returns the number of active SSH connections,
// or 0 if SSH is not linked into the binary or available on the platform.
func (b *LocalBackend) ActiveSSHConns() int {

View File

@@ -47,6 +47,7 @@
"tailscale.com/wgengine"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/netlog"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgint"
@@ -1984,6 +1985,7 @@ func (e *mockEngine) InstallCaptureHook(packet.CaptureCallback) {}
func (e *mockEngine) SetPeerByIPPacketFunc(func(netip.Addr) (_ key.NodePublic, ok bool)) {}
func (e *mockEngine) SetPeerSessionStateFunc(func(key.NodePublic, wgengine.PeerWireGuardState)) {
}
func (e *mockEngine) SetNetLogNodeSource(netlog.NodeSource) {}
func (e *mockEngine) Close() {
e.mu.Lock()

View File

@@ -312,7 +312,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/wgengine/filter from tailscale.com/control/controlclient+
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/netlog from tailscale.com/wgengine
tailscale.com/wgengine/netlog from tailscale.com/wgengine+
tailscale.com/wgengine/netstack from tailscale.com/tsnet
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+

View File

@@ -24,12 +24,12 @@
"tailscale.com/net/sockstats"
"tailscale.com/net/tsaddr"
"tailscale.com/syncs"
"tailscale.com/tailcfg"
"tailscale.com/types/ipproto"
"tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/types/netlogfunc"
"tailscale.com/types/netlogtype"
"tailscale.com/types/netmap"
"tailscale.com/util/eventbus"
"tailscale.com/util/set"
"tailscale.com/wgengine/router"
@@ -51,6 +51,19 @@ type noopDevice struct{
func (noopDevice) SetConnectionCounter(netlogfunc.ConnectionCounter) {}
// NodeSource provides node lookups for the network logger.
// Methods may be called concurrently.
type NodeSource interface {
// SelfNode returns the local node and its owning user profile.
// Both views may be invalid if no self node is known yet.
SelfNode() (node tailcfg.NodeView, user tailcfg.UserProfileView)
// NodeByAddr returns the node assigned the given address along with
// its owning user profile.
// ok is false if no node is known to own addr.
NodeByAddr(addr netip.Addr) (node tailcfg.NodeView, user tailcfg.UserProfileView, ok bool)
}
// Logger logs statistics about every connection.
// At present, it only logs connections within a tailscale network.
// By default, exit node traffic is not logged for privacy reasons
@@ -69,10 +82,8 @@ type Logger struct {
recordsChan chan record // set to nil when shutdown
flushTimer *time.Timer // fires when record should flush to recordsChan
// Information about Tailscale nodes.
// These are read-only once updated by ReconfigNetworkMap.
selfNode nodeUser
allNodes map[netip.Addr]nodeUser // includes selfNode; nodeUser values are always valid
// source provides node lookups. Set by Startup, cleared by shutdownLocked.
source NodeSource
// Information about routes.
// These are read-only once updated by ReconfigRoutes.
@@ -115,14 +126,20 @@ func (nl *Logger) Running() bool {
// The sock is used to populated the PhysicalTraffic field in [netlogtype.Message].
//
// The netMon parameter is optional; if non-nil it's used to do faster interface lookups.
func (nl *Logger) Startup(logf logger.Logf, nm *netmap.NetworkMap, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor, health *health.Tracker, bus *eventbus.Bus, logExitFlowEnabledEnabled bool) error {
//
// source provides on-demand node lookups for connections seen by the logger.
// It must be non-nil.
func (nl *Logger) Startup(logf logger.Logf, source NodeSource, nodeLogID, domainLogID logid.PrivateID, tun, sock Device, netMon *netmon.Monitor, health *health.Tracker, bus *eventbus.Bus, logExitFlowEnabledEnabled bool) error {
nl.mu.Lock()
defer nl.mu.Unlock()
if nl.shutdownLocked != nil {
return fmt.Errorf("network logger already running")
}
nl.selfNode, nl.allNodes = makeNodeMaps(nm)
if source == nil {
return fmt.Errorf("network logger requires a non-nil NodeSource")
}
nl.source = source
// Startup a log stream to Tailscale's logging service.
if logf == nil {
@@ -194,8 +211,7 @@ func (nl *Logger) Startup(logf logger.Logf, nm *netmap.NetworkMap, nodeLogID, do
// Purge state.
nl.shutdownLocked = nil
nl.selfNode = nodeUser{}
nl.allNodes = nil
nl.source = nil
nl.routeAddrs = nil
nl.routePrefixes = nil
@@ -249,15 +265,15 @@ func (nl *Logger) addNewVirtConnLocked(c netlogtype.Connection) connType {
var srcNodeLen, dstNodeLen int
srcNode, srcSeen := nl.record.seenNodes[c.Src.Addr()]
if !srcSeen {
srcNode = nl.allNodes[c.Src.Addr()]
if srcNode.Valid() {
if node, user, ok := nl.source.NodeByAddr(c.Src.Addr()); ok {
srcNode = nodeUser{node, user}
srcNodeLen = srcNode.jsonLen()
}
}
dstNode, dstSeen := nl.record.seenNodes[c.Dst.Addr()]
if !dstSeen {
dstNode = nl.allNodes[c.Dst.Addr()]
if dstNode.Valid() {
if node, user, ok := nl.source.NodeByAddr(c.Dst.Addr()); ok {
dstNode = nodeUser{node, user}
dstNodeLen = dstNode.jsonLen()
}
}
@@ -279,12 +295,10 @@ func (nl *Logger) addNewVirtConnLocked(c netlogtype.Connection) connType {
nl.recordLen += netlogtype.MaxConnectionCountsJSONSize + srcNodeLen + dstNodeLen
// Classify the traffic type.
var srcIsSelfNode bool
if nl.selfNode.Valid() {
srcIsSelfNode = nl.selfNode.Addresses().ContainsFunc(func(p netip.Prefix) bool {
return c.Src.Addr() == p.Addr() && p.IsSingleIP()
})
}
// srcNode == self iff NodeByAddr resolved the source to the same node as
// the current record's self.
srcIsSelfNode := srcNode.Valid() && nl.record.selfNode.Valid() &&
srcNode.ID() == nl.record.selfNode.ID()
switch {
case srcIsSelfNode && dstNode.Valid():
return virtualTraffic
@@ -335,8 +349,8 @@ func (nl *Logger) addNewPhysConnLocked(c netlogtype.Connection) {
var srcNodeLen int
srcNode, srcSeen := nl.record.seenNodes[c.Src.Addr()]
if !srcSeen {
srcNode = nl.allNodes[c.Src.Addr()]
if srcNode.Valid() {
if node, user, ok := nl.source.NodeByAddr(c.Src.Addr()); ok {
srcNode = nodeUser{node, user}
srcNodeLen = srcNode.jsonLen()
}
}
@@ -361,14 +375,16 @@ func (nl *Logger) initRecordLocked() {
if nl.recordLen != 0 {
return
}
node, user := nl.source.SelfNode()
self := nodeUser{node, user}
nl.record = record{
selfNode: nl.selfNode,
selfNode: self,
start: time.Now().UTC(),
seenNodes: make(map[netip.Addr]nodeUser),
virtConns: make(map[netlogtype.Connection]countsType),
physConns: make(map[netlogtype.Connection]netlogtype.Counts),
}
nl.recordLen = netlogtype.MinMessageJSONSize + nl.selfNode.jsonLen()
nl.recordLen = netlogtype.MinMessageJSONSize + self.jsonLen()
// Start a time to auto-flush the record.
// Avoid tickers since continually waking up a goroutine
@@ -406,39 +422,6 @@ func (nl *Logger) flushRecordLocked() {
nl.recordLen = 0
}
func makeNodeMaps(nm *netmap.NetworkMap) (selfNode nodeUser, allNodes map[netip.Addr]nodeUser) {
if nm == nil {
return
}
allNodes = make(map[netip.Addr]nodeUser)
if nm.SelfNode.Valid() {
selfNode = nodeUser{nm.SelfNode, nm.UserProfiles[nm.SelfNode.User()]}
for _, addr := range nm.SelfNode.Addresses().All() {
if addr.IsSingleIP() {
allNodes[addr.Addr()] = selfNode
}
}
}
for _, peer := range nm.Peers {
if peer.Valid() {
for _, addr := range peer.Addresses().All() {
if addr.IsSingleIP() {
allNodes[addr.Addr()] = nodeUser{peer, nm.UserProfiles[peer.User()]}
}
}
}
}
return selfNode, allNodes
}
// ReconfigNetworkMap configures the network logger with an updated netmap.
func (nl *Logger) ReconfigNetworkMap(nm *netmap.NetworkMap) {
selfNode, allNodes := makeNodeMaps(nm) // avoid holding lock while making maps
nl.mu.Lock()
nl.selfNode, nl.allNodes = selfNode, allNodes
nl.mu.Unlock()
}
func makeRouteMaps(cfg *router.Config) (addrs set.Set[netip.Addr], prefixes []netip.Prefix) {
addrs = make(set.Set[netip.Addr])
insertPrefixes := func(rs []netip.Prefix) {

View File

@@ -7,8 +7,11 @@
type Logger struct{}
func (*Logger) Startup(...any) error { return nil }
func (*Logger) Running() bool { return false }
func (*Logger) Shutdown(any) error { return nil }
func (*Logger) ReconfigNetworkMap(any) {}
func (*Logger) ReconfigRoutes(any) {}
// NodeSource is a stub kept so the omit build does not break consumers that
// reference the type. It has no methods.
type NodeSource any
func (*Logger) Startup(...any) error { return nil }
func (*Logger) Running() bool { return false }
func (*Logger) Shutdown(any) error { return nil }
func (*Logger) ReconfigRoutes(any) {}

View File

@@ -21,22 +21,74 @@
"tailscale.com/types/bools"
"tailscale.com/types/ipproto"
"tailscale.com/types/netlogtype"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/router"
)
// nodeAndUser pairs a [tailcfg.NodeView] with its owning user profile.
type nodeAndUser struct {
node tailcfg.NodeView
user tailcfg.UserProfileView
}
// fakeNodeSource is a [NodeSource] implementation backed by static maps,
// for tests.
type fakeNodeSource struct {
self tailcfg.NodeView
selfUser tailcfg.UserProfileView
byAddr map[netip.Addr]nodeAndUser
}
func (s *fakeNodeSource) SelfNode() (tailcfg.NodeView, tailcfg.UserProfileView) {
return s.self, s.selfUser
}
func (s *fakeNodeSource) NodeByAddr(a netip.Addr) (_ tailcfg.NodeView, _ tailcfg.UserProfileView, ok bool) {
nu, ok := s.byAddr[a]
return nu.node, nu.user, ok
}
// newFakeNodeSource builds a [fakeNodeSource] from a self [tailcfg.NodeView],
// a list of peer [tailcfg.NodeView] values, and a UserProfile map.
// All single-IP addresses on self and the peers are indexed for NodeByAddr.
func newFakeNodeSource(self tailcfg.NodeView, peers []tailcfg.NodeView, users map[tailcfg.UserID]tailcfg.UserProfileView) *fakeNodeSource {
s := &fakeNodeSource{
byAddr: map[netip.Addr]nodeAndUser{},
}
if self.Valid() {
s.self = self
s.selfUser = users[self.User()]
for _, p := range self.Addresses().All() {
if p.IsSingleIP() {
s.byAddr[p.Addr()] = nodeAndUser{self, s.selfUser}
}
}
}
for _, peer := range peers {
if !peer.Valid() {
continue
}
pu := users[peer.User()]
for _, p := range peer.Addresses().All() {
if p.IsSingleIP() {
s.byAddr[p.Addr()] = nodeAndUser{peer, pu}
}
}
}
return s
}
func TestEmbedNodeInfo(t *testing.T) {
// Initialize the logger with a particular view of the netmap.
// Initialize the logger with a particular view of the node state.
var logger Logger
logger.ReconfigNetworkMap(&netmap.NetworkMap{
SelfNode: (&tailcfg.Node{
logger.source = newFakeNodeSource(
(&tailcfg.Node{
StableID: "n123456CNTL",
ID: 123456,
Name: "test.tail123456.ts.net",
Addresses: []netip.Prefix{prefix("100.1.2.3")},
Tags: []string{"tag:foo", "tag:bar"},
}).View(),
Peers: []tailcfg.NodeView{
[]tailcfg.NodeView{
(&tailcfg.Node{
StableID: "n123457CNTL",
ID: 123457,
@@ -52,10 +104,10 @@ func TestEmbedNodeInfo(t *testing.T) {
User: 54321,
}).View(),
},
UserProfiles: map[tailcfg.UserID]tailcfg.UserProfileView{
map[tailcfg.UserID]tailcfg.UserProfileView{
54321: (&tailcfg.UserProfile{ID: 54321, LoginName: "peer@example.com"}).View(),
},
})
)
logger.ReconfigRoutes(&router.Config{
SubnetRoutes: []netip.Prefix{
prefix("172.16.1.1/16"),
@@ -148,6 +200,9 @@ func TestEmbedNodeInfo(t *testing.T) {
func TestUpdateRace(t *testing.T) {
var logger Logger
// Install an empty fake source so NodeByAddr / SelfNode are exercised on
// the lookup path without returning any matches.
logger.source = &fakeNodeSource{}
logger.recordsChan = make(chan record, 1)
go func(recordsChan chan record) {
for range recordsChan {
@@ -167,11 +222,6 @@ func TestUpdateRace(t *testing.T) {
}
}
})
group.Go(func() {
for range 1000 {
logger.ReconfigNetworkMap(new(netmap.NetworkMap))
}
})
group.Go(func() {
for range 1000 {
logger.ReconfigRoutes(new(router.Config))
@@ -194,6 +244,7 @@ func randAddrPort() netip.AddrPort {
func TestAutoFlushMaxConns(t *testing.T) {
var logger Logger
logger.source = &fakeNodeSource{}
logger.recordsChan = make(chan record, 1)
for i := 0; len(logger.recordsChan) == 0; i++ {
logger.updateVirtConn(0, netip.AddrPortFrom(netip.Addr{}, uint16(i)), netip.AddrPort{}, 1, 1, false)
@@ -206,6 +257,7 @@ func TestAutoFlushMaxConns(t *testing.T) {
func TestAutoFlushTimeout(t *testing.T) {
var logger Logger
logger.source = &fakeNodeSource{}
logger.recordsChan = make(chan record, 1)
synctest.Test(t, func(t *testing.T) {
logger.updateVirtConn(0, netip.AddrPort{}, netip.AddrPort{}, 1, 1, false)
@@ -222,6 +274,7 @@ func TestAutoFlushTimeout(t *testing.T) {
func BenchmarkUpdateSameConn(b *testing.B) {
var logger Logger
logger.source = &fakeNodeSource{}
b.ReportAllocs()
for range b.N {
logger.updateVirtConn(0, netip.AddrPort{}, netip.AddrPort{}, 1, 1, false)
@@ -230,6 +283,7 @@ func BenchmarkUpdateSameConn(b *testing.B) {
func BenchmarkUpdateNewConns(b *testing.B) {
var logger Logger
logger.source = &fakeNodeSource{}
b.ReportAllocs()
for i := range b.N {
logger.updateVirtConn(0, netip.AddrPortFrom(netip.Addr{}, uint16(i)), netip.AddrPort{}, 1, 1, false)

View File

@@ -157,6 +157,11 @@ type userspaceEngine struct {
// networkLogger logs statistics about network connections.
networkLogger netlog.Logger
// 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]
// tsmpLearnedDisco tracks per node key if a peer disco key was learned via TSMP.
// wgLock must be held when using this map.
tsmpLearnedDisco map[key.NodePublic]key.DiscoPublic
@@ -735,6 +740,12 @@ 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) {
e.netLogSource.Store(src)
}
func peerWireGuardStateFromDevice(state device.PeerSessionState) PeerWireGuardState {
switch state {
case device.PeerSessionNone:
@@ -969,7 +980,10 @@ func (e *userspaceEngine) Reconfig(cfg *wgcfg.Config, routerCfg *router.Config,
tid := cfg.NetworkLogging.DomainID
logExitFlowEnabled := cfg.NetworkLogging.LogExitFlowEnabled
e.logf("wgengine: Reconfig: starting up network logger (node:%s tailnet:%s)", nid.Public(), tid.Public())
if err := e.networkLogger.Startup(e.logf, nm, nid, tid, e.tundev, e.magicConn, e.netMon, e.health, e.eventBus, logExitFlowEnabled); err != nil {
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)
@@ -1309,9 +1323,6 @@ func (e *userspaceEngine) SetNetworkMap(nm *netmap.NetworkMap) {
curUDP, curTCP)
e.tundev.ApplyGROKnobs(e.controlKnobs)
}
if e.networkLogger.Running() {
e.networkLogger.ReconfigNetworkMap(nm)
}
}
func (e *userspaceEngine) UpdateStatus(sb *ipnstate.StatusBuilder) {

View File

@@ -16,6 +16,7 @@
"tailscale.com/types/key"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/netlog"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgint"
@@ -164,6 +165,13 @@ type Engine interface {
// look up which peer should handle an outbound packet by destination IP.
SetPeerByIPPacketFunc(func(netip.Addr) (_ key.NodePublic, ok bool))
// SetNetLogNodeSource installs the [netlog.NodeSource] used by the engine's
// network logger to look up Tailscale node info on demand.
//
// It is expected to be called once during LocalBackend construction,
// before any [Engine.Reconfig] call that starts up the network logger.
SetNetLogNodeSource(netlog.NodeSource)
// SetPeerSessionStateFunc installs a callback used to observe WireGuard
// peer session state transitions.
//