package peer import ( "context" "sync" "testing" "time" log "github.com/sirupsen/logrus" "github.com/netbirdio/netbird/client/iface/configurer" ) type MocWgIface struct { stop bool } func (m *MocWgIface) GetStats() (map[string]configurer.WGStats, error) { return map[string]configurer.WGStats{}, nil } func (m *MocWgIface) disconnect() { m.stop = true } type mockHandshakeStats struct { mu sync.Mutex handshake time.Time } func (m *mockHandshakeStats) GetStats() (map[string]configurer.WGStats, error) { m.mu.Lock() defer m.mu.Unlock() return map[string]configurer.WGStats{"": {LastHandshake: m.handshake}}, nil } func (m *mockHandshakeStats) advance() { m.mu.Lock() defer m.mu.Unlock() m.handshake = time.Now() } // TestWGWatcher_CheckSuccessCallback: onCheckSuccessFn must fire for a fresh // handshake even when the watcher started with an existing handshake baseline, // the case where onHandshakeSuccessFn stays silent. func TestWGWatcher_CheckSuccessCallback(t *testing.T) { // checkPeriod bounds how stale a handshake may be before the watcher treats it // as a suspended-machine timeout. The first check fires after wgHandshakeOvertime, // so keep checkPeriod well above any scheduling jitter to avoid a false timeout // converting the expected success into a disconnect on a loaded runner. checkPeriod = 1 * time.Minute wgHandshakeOvertime = 1 * time.Second mlog := log.WithField("peer", "tet") // Use an old baseline so advance() yields a strictly newer handshake even on // platforms with coarse clock resolution (Windows), where two time.Now() calls // microseconds apart can return the same instant and read as a timed-out handshake. stats := &mockHandshakeStats{handshake: time.Now().Add(-time.Hour)} watcher := NewWGWatcher(mlog, stats, "", newStateDump("peer", mlog, &Status{})) ctx, cancel := context.WithCancel(context.Background()) defer cancel() watcher.PrepareInitialHandshake() firstHandshake := make(chan struct{}, 1) checkSuccess := make(chan struct{}, 1) go watcher.EnableWgWatcher(ctx, time.Now(), func() {}, func(when time.Time) { firstHandshake <- struct{}{} }, func() { select { case checkSuccess <- struct{}{}: default: } }) stats.advance() select { case <-checkSuccess: case <-time.After(10 * time.Second): t.Errorf("timeout waiting for check success callback") } select { case <-firstHandshake: t.Errorf("first-handshake callback must not fire for a non-zero baseline") default: } } func TestWGWatcher_EnableWgWatcher(t *testing.T) { checkPeriod = 5 * time.Second wgHandshakeOvertime = 1 * time.Second mlog := log.WithField("peer", "tet") mocWgIface := &MocWgIface{} watcher := NewWGWatcher(mlog, mocWgIface, "", newStateDump("peer", mlog, &Status{})) ctx, cancel := context.WithCancel(context.Background()) defer cancel() watcher.PrepareInitialHandshake() onDisconnected := make(chan struct{}, 1) go watcher.EnableWgWatcher(ctx, time.Now(), func() { mlog.Infof("onDisconnectedFn") onDisconnected <- struct{}{} }, func(when time.Time) { mlog.Infof("onHandshakeSuccess: %v", when) }, nil) // wait for initial reading time.Sleep(2 * time.Second) mocWgIface.disconnect() select { case <-onDisconnected: case <-time.After(10 * time.Second): t.Errorf("timeout") } } func TestWGWatcher_ReEnable(t *testing.T) { checkPeriod = 5 * time.Second wgHandshakeOvertime = 1 * time.Second mlog := log.WithField("peer", "tet") mocWgIface := &MocWgIface{} watcher := NewWGWatcher(mlog, mocWgIface, "", newStateDump("peer", mlog, &Status{})) ctx, cancel := context.WithCancel(context.Background()) watcher.PrepareInitialHandshake() wg := &sync.WaitGroup{} wg.Add(1) go func() { defer wg.Done() watcher.EnableWgWatcher(ctx, time.Now(), func() {}, func(when time.Time) {}, nil) }() cancel() wg.Wait() // Re-enable with a new context ctx, cancel = context.WithCancel(context.Background()) defer cancel() watcher.PrepareInitialHandshake() onDisconnected := make(chan struct{}, 1) go watcher.EnableWgWatcher(ctx, time.Now(), func() { onDisconnected <- struct{}{} }, func(when time.Time) {}, nil) time.Sleep(2 * time.Second) mocWgIface.disconnect() select { case <-onDisconnected: case <-time.After(10 * time.Second): t.Errorf("timeout") } }