mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-15 07:27:30 +00:00
cmd/containerboot,util/linuxfw: create a SNAT rule for dst/src only once, clean up if needed (#13658)
The AddSNATRuleForDst rule was adding a new rule each time it was called including: - if a rule already existed - if a rule matching the destination, but with different desired source already existed This was causing issues especially for the in-progress egress HA proxies work, where the rules are now refreshed more frequently, so more redundant rules were being created. This change: - only creates the rule if it doesn't already exist - if a rule for the same dst, but different source is found, delete it - also ensures that egress proxies refresh firewall rules if the node's tailnet IP changes Updates tailscale/tailscale#13406 Signed-off-by: Irbe Krumina <irbe@tailscale.com>
This commit is contained in:
@@ -193,7 +193,7 @@ func (n *nftablesRunner) DNATNonTailscaleTraffic(tunname string, dst netip.Addr)
|
||||
return n.conn.Flush()
|
||||
}
|
||||
|
||||
func (n *nftablesRunner) AddSNATRuleForDst(src, dst netip.Addr) error {
|
||||
func (n *nftablesRunner) EnsureSNATForDst(src, dst netip.Addr) error {
|
||||
polAccept := nftables.ChainPolicyAccept
|
||||
table, err := n.getNFTByAddr(dst)
|
||||
if err != nil {
|
||||
@@ -216,44 +216,26 @@ func (n *nftablesRunner) AddSNATRuleForDst(src, dst netip.Addr) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error ensuring postrouting chain: %w", err)
|
||||
}
|
||||
var daddrOffset, fam, daddrLen uint32
|
||||
if dst.Is4() {
|
||||
daddrOffset = 16
|
||||
daddrLen = 4
|
||||
fam = unix.NFPROTO_IPV4
|
||||
} else {
|
||||
daddrOffset = 24
|
||||
daddrLen = 16
|
||||
fam = unix.NFPROTO_IPV6
|
||||
}
|
||||
|
||||
snatRule := &nftables.Rule{
|
||||
Table: nat,
|
||||
Chain: postRoutingCh,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Payload{
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: daddrOffset,
|
||||
Len: daddrLen,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: dst.AsSlice(),
|
||||
},
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: src.AsSlice(),
|
||||
},
|
||||
&expr.NAT{
|
||||
Type: expr.NATTypeSourceNAT,
|
||||
Family: fam,
|
||||
RegAddrMin: 1,
|
||||
},
|
||||
},
|
||||
rules, err := n.conn.GetRules(nat, postRoutingCh)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing rules: %w", err)
|
||||
}
|
||||
n.conn.AddRule(snatRule)
|
||||
snatRulePrefixMatch := fmt.Sprintf("dst:%s,src:", dst.String())
|
||||
snatRuleFullMatch := fmt.Sprintf("%s%s", snatRulePrefixMatch, src.String())
|
||||
for _, rule := range rules {
|
||||
current := string(rule.UserData)
|
||||
if strings.HasPrefix(string(rule.UserData), snatRulePrefixMatch) {
|
||||
if strings.EqualFold(current, snatRuleFullMatch) {
|
||||
return nil // already exists, do nothing
|
||||
}
|
||||
if err := n.conn.DelRule(rule); err != nil {
|
||||
return fmt.Errorf("error deleting SNAT rule: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
rule := snatRule(nat, postRoutingCh, src, dst, []byte(snatRuleFullMatch))
|
||||
n.conn.AddRule(rule)
|
||||
return n.conn.Flush()
|
||||
}
|
||||
|
||||
@@ -557,11 +539,12 @@ type NetfilterRunner interface {
|
||||
// in the Kubernetes ingress proxies.
|
||||
DNATWithLoadBalancer(origDst netip.Addr, dsts []netip.Addr) error
|
||||
|
||||
// AddSNATRuleForDst adds a rule to the nat/POSTROUTING chain to SNAT
|
||||
// traffic destined for dst to src.
|
||||
// EnsureSNATForDst sets up firewall to mask the source for traffic destined for dst to src:
|
||||
// - creates a SNAT rule if it doesn't already exist
|
||||
// - deletes any pre-existing rules matching the destination
|
||||
// This is used to forward traffic destined for the local machine over
|
||||
// the Tailscale interface, as used in the Kubernetes egress proxies.
|
||||
AddSNATRuleForDst(src, dst netip.Addr) error
|
||||
EnsureSNATForDst(src, dst netip.Addr) error
|
||||
|
||||
// DNATNonTailscaleTraffic adds a rule to the nat/PREROUTING chain to DNAT
|
||||
// all traffic inbound from any interface except exemptInterface to dst.
|
||||
@@ -2028,3 +2011,45 @@ func NfTablesCleanUp(logf logger.Logf) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func snatRule(t *nftables.Table, ch *nftables.Chain, src, dst netip.Addr, meta []byte) *nftables.Rule {
|
||||
var daddrOffset, fam, daddrLen uint32
|
||||
if dst.Is4() {
|
||||
daddrOffset = 16
|
||||
daddrLen = 4
|
||||
fam = unix.NFPROTO_IPV4
|
||||
} else {
|
||||
daddrOffset = 24
|
||||
daddrLen = 16
|
||||
fam = unix.NFPROTO_IPV6
|
||||
}
|
||||
|
||||
return &nftables.Rule{
|
||||
Table: t,
|
||||
Chain: ch,
|
||||
Exprs: []expr.Any{
|
||||
&expr.Payload{
|
||||
DestRegister: 1,
|
||||
Base: expr.PayloadBaseNetworkHeader,
|
||||
Offset: daddrOffset,
|
||||
Len: daddrLen,
|
||||
},
|
||||
&expr.Cmp{
|
||||
Op: expr.CmpOpEq,
|
||||
Register: 1,
|
||||
Data: dst.AsSlice(),
|
||||
},
|
||||
&expr.Immediate{
|
||||
Register: 1,
|
||||
Data: src.AsSlice(),
|
||||
},
|
||||
&expr.NAT{
|
||||
Type: expr.NATTypeSourceNAT,
|
||||
Family: fam,
|
||||
RegAddrMin: 1,
|
||||
RegAddrMax: 1,
|
||||
},
|
||||
},
|
||||
UserData: meta,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user