ipn/ipnlocal: allowed suggested exit nodes policy (#12240)
Updates tailscale/corp#19681 Signed-off-by: Claire Wang <claire@tailscale.com>
This commit is contained in:
parent
5ad0dad15e
commit
f1d10c12ac
|
@ -6453,8 +6453,17 @@ func suggestExitNode(report *netcheck.Report, netMap *netmap.NetworkMap, r *rand
|
||||||
if report.PreferredDERP == 0 {
|
if report.PreferredDERP == 0 {
|
||||||
return res, ErrNoPreferredDERP
|
return res, ErrNoPreferredDERP
|
||||||
}
|
}
|
||||||
|
var allowedCandidates set.Set[string]
|
||||||
|
if allowed, err := syspolicy.GetStringArray(syspolicy.AllowedSuggestedExitNodes, nil); err != nil {
|
||||||
|
return res, fmt.Errorf("unable to read %s policy: %w", syspolicy.AllowedSuggestedExitNodes, err)
|
||||||
|
} else if allowed != nil {
|
||||||
|
allowedCandidates = set.SetOf(allowed)
|
||||||
|
}
|
||||||
candidates := make([]tailcfg.NodeView, 0, len(netMap.Peers))
|
candidates := make([]tailcfg.NodeView, 0, len(netMap.Peers))
|
||||||
for _, peer := range netMap.Peers {
|
for _, peer := range netMap.Peers {
|
||||||
|
if allowedCandidates != nil && !allowedCandidates.Contains(string(peer.StableID())) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if peer.CapMap().Has(tailcfg.NodeAttrSuggestExitNode) && tsaddr.ContainsExitRoutes(peer.AllowedIPs()) {
|
if peer.CapMap().Has(tailcfg.NodeAttrSuggestExitNode) && tsaddr.ContainsExitRoutes(peer.AllowedIPs()) {
|
||||||
candidates = append(candidates, peer)
|
candidates = append(candidates, peer)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1595,6 +1595,9 @@ type mockSyspolicyHandler struct {
|
||||||
// queried by the current test. If the policy is expected but unset, then
|
// queried by the current test. If the policy is expected but unset, then
|
||||||
// use nil, otherwise use a string equal to the policy's desired value.
|
// use nil, otherwise use a string equal to the policy's desired value.
|
||||||
stringPolicies map[syspolicy.Key]*string
|
stringPolicies map[syspolicy.Key]*string
|
||||||
|
// stringArrayPolicies is the collection of policies that we expected to see
|
||||||
|
// queries by the current test, that return policy string arrays.
|
||||||
|
stringArrayPolicies map[syspolicy.Key][]string
|
||||||
// failUnknownPolicies is set if policies other than those in stringPolicies
|
// failUnknownPolicies is set if policies other than those in stringPolicies
|
||||||
// (uint64 or bool policies are not supported by mockSyspolicyHandler yet)
|
// (uint64 or bool policies are not supported by mockSyspolicyHandler yet)
|
||||||
// should be considered a test failure if they are queried.
|
// should be considered a test failure if they are queried.
|
||||||
|
@ -1632,6 +1635,12 @@ func (h *mockSyspolicyHandler) ReadStringArray(key string) ([]string, error) {
|
||||||
if h.failUnknownPolicies {
|
if h.failUnknownPolicies {
|
||||||
h.t.Errorf("ReadStringArray(%q) unexpectedly called", key)
|
h.t.Errorf("ReadStringArray(%q) unexpectedly called", key)
|
||||||
}
|
}
|
||||||
|
if s, ok := h.stringArrayPolicies[syspolicy.Key(key)]; ok {
|
||||||
|
if s == nil {
|
||||||
|
return []string{}, syspolicy.ErrNoSuchKey
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
return nil, syspolicy.ErrNoSuchKey
|
return nil, syspolicy.ErrNoSuchKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3474,6 +3483,7 @@ func TestLocalBackendSuggestExitNode(t *testing.T) {
|
||||||
lastSuggestedExitNode lastSuggestedExitNode
|
lastSuggestedExitNode lastSuggestedExitNode
|
||||||
report *netcheck.Report
|
report *netcheck.Report
|
||||||
netMap netmap.NetworkMap
|
netMap netmap.NetworkMap
|
||||||
|
allowedSuggestedExitNodes []string
|
||||||
wantID tailcfg.StableNodeID
|
wantID tailcfg.StableNodeID
|
||||||
wantName string
|
wantName string
|
||||||
wantErr error
|
wantErr error
|
||||||
|
@ -3766,10 +3776,138 @@ func TestLocalBackendSuggestExitNode(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: ErrCannotSuggestExitNode,
|
wantErr: ErrCannotSuggestExitNode,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "only pick from allowed suggested exit nodes",
|
||||||
|
lastSuggestedExitNode: lastSuggestedExitNode{name: "test", id: "test"},
|
||||||
|
report: &netcheck.Report{
|
||||||
|
RegionLatency: map[int]time.Duration{
|
||||||
|
1: 10,
|
||||||
|
2: 10,
|
||||||
|
3: 5,
|
||||||
|
},
|
||||||
|
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: "test",
|
||||||
|
Name: "test",
|
||||||
|
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: {},
|
||||||
|
tailcfg.NodeAttrAutoExitNode: {},
|
||||||
|
}),
|
||||||
|
}).View(),
|
||||||
|
(&tailcfg.Node{
|
||||||
|
ID: 3,
|
||||||
|
StableID: "foo",
|
||||||
|
Name: "foo",
|
||||||
|
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: {},
|
||||||
|
tailcfg.NodeAttrAutoExitNode: {},
|
||||||
|
}),
|
||||||
|
}).View(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
allowedSuggestedExitNodes: []string{"test"},
|
||||||
|
wantID: "test",
|
||||||
|
wantName: "test",
|
||||||
|
wantLastSuggestedExitNode: lastSuggestedExitNode{name: "test", id: "test"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "allowed suggested exit nodes not nil but length 0",
|
||||||
|
lastSuggestedExitNode: lastSuggestedExitNode{name: "test", id: "test"},
|
||||||
|
report: &netcheck.Report{
|
||||||
|
RegionLatency: map[int]time.Duration{
|
||||||
|
1: 10,
|
||||||
|
2: 10,
|
||||||
|
3: 5,
|
||||||
|
},
|
||||||
|
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: "test",
|
||||||
|
Name: "test",
|
||||||
|
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: {},
|
||||||
|
tailcfg.NodeAttrAutoExitNode: {},
|
||||||
|
}),
|
||||||
|
}).View(),
|
||||||
|
(&tailcfg.Node{
|
||||||
|
ID: 3,
|
||||||
|
StableID: "foo",
|
||||||
|
Name: "foo",
|
||||||
|
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: {},
|
||||||
|
tailcfg.NodeAttrAutoExitNode: {},
|
||||||
|
}),
|
||||||
|
}).View(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
allowedSuggestedExitNodes: []string{},
|
||||||
|
wantID: "foo",
|
||||||
|
wantName: "foo",
|
||||||
|
wantLastSuggestedExitNode: lastSuggestedExitNode{name: "foo", id: "foo"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
lb := newTestLocalBackend(t)
|
lb := newTestLocalBackend(t)
|
||||||
|
msh := &mockSyspolicyHandler{
|
||||||
|
t: t,
|
||||||
|
stringArrayPolicies: map[syspolicy.Key][]string{
|
||||||
|
syspolicy.AllowedSuggestedExitNodes: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if len(tt.allowedSuggestedExitNodes) != 0 {
|
||||||
|
msh.stringArrayPolicies[syspolicy.AllowedSuggestedExitNodes] = tt.allowedSuggestedExitNodes
|
||||||
|
}
|
||||||
|
syspolicy.SetHandlerForTest(t, msh)
|
||||||
lb.lastSuggestedExitNode = tt.lastSuggestedExitNode
|
lb.lastSuggestedExitNode = tt.lastSuggestedExitNode
|
||||||
lb.netMap = &tt.netMap
|
lb.netMap = &tt.netMap
|
||||||
lb.sys.MagicSock.Get().SetLastNetcheckReportForTest(context.Background(), tt.report)
|
lb.sys.MagicSock.Get().SetLastNetcheckReportForTest(context.Background(), tt.report)
|
||||||
|
|
Loading…
Reference in New Issue