Skip to content

Commit

Permalink
fix: plan v4/v6 STUN probing based on relative index
Browse files Browse the repository at this point in the history
Signed-off-by: Spike Curtis <[email protected]>
  • Loading branch information
spikecurtis committed Nov 6, 2023
1 parent 3f0f979 commit ebb6b9a
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 3 deletions.
9 changes: 6 additions & 3 deletions net/netcheck/netcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *interfaces.State, last *Report)
// Coder: Some regions don't have STUN, so we need to make sure we have probed
// enough STUN regions
numSTUN := 0
for ri, reg := range sortRegions(dm, last) {
for _, reg := range sortRegions(dm, last) {
if numSTUN == numIncrementalRegions {
break
}
Expand All @@ -430,14 +430,14 @@ func makeProbePlan(dm *tailcfg.DERPMap, ifState *interfaces.State, last *Report)
// By default, each node only gets one STUN packet sent,
// except the fastest two from the previous round.
tries := 1
isFastestTwo := ri < 2
isFastestTwo := numSTUN < 2

if isFastestTwo {
tries = 2
} else if hadBoth {
// For dual stack machines, make the 3rd & slower nodes alternate
// between.
if ri%2 == 0 {
if numSTUN%2 == 0 {
do4, do6 = true, false
} else {
do4, do6 = false, true
Expand Down Expand Up @@ -979,6 +979,9 @@ func (c *Client) GetReport(ctx context.Context, dm *tailcfg.DERPMap) (_ *Report,
}

plan := makeProbePlan(dm, ifState, last)
if len(plan) == 0 {
c.logf("empty probe plan; do we have STUN regions?")
}

// If we're doing a full probe, also check for a captive portal. We
// delay by a bit to wait for UDP STUN to finish, to avoid the probe if
Expand Down
114 changes: 114 additions & 0 deletions net/netcheck/netcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1202,3 +1202,117 @@ func TestNeverPickSTUNOnlyRegionAsPreferredDERP(t *testing.T) {
t.Errorf("got PreferredDERP=%d; want 1", r.PreferredDERP)
}
}

func Test_makeProbePlan_incremental(t *testing.T) {
type testcase struct {
name string
haveV4 bool
haveV6 bool
stunPorts []int
expected []string
}
testcases := []testcase{
{
name: "slowSTUN",
haveV4: true,
stunPorts: []int{-1, -1, -1, 4, 5, 6},
expected: []string{"region-4-v4", "region-5-v4", "region-6-v4"},
},
{
name: "fastSTUN",
haveV4: true,
stunPorts: []int{1, 2, 3, -1, -1, -1},
expected: []string{"region-1-v4", "region-2-v4", "region-2-v4"},
},
{
name: "dualStackFast",
haveV4: true,
haveV6: true,
stunPorts: []int{1, 2, 3, -1, -1, -1},
expected: []string{
"region-1-v4", "region-2-v4", "region-2-v4",
"region-1-v6", "region-2-v6", // only fastest 2 have v6
},
},
{
name: "dualStackSlow",
haveV4: true,
haveV6: true,
stunPorts: []int{-1, -1, -1, 4, 5, 6},
expected: []string{
"region-4-v4", "region-5-v4", "region-6-v4",
"region-4-v6", "region-5-v6", // only fastest 2 have v6
},
},
{
// pathological case: STUN regions are not in top 3 and even
// indexes re: latency
name: "dualStackInterspersed",
haveV4: true,
haveV6: true,
stunPorts: []int{-1, -1, -1, 4, -1, 6, -1, 8},
expected: []string{
"region-4-v4", "region-6-v4", "region-8-v4",
"region-4-v6", "region-6-v6", // only fastest 2 have v6
},
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
state := &interfaces.State{
HaveV4: tc.haveV4,
HaveV6: tc.haveV6,
}
plan := makeProbePlan(
derpMapBySTUNPort(tc.stunPorts),
state,
reportLatencyAscending(len(tc.stunPorts), tc.haveV4, tc.haveV6),
)
for _, e := range tc.expected {
if _, ok := plan[e]; !ok {
t.Errorf("expected %s in the plan", e)
}
}
})
}

}

func derpMapBySTUNPort(ports []int) *tailcfg.DERPMap {
derpMap := &tailcfg.DERPMap{Regions: make(map[int]*tailcfg.DERPRegion)}
for i, p := range ports {
j := i + 1
var name string
if p < 0 {
name = fmt.Sprintf("noSTUN%d", j)
} else {
name = fmt.Sprintf("STUN%d", j)
}
derpMap.Regions[j] = &tailcfg.DERPRegion{
RegionID: j,
RegionCode: name,
Nodes: []*tailcfg.DERPNode{
{Name: name, STUNPort: p},
}}
}
return derpMap
}

func reportLatencyAscending(n int, haveV4, haveV6 bool) *Report {
r := &Report{
RegionLatency: make(map[int]time.Duration),
RegionV4Latency: make(map[int]time.Duration),
RegionV6Latency: make(map[int]time.Duration),
}
for i := 1; i <= n; i++ {
r.RegionLatency[i] = time.Duration(i) * time.Millisecond
if haveV4 {
r.RegionV4Latency[i] = time.Duration(i) * time.Millisecond
}
if haveV6 {
r.RegionV6Latency[i] = time.Duration(i) * time.Millisecond
}
}
return r
}

0 comments on commit ebb6b9a

Please sign in to comment.