mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-17 20:19:55 +00:00
The synthesized "-v6" exit route shares its v4 base's NetID plus a "-v6" suffix. Selection state was reconciled at read time via effectiveNetID, a mirror that could only be applied on exit-node code paths, which forced a parallel IsSelectedForExitNode() alongside IsSelected() and a clearPairedV6Locked() orphan cleanup on every toggle. That machinery still missed the case observed in the field: a persisted state with the v4 base deselected but its "-v6" sibling explicitly selected (orphaned). Because effectiveNetID returns the v6 entry itself once it carries explicit state, and clearPairedV6Locked only fires on a live toggle, the loaded orphan survived and the ::/0 route leaked onto the tunnel despite the exit node being disabled, breaking IPv6 (happy eyeballs). Treat the v4/v6 exit pair as a single toggle and keep state consistent at write time instead. RouteSelector.SyncPairedSelection forces the "-v6" entry to match its v4 base unconditionally, resetting any orphaned explicit state. The route manager, which knows the route prefixes, computes the pairs (V6ExitMergeSet) and calls it from updateRouteSelectorFromManagement before selection is read, so both collectExitNodeInfo and FilterSelectedExitNodes see consistent state, including pairs loaded from persisted selector state. This removes effectiveNetID, IsSelectedForExitNode and clearPairedV6Locked; the selector is literal again and no longer needs the "exit-node paths only" caveat. HasUserSelectionForRoute and applyExitNodeFilter use the raw NetID. Adds a selector test for SyncPairedSelection (including the orphaned-v6 case) and a route-manager test reproducing the persisted-orphan scenario from the field log.