mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-19 11:59:09 +02:00
* [client] Do not move an HA route on latency before both peers have a sample A direct connection reports no latency until the first sample of its new ICE agent, and a drop to relay clears the previous one. Route selection ranked such a peer at unknownLatency, so a routing peer whose direct connection came back within the relayed switch delay lost the route to an equivalent peer on a "lower latency" switch right after recovering, resetting every masqueraded flow the delay was meant to keep alive. The sample usually lands before the evaluation, but nothing orders the two, and a pair with no round trip time yet postpones it to the next sampling tick. Latency-only switches now require a sample on both peers. Categorical criteria (connected, metric, direct versus relayed) are unaffected. * [client] Hold a never-direct HA routing peer before a direct peer takes over The peers of an HA group come up over relay first and turn direct one after the other, as when lazy connections wake the group or the client reconnects. A routing peer that was relayed when it carried the route gave way at once to the first equivalent peer that turned direct, so in a group of N peers the route moved right after the session started in roughly (N-1)/N of the cases, resetting every masqueraded flow, typically an SSH session opened on the wake-up traffic. Such a peer is now kept for the relayedSwitch delay, counted from the first evaluation that finds a direct contender. If it turns direct within the delay the route stays; otherwise it moves when the delay runs out, so the route does not stay on a relay for longer than the delay. A peer that dropped from direct keeps counting from the drop. * [client] Keep allowed IPs when only the chosen routing peer's status changes recalculateRoutes did not tell a status change of the chosen routing peer (idle to connected when traffic wakes it, connected to relayed and back) from a switch to another peer: it removed the allowed IPs and added them back on the same peer. In between the prefix had no owner and packets for it were dropped, and default routes published a spurious "disconnected due to high availability change" event followed by two "connected" ones. When the same route stays on the same peer with the same definition, only the recorded status is updated now. * [client] Limit the "never" relayed switch policy to peers that dropped from direct NB_HA_RELAYED_SWITCH_DELAY=never keeps a routing peer whose direct connection dropped to relay for as long as it stays connected. Once the relayed delay also covered peers that were never direct, "never" pinned those to the relay for good, although a direct equivalent was available and the peer showed no sign of turning direct. A peer that was never direct now uses the default delay under "never", which keeps the policy's original scope. * [client] Merge the unchanged-route checks of the HA route watcher shouldSkipRecalculation and the status-only fast path in recalculateRoutes both handled the same route staying on the same routing peer, one for an unchanged status and one for a changed one. They are now a single keepChosenRoute check. Pure refactor, no behavior change. * [client] Re-apply routed allowed IPs when a kept routing peer connects A peer update can recreate a routing peer: modifyPeers removes it from the interface, which drops its routed prefixes, and adds it back idle with its overlay addresses only. The remove and re-add that recalculateRoutes used to run on every status change of the chosen peer put the prefixes back as a side effect once the peer connected. Without it, a watcher that sees the same peer go idle and then connected leaves the routed subnet without an owner until a route update or an HA switch. When the chosen peer connects while keeping the route, the watcher now re-applies its routed allowed IPs through the route manager's reconcile, which is add-only and idempotent. Lazy connections already do the same when they arm an idle peer. * [client] Bound the HA route hold on a routing peer without a latency sample A latency-only switch waited for a sample on both peers. If the current peer never got one, because its candidate pair stats kept failing or reporting no round trip time, it kept the route indefinitely against a measured peer, however much faster that one was. The hold on an unsampled current peer now lasts unmeasuredLatencyHold (30s), long enough for a recovered direct connection to report its first sample. After that the peer is ranked at unknownLatency again and the usual switch margins apply. relayedHold is armed so the switch is reconsidered when the hold runs out. * [client] Restart the relayed delay of a never-direct peer when its contender goes For a routing peer that was never direct, the relayed delay runs from the first evaluation that finds a direct contender. It was only reset when the current peer changed or turned direct, so if that contender went away and another one showed up later, the delay counted from the first one and the new contender took over at once. The delay of such a peer now restarts once no direct contender is left. The delay of a peer that dropped from direct keeps running from the drop. * [client] Retrigger pull request synchronization * [client] Retry a failed routed allowed IPs reconcile on the next evaluation keepChosenRoute recorded the connected status before re-applying the routed allowed IPs and only logged a failed reconcile. Later evaluations then saw an unchanged status and never retried, so a transient AddAllowedIP failure left the recreated routing peer without its routed prefixes until another status transition or a route switch. The status is now recorded only once the reconcile succeeds. A failure leaves the previous status in place, so the next evaluation, at the latest the periodic re-evaluation, sees the change again and retries.