diff --git a/client/internal/netflow/logger/logger.go b/client/internal/netflow/logger/logger.go index 688418c38..cdede224d 100644 --- a/client/internal/netflow/logger/logger.go +++ b/client/internal/netflow/logger/logger.go @@ -83,7 +83,7 @@ func (l *Logger) startReceiver() { EventFields: *eventFields, Timestamp: time.Now(), } - srcResId, dstResId := l.statusRecorder.CheckRoutes(event.SourceIP, event.DestIP) + srcResId, dstResId := l.statusRecorder.CheckRoutes(event.SourceIP, event.DestIP, event.Direction) event.SourceResourceID = []byte(srcResId) event.DestResourceID = []byte(dstResId) l.Store.StoreEvent(&event) diff --git a/client/internal/peer/status.go b/client/internal/peer/status.go index 26f0bb757..e59874ae2 100644 --- a/client/internal/peer/status.go +++ b/client/internal/peer/status.go @@ -17,6 +17,7 @@ import ( firewall "github.com/netbirdio/netbird/client/firewall/manager" "github.com/netbirdio/netbird/client/iface/configurer" "github.com/netbirdio/netbird/client/internal/ingressgw" + nftypes "github.com/netbirdio/netbird/client/internal/netflow/types" "github.com/netbirdio/netbird/client/internal/relay" "github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/management/domain" @@ -34,11 +35,6 @@ type EventListener interface { OnEvent(event *proto.SystemEvent) } -type RouteWithResourceId struct { - Route string - ResourceId string -} - // State contains the latest state of a peer type State struct { Mux *sync.RWMutex @@ -58,58 +54,47 @@ type State struct { BytesRx int64 Latency time.Duration RosenpassEnabled bool - routes map[RouteWithResourceId]struct{} + routes map[string]struct{} } // AddRoute add a single route to routes map -func (s *State) AddRoute(network, resourceId string) { +func (s *State) AddRoute(network string) { s.Mux.Lock() defer s.Mux.Unlock() if s.routes == nil { - s.routes = make(map[RouteWithResourceId]struct{}) + s.routes = make(map[string]struct{}) } - s.routes[RouteWithResourceId{network, resourceId}] = struct{}{} + s.routes[network] = struct{}{} } // SetRoutes set state routes -func (s *State) SetRoutes(routes map[RouteWithResourceId]struct{}) { +func (s *State) SetRoutes(routes map[string]struct{}) { s.Mux.Lock() defer s.Mux.Unlock() s.routes = routes } // DeleteRoute removes a route from the network amp -func (s *State) DeleteRoute(network, resourceId string) { +func (s *State) DeleteRoute(network string) { s.Mux.Lock() defer s.Mux.Unlock() - delete(s.routes, RouteWithResourceId{network, resourceId}) + delete(s.routes, network) } // GetRoutes return routes map -func (s *State) GetRoutes() map[RouteWithResourceId]struct{} { +func (s *State) GetRoutes() map[string]struct{} { s.Mux.RLock() defer s.Mux.RUnlock() return maps.Clone(s.routes) } -// GetRoutes return routes map -func (s *State) GetRoutesSlice() []string { - s.Mux.RLock() - defer s.Mux.RUnlock() - routes := make([]string, 0, len(s.routes)) - for route := range s.routes { - routes = append(routes, route.Route) - } - return routes -} - // LocalPeerState contains the latest state of the local peer type LocalPeerState struct { IP string PubKey string KernelInterface bool FQDN string - Routes map[RouteWithResourceId]struct{} + Routes map[string]struct{} } // Clone returns a copy of the LocalPeerState @@ -192,6 +177,9 @@ type Status struct { eventQueue *EventQueue ingressGwMgr *ingressgw.Manager + + resIdMux sync.Mutex + resIdMap map[netip.Prefix]string } // NewRecorder returns a new Status instance @@ -336,15 +324,24 @@ func (d *Status) AddPeerStateRoute(peer string, route string, resourceId string) return errors.New("peer doesn't exist") } - peerState.AddRoute(route, resourceId) + peerState.AddRoute(route) d.peers[peer] = peerState + pref, err := netip.ParsePrefix(route) + if err != nil { + log.Errorf("failed to parse prefix %s: %v", route, err) + } else { + d.resIdMux.Lock() + d.resIdMap[pref] = resourceId + d.resIdMux.Unlock() + } + // todo: consider to make sense of this notification or not d.notifyPeerListChanged() return nil } -func (d *Status) RemovePeerStateRoute(peer string, route string, resourceId string) error { +func (d *Status) RemovePeerStateRoute(peer string, route string) error { d.mux.Lock() defer d.mux.Unlock() @@ -353,9 +350,18 @@ func (d *Status) RemovePeerStateRoute(peer string, route string, resourceId stri return errors.New("peer doesn't exist") } - peerState.DeleteRoute(route, resourceId) + peerState.DeleteRoute(route) d.peers[peer] = peerState + pref, err := netip.ParsePrefix(route) + if err != nil { + log.Errorf("failed to parse prefix %s: %v", route, err) + } else { + d.resIdMux.Lock() + delete(d.resIdMap, pref) + d.resIdMux.Unlock() + } + // todo: consider to make sense of this notification or not d.notifyPeerListChanged() return nil @@ -365,55 +371,21 @@ func (d *Status) RemovePeerStateRoute(peer string, route string, resourceId stri // and then in the remote peers' routes. It returns the IP address of the matching peer (as a string) // for source and destination. If a match is found in local peer routes, the local peer IP is returned; // otherwise, the remote peer IP is returned. If no match is found, an empty string is returned for that IP. -func (d *Status) CheckRoutes(src, dst netip.Addr) (srcMatchedPeerIP string, dstMatchedPeerIP string) { - if d == nil { - return - } - // check local peer routes. - if d.localPeer.Routes != nil { - for route := range d.localPeer.Routes { - prefix, err := netip.ParsePrefix(route.Route) - if err != nil { - log.Debugf("failed to parse route %s: %v", route, err) - continue - } - if srcMatchedPeerIP == "" && prefix.Contains(src) { - srcMatchedPeerIP = d.localPeer.IP - } - if dstMatchedPeerIP == "" && prefix.Contains(dst) { - dstMatchedPeerIP = d.localPeer.IP - } - // early return if both source and destination are matched. - if srcMatchedPeerIP != "" && dstMatchedPeerIP != "" { - return srcMatchedPeerIP, dstMatchedPeerIP - } +func (d *Status) CheckRoutes(src, dst netip.Addr, direction nftypes.Direction) (srcResId string, dstResId string) { + d.mux.Lock() + d.resIdMux.Lock() + defer d.resIdMux.Unlock() + defer d.mux.Unlock() + + for route, resId := range d.resIdMap { + if route.Contains(src) { + srcResId = resId + } else if route.Contains(dst) { + dstResId = resId } } - // if one or both addresses were not found in local routes, check remote peers. - d.mux.Lock() - defer d.mux.Unlock() - for _, peer := range d.peers { - peerRoutes := peer.GetRoutes() - for route := range peerRoutes { - prefix, err := netip.ParsePrefix(route.Route) - if err != nil { - log.Debugf("failed to parse route %s: %v", route, err) - continue - } - if srcMatchedPeerIP == "" && prefix.Contains(src) { - srcMatchedPeerIP = peer.IP - } - if dstMatchedPeerIP == "" && prefix.Contains(dst) { - dstMatchedPeerIP = peer.IP - } - // early return if both addresses are matched. - if srcMatchedPeerIP != "" && dstMatchedPeerIP != "" { - return srcMatchedPeerIP, dstMatchedPeerIP - } - } - } - return srcMatchedPeerIP, dstMatchedPeerIP + return } func (d *Status) UpdatePeerICEState(receivedState State) error { @@ -712,7 +684,7 @@ func (d *Status) UpdateDNSStates(dnsStates []NSGroupState) { d.nsGroupStates = dnsStates } -func (d *Status) UpdateResolvedDomainsStates(originalDomain domain.Domain, resolvedDomain domain.Domain, prefixes []netip.Prefix) { +func (d *Status) UpdateResolvedDomainsStates(originalDomain domain.Domain, resolvedDomain domain.Domain, prefixes []netip.Prefix, resourceId string) { d.mux.Lock() defer d.mux.Unlock() @@ -721,6 +693,12 @@ func (d *Status) UpdateResolvedDomainsStates(originalDomain domain.Domain, resol Prefixes: prefixes, ParentDomain: originalDomain, } + + d.resIdMux.Lock() + for _, prefix := range prefixes { + d.resIdMap[prefix] = resourceId + } + d.resIdMux.Unlock() } func (d *Status) DeleteResolvedDomainsStates(domain domain.Domain) { @@ -731,6 +709,12 @@ func (d *Status) DeleteResolvedDomainsStates(domain domain.Domain) { for k, v := range d.resolvedDomainsStates { if v.ParentDomain == domain { delete(d.resolvedDomainsStates, k) + + d.resIdMux.Lock() + for _, prefix := range v.Prefixes { + delete(d.resIdMap, prefix) + } + d.resIdMux.Unlock() } } } diff --git a/client/internal/routemanager/client.go b/client/internal/routemanager/client.go index 430d74823..847949a53 100644 --- a/client/internal/routemanager/client.go +++ b/client/internal/routemanager/client.go @@ -255,7 +255,7 @@ func (c *clientNetwork) startPeersStatusChangeWatcher() { } func (c *clientNetwork) removeRouteFromWireGuardPeer() error { - if err := c.statusRecorder.RemovePeerStateRoute(c.currentChosen.Peer, c.handler.String(), c.currentChosen.GetResourceID()); err != nil { + if err := c.statusRecorder.RemovePeerStateRoute(c.currentChosen.Peer, c.handler.String()); err != nil { log.Warnf("Failed to update peer state: %v", err) } diff --git a/client/internal/routemanager/dnsinterceptor/handler.go b/client/internal/routemanager/dnsinterceptor/handler.go index f36285cc4..0c0d964d0 100644 --- a/client/internal/routemanager/dnsinterceptor/handler.go +++ b/client/internal/routemanager/dnsinterceptor/handler.go @@ -315,7 +315,7 @@ func (d *DnsInterceptor) updateDomainPrefixes(resolvedDomain, originalDomain dom if len(toAdd) > 0 || len(toRemove) > 0 { d.interceptedDomains[resolvedDomain] = newPrefixes originalDomain = domain.Domain(strings.TrimSuffix(string(originalDomain), ".")) - d.statusRecorder.UpdateResolvedDomainsStates(originalDomain, resolvedDomain, newPrefixes) + d.statusRecorder.UpdateResolvedDomainsStates(originalDomain, resolvedDomain, newPrefixes, d.route.GetResourceID()) if len(toAdd) > 0 { log.Debugf("added dynamic route(s) for domain=%s (pattern: domain=%s): %s", diff --git a/client/internal/routemanager/dynamic/route.go b/client/internal/routemanager/dynamic/route.go index 5ef18a47e..079134701 100644 --- a/client/internal/routemanager/dynamic/route.go +++ b/client/internal/routemanager/dynamic/route.go @@ -288,7 +288,7 @@ func (r *Route) updateDynamicRoutes(ctx context.Context, newDomains domainMap) e updatedPrefixes := combinePrefixes(oldPrefixes, removedPrefixes, addedPrefixes) r.dynamicDomains[domain] = updatedPrefixes - r.statusRecorder.UpdateResolvedDomainsStates(domain, domain, updatedPrefixes) + r.statusRecorder.UpdateResolvedDomainsStates(domain, domain, updatedPrefixes, r.route.GetResourceID()) } return nberrors.FormatErrorOrNil(merr) diff --git a/client/internal/routemanager/server_nonandroid.go b/client/internal/routemanager/server_nonandroid.go index 92505e227..5b6a788f8 100644 --- a/client/internal/routemanager/server_nonandroid.go +++ b/client/internal/routemanager/server_nonandroid.go @@ -104,10 +104,7 @@ func (m *serverRouter) removeFromServerNetwork(route *route.Route) error { delete(m.routes, route.ID) state := m.statusRecorder.GetLocalPeerState() - delete(state.Routes, peer.RouteWithResourceId{ - Route: route.Network.String(), - ResourceId: route.GetResourceID(), - }) + delete(state.Routes, route.Network.String()) m.statusRecorder.UpdateLocalPeerState(state) return nil @@ -136,17 +133,14 @@ func (m *serverRouter) addToServerNetwork(route *route.Route) error { state := m.statusRecorder.GetLocalPeerState() if state.Routes == nil { - state.Routes = map[peer.RouteWithResourceId]struct{}{} + state.Routes = map[string]struct{}{} } routeStr := route.Network.String() if route.IsDynamic() { routeStr = route.Domains.SafeString() } - state.Routes[peer.RouteWithResourceId{ - Route: routeStr, - ResourceId: route.GetResourceID(), - }] = struct{}{} + state.Routes[routeStr] = struct{}{} m.statusRecorder.UpdateLocalPeerState(state) diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go index 19a85eb27..622f8e840 100644 --- a/client/ios/NetBirdSDK/client.go +++ b/client/ios/NetBirdSDK/client.go @@ -155,7 +155,7 @@ func (c *Client) GetStatusDetails() *StatusDetails { for n, p := range fullStatus.Peers { var routes = RoutesDetails{} for r := range p.GetRoutes() { - routeInfo := RoutesInfo{r.Route} + routeInfo := RoutesInfo{r} routes.items = append(routes.items, routeInfo) } pi := PeerInfo{ diff --git a/client/server/debug.go b/client/server/debug.go index b5c10c2c8..749220d62 100644 --- a/client/server/debug.go +++ b/client/server/debug.go @@ -614,12 +614,12 @@ func seedFromStatus(a *anonymize.Anonymizer, status *peer.FullStatus) { for _, peer := range status.Peers { a.AnonymizeDomain(peer.FQDN) for route := range peer.GetRoutes() { - a.AnonymizeRoute(route.Route) + a.AnonymizeRoute(route) } } for route := range status.LocalPeerState.Routes { - a.AnonymizeRoute(route.Route) + a.AnonymizeRoute(route) } for _, nsGroup := range status.NSGroupStates { diff --git a/client/server/server.go b/client/server/server.go index 8d3ca2627..8907f541f 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -11,6 +11,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" + "golang.org/x/exp/maps" "google.golang.org/protobuf/types/known/durationpb" log "github.com/sirupsen/logrus" @@ -802,19 +803,13 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus { pbFullStatus.SignalState.Error = err.Error() } - localState := fullStatus.LocalPeerState.Clone() - var routesSlice []string - for route := range localState.Routes { - routesSlice = append(routesSlice, route.Route) - } - pbFullStatus.LocalPeerState.IP = fullStatus.LocalPeerState.IP pbFullStatus.LocalPeerState.PubKey = fullStatus.LocalPeerState.PubKey pbFullStatus.LocalPeerState.KernelInterface = fullStatus.LocalPeerState.KernelInterface pbFullStatus.LocalPeerState.Fqdn = fullStatus.LocalPeerState.FQDN pbFullStatus.LocalPeerState.RosenpassPermissive = fullStatus.RosenpassState.Permissive pbFullStatus.LocalPeerState.RosenpassEnabled = fullStatus.RosenpassState.Enabled - pbFullStatus.LocalPeerState.Networks = routesSlice + pbFullStatus.LocalPeerState.Networks = maps.Keys(fullStatus.LocalPeerState.Routes) pbFullStatus.NumberOfForwardingRules = int32(fullStatus.NumOfForwardingRules) for _, peerState := range fullStatus.Peers { @@ -834,7 +829,7 @@ func toProtoFullStatus(fullStatus peer.FullStatus) *proto.FullStatus { BytesRx: peerState.BytesRx, BytesTx: peerState.BytesTx, RosenpassEnabled: peerState.RosenpassEnabled, - Networks: peerState.GetRoutesSlice(), + Networks: maps.Keys(peerState.GetRoutes()), Latency: durationpb.New(peerState.Latency), } pbFullStatus.Peers = append(pbFullStatus.Peers, pbPeerState)