{"record":{"id":"0233aaef85645056","repo":"netbirdio/netbird","slug":"flush-rules-w","errorCode":null,"errorMessage":"flush rules: %w","messagePattern":"flush rules: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":1594,"sourceCode":"\t\t\tlog.Warnf(\"rollback forwarding refcount: %v\", rerr)\n\t\t}\n\t\treturn nil, err\n\t}\n\n\tr.addDnatMasq(rule, protoNum, ruleKey)\n\n\t// Unlike iptables, there's no point in adding \"out\" rules in the forward chain here as our policy is ACCEPT.\n\t// To overcome DROP policies in other chains, we'd have to add rules to the chains there.\n\t// We also cannot just add \"oif <iface> accept\" there and filter in our own table as we don't know what is supposed to be allowed.\n\t// TODO: find chains with drop policies and add rules there\n\n\tif err := r.conn.Flush(); err != nil {\n\t\tif rerr := r.ipFwdState.ReleaseForwarding(v6); rerr != nil {\n\t\t\tlog.Warnf(\"rollback forwarding refcount: %v\", rerr)\n\t\t}\n\t\tdelete(r.rules, ruleKey+dnatSuffix)\n\t\tdelete(r.rules, ruleKey+snatSuffix)\n\t\treturn nil, fmt.Errorf(\"flush rules: %w\", err)\n\t}\n\n\treturn &rule, nil\n}\n\nfunc (r *router) addDnatRedirect(rule firewall.ForwardRule, protoNum uint8, ruleKey string) error {\n\tdnatExprs := []expr.Any{\n\t\t&expr.Meta{Key: expr.MetaKeyIIFNAME, Register: 1},\n\t\t&expr.Cmp{\n\t\t\tOp:       expr.CmpOpNeq,\n\t\t\tRegister: 1,\n\t\t\tData:     ifname(r.wgIface.Name()),\n\t\t},\n\t\t&expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},\n\t\t&expr.Cmp{\n\t\t\tOp:       expr.CmpOpEq,\n\t\t\tRegister: 1,\n\t\t\tData:     []byte{protoNum},","sourceCodeStart":1576,"sourceCodeEnd":1612,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L1576-L1612","documentation":"After addDnatRedirect and addDnatMasq buffer NFT_MSG_NEWRULE messages on the shared conn, AddDNATRule commits them with Flush. 'flush rules' means the kernel rejected the batch or the netlink exchange failed. The code rolls back the forwarding refcount and drops the buffered map entries, so the DNAT rule is not considered installed.","triggerScenarios":"Kernel lacks the requested NAT feature (IPv6 NAT needs Linux 4.18+), a malformed expression triggers EINVAL, duplicate rules yield EEXIST, CAP_NET_ADMIN is missing, or the netlink batch is oversized.","commonSituations":"Old or cut-down container kernels; concurrent duplicate forward rules; state skew where the rule already exists in kernel but not in r.rules after external changes; user namespaces without NET_ADMIN.","solutions":["Read the wrapped errno: EPERM means missing privilege, EINVAL means the kernel rejected the NAT expression for this family, EEXIST means the rule already exists.","Check the kernel version: IPv6 DNAT requires Linux 4.18 or newer.","Reproduce with the equivalent 'nft add rule ... dnat to' command to see the kernel's own complaint.","For EEXIST, retry once after refreshRulesMap resynchronizes handles.","Verify NET_ADMIN in containerized deployments."],"exampleFix":"// before\nif err := r.conn.Flush(); err != nil {\n    return nil, fmt.Errorf(\"flush rules: %w\", err)\n}\n\n// after: classify the errno so operators can act on it\nif err := r.conn.Flush(); err != nil {\n    switch {\n    case errors.Is(err, unix.EPERM):\n        return nil, fmt.Errorf(\"flush rules (missing CAP_NET_ADMIN): %w\", err)\n    case errors.Is(err, unix.EINVAL):\n        return nil, fmt.Errorf(\"flush rules (kernel rejected NAT expression; v6 NAT needs >= 4.18): %w\", err)\n    default:\n        return nil, fmt.Errorf(\"flush rules: %w\", err)\n    }\n}","handlingStrategy":"try-catch","validationCode":"// Rough capability preflight before NAT rule installation\nfunc natPrerequisitesOk(v6 bool) error {\n    if v6 {\n        var uts unix.Utsname\n        if err := unix.Uname(&uts); err != nil {\n            return err\n        }\n        rel := unix.ByteSliceToString(uts.Release[:])\n        var major, minor int\n        if _, err := fmt.Sscanf(rel, \"%d.%d\", &major, &minor); err == nil {\n            if major < 4 || (major == 4 && minor < 18) {\n                return fmt.Errorf(\"IPv6 NAT requires kernel >= 4.18, running %s\", rel)\n            }\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := r.conn.Flush(); err != nil {\n    switch {\n    case errors.Is(err, unix.EPERM):\n        return nil, fmt.Errorf(\"flush rules: missing CAP_NET_ADMIN: %w\", err)\n    case errors.Is(err, unix.EEXIST):\n        // duplicate in kernel but not in map: resync and retry once\n        if rerr := r.refreshRulesMap(); rerr == nil {\n            return r.AddDNATRule(rule)\n        }\n        return nil, err\n    default:\n        return nil, fmt.Errorf(\"flush rules: %w\", err)\n    }\n}","preventionTips":["Check kernel NAT support (4.18+ for IPv6) before deploying v6 port forwarding.","Keep exactly one writer to the netbird table to avoid EEXIST duplicates.","Surface wrapped errnos in logs so EPERM vs EINVAL is distinguishable in the field."],"tags":["nftables","netlink","nat","kernel","batch-commit"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}