2024-01-23 09:37:32 -08:00
|
|
|
// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package magicsock
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"net/netip"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-08-13 13:13:11 -07:00
|
|
|
"tailscale.com/net/packet"
|
2025-06-27 13:56:55 -07:00
|
|
|
"tailscale.com/tailcfg"
|
2025-07-14 15:09:31 -07:00
|
|
|
"tailscale.com/tstime/mono"
|
2024-01-23 09:37:32 -08:00
|
|
|
"tailscale.com/types/key"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestProbeUDPLifetimeConfig_Equals(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
a *ProbeUDPLifetimeConfig
|
|
|
|
|
b *ProbeUDPLifetimeConfig
|
|
|
|
|
want bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
"both sides nil",
|
|
|
|
|
nil,
|
|
|
|
|
nil,
|
|
|
|
|
true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"equal pointers",
|
|
|
|
|
defaultProbeUDPLifetimeConfig,
|
|
|
|
|
defaultProbeUDPLifetimeConfig,
|
|
|
|
|
true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"a nil",
|
|
|
|
|
nil,
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"b nil",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
nil,
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Cliffs unequal",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second * 2},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"CycleCanStartEvery unequal",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second},
|
|
|
|
|
CycleCanStartEvery: time.Hour * 2,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
if got := tt.a.Equals(tt.b); got != tt.want {
|
|
|
|
|
t.Errorf("Equals() = %v, want %v", got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestProbeUDPLifetimeConfig_Valid(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
p *ProbeUDPLifetimeConfig
|
|
|
|
|
want bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
"default config valid",
|
|
|
|
|
defaultProbeUDPLifetimeConfig,
|
|
|
|
|
true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"no cliffs",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"zero CycleCanStartEvery",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second * 10},
|
|
|
|
|
CycleCanStartEvery: 0,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cliff too small",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{min(udpLifetimeProbeCliffSlack*2, heartbeatInterval)},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"duplicate Cliffs values",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second * 2, time.Second * 2},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"Cliffs not ascending",
|
|
|
|
|
&ProbeUDPLifetimeConfig{
|
|
|
|
|
Cliffs: []time.Duration{time.Second * 2, time.Second * 1},
|
|
|
|
|
CycleCanStartEvery: time.Hour,
|
|
|
|
|
},
|
|
|
|
|
false,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
if got := tt.p.Valid(); got != tt.want {
|
|
|
|
|
t.Errorf("Valid() = %v, want %v", got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func Test_endpoint_maybeProbeUDPLifetimeLocked(t *testing.T) {
|
types/key,wgengine/magicsock,control/controlclient,ipn: add debug disco key rotation
Adds the ability to rotate discovery keys on running clients, needed for
testing upcoming disco key distribution changes.
Introduces key.DiscoKey, an atomic container for a disco private key,
public key, and the public key's ShortString, replacing the prior
separate atomic fields.
magicsock.Conn has a new RotateDiscoKey method, and access to this is
provided via localapi and a CLI debug command.
Note that this implementation is primarily for testing as it stands, and
regular use should likely introduce an additional mechanism that allows
the old key to be used for some time, to provide a seamless key rotation
rather than one that invalidates all sessions.
Updates tailscale/corp#34037
Signed-off-by: James Tucker <james@tailscale.com>
2025-11-03 16:41:37 -08:00
|
|
|
var lowerPriv, higherPriv key.DiscoPrivate
|
2024-01-23 09:37:32 -08:00
|
|
|
var lower, higher key.DiscoPublic
|
types/key,wgengine/magicsock,control/controlclient,ipn: add debug disco key rotation
Adds the ability to rotate discovery keys on running clients, needed for
testing upcoming disco key distribution changes.
Introduces key.DiscoKey, an atomic container for a disco private key,
public key, and the public key's ShortString, replacing the prior
separate atomic fields.
magicsock.Conn has a new RotateDiscoKey method, and access to this is
provided via localapi and a CLI debug command.
Note that this implementation is primarily for testing as it stands, and
regular use should likely introduce an additional mechanism that allows
the old key to be used for some time, to provide a seamless key rotation
rather than one that invalidates all sessions.
Updates tailscale/corp#34037
Signed-off-by: James Tucker <james@tailscale.com>
2025-11-03 16:41:37 -08:00
|
|
|
privA := key.NewDisco()
|
|
|
|
|
privB := key.NewDisco()
|
|
|
|
|
a := privA.Public()
|
|
|
|
|
b := privB.Public()
|
2024-01-23 09:37:32 -08:00
|
|
|
if a.String() < b.String() {
|
|
|
|
|
lower = a
|
|
|
|
|
higher = b
|
types/key,wgengine/magicsock,control/controlclient,ipn: add debug disco key rotation
Adds the ability to rotate discovery keys on running clients, needed for
testing upcoming disco key distribution changes.
Introduces key.DiscoKey, an atomic container for a disco private key,
public key, and the public key's ShortString, replacing the prior
separate atomic fields.
magicsock.Conn has a new RotateDiscoKey method, and access to this is
provided via localapi and a CLI debug command.
Note that this implementation is primarily for testing as it stands, and
regular use should likely introduce an additional mechanism that allows
the old key to be used for some time, to provide a seamless key rotation
rather than one that invalidates all sessions.
Updates tailscale/corp#34037
Signed-off-by: James Tucker <james@tailscale.com>
2025-11-03 16:41:37 -08:00
|
|
|
lowerPriv = privA
|
|
|
|
|
higherPriv = privB
|
2024-01-23 09:37:32 -08:00
|
|
|
} else {
|
|
|
|
|
lower = b
|
|
|
|
|
higher = a
|
types/key,wgengine/magicsock,control/controlclient,ipn: add debug disco key rotation
Adds the ability to rotate discovery keys on running clients, needed for
testing upcoming disco key distribution changes.
Introduces key.DiscoKey, an atomic container for a disco private key,
public key, and the public key's ShortString, replacing the prior
separate atomic fields.
magicsock.Conn has a new RotateDiscoKey method, and access to this is
provided via localapi and a CLI debug command.
Note that this implementation is primarily for testing as it stands, and
regular use should likely introduce an additional mechanism that allows
the old key to be used for some time, to provide a seamless key rotation
rather than one that invalidates all sessions.
Updates tailscale/corp#34037
Signed-off-by: James Tucker <james@tailscale.com>
2025-11-03 16:41:37 -08:00
|
|
|
lowerPriv = privB
|
|
|
|
|
higherPriv = privA
|
2024-01-23 09:37:32 -08:00
|
|
|
}
|
wgengine/magicsock: make endpoint.bestAddr Geneve-aware (#16195)
This commit adds a new type to magicsock, epAddr, which largely ends up
replacing netip.AddrPort in packet I/O paths throughout, enabling
Geneve encapsulation over UDP awareness.
The conn.ReceiveFunc for UDP has been revamped to fix and more clearly
distinguish the different classes of packets we expect to receive: naked
STUN binding messages, naked disco, naked WireGuard, Geneve-encapsulated
disco, and Geneve-encapsulated WireGuard.
Prior to this commit, STUN matching logic in the RX path could swallow
a naked WireGuard packet if the keypair index, which is randomly
generated, happened to overlap with a subset of the STUN magic cookie.
Updates tailscale/corp#27502
Updates tailscale/corp#29326
Signed-off-by: Jordan Whited <jordan@tailscale.com>
2025-06-06 09:46:29 -07:00
|
|
|
addr := addrQuality{epAddr: epAddr{ap: netip.MustParseAddrPort("1.1.1.1:1")}}
|
2024-01-23 09:37:32 -08:00
|
|
|
newProbeUDPLifetime := func() *probeUDPLifetime {
|
|
|
|
|
return &probeUDPLifetime{
|
|
|
|
|
config: *defaultProbeUDPLifetimeConfig,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
localDisco key.DiscoPublic
|
|
|
|
|
remoteDisco *key.DiscoPublic
|
|
|
|
|
probeUDPLifetimeFn func() *probeUDPLifetime
|
|
|
|
|
bestAddr addrQuality
|
|
|
|
|
wantAfterInactivityForFn func(*probeUDPLifetime) time.Duration
|
|
|
|
|
wantMaybe bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "nil probeUDPLifetime",
|
|
|
|
|
localDisco: higher,
|
|
|
|
|
remoteDisco: &lower,
|
|
|
|
|
probeUDPLifetimeFn: func() *probeUDPLifetime {
|
2024-01-23 09:37:32 -08:00
|
|
|
return nil
|
|
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
bestAddr: addr,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "local higher disco key",
|
|
|
|
|
localDisco: higher,
|
|
|
|
|
remoteDisco: &lower,
|
|
|
|
|
probeUDPLifetimeFn: newProbeUDPLifetime,
|
|
|
|
|
bestAddr: addr,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "remote no disco key",
|
|
|
|
|
localDisco: higher,
|
|
|
|
|
remoteDisco: nil,
|
|
|
|
|
probeUDPLifetimeFn: newProbeUDPLifetime,
|
|
|
|
|
bestAddr: addr,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "invalid bestAddr",
|
|
|
|
|
localDisco: lower,
|
|
|
|
|
remoteDisco: &higher,
|
|
|
|
|
probeUDPLifetimeFn: newProbeUDPLifetime,
|
|
|
|
|
bestAddr: addrQuality{},
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "cycle started too recently",
|
|
|
|
|
localDisco: lower,
|
|
|
|
|
remoteDisco: &higher,
|
|
|
|
|
probeUDPLifetimeFn: func() *probeUDPLifetime {
|
|
|
|
|
lt := newProbeUDPLifetime()
|
|
|
|
|
lt.cycleActive = false
|
|
|
|
|
lt.cycleStartedAt = time.Now()
|
|
|
|
|
return lt
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
bestAddr: addr,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "maybe cliff 0 cycle not active",
|
|
|
|
|
localDisco: lower,
|
|
|
|
|
remoteDisco: &higher,
|
|
|
|
|
probeUDPLifetimeFn: func() *probeUDPLifetime {
|
|
|
|
|
lt := newProbeUDPLifetime()
|
|
|
|
|
lt.cycleActive = false
|
|
|
|
|
lt.cycleStartedAt = time.Now().Add(-lt.config.CycleCanStartEvery).Add(-time.Second)
|
|
|
|
|
return lt
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
bestAddr: addr,
|
|
|
|
|
wantAfterInactivityForFn: func(lifetime *probeUDPLifetime) time.Duration {
|
2024-01-23 09:37:32 -08:00
|
|
|
return lifetime.config.Cliffs[0] - udpLifetimeProbeCliffSlack
|
|
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
wantMaybe: true,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "maybe cliff 0",
|
|
|
|
|
localDisco: lower,
|
|
|
|
|
remoteDisco: &higher,
|
|
|
|
|
probeUDPLifetimeFn: func() *probeUDPLifetime {
|
|
|
|
|
lt := newProbeUDPLifetime()
|
|
|
|
|
lt.cycleActive = true
|
|
|
|
|
lt.currentCliff = 0
|
|
|
|
|
return lt
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
bestAddr: addr,
|
|
|
|
|
wantAfterInactivityForFn: func(lifetime *probeUDPLifetime) time.Duration {
|
2024-01-23 09:37:32 -08:00
|
|
|
return lifetime.config.Cliffs[0] - udpLifetimeProbeCliffSlack
|
|
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
wantMaybe: true,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "maybe cliff 1",
|
|
|
|
|
localDisco: lower,
|
|
|
|
|
remoteDisco: &higher,
|
|
|
|
|
probeUDPLifetimeFn: func() *probeUDPLifetime {
|
|
|
|
|
lt := newProbeUDPLifetime()
|
|
|
|
|
lt.cycleActive = true
|
|
|
|
|
lt.currentCliff = 1
|
|
|
|
|
return lt
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
bestAddr: addr,
|
|
|
|
|
wantAfterInactivityForFn: func(lifetime *probeUDPLifetime) time.Duration {
|
2024-01-23 09:37:32 -08:00
|
|
|
return lifetime.config.Cliffs[1] - udpLifetimeProbeCliffSlack
|
|
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
wantMaybe: true,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
{
|
2025-10-17 11:13:14 +01:00
|
|
|
name: "maybe cliff 2",
|
|
|
|
|
localDisco: lower,
|
|
|
|
|
remoteDisco: &higher,
|
|
|
|
|
probeUDPLifetimeFn: func() *probeUDPLifetime {
|
|
|
|
|
lt := newProbeUDPLifetime()
|
|
|
|
|
lt.cycleActive = true
|
|
|
|
|
lt.currentCliff = 2
|
|
|
|
|
return lt
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
bestAddr: addr,
|
|
|
|
|
wantAfterInactivityForFn: func(lifetime *probeUDPLifetime) time.Duration {
|
2024-01-23 09:37:32 -08:00
|
|
|
return lifetime.config.Cliffs[2] - udpLifetimeProbeCliffSlack
|
|
|
|
|
},
|
2025-10-17 11:13:14 +01:00
|
|
|
wantMaybe: true,
|
2024-01-23 09:37:32 -08:00
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
types/key,wgengine/magicsock,control/controlclient,ipn: add debug disco key rotation
Adds the ability to rotate discovery keys on running clients, needed for
testing upcoming disco key distribution changes.
Introduces key.DiscoKey, an atomic container for a disco private key,
public key, and the public key's ShortString, replacing the prior
separate atomic fields.
magicsock.Conn has a new RotateDiscoKey method, and access to this is
provided via localapi and a CLI debug command.
Note that this implementation is primarily for testing as it stands, and
regular use should likely introduce an additional mechanism that allows
the old key to be used for some time, to provide a seamless key rotation
rather than one that invalidates all sessions.
Updates tailscale/corp#34037
Signed-off-by: James Tucker <james@tailscale.com>
2025-11-03 16:41:37 -08:00
|
|
|
c := &Conn{}
|
|
|
|
|
if tt.localDisco.IsZero() {
|
|
|
|
|
c.discoAtomic.Set(key.NewDisco())
|
|
|
|
|
} else if tt.localDisco.Compare(lower) == 0 {
|
|
|
|
|
c.discoAtomic.Set(lowerPriv)
|
|
|
|
|
} else if tt.localDisco.Compare(higher) == 0 {
|
|
|
|
|
c.discoAtomic.Set(higherPriv)
|
|
|
|
|
} else {
|
|
|
|
|
t.Fatalf("unexpected localDisco value")
|
|
|
|
|
}
|
2024-01-23 09:37:32 -08:00
|
|
|
de := &endpoint{
|
types/key,wgengine/magicsock,control/controlclient,ipn: add debug disco key rotation
Adds the ability to rotate discovery keys on running clients, needed for
testing upcoming disco key distribution changes.
Introduces key.DiscoKey, an atomic container for a disco private key,
public key, and the public key's ShortString, replacing the prior
separate atomic fields.
magicsock.Conn has a new RotateDiscoKey method, and access to this is
provided via localapi and a CLI debug command.
Note that this implementation is primarily for testing as it stands, and
regular use should likely introduce an additional mechanism that allows
the old key to be used for some time, to provide a seamless key rotation
rather than one that invalidates all sessions.
Updates tailscale/corp#34037
Signed-off-by: James Tucker <james@tailscale.com>
2025-11-03 16:41:37 -08:00
|
|
|
c: c,
|
2024-01-23 09:37:32 -08:00
|
|
|
bestAddr: tt.bestAddr,
|
|
|
|
|
}
|
|
|
|
|
if tt.remoteDisco != nil {
|
|
|
|
|
remote := &endpointDisco{
|
|
|
|
|
key: *tt.remoteDisco,
|
|
|
|
|
}
|
|
|
|
|
de.disco.Store(remote)
|
|
|
|
|
}
|
|
|
|
|
p := tt.probeUDPLifetimeFn()
|
|
|
|
|
de.probeUDPLifetime = p
|
|
|
|
|
gotAfterInactivityFor, gotMaybe := de.maybeProbeUDPLifetimeLocked()
|
2025-10-17 11:13:14 +01:00
|
|
|
var wantAfterInactivityFor time.Duration
|
|
|
|
|
if tt.wantAfterInactivityForFn != nil {
|
|
|
|
|
wantAfterInactivityFor = tt.wantAfterInactivityForFn(p)
|
|
|
|
|
}
|
2024-01-23 09:37:32 -08:00
|
|
|
if gotAfterInactivityFor != wantAfterInactivityFor {
|
|
|
|
|
t.Errorf("maybeProbeUDPLifetimeLocked() gotAfterInactivityFor = %v, want %v", gotAfterInactivityFor, wantAfterInactivityFor)
|
|
|
|
|
}
|
|
|
|
|
if gotMaybe != tt.wantMaybe {
|
|
|
|
|
t.Errorf("maybeProbeUDPLifetimeLocked() gotMaybe = %v, want %v", gotMaybe, tt.wantMaybe)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-27 13:56:55 -07:00
|
|
|
|
|
|
|
|
func Test_epAddr_isDirectUDP(t *testing.T) {
|
2025-08-13 13:13:11 -07:00
|
|
|
vni := packet.VirtualNetworkID{}
|
|
|
|
|
vni.Set(7)
|
2025-06-27 13:56:55 -07:00
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
ap netip.AddrPort
|
2025-08-13 13:13:11 -07:00
|
|
|
vni packet.VirtualNetworkID
|
2025-06-27 13:56:55 -07:00
|
|
|
want bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "true",
|
|
|
|
|
ap: netip.MustParseAddrPort("192.0.2.1:7"),
|
2025-08-13 13:13:11 -07:00
|
|
|
vni: packet.VirtualNetworkID{},
|
2025-06-27 13:56:55 -07:00
|
|
|
want: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "false derp magic addr",
|
|
|
|
|
ap: netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, 0),
|
2025-08-13 13:13:11 -07:00
|
|
|
vni: packet.VirtualNetworkID{},
|
2025-06-27 13:56:55 -07:00
|
|
|
want: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "false vni set",
|
|
|
|
|
ap: netip.MustParseAddrPort("192.0.2.1:7"),
|
|
|
|
|
vni: vni,
|
|
|
|
|
want: false,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
e := epAddr{
|
|
|
|
|
ap: tt.ap,
|
|
|
|
|
vni: tt.vni,
|
|
|
|
|
}
|
|
|
|
|
if got := e.isDirect(); got != tt.want {
|
|
|
|
|
t.Errorf("isDirect() = %v, want %v", got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-14 15:09:31 -07:00
|
|
|
|
|
|
|
|
func Test_endpoint_udpRelayEndpointReady(t *testing.T) {
|
|
|
|
|
directAddrQuality := addrQuality{epAddr: epAddr{ap: netip.MustParseAddrPort("192.0.2.1:7")}}
|
|
|
|
|
peerRelayAddrQuality := addrQuality{epAddr: epAddr{ap: netip.MustParseAddrPort("192.0.2.2:77")}, latency: time.Second}
|
2025-08-13 13:13:11 -07:00
|
|
|
peerRelayAddrQuality.vni.Set(1)
|
2025-07-14 15:09:31 -07:00
|
|
|
peerRelayAddrQualityHigherLatencySameServer := addrQuality{
|
|
|
|
|
epAddr: epAddr{ap: netip.MustParseAddrPort("192.0.2.3:77"), vni: peerRelayAddrQuality.vni},
|
|
|
|
|
latency: peerRelayAddrQuality.latency * 10,
|
|
|
|
|
}
|
|
|
|
|
peerRelayAddrQualityHigherLatencyDiffServer := addrQuality{
|
|
|
|
|
epAddr: epAddr{ap: netip.MustParseAddrPort("192.0.2.3:77"), vni: peerRelayAddrQuality.vni},
|
|
|
|
|
latency: peerRelayAddrQuality.latency * 10,
|
|
|
|
|
relayServerDisco: key.NewDisco().Public(),
|
|
|
|
|
}
|
|
|
|
|
peerRelayAddrQualityLowerLatencyDiffServer := addrQuality{
|
|
|
|
|
epAddr: epAddr{ap: netip.MustParseAddrPort("192.0.2.4:77"), vni: peerRelayAddrQuality.vni},
|
|
|
|
|
latency: peerRelayAddrQuality.latency / 10,
|
|
|
|
|
relayServerDisco: key.NewDisco().Public(),
|
|
|
|
|
}
|
|
|
|
|
peerRelayAddrQualityEqualLatencyDiffServer := addrQuality{
|
|
|
|
|
epAddr: epAddr{ap: netip.MustParseAddrPort("192.0.2.4:77"), vni: peerRelayAddrQuality.vni},
|
|
|
|
|
latency: peerRelayAddrQuality.latency,
|
|
|
|
|
relayServerDisco: key.NewDisco().Public(),
|
|
|
|
|
}
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
curBestAddr addrQuality
|
|
|
|
|
trustBestAddrUntil mono.Time
|
|
|
|
|
maybeBest addrQuality
|
|
|
|
|
wantBestAddr addrQuality
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "bestAddr trusted direct",
|
|
|
|
|
curBestAddr: directAddrQuality,
|
|
|
|
|
trustBestAddrUntil: mono.Now().Add(1 * time.Hour),
|
|
|
|
|
maybeBest: peerRelayAddrQuality,
|
|
|
|
|
wantBestAddr: directAddrQuality,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "bestAddr untrusted direct",
|
|
|
|
|
curBestAddr: directAddrQuality,
|
|
|
|
|
trustBestAddrUntil: mono.Now().Add(-1 * time.Hour),
|
|
|
|
|
maybeBest: peerRelayAddrQuality,
|
|
|
|
|
wantBestAddr: peerRelayAddrQuality,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "maybeBest same relay server higher latency bestAddr trusted",
|
|
|
|
|
curBestAddr: peerRelayAddrQuality,
|
|
|
|
|
trustBestAddrUntil: mono.Now().Add(1 * time.Hour),
|
|
|
|
|
maybeBest: peerRelayAddrQualityHigherLatencySameServer,
|
|
|
|
|
wantBestAddr: peerRelayAddrQualityHigherLatencySameServer,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "maybeBest diff relay server higher latency bestAddr trusted",
|
|
|
|
|
curBestAddr: peerRelayAddrQuality,
|
|
|
|
|
trustBestAddrUntil: mono.Now().Add(1 * time.Hour),
|
|
|
|
|
maybeBest: peerRelayAddrQualityHigherLatencyDiffServer,
|
|
|
|
|
wantBestAddr: peerRelayAddrQuality,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "maybeBest diff relay server lower latency bestAddr trusted",
|
|
|
|
|
curBestAddr: peerRelayAddrQuality,
|
|
|
|
|
trustBestAddrUntil: mono.Now().Add(1 * time.Hour),
|
|
|
|
|
maybeBest: peerRelayAddrQualityLowerLatencyDiffServer,
|
|
|
|
|
wantBestAddr: peerRelayAddrQualityLowerLatencyDiffServer,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "maybeBest diff relay server equal latency bestAddr trusted",
|
|
|
|
|
curBestAddr: peerRelayAddrQuality,
|
|
|
|
|
trustBestAddrUntil: mono.Now().Add(1 * time.Hour),
|
|
|
|
|
maybeBest: peerRelayAddrQualityEqualLatencyDiffServer,
|
|
|
|
|
wantBestAddr: peerRelayAddrQuality,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
de := &endpoint{
|
|
|
|
|
c: &Conn{logf: func(msg string, args ...any) { return }},
|
|
|
|
|
bestAddr: tt.curBestAddr,
|
|
|
|
|
trustBestAddrUntil: tt.trustBestAddrUntil,
|
|
|
|
|
}
|
|
|
|
|
de.udpRelayEndpointReady(tt.maybeBest)
|
|
|
|
|
if de.bestAddr != tt.wantBestAddr {
|
|
|
|
|
t.Errorf("de.bestAddr = %v, want %v", de.bestAddr, tt.wantBestAddr)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|