|
|
|
@ -8,6 +8,8 @@ import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"math"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/netip"
|
|
|
|
@ -29,6 +31,7 @@ import (
|
|
|
|
|
"tailscale.com/ipn"
|
|
|
|
|
"tailscale.com/ipn/store/mem"
|
|
|
|
|
"tailscale.com/net/interfaces"
|
|
|
|
|
"tailscale.com/net/netcheck"
|
|
|
|
|
"tailscale.com/net/tsaddr"
|
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
|
"tailscale.com/tsd"
|
|
|
|
@ -2634,3 +2637,734 @@ func (b *LocalBackend) SetPrefsForTest(newp *ipn.Prefs) {
|
|
|
|
|
defer unlock()
|
|
|
|
|
b.setPrefsLockedOnEntry(newp, unlock)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSuggestExitNode(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
lastReport netcheck.Report
|
|
|
|
|
netMap netmap.NetworkMap
|
|
|
|
|
wantID tailcfg.StableNodeID
|
|
|
|
|
wantName string
|
|
|
|
|
wantLocation tailcfg.LocationView
|
|
|
|
|
wantError error
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "2 exit nodes in same region",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 10 * time.Millisecond,
|
|
|
|
|
2: 20 * time.Millisecond,
|
|
|
|
|
3: 30 * time.Millisecond,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
Name: "2",
|
|
|
|
|
StableID: "2",
|
|
|
|
|
DERP: "127.3.3.40:1",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
Name: "3",
|
|
|
|
|
StableID: "3",
|
|
|
|
|
DERP: "127.3.3.40:1",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantName: "3",
|
|
|
|
|
wantID: tailcfg.StableNodeID("3"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "2 derp based exit nodes, different regions, no latency measurements",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
Name: "2",
|
|
|
|
|
DERP: "127.3.3.40:2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
Name: "3",
|
|
|
|
|
DERP: "127.3.3.40:3",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantName: "3",
|
|
|
|
|
wantID: tailcfg.StableNodeID("3"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "2 derp based exit nodes, different regions, same latency",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 10,
|
|
|
|
|
2: 10,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
Name: "2",
|
|
|
|
|
DERP: "127.3.3.40:1",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
Name: "3",
|
|
|
|
|
DERP: "127.3.3.40:2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantName: "2",
|
|
|
|
|
wantID: tailcfg.StableNodeID("2"),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "mullvad nodes, no derp based exit nodes",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {
|
|
|
|
|
Latitude: 40.73061,
|
|
|
|
|
Longitude: -73.935242,
|
|
|
|
|
},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "Dallas",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 32.89748,
|
|
|
|
|
Longitude: -97.040443,
|
|
|
|
|
Priority: 100,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "San Jose",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 37.3382082,
|
|
|
|
|
Longitude: -121.8863286,
|
|
|
|
|
Priority: 20,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantID: tailcfg.StableNodeID("2"),
|
|
|
|
|
wantLocation: (&tailcfg.Location{
|
|
|
|
|
Latitude: 32.89748,
|
|
|
|
|
Longitude: -97.040443,
|
|
|
|
|
Priority: 100,
|
|
|
|
|
}).View(),
|
|
|
|
|
wantName: "Dallas",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "mullvad nodes close to each other, different priorities",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {
|
|
|
|
|
Latitude: 40.73061,
|
|
|
|
|
Longitude: -73.935242,
|
|
|
|
|
},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "Dallas",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 32.89748,
|
|
|
|
|
Longitude: -97.040443,
|
|
|
|
|
Priority: 10,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "Fort Worth",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 37.768799,
|
|
|
|
|
Longitude: -97.309341,
|
|
|
|
|
Priority: 50,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantID: tailcfg.StableNodeID("3"),
|
|
|
|
|
wantLocation: (&tailcfg.Location{
|
|
|
|
|
Latitude: 37.768799,
|
|
|
|
|
Longitude: -97.309341,
|
|
|
|
|
Priority: 50,
|
|
|
|
|
}).View(),
|
|
|
|
|
wantName: "Fort Worth",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "mullvad nodes, no preferred derp region exit nodes",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {
|
|
|
|
|
Latitude: 40.73061,
|
|
|
|
|
Longitude: -73.935242,
|
|
|
|
|
},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "Dallas",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 32.89748,
|
|
|
|
|
Longitude: -97.040443,
|
|
|
|
|
Priority: 20,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "San Jose",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 37.3382082,
|
|
|
|
|
Longitude: -121.8863286,
|
|
|
|
|
Priority: 30,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
Name: "3",
|
|
|
|
|
DERP: "127.3.3.40:2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
CapMap: (tailcfg.NodeCapMap)(map[tailcfg.NodeCapability][]tailcfg.RawMessage{
|
|
|
|
|
tailcfg.NodeAttrSuggestExitNode: {},
|
|
|
|
|
}),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantID: tailcfg.StableNodeID("3"),
|
|
|
|
|
wantName: "3",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "no mullvad nodes; no derp nodes",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "no preferred derp region",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: -1,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
wantError: ErrNoPreferredDERP,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "derp exit node and mullvad exit node both with no suggest exit node attribute",
|
|
|
|
|
lastReport: netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 0,
|
|
|
|
|
2: 0,
|
|
|
|
|
3: 0,
|
|
|
|
|
},
|
|
|
|
|
PreferredDERP: 1,
|
|
|
|
|
},
|
|
|
|
|
netMap: netmap.NetworkMap{
|
|
|
|
|
SelfNode: (&tailcfg.Node{
|
|
|
|
|
Addresses: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("100.64.1.1/32"),
|
|
|
|
|
netip.MustParsePrefix("fe70::1/128"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
DERPMap: &tailcfg.DERPMap{
|
|
|
|
|
Regions: map[int]*tailcfg.DERPRegion{
|
|
|
|
|
1: {},
|
|
|
|
|
2: {},
|
|
|
|
|
3: {},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Peers: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
Name: "2",
|
|
|
|
|
DERP: "127.3.3.40:1",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Name: "Dallas",
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Latitude: 32.89748,
|
|
|
|
|
Longitude: -97.040443,
|
|
|
|
|
Priority: 30,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
r := rand.New(rand.NewSource(100))
|
|
|
|
|
got, err := suggestExitNode(&tt.lastReport, &tt.netMap, r)
|
|
|
|
|
if got.Name != tt.wantName {
|
|
|
|
|
t.Errorf("name=%v, want %v", got.Name, tt.wantName)
|
|
|
|
|
}
|
|
|
|
|
if got.ID != tt.wantID {
|
|
|
|
|
t.Errorf("ID=%v, want %v", got.ID, tt.wantID)
|
|
|
|
|
}
|
|
|
|
|
if tt.wantError == nil && err != nil {
|
|
|
|
|
t.Errorf("err=%v, want no error", err)
|
|
|
|
|
}
|
|
|
|
|
if tt.wantError != nil && !errors.Is(err, tt.wantError) {
|
|
|
|
|
t.Errorf("err=%v, want %v", err, tt.wantError)
|
|
|
|
|
}
|
|
|
|
|
if !reflect.DeepEqual(got.Location, tt.wantLocation) {
|
|
|
|
|
t.Errorf("location=%v, want %v", got.Location, tt.wantLocation)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSuggestExitNodePickWeighted(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
candidates []tailcfg.NodeView
|
|
|
|
|
wantValue tailcfg.NodeView
|
|
|
|
|
wantValid bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: ">1 candidates",
|
|
|
|
|
candidates: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Priority: 20,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
}).View(),
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 3,
|
|
|
|
|
StableID: "3",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Priority: 10,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
wantValue: (&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Priority: 20,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
}).View(),
|
|
|
|
|
wantValid: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "<1 candidates",
|
|
|
|
|
candidates: []tailcfg.NodeView{},
|
|
|
|
|
wantValid: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "1 candidate",
|
|
|
|
|
candidates: []tailcfg.NodeView{
|
|
|
|
|
(&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Priority: 20,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
}).View(),
|
|
|
|
|
},
|
|
|
|
|
wantValue: (&tailcfg.Node{
|
|
|
|
|
ID: 2,
|
|
|
|
|
StableID: "2",
|
|
|
|
|
AllowedIPs: []netip.Prefix{
|
|
|
|
|
netip.MustParsePrefix("0.0.0.0/0"), netip.MustParsePrefix("::/0"),
|
|
|
|
|
},
|
|
|
|
|
Hostinfo: (&tailcfg.Hostinfo{
|
|
|
|
|
Location: &tailcfg.Location{
|
|
|
|
|
Priority: 20,
|
|
|
|
|
},
|
|
|
|
|
}).View(),
|
|
|
|
|
}).View(),
|
|
|
|
|
wantValid: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got := pickWeighted(tt.candidates)
|
|
|
|
|
if !reflect.DeepEqual(got, tt.wantValue) {
|
|
|
|
|
t.Errorf("got value %v want %v", got, tt.wantValue)
|
|
|
|
|
if tt.wantValid != got.Valid() {
|
|
|
|
|
t.Errorf("got invalid candidate expected valid")
|
|
|
|
|
}
|
|
|
|
|
if tt.wantValid {
|
|
|
|
|
if !reflect.DeepEqual(got, tt.wantValue) {
|
|
|
|
|
t.Errorf("got value %v want %v", got, tt.wantValue)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSuggestExitNodeLongLatDistance(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
fromLat float64
|
|
|
|
|
fromLong float64
|
|
|
|
|
toLat float64
|
|
|
|
|
toLong float64
|
|
|
|
|
want float64
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "zero values",
|
|
|
|
|
fromLat: 0,
|
|
|
|
|
fromLong: 0,
|
|
|
|
|
toLat: 0,
|
|
|
|
|
toLong: 0,
|
|
|
|
|
want: 0,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid values",
|
|
|
|
|
fromLat: 40.73061,
|
|
|
|
|
fromLong: -73.935242,
|
|
|
|
|
toLat: 37.3382082,
|
|
|
|
|
toLong: -121.8863286,
|
|
|
|
|
want: 4117266.873301274,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid values, locations in north and south of equator",
|
|
|
|
|
fromLat: 40.73061,
|
|
|
|
|
fromLong: -73.935242,
|
|
|
|
|
toLat: -33.861481,
|
|
|
|
|
toLong: 151.205475,
|
|
|
|
|
want: 15994089.144368416,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
// The wanted values are computed using a more precise algorithm using the WGS84 model but
|
|
|
|
|
// longLatDistance uses a spherical approximation for simplicity. To account for this, we allow for
|
|
|
|
|
// 10km of error.
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got := longLatDistance(tt.fromLat, tt.fromLong, tt.toLat, tt.toLong)
|
|
|
|
|
const maxError = 10000 // 10km
|
|
|
|
|
if math.Abs(got-tt.want) > maxError {
|
|
|
|
|
t.Errorf("distance=%vm, want within %vm of %vm", got, maxError, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestMinLatencyDERPregion(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
regions []int
|
|
|
|
|
report *netcheck.Report
|
|
|
|
|
wantRegion int
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "regions, no latency values",
|
|
|
|
|
regions: []int{1, 2, 3},
|
|
|
|
|
wantRegion: 0,
|
|
|
|
|
report: &netcheck.Report{},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "regions, different latency values",
|
|
|
|
|
regions: []int{1, 2, 3},
|
|
|
|
|
wantRegion: 2,
|
|
|
|
|
report: &netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 10 * time.Millisecond,
|
|
|
|
|
2: 5 * time.Millisecond,
|
|
|
|
|
3: 30 * time.Millisecond,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "regions, same values",
|
|
|
|
|
regions: []int{1, 2, 3},
|
|
|
|
|
wantRegion: 1,
|
|
|
|
|
report: &netcheck.Report{
|
|
|
|
|
RegionLatency: map[int]time.Duration{
|
|
|
|
|
1: 10 * time.Millisecond,
|
|
|
|
|
2: 10 * time.Millisecond,
|
|
|
|
|
3: 10 * time.Millisecond,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got := minLatencyDERPRegion(tt.regions, tt.report)
|
|
|
|
|
if got != tt.wantRegion {
|
|
|
|
|
t.Errorf("got region %v want region %v", got, tt.wantRegion)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|