diff --git a/cmd/k8s-operator/depaware.txt b/cmd/k8s-operator/depaware.txt index c7499cd8f..a862509b4 100644 --- a/cmd/k8s-operator/depaware.txt +++ b/cmd/k8s-operator/depaware.txt @@ -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+ diff --git a/cmd/tailscaled/depaware-min.txt b/cmd/tailscaled/depaware-min.txt index 150cb13c6..2f1dd57b5 100644 --- a/cmd/tailscaled/depaware-min.txt +++ b/cmd/tailscaled/depaware-min.txt @@ -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+ diff --git a/cmd/tailscaled/depaware-minbox.txt b/cmd/tailscaled/depaware-minbox.txt index 04dd21f28..7e2f759a7 100644 --- a/cmd/tailscaled/depaware-minbox.txt +++ b/cmd/tailscaled/depaware-minbox.txt @@ -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+ diff --git a/cmd/tailscaled/depaware.txt b/cmd/tailscaled/depaware.txt index ade4da576..58bc03616 100644 --- a/cmd/tailscaled/depaware.txt +++ b/cmd/tailscaled/depaware.txt @@ -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+ diff --git a/cmd/tsidp/depaware.txt b/cmd/tsidp/depaware.txt index 7c6de76c3..1af72f703 100644 --- a/cmd/tsidp/depaware.txt +++ b/cmd/tsidp/depaware.txt @@ -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+ diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index c5bdd3f0f..ced82a22a 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -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 { diff --git a/ipn/ipnlocal/state_test.go b/ipn/ipnlocal/state_test.go index 8190aee03..85a421f02 100644 --- a/ipn/ipnlocal/state_test.go +++ b/ipn/ipnlocal/state_test.go @@ -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() diff --git a/tsnet/depaware.txt b/tsnet/depaware.txt index 6fe48ad2d..f7afe97bb 100644 --- a/tsnet/depaware.txt +++ b/tsnet/depaware.txt @@ -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+ diff --git a/wgengine/netlog/netlog.go b/wgengine/netlog/netlog.go index e03a520f2..2da0c5d31 100644 --- a/wgengine/netlog/netlog.go +++ b/wgengine/netlog/netlog.go @@ -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) { diff --git a/wgengine/netlog/netlog_omit.go b/wgengine/netlog/netlog_omit.go index 041a18376..827904bb7 100644 --- a/wgengine/netlog/netlog_omit.go +++ b/wgengine/netlog/netlog_omit.go @@ -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) {} diff --git a/wgengine/netlog/netlog_test.go b/wgengine/netlog/netlog_test.go index cc6968547..57bb16344 100644 --- a/wgengine/netlog/netlog_test.go +++ b/wgengine/netlog/netlog_test.go @@ -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) diff --git a/wgengine/userspace.go b/wgengine/userspace.go index d10c7464b..3d2b5834d 100644 --- a/wgengine/userspace.go +++ b/wgengine/userspace.go @@ -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) { diff --git a/wgengine/wgengine.go b/wgengine/wgengine.go index a2504f84c..a3079fbaf 100644 --- a/wgengine/wgengine.go +++ b/wgengine/wgengine.go @@ -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. //