mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-10-12 01:58:46 +02:00
129 lines
5.0 KiB
Go
129 lines
5.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"net"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
ctrld "github.com/Control-D-Inc/ctrld"
|
|
"tailscale.com/net/netmon"
|
|
)
|
|
|
|
func TestNetmonMinorAndWakeSnapshotReconciliation(t *testing.T) {
|
|
for _, timeJump := range []bool{false, true} {
|
|
t.Run(map[bool]string{false: "minor", true: "time_jump"}[timeJump], func(t *testing.T) {
|
|
sourceTestGlobals(t)
|
|
mon := netmon.NewStatic()
|
|
// NewStatic has no monitoring resources to close.
|
|
old := mon.InterfaceState()
|
|
next := *old
|
|
// An identical snapshot is non-major; NewStatic has no OS monitor for IsMajorChangeFrom.
|
|
major := false
|
|
networkChangeValidInterfacesFn = func() map[string]struct{} { return map[string]struct{}{} }
|
|
calls := 0
|
|
networkChangeIgnoredInterceptFn = func(*prog, *netmon.ChangeDelta, time.Time) { calls++ }
|
|
p := &prog{cfg: &ctrld.Config{}, dnsInterceptState: &interceptStateStub{}}
|
|
delta := &netmon.ChangeDelta{Monitor: mon, Old: old, New: &next, TimeJumped: timeJump}
|
|
//lint:ignore SA1019 This fixture reproduces netmon v1.74.0's legacy wake flag.
|
|
delta.Major = timeJump
|
|
p.handleNetworkChange(delta, major)
|
|
if calls != 1 {
|
|
t.Fatalf("real netmon non-major delta discarded: ignored-path calls=%d transition ID=%d", calls, p.networkTransitionGen.Load())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIgnoredDeltaPreservesAcceptedRecovery(t *testing.T) {
|
|
sourceTestGlobals(t)
|
|
dnsIntercept = false
|
|
oldRecovery := handleRecoveryForTransitionFn
|
|
t.Cleanup(func() { handleRecoveryForTransitionFn = oldRecovery })
|
|
calls := make(chan uint64, 2)
|
|
handleRecoveryForTransitionFn = func(_ *prog, _ RecoveryReason, id uint64) { calls <- id }
|
|
networkChangeReconcileFn = (*prog).reconcileNetworkChange // Real accepted-path wiring, with recovery stubbed below.
|
|
p := &prog{cfg: &ctrld.Config{}}
|
|
a := sourceTestState("en0", true, "192.0.2.20/24")
|
|
p.handleNetworkChange(&netmon.ChangeDelta{Old: sourceTestState("en0", true, "192.0.2.10/24"), New: a}, true)
|
|
b := sourceTestState("en0", true, "192.0.2.20/24")
|
|
p.handleNetworkChange(&netmon.ChangeDelta{Old: a, New: b}, false)
|
|
select {
|
|
case <-calls:
|
|
case <-time.After(1200 * time.Millisecond):
|
|
t.Fatal("ignored delta canceled accepted recovery; no replacement was scheduled")
|
|
}
|
|
}
|
|
|
|
func TestIgnoredDeltaPreservesValidPendingSource(t *testing.T) {
|
|
sourceTestGlobals(t)
|
|
dnsIntercept = false
|
|
ctrld.SetDefaultLocalIPv4(nil)
|
|
ctrld.SetDefaultLocalIPv6(nil)
|
|
entered, release, done := make(chan struct{}), make(chan struct{}), make(chan struct{})
|
|
networkChangeDefaultRouteIPFn = func() string { close(entered); <-release; return "192.0.2.20" }
|
|
reconciled := make(chan uint64, 2)
|
|
networkChangeReconcileFn = func(_ *prog, id uint64) { reconciled <- id }
|
|
p := &prog{cfg: &ctrld.Config{}}
|
|
a := sourceTestState("en0", true, "192.0.2.20/24")
|
|
go func() {
|
|
defer close(done)
|
|
p.handleNetworkChange(&netmon.ChangeDelta{Old: sourceTestState("en0", true, "192.0.2.10/24"), New: a}, true)
|
|
}()
|
|
<-entered
|
|
p.handleNetworkChange(&netmon.ChangeDelta{Old: a, New: sourceTestState("en0", true, "192.0.2.20/24")}, false)
|
|
close(release)
|
|
<-done
|
|
if !ctrld.GetDefaultLocalIPv4().Equal(net.ParseIP("192.0.2.20")) || len(reconciled) != 1 {
|
|
t.Fatalf("valid source discarded after ignored delta: source=%v reconciliations=%d", ctrld.GetDefaultLocalIPv4(), len(reconciled))
|
|
}
|
|
}
|
|
|
|
func TestNewAcceptedDeltaSupersedesPendingAcceptedSource(t *testing.T) {
|
|
sourceTestGlobals(t)
|
|
dnsIntercept = false
|
|
entered, release, done := make(chan struct{}), make(chan struct{}), make(chan struct{})
|
|
var lookups atomic.Int32
|
|
networkChangeDefaultRouteIPFn = func() string {
|
|
if lookups.Add(1) == 1 {
|
|
close(entered)
|
|
<-release
|
|
return "192.0.2.20"
|
|
}
|
|
return ""
|
|
}
|
|
reconciled := make(chan uint64, 2)
|
|
networkChangeReconcileFn = func(_ *prog, id uint64) { reconciled <- id }
|
|
p := &prog{cfg: &ctrld.Config{}}
|
|
a := sourceTestState("en0", true, "192.0.2.20/24")
|
|
go func() {
|
|
defer close(done)
|
|
p.handleNetworkChange(&netmon.ChangeDelta{Old: sourceTestState("en0", true, "192.0.2.10/24"), New: a}, true)
|
|
}()
|
|
<-entered
|
|
p.handleNetworkChange(&netmon.ChangeDelta{Old: a, New: sourceTestState("en0", true, "192.0.2.30/24")}, true)
|
|
close(release)
|
|
<-done
|
|
if !ctrld.GetDefaultLocalIPv4().Equal(net.ParseIP("192.0.2.30")) || len(reconciled) != 1 || <-reconciled != 2 {
|
|
t.Fatal("older accepted delta replaced the newer source or recovery")
|
|
}
|
|
}
|
|
|
|
func TestSourceCommitUsesCurrentMajorEpoch(t *testing.T) {
|
|
mon := netmon.NewStatic()
|
|
latest := mon.InterfaceState()
|
|
minor := *latest
|
|
p := &prog{networkSourceState: &minor, networkSourceEpoch: latest}
|
|
delta := &netmon.ChangeDelta{Monitor: mon, Old: latest, New: &minor}
|
|
if p.sourceCommitState(delta) != &minor {
|
|
t.Fatal("discarded the observed minor update within the current epoch")
|
|
}
|
|
p.networkSourceEpoch = &minor
|
|
if p.sourceCommitState(delta) != latest {
|
|
t.Fatal("did not refresh from a newer major epoch")
|
|
}
|
|
if networkSnapshotCurrent(&netmon.ChangeDelta{Monitor: mon, Old: &minor, New: &minor}, false, latest) {
|
|
t.Fatal("accepted a minor callback from an obsolete major epoch")
|
|
}
|
|
}
|