mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-04 04:34:07 +09:00
mapper: send a disable-ipv4 holder its peers over IPv6 only
As SaaS. Peers' 100.x addresses routed into tailscale0 and hid a local host with the same address, which is what the attr is meant to avoid.
This commit is contained in:
1 parent
68b0274694
commit
b04bbd4317
4 files changed
+46
-3
No files matched your search
@@ -281,6 +281,8 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) (
|
||||
// Snapshot these peers' policy caps under one policy-manager lock.
|
||||
peerCapMaps := b.mapper.state.NodeCapMapsForPeers(peers)
|
||||
|
||||
_, ipv4Disabled := b.mapper.state.NodeCapMap(node.ID())[types.NodeAttrDisableIPv4]
|
||||
|
||||
// Build tail nodes with per-peer via-aware route function.
|
||||
tailPeers := make([]*tailcfg.Node, 0, peers.Len())
|
||||
|
||||
@@ -327,6 +329,16 @@ func (b *MapResponseBuilder) buildTailPeers(peers views.Slice[types.NodeView]) (
|
||||
tn.CapMap = policyv2.PeerCapMap(peer, peerCapMaps[peer.ID()])
|
||||
tn.Hostinfo = types.PeerHostinfo(tn.Hostinfo)
|
||||
|
||||
// A disable-ipv4 holder sees its peers over IPv6 only, as on SaaS, so
|
||||
// their 100.x addresses stay off its interface. Routes stay.
|
||||
if ipv4Disabled {
|
||||
ownIPv4 := func(p netip.Prefix) bool {
|
||||
return p.Addr().Is4() && slices.Contains(tn.Addresses, p)
|
||||
}
|
||||
tn.AllowedIPs = slices.DeleteFunc(tn.AllowedIPs, ownIPv4)
|
||||
tn.Addresses = slices.DeleteFunc(tn.Addresses, func(p netip.Prefix) bool { return p.Addr().Is4() })
|
||||
}
|
||||
|
||||
// As on Tailscale SaaS, a connected expired peer shows online, but
|
||||
// without the endpoints and home DERP region that would reach it.
|
||||
if peer.IsExpired() {
|
||||
|
||||
@@ -1812,6 +1812,14 @@ func (b *MapResponseBuilder) buildTailPeersCapMapsReference(peers views.Slice[ty
|
||||
tn.CapMap = policyv2.PeerCapMap(peer, allCapMaps[peer.ID()])
|
||||
tn.Hostinfo = types.PeerHostinfo(tn.Hostinfo)
|
||||
|
||||
if _, ipv4Disabled := allCapMaps[node.ID()][types.NodeAttrDisableIPv4]; ipv4Disabled {
|
||||
ownIPv4 := func(p netip.Prefix) bool {
|
||||
return p.Addr().Is4() && slices.Contains(tn.Addresses, p)
|
||||
}
|
||||
tn.AllowedIPs = slices.DeleteFunc(tn.AllowedIPs, ownIPv4)
|
||||
tn.Addresses = slices.DeleteFunc(tn.Addresses, func(p netip.Prefix) bool { return p.Addr().Is4() })
|
||||
}
|
||||
|
||||
if peer.IsExpired() {
|
||||
if peer.ActiveSessions() > 0 {
|
||||
tn.Online, tn.LastSeen = new(true), nil
|
||||
|
||||
@@ -353,6 +353,29 @@ func runViaMapCompat(t *testing.T, c *testcapture.Capture) {
|
||||
compareViaMapCompat(t, c, clients)
|
||||
}
|
||||
|
||||
// TestDisableIPv4PeersCompat: a disable-ipv4 holder sees its peers over IPv6
|
||||
// only, in both recordings of such a viewer.
|
||||
func TestDisableIPv4PeersCompat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, id := range []string{"nodeattrs-attr-c15-disable-ipv4", "nodeattrs-attr-c16-disable-ipv4-router"} {
|
||||
t.Run(id, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c, err := testcapture.Read(filepath.Join("..", "policy", "v2", "testdata", "nodeattrs_results", id+".hujson"))
|
||||
require.NoError(t, err)
|
||||
|
||||
_, clients := startViaMapCompat(t, c, true)
|
||||
|
||||
for viewer, want := range c.Captures {
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
comparePeers(collect, clients[viewer].Netmap(), want, clients, id+"/"+viewer, nil)
|
||||
}, 20*time.Second, 200*time.Millisecond, "viewer %s", viewer)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// startViaMapCompat starts a server holding the capture's policy and a
|
||||
// client per captured node, approving routes as [startCaptureClients] does,
|
||||
// and waits for every client's first netmap and the peers SaaS gave it.
|
||||
|
||||
@@ -44,11 +44,11 @@ var (
|
||||
// [netip.Prefix] values representing the routes for that node.
|
||||
type RouteFunc func(id NodeID) []netip.Prefix
|
||||
|
||||
// nodeAttrDisableIPv4 is the policy nodeAttr key that suppresses the
|
||||
// NodeAttrDisableIPv4 is the policy nodeAttr key that suppresses the
|
||||
// node's own IPv4 CGNAT prefix in [tailcfg.Node.Addresses] and
|
||||
// [tailcfg.Node.AllowedIPs]. Subnet routes the node advertises remain.
|
||||
// See https://tailscale.com/docs/reference/troubleshooting/network-configuration/cgnat-conflicts.
|
||||
const nodeAttrDisableIPv4 nodecap.Cap = "disable-ipv4"
|
||||
const NodeAttrDisableIPv4 nodecap.Cap = "disable-ipv4"
|
||||
|
||||
// filterIPv4 returns ps with every IPv4 prefix dropped. Used by
|
||||
// [NodeView.TailNode] when the node carries the disable-ipv4 nodeAttr.
|
||||
@@ -1402,7 +1402,7 @@ func (nv NodeView) TailNode(
|
||||
// subnet routes -- even IPv4 ones -- survive: routes belong to
|
||||
// the routing layer, not the node's identity. Mirrors the SaaS
|
||||
// captures in testdata/nodeattrs_results/nodeattrs-attr-c1{5,6}-disable-ipv4*.
|
||||
_, ipv4Disabled := selfPolicyCaps[nodeAttrDisableIPv4]
|
||||
_, ipv4Disabled := selfPolicyCaps[NodeAttrDisableIPv4]
|
||||
|
||||
addresses := nv.Prefixes()
|
||||
if ipv4Disabled {
|
||||
|
||||
Reference in new issue
Block a user