tailscale/tailscale · error

deleting connmark rule in mangle/OUTPUT: %w

Error message

deleting connmark rule in mangle/OUTPUT: %w

What it means

The second step of iptablesRunner.DelConnmarkSaveRule (util/linuxfw/iptables_runner.go:632): deleting the OUTPUT rule that saved marks on NEW connections (conntrack --ctstate NEW, mark ! 0x0/0xff00, CONNMARK --save-mark). As with the PREROUTING delete, a 'rule does not exist' outcome is silently accepted, so this error means the iptables -t mangle -D OUTPUT call failed for a real reason such as missing CAP_NET_ADMIN, unavailable connmark support, or an iptables binary/backend problem. It surfaces during firewall teardown on both the IPv4 and IPv6 tables (the loop over getTables).

Source

Thrown at util/linuxfw/iptables_runner.go:632

				return fmt.Errorf("deleting connmark rule in mangle/PREROUTING: %w", err)
			}
			// Rule doesn't exist - this is fine for idempotency
		}

		// Delete OUTPUT rule
		args = []string{
			"-m", "conntrack",
			"--ctstate", "NEW",
			"-m", "mark",
			"!", "--mark", "0x0/" + fwmarkMask,
			"-j", "CONNMARK",
			"--save-mark",
			"--nfmask", fwmarkMask,
			"--ctmask", fwmarkMask,
		}
		if err := ipt.Delete("mangle", "OUTPUT", args...); err != nil {
			if !isNotExistError(err) {
				return fmt.Errorf("deleting connmark rule in mangle/OUTPUT: %w", err)
			}
			// Rule doesn't exist - this is fine for idempotency
		}
	}
	return nil
}

// buildMagicsockPortRule generates the string slice containing the arguments
// to describe a rule accepting traffic on a particular port to iptables. It is
// separated out here to avoid repetition in AddMagicsockPortRule and
// RemoveMagicsockPortRule, since it is important that the same rule is passed
// to Append() and Delete().
func buildMagicsockPortRule(port uint16) []string {
	return []string{"-p", "udp", "--dport", strconv.FormatUint(uint64(port), 10), "-j", "ACCEPT"}
}

// AddMagicsockPortRule adds a rule to iptables to allow incoming traffic on
// the specified UDP port, so magicsock can accept incoming connections.

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Run with root/CAP_NET_ADMIN so mangle/OUTPUT is writable
  2. Check the rule manually: iptables-save -t mangle | grep save-mark, then delete the exact rule by hand
  3. modprobe xt_connmark xt_conntrack if matches are missing
  4. Treat as best-effort during shutdown and log it; the rule set is rebuilt on next start

Example fix

// before
if err := ipt.DelConnmarkSaveRule(); err != nil {
	return err
}
// after
if err := ipt.DelConnmarkSaveRule(); err != nil {
	logf("mangle/OUTPUT connmark cleanup failed: %v", err)
	// continue shutdown; stale rules are replaced on next AddConnmarkSaveRule
}
Defensive patterns

Strategy: try-catch

Validate before calling

func mangleWritable(ipt *iptables.IPTables) bool {
	return ipt.Probe() == nil // iptables binary reachable and responsive
}

Try / catch

if err := ipt.DelConnmarkSaveRule(); err != nil {
	logf("mangle/OUTPUT connmark cleanup: %v", err)
	// continue shutdown; do not abort the process over stale marks
}

Prevention

When it happens

Trigger: DelConnmarkSaveRule invoked when the process cannot modify mangle/OUTPUT: no CAP_NET_ADMIN, missing xt_connmark/xt_conntrack modules, iptables binary missing, or rule text mismatch after a version upgrade (the Add and Del paths must build byte-identical args).

Common situations: Daemon shutdown inside an unprivileged container; downgrade/upgrade of tailscaled where fwmarkMask or rule construction changed; systems where another firewall tool rewrote or flushed chains mid-teardown.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/3a3fb656f03e3dfc. Report an issue: GitHub.