{"record":{"id":"881a3a240cba562b","repo":"netbirdio/netbird","slug":"remove-mangle-postrouting-rule-w","errorCode":null,"errorMessage":"remove mangle postrouting rule: %w","messagePattern":"remove mangle postrouting rule: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/firewall/iptables/router_linux.go","lineNumber":527,"sourceCode":"\t\tr.rules[markManglePost] = postRule\n\t}\n\n\treturn nberrors.FormatErrorOrNil(merr)\n}\n\nfunc (r *router) cleanupDataPlaneMark() error {\n\tvar merr *multierror.Error\n\tif preRule, exists := r.rules[markManglePre]; exists {\n\t\tif err := r.iptablesClient.DeleteIfExists(tableMangle, chainPREROUTING, preRule...); err != nil {\n\t\t\tmerr = multierror.Append(merr, fmt.Errorf(\"remove mangle prerouting rule: %w\", err))\n\t\t} else {\n\t\t\tdelete(r.rules, markManglePre)\n\t\t}\n\t}\n\n\tif postRule, exists := r.rules[markManglePost]; exists {\n\t\tif err := r.iptablesClient.DeleteIfExists(tableMangle, chainPOSTROUTING, postRule...); err != nil {\n\t\t\tmerr = multierror.Append(merr, fmt.Errorf(\"remove mangle postrouting rule: %w\", err))\n\t\t} else {\n\t\t\tdelete(r.rules, markManglePost)\n\t\t}\n\t}\n\n\treturn nberrors.FormatErrorOrNil(merr)\n}\n\nfunc (r *router) addPostroutingRules() error {\n\t// First rule for outbound masquerade\n\trule1 := []string{\n\t\t\"-m\", \"mark\", \"--mark\", fmt.Sprintf(\"%#x\", nbnet.PreroutingFwmarkMasquerade),\n\t\t\"!\", \"-o\", \"lo\",\n\t\t\"-j\", routingFinalNatJump,\n\t}\n\tif err := r.iptablesClient.Append(tableNat, chainRTNAT, rule1...); err != nil {\n\t\treturn fmt.Errorf(\"add outbound masquerade rule: %v\", err)\n\t}","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/iptables/router_linux.go#L509-L545","documentation":"The POSTROUTING half of cleanupDataPlaneMark(): deleting the outbound CONNMARK rule from mangle POSTROUTING failed. Like its sibling it only fires when the iptables delete command itself errors (DeleteIfExists tolerates an absent rule), the rule remains in the kernel, and the entry is kept in r.rules so a later cleanup can retry.","triggerScenarios":"DeleteIfExists(\"mangle\", \"POSTROUTING\", postRule...) failing during Reset/Stop because CAP_NET_ADMIN was lost, the iptables binary or mangle table vanished, the xtables lock is held, or another tool already rewrote the chain between the existence check and the delete.","commonSituations":"Teardown in containers whose netns/modules are being dismantled concurrently; `netbird down` executed after iptables packages were upgraded/removed; long-running hosts where firewalld rewrote chains; leftover marks causing policy-routing surprises after a failed teardown.","solutions":["List and remove leftovers manually: `sudo iptables -t mangle -D POSTROUTING -o wt0 -m conntrack --ctstate NEW -j CONNMARK --set-mark 0x...`","Verify the daemon still has CAP_NET_ADMIN at teardown time and that /run/xtables.lock is free","Read the whole multierror: paired prerouting+postrouting failures mean a host-level cause","Re-run `netbird down` (it is idempotent) once the host issue is cleared","Reboot the host as a last resort to clear stale mangle rules, then start the agent fresh"],"exampleFix":"// before\nif err := r.iptablesClient.DeleteIfExists(tableMangle, chainPOSTROUTING, postRule...); err != nil {\n    merr = multierror.Append(merr, fmt.Errorf(\"remove mangle postrouting rule: %w\", err))\n}\n\n// after: retry once to ride out xtables lock contention\nif err := r.iptablesClient.DeleteIfExists(tableMangle, chainPOSTROUTING, postRule...); err != nil {\n    time.Sleep(200 * time.Millisecond)\n    err = r.iptablesClient.DeleteIfExists(tableMangle, chainPOSTROUTING, postRule...)\n    if err != nil {\n        merr = multierror.Append(merr, fmt.Errorf(\"remove mangle postrouting rule: %w\", err))\n    }\n}","handlingStrategy":"retry","validationCode":"func markRulesPresent(ipt *iptables.IPTables) (bool, error) {\n    pre, err1 := ipt.List(\"mangle\", \"PREROUTING\")\n    post, err2 := ipt.List(\"mangle\", \"POSTROUTING\")\n    if err1 != nil || err2 != nil {\n        return false, errors.Join(err1, err2)\n    }\n    for _, rs := range [][]string{pre, post} {\n        for _, r := range rs {\n            if strings.Contains(r, \"CONNMARK\") && strings.Contains(r, \"--set-mark\") {\n                return true, nil\n            }\n        }\n    }\n    return false, nil\n}","typeGuard":null,"tryCatchPattern":"Use a bounded retry (for example two attempts with 200ms backoff) around DeleteIfExists on teardown paths; log and continue if the second attempt still fails, keeping the entry in the rules map for the next cleanup.","preventionTips":["Free /run/xtables.lock before scheduled teardown windows (stop docker/firewalld batch jobs)","Run teardown inside the same privileged context as setup","Audit for CONNMARK leftovers after any failed `netbird down`"],"tags":["network","linux","iptables","cleanup","mangle"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}