mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-03 23:56:38 +00:00
Add gw change polling and time drift detection (informational only)
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net/netip"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -46,14 +47,22 @@ func checkChange(ctx context.Context, nexthopv4, nexthopv6 systemops.Nexthop) er
|
||||
close(wakeUp)
|
||||
}()
|
||||
|
||||
gatewayChanged := make(chan string)
|
||||
go func() {
|
||||
gatewayPoll(ctx, nexthopv4, nexthopv6, gatewayChanged)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-routeChanged:
|
||||
log.Infof("route change detected")
|
||||
log.Infof("route change detected via routing socket")
|
||||
return nil
|
||||
case <-wakeUp:
|
||||
log.Infof("wakeup detected")
|
||||
log.Infof("wakeup detected via sleep hash change")
|
||||
return nil
|
||||
case reason := <-gatewayChanged:
|
||||
log.Infof("gateway change detected via polling: %s", reason)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -162,6 +171,9 @@ func wakeUpListen(ctx context.Context) {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
lastCheck := time.Now()
|
||||
const maxTickerDrift = 1 * time.Minute
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -169,14 +181,42 @@ func wakeUpListen(ctx context.Context) {
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
now := time.Now()
|
||||
elapsed := now.Sub(lastCheck)
|
||||
|
||||
// If more time passed than expected, system likely slept (informational only)
|
||||
if elapsed > maxTickerDrift {
|
||||
upOut, err := exec.Command("uptime").Output()
|
||||
if err != nil {
|
||||
log.Errorf("failed to run uptime command: %v", err)
|
||||
upOut = []byte("unknown")
|
||||
}
|
||||
log.Infof("Time drift detected (potential wakeup): expected ~5s, actual %s, uptime: %s", elapsed, upOut)
|
||||
|
||||
currentV4, errV4 := systemops.GetNextHop(netip.IPv4Unspecified())
|
||||
currentV6, errV6 := systemops.GetNextHop(netip.IPv6Unspecified())
|
||||
if errV4 == nil {
|
||||
log.Infof("Current IPv4 default gateway: %s via %s", currentV4.IP, currentV4.Intf.Name)
|
||||
} else {
|
||||
log.Debugf("No IPv4 default gateway: %v", errV4)
|
||||
}
|
||||
if errV6 == nil {
|
||||
log.Infof("Current IPv6 default gateway: %s via %s", currentV6.IP, currentV6.Intf.Name)
|
||||
} else {
|
||||
log.Debugf("No IPv6 default gateway: %v", errV6)
|
||||
}
|
||||
}
|
||||
|
||||
newHash, err := readSleepTimeHash()
|
||||
if err != nil {
|
||||
log.Errorf("failed to read sleep time hash: %v", err)
|
||||
lastCheck = now
|
||||
continue
|
||||
}
|
||||
|
||||
if newHash == initialHash {
|
||||
log.Infof("no wakeup detected")
|
||||
log.Debugf("no wakeup detected (hash unchanged: %d, time drift: %s)", initialHash, elapsed)
|
||||
lastCheck = now
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -185,7 +225,21 @@ func wakeUpListen(ctx context.Context) {
|
||||
log.Errorf("failed to run uptime command: %v", err)
|
||||
upOut = []byte("unknown")
|
||||
}
|
||||
log.Infof("Wakeup detected: %d -> %d, uptime: %s", initialHash, newHash, upOut)
|
||||
log.Infof("Wakeup detected via hash change: %d -> %d, uptime: %s", initialHash, newHash, upOut)
|
||||
|
||||
currentV4, errV4 := systemops.GetNextHop(netip.IPv4Unspecified())
|
||||
currentV6, errV6 := systemops.GetNextHop(netip.IPv6Unspecified())
|
||||
if errV4 == nil {
|
||||
log.Infof("Current IPv4 default gateway after wakeup: %s via %s", currentV4.IP, currentV4.Intf.Name)
|
||||
} else {
|
||||
log.Debugf("No IPv4 default gateway after wakeup: %v", errV4)
|
||||
}
|
||||
if errV6 == nil {
|
||||
log.Infof("Current IPv6 default gateway after wakeup: %s via %s", currentV6.IP, currentV6.Intf.Name)
|
||||
} else {
|
||||
log.Debugf("No IPv6 default gateway after wakeup: %v", errV6)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -207,9 +261,84 @@ func readSleepTimeHash() (uint32, error) {
|
||||
}
|
||||
|
||||
func hash(data []byte) (uint32, error) {
|
||||
hasher := fnv.New32a() // Create a new 32-bit FNV-1a hasher
|
||||
hasher := fnv.New32a()
|
||||
if _, err := hasher.Write(data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return hasher.Sum32(), nil
|
||||
}
|
||||
|
||||
// gatewayPoll polls the default gateway every 5 seconds to detect changes that might be missed by routing socket or wake-up detection.
|
||||
func gatewayPoll(ctx context.Context, initialV4, initialV6 systemops.Nexthop, changed chan<- string) {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
log.Infof("Gateway polling started - initial v4: %s via %v, v6: %s via %v",
|
||||
initialV4.IP, initialV4.Intf, initialV6.IP, initialV6.Intf)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Debug("context canceled, stopping gateway polling")
|
||||
return
|
||||
|
||||
case <-ticker.C:
|
||||
currentV4, errV4 := systemops.GetNextHop(netip.IPv4Unspecified())
|
||||
currentV6, errV6 := systemops.GetNextHop(netip.IPv6Unspecified())
|
||||
|
||||
var reason string
|
||||
|
||||
if errV4 == nil && initialV4.IP.IsValid() {
|
||||
if currentV4.IP.Compare(initialV4.IP) != 0 {
|
||||
reason = fmt.Sprintf("IPv4 gateway changed from %s to %s", initialV4.IP, currentV4.IP)
|
||||
log.Infof("Gateway poll detected change: %s", reason)
|
||||
changed <- reason
|
||||
return
|
||||
}
|
||||
if initialV4.Intf != nil && currentV4.Intf != nil && currentV4.Intf.Name != initialV4.Intf.Name {
|
||||
reason = fmt.Sprintf("IPv4 interface changed from %s to %s", initialV4.Intf.Name, currentV4.Intf.Name)
|
||||
log.Infof("Gateway poll detected change: %s", reason)
|
||||
changed <- reason
|
||||
return
|
||||
}
|
||||
} else if errV4 == nil && !initialV4.IP.IsValid() {
|
||||
reason = "IPv4 gateway appeared"
|
||||
log.Infof("Gateway poll detected change: %s (new: %s)", reason, currentV4.IP)
|
||||
changed <- reason
|
||||
return
|
||||
} else if errV4 != nil && initialV4.IP.IsValid() {
|
||||
reason = "IPv4 gateway disappeared"
|
||||
log.Infof("Gateway poll detected change: %s", reason)
|
||||
changed <- reason
|
||||
return
|
||||
}
|
||||
|
||||
if errV6 == nil && initialV6.IP.IsValid() {
|
||||
if currentV6.IP.Compare(initialV6.IP) != 0 {
|
||||
reason = fmt.Sprintf("IPv6 gateway changed from %s to %s", initialV6.IP, currentV6.IP)
|
||||
log.Infof("Gateway poll detected change: %s", reason)
|
||||
changed <- reason
|
||||
return
|
||||
}
|
||||
if initialV6.Intf != nil && currentV6.Intf != nil && currentV6.Intf.Name != initialV6.Intf.Name {
|
||||
reason = fmt.Sprintf("IPv6 interface changed from %s to %s", initialV6.Intf.Name, currentV6.Intf.Name)
|
||||
log.Infof("Gateway poll detected change: %s", reason)
|
||||
changed <- reason
|
||||
return
|
||||
}
|
||||
} else if errV6 == nil && !initialV6.IP.IsValid() {
|
||||
reason = "IPv6 gateway appeared"
|
||||
log.Infof("Gateway poll detected change: %s (new: %s)", reason, currentV6.IP)
|
||||
changed <- reason
|
||||
return
|
||||
} else if errV6 != nil && initialV6.IP.IsValid() {
|
||||
reason = "IPv6 gateway disappeared"
|
||||
log.Infof("Gateway poll detected change: %s", reason)
|
||||
changed <- reason
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Gateway poll: no change detected")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user