package peer import ( "net/netip" "sort" "sync" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/route" ) // routeEntry holds the route prefix and the corresponding resource ID. type routeEntry struct { prefix netip.Prefix resourceID route.ResID } type routeIDLookup struct { localRoutes []routeEntry localLock sync.RWMutex remoteRoutes []routeEntry remoteLock sync.RWMutex resolvedIPs sync.Map } func (r *routeIDLookup) AddLocalRouteID(resourceID route.ResID, route netip.Prefix) { r.localLock.Lock() defer r.localLock.Unlock() // update the resource id if the route already exists. for i, entry := range r.localRoutes { if entry.prefix == route { r.localRoutes[i].resourceID = resourceID log.Tracef("resourceID for route %v updated to %s in local routes", route, resourceID) return } } // append and sort descending by prefix bits (more specific first) r.localRoutes = append(r.localRoutes, routeEntry{prefix: route, resourceID: resourceID}) sort.Slice(r.localRoutes, func(i, j int) bool { return r.localRoutes[i].prefix.Bits() > r.localRoutes[j].prefix.Bits() }) } func (r *routeIDLookup) RemoveLocalRouteID(route netip.Prefix) { r.localLock.Lock() defer r.localLock.Unlock() for i, entry := range r.localRoutes { if entry.prefix == route { r.localRoutes = append(r.localRoutes[:i], r.localRoutes[i+1:]...) return } } } func (r *routeIDLookup) AddRemoteRouteID(resourceID route.ResID, route netip.Prefix) { r.remoteLock.Lock() defer r.remoteLock.Unlock() for i, entry := range r.remoteRoutes { if entry.prefix == route { r.remoteRoutes[i].resourceID = resourceID log.Tracef("resourceID for route %v updated to %s in remote routes", route, resourceID) return } } // append and sort descending by prefix bits. r.remoteRoutes = append(r.remoteRoutes, routeEntry{prefix: route, resourceID: resourceID}) sort.Slice(r.remoteRoutes, func(i, j int) bool { return r.remoteRoutes[i].prefix.Bits() > r.remoteRoutes[j].prefix.Bits() }) } func (r *routeIDLookup) RemoveRemoteRouteID(route netip.Prefix) { r.remoteLock.Lock() defer r.remoteLock.Unlock() for i, entry := range r.remoteRoutes { if entry.prefix == route { r.remoteRoutes = append(r.remoteRoutes[:i], r.remoteRoutes[i+1:]...) return } } } func (r *routeIDLookup) AddResolvedIP(resourceID route.ResID, route netip.Prefix) { r.resolvedIPs.Store(route.Addr(), resourceID) } func (r *routeIDLookup) RemoveResolvedIP(route netip.Prefix) { r.resolvedIPs.Delete(route.Addr()) } // Lookup returns the resource ID for the given IP address // and a bool indicating if the IP is an exit node. func (r *routeIDLookup) Lookup(ip netip.Addr) (route.ResID, bool) { if res, ok := r.resolvedIPs.Load(ip); ok { return res.(route.ResID), false } var resourceID route.ResID var isExitNode bool r.localLock.RLock() for _, entry := range r.localRoutes { if entry.prefix.Contains(ip) { resourceID = entry.resourceID isExitNode = entry.prefix.Bits() == 0 break } } r.localLock.RUnlock() if resourceID == "" { r.remoteLock.RLock() for _, entry := range r.remoteRoutes { if entry.prefix.Contains(ip) { resourceID = entry.resourceID isExitNode = entry.prefix.Bits() == 0 break } } r.remoteLock.RUnlock() } return resourceID, isExitNode }