{"record":{"id":"f653ae9b8899854d","repo":"netbirdio/netbird","slug":"add-outbound-masquerade-rule-v","errorCode":null,"errorMessage":"add outbound masquerade rule: %v","messagePattern":"add outbound masquerade rule: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/iptables/router_linux.go","lineNumber":544,"sourceCode":"\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}\n\tr.rules[\"static-nat-outbound\"] = rule1\n\n\t// Second rule for return traffic masquerade\n\trule2 := []string{\n\t\t\"-m\", \"mark\", \"--mark\", fmt.Sprintf(\"%#x\", nbnet.PreroutingFwmarkMasqueradeReturn),\n\t\t\"-o\", r.wgIface.Name(),\n\t\t\"-j\", routingFinalNatJump,\n\t}\n\tif err := r.iptablesClient.Append(tableNat, chainRTNAT, rule2...); err != nil {\n\t\treturn fmt.Errorf(\"add return masquerade rule: %v\", err)\n\t}\n\tr.rules[\"static-nat-return\"] = rule2\n\n\treturn nil\n}\n\n// addMSSClampingRules adds MSS clamping rules to prevent fragmentation for forwarded traffic.","sourceCodeStart":526,"sourceCodeEnd":562,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/iptables/router_linux.go#L526-L562","documentation":"First concrete failure inside addPostroutingRules(): appending the outbound masquerade rule (match on PreroutingFwmarkMasquerade, not lo, jump MASQUERADE) to the NETBIRD-RT-NAT chain in the nat table. It propagates through 'add static nat rules' from createContainers() and stops router setup, meaning marked outbound traffic will never be masqueraded.","triggerScenarios":"`iptables -t nat -A NETBIRD-RT-NAT -m mark --mark 0x... ! -o lo -j MASQUERADE` failing: xt_mark match or MASQUERADE target unavailable, iptable_nat not loaded, NETBIRD-RT-NAT chain missing after a partially failed earlier cleanup, CAP_NET_ADMIN absent, or xtables lock held.","commonSituations":"Hosts where the nat table exists but masquerade support is missing (custom kernels); containers with NET_ADMIN but no iptable_nat module on the host; a previous unclean shutdown left the chain-deletion half done; Alpine images using iptables-legacy while the host uses nft.","solutions":["Run the exact append by hand as root and read stderr to identify the missing match/target","`modprobe iptable_nat xt_MASQUERADE xt_mark`","Ensure `sudo iptables -t nat -N NETBIRD-RT-NAT` succeeds (chain creation happens earlier in createContainers; if it failed, read that error first)","Confirm iptables and ip6tables binaries exist and are the same implementation family","Retry `netbird up` after clearing xtables.lock contention"],"exampleFix":"// before\nif err := r.iptablesClient.Append(tableNat, chainRTNAT, rule1...); err != nil {\n    return fmt.Errorf(\"add outbound masquerade rule: %v\", err)\n}\n\n// after: verify chain presence, then append with positional insert so reruns converge\nif ok, _ := r.iptablesClient.ChainExists(tableNat, chainRTNAT); !ok {\n    if err := r.iptablesClient.NewChain(tableNat, chainRTNAT); err != nil {\n        return fmt.Errorf(\"recreate %s: %w\", chainRTNAT, err)\n    }\n}\nif err := r.iptablesClient.Insert(tableNat, chainRTNAT, 1, rule1...); err != nil {\n    return fmt.Errorf(\"add outbound masquerade rule: %w\", err)\n}","handlingStrategy":"validation","validationCode":"func masqueradeSupported(ipt *iptables.IPTables) error {\n    if err := ipt.NewChain(\"nat\", \"NB-PROBE\"); err != nil {\n        return fmt.Errorf(\"create probe chain: %w\", err)\n    }\n    defer ipt.ClearAndDeleteChain(\"nat\", \"NB-PROBE\")\n    rule := []string{\"-o\", \"lo\", \"-j\", \"MASQUERADE\"}\n    return ipt.Append(\"nat\", \"NB-PROBE\", rule...)\n}","typeGuard":null,"tryCatchPattern":"Fail fast: return the error up through createContainers so the route manager reports setup failure; do not continue with jump rules pointing at an empty NAT chain.","preventionTips":["modprobe iptable_nat xt_MASQUERADE xt_mark in the host baseline","Include `iptables -t nat -A <chain> -j MASQUERADE` in container image CI smoke tests","Never run the routed peer as an unprivileged user"],"tags":["network","linux","iptables","nat","masquerade"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}