util/linuxfw,wgengine/router: allow incoming CGNAT range traffic with nodeattr

Clients with the newly added node attribute
`"disable-linux-cgnat-drop-rule"` will not automatically drop inbound
traffic on non-Tailscale network interfaces with the source IP in the
CGNAT IP range. This is an initial proof-of-concept for enabling
connectivity with off-Tailnet CGNAT endpoints.

Fixes tailscale/corp#36270.

Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
Naman Sood
2026-04-14 16:45:06 -04:00
committed by GitHub
parent 5834058269
commit 6301a6ce4b
14 changed files with 527 additions and 69 deletions
+22 -4
View File
@@ -197,6 +197,22 @@ func sameLAN(c *vnet.Config) *vnet.Node {
return c.AddNode(nw)
}
func sameLANNoDropCGNAT(c *vnet.Config) *vnet.Node {
nw := c.FirstNetwork()
if nw == nil {
return nil
}
if !nw.CanTakeMoreNodes() {
return nil
}
return c.AddNode(
nw,
tailcfg.NodeCapMap{
tailcfg.NodeAttrDisableLinuxCGNATDropRule: nil,
},
)
}
func one2one(c *vnet.Config) *vnet.Node {
n := c.NumNodes() + 1
return c.AddNode(c.AddNetwork(
@@ -437,6 +453,11 @@ func (nt *natTest) setupTest(ctx context.Context, addNode ...addNodeFunc) (nodes
return fmt.Errorf("%v status: %w", node, err)
}
if capMap := node.WantCapMap(); capMap != nil {
nt.tb.Logf("using capmap for %s: %+v", node.String(), capMap)
nt.vnet.ControlServer().SetNodeCapMap(st.Self.PublicKey, capMap)
}
if st.BackendState != "Running" {
return fmt.Errorf("%v state = %q", node, st.BackendState)
}
@@ -788,11 +809,8 @@ func cgnatNoTailnet(c *vnet.Config) *vnet.Node {
}
func TestNonTailscaleCGNATEndpoint(t *testing.T) {
if !*knownBroken {
t.Skip("skipping known-broken test; set --known-broken to run; see https://github.com/tailscale/corp/issues/36270")
}
nt := newNatTest(t)
if !nt.runHostConnectivityTest(cgnatNoTailnet, sameLAN) {
if !nt.runHostConnectivityTest(cgnatNoTailnet, sameLANNoDropCGNAT) {
t.Fatalf("could not ping")
}
}
+10
View File
@@ -15,6 +15,7 @@ import (
"github.com/google/gopacket/layers"
"github.com/google/gopacket/pcapgo"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
"tailscale.com/util/must"
"tailscale.com/util/set"
@@ -137,6 +138,8 @@ func (c *Config) AddNode(opts ...any) *Node {
}
case MAC:
n.mac = o
case tailcfg.NodeCapMap:
n.capMap = o
default:
if n.err == nil {
n.err = fmt.Errorf("unknown AddNode option type %T", o)
@@ -225,6 +228,7 @@ type Node struct {
preICMPPing bool
verboseSyslog bool
dontJoinTailnet bool
capMap tailcfg.NodeCapMap
// TODO(bradfitz): this is halfway converted to supporting multiple NICs
// but not done. We need a MAC-per-Network.
@@ -318,6 +322,12 @@ func (n *Node) ShouldJoinTailnet() bool {
return !n.dontJoinTailnet
}
// WantCapMap returns the [tailcfg.NodeCapMap] that control should send down to
// this node, if any.
func (n *Node) WantCapMap() tailcfg.NodeCapMap {
return n.capMap
}
// IsV6Only reports whether this node is only connected to IPv6 networks.
func (n *Node) IsV6Only() bool {
for _, net := range n.nets {