mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-30 03:21:29 +02:00
[client] Skip engine restart when default route is unchanged
After the network monitor's debounce window, re-check the default next hop before triggering a client restart. A flapping NIC that returns to the same default route no longer forces a restart, avoiding redundant sync stream reconnects and peer meta churn.
This commit is contained in:
@@ -97,7 +97,12 @@ func (nw *NetworkMonitor) Listen(ctx context.Context) (err error) {
|
||||
case <-event:
|
||||
timer.Reset(debounceTime)
|
||||
case <-timer.C:
|
||||
return nil
|
||||
// A flapping NIC may return to the same default route after the
|
||||
// debounce window; only restart if the next hop actually changed.
|
||||
if nexthopChanged(nexthop4, nexthop6) {
|
||||
return nil
|
||||
}
|
||||
log.Debug("Network monitor: default route unchanged after debounce, ignoring")
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return ctx.Err()
|
||||
@@ -134,3 +139,28 @@ func (nw *NetworkMonitor) checkChanges(ctx context.Context, event chan struct{},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nexthopChanged reports whether the current default next hop differs from the
|
||||
// given baseline. A lookup error is treated as a change so we fail safe and
|
||||
// restart rather than miss a real network change.
|
||||
func nexthopChanged(oldv4, oldv6 systemops.Nexthop) bool {
|
||||
newv4, errv4 := systemops.GetNextHop(netip.IPv4Unspecified())
|
||||
newv6, errv6 := systemops.GetNextHop(netip.IPv6Unspecified())
|
||||
if errv4 != nil || errv6 != nil {
|
||||
return true
|
||||
}
|
||||
return !sameNexthop(oldv4, newv4) || !sameNexthop(oldv6, newv6)
|
||||
}
|
||||
|
||||
func sameNexthop(a, b systemops.Nexthop) bool {
|
||||
if a.IP != b.IP {
|
||||
return false
|
||||
}
|
||||
if (a.Intf == nil) != (b.Intf == nil) {
|
||||
return false
|
||||
}
|
||||
if a.Intf != nil && a.Intf.Index != b.Intf.Index {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package networkmonitor
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -97,3 +99,31 @@ func TestNetworkMonitor_MultiEvent(t *testing.T) {
|
||||
t.Errorf("unexpected duration: %v", time.Since(started))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSameNexthop(t *testing.T) {
|
||||
eth0 := &net.Interface{Index: 1, Name: "eth0"}
|
||||
eth1 := &net.Interface{Index: 2, Name: "eth1"}
|
||||
ip1 := netip.MustParseAddr("192.168.1.1")
|
||||
ip2 := netip.MustParseAddr("192.168.2.1")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a, b systemops.Nexthop
|
||||
want bool
|
||||
}{
|
||||
{"identical", systemops.Nexthop{IP: ip1, Intf: eth0}, systemops.Nexthop{IP: ip1, Intf: eth0}, true},
|
||||
{"same index different pointer", systemops.Nexthop{IP: ip1, Intf: eth0}, systemops.Nexthop{IP: ip1, Intf: &net.Interface{Index: 1, Name: "eth0"}}, true},
|
||||
{"different ip", systemops.Nexthop{IP: ip1, Intf: eth0}, systemops.Nexthop{IP: ip2, Intf: eth0}, false},
|
||||
{"different interface", systemops.Nexthop{IP: ip1, Intf: eth0}, systemops.Nexthop{IP: ip1, Intf: eth1}, false},
|
||||
{"both nil interface", systemops.Nexthop{IP: ip1}, systemops.Nexthop{IP: ip1}, true},
|
||||
{"one nil interface", systemops.Nexthop{IP: ip1, Intf: eth0}, systemops.Nexthop{IP: ip1}, false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := sameNexthop(tt.a, tt.b); got != tt.want {
|
||||
t.Errorf("sameNexthop() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user