mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-04 04:29:06 +02:00
Move the WireGuard handshake watcher out of the peer package into client/internal/peer/wg_watcher. The test stays an internal test since it drives the unexported checkPeriod. Update conn.go to reference the watcher through the package.
176 lines
4.6 KiB
Go
176 lines
4.6 KiB
Go
package wg_watcher
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/client/iface/configurer"
|
|
"github.com/netbirdio/netbird/client/internal/peer/state_dump"
|
|
"github.com/netbirdio/netbird/client/internal/peer/status"
|
|
)
|
|
|
|
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, "", state_dump.NewStateDump("peer", mlog, &status.Recorder{}))
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
watcher.PrepareInitialHandshake()
|
|
|
|
firstHandshake := make(chan struct{}, 1)
|
|
checkSuccess := make(chan struct{}, 1)
|
|
watcherDone := make(chan struct{})
|
|
go func() {
|
|
defer close(watcherDone)
|
|
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:
|
|
}
|
|
|
|
// Wait for the watcher goroutine to exit so it cannot race with other
|
|
// tests mutating the package-level check timing variables.
|
|
cancel()
|
|
<-watcherDone
|
|
}
|
|
|
|
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, "", state_dump.NewStateDump("peer", mlog, &status.Recorder{}))
|
|
|
|
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, "", state_dump.NewStateDump("peer", mlog, &status.Recorder{}))
|
|
|
|
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")
|
|
}
|
|
}
|