tailscale/tailscale · error
add match subnet route mark rule v4: %w
Error message
add match subnet route mark rule v4: %w
What it means
addBase4 failed to install the mark-match accept rule into IPv4 ts-forward. The rule matches packets whose mark, masked with the Tailscale fwmark mask, equals the subnet-route mark, then accepts them; construction (createMatchSubnetRouteMarkRule) cannot fail, so the wrapped error is the helper's conn.Flush() netlink commit error.
Source
Thrown at util/linuxfw/nftables_runner.go:1742
inputChain, err := getChainFromTable(conn, n.nft4.Filter, chainNameInput)
if err != nil {
return fmt.Errorf("get input chain v4: %v", err)
}
if err = addAcceptIncomingPacketRule(conn, n.nft4.Filter, inputChain, tunname); err != nil {
return fmt.Errorf("add accept incoming packet rule v4: %w", err)
}
forwardChain, err := getChainFromTable(conn, n.nft4.Filter, chainNameForward)
if err != nil {
return fmt.Errorf("get forward chain v4: %v", err)
}
if err = addSetSubnetRouteMarkRule(conn, n.nft4.Filter, forwardChain, tunname); err != nil {
return fmt.Errorf("add set subnet route mark rule v4: %w", err)
}
if err = addMatchSubnetRouteMarkRule(conn, n.nft4.Filter, forwardChain, Accept); err != nil {
return fmt.Errorf("add match subnet route mark rule v4: %w", err)
}
if err = addDropOutgoingPacketFromCGNATRangeRuleWithTunname(conn, n.nft4.Filter, forwardChain, tunname); err != nil {
return fmt.Errorf("add drop outgoing packet from cgnat range rule v4: %w", err)
}
if err = addAcceptOutgoingPacketRule(conn, n.nft4.Filter, forwardChain, tunname); err != nil {
return fmt.Errorf("add accept outgoing packet rule v4: %w", err)
}
if err = conn.Flush(); err != nil {
return fmt.Errorf("flush base v4: %w", err)
}
return nil
}
// addBase6 adds some basic IPv6 processing rules.View on GitHub (pinned to 6e0912f979)
Solutions
- Verify CAP_NET_ADMIN and retry after AddChains() on ENOENT
- Classify errno and switch firewall mode on EOPNOTSUPP
- Remove concurrent ruleset flushes
- Check Conn lifecycle
Defensive patterns
Strategy: retry
Validate before calling
// same pre-flight as the other flush failures: probe + re-ensure chains
conn, err := nftables.New()
if err != nil { return err }
defer conn.Close()
if _, err := conn.ListTables(); err != nil { return err }
if err := fw.AddChains(); err != nil { return err } Type guard
func isFlushCommitErr(err error) bool {
return errors.Is(err, unix.EPERM) || errors.Is(err, unix.ENOENT) ||
errors.Is(err, unix.EOPNOTSUPP) || errors.Is(err, unix.ENOTSUPP)
} Try / catch
if err := fw.AddBase(tun); err != nil {
if errors.Is(err, unix.ENOENT) {
if fw.AddChains() == nil {
return fw.AddBase(tun)
}
}
return err
} Prevention
- Do not drop capabilities between adding rules in one AddBase call
- Retry once after AddChains on ENOENT races
- Verify the final chain contents after any retry to catch duplicated rules
- Serialize firewall mutation with external tooling
When it happens
Trigger: EPERM at commit without CAP_NET_ADMIN; ENOENT when ts-forward/filter was flushed concurrently; EOPNOTSUPP/EINVAL on kernels lacking the needed nft expressions; closed nftables.Conn.
Common situations: Same family as the other base-rule flush failures: restricted containers, unsupported kernels, racing firewall managers.
Related errors
- add set subnet route mark rule v4: %w
- add set subnet route mark rule v6: %w
- add match subnet route mark rule v6: %w
- add accept incoming packet rule v4: %w
- add drop outgoing packet from cgnat range rule v4: %w
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/bc8d80f53a69360f.
Report an issue: GitHub.