{"record":{"id":"1b591bb249376dd9","repo":"tailscale/tailscale","slug":"add-return-cgnat-range-rule-v4-w","errorCode":null,"errorMessage":"add return cgnat range rule v4: %w","messagePattern":"add return cgnat range rule v4: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/linuxfw/nftables_runner.go","lineNumber":1634,"sourceCode":"// interfaces.\nfunc (n *nftablesRunner) AddExternalCGNATRules(mode CGNATMode, tunname string) error {\n\tconn := n.conn\n\n\tinputChain, err := getChainFromTable(conn, n.nft4.Filter, chainNameInput)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get input chain v4: %v\", err)\n\t}\n\tswitch mode {\n\tcase CGNATModeDrop:\n\t\tif err = addReturnChromeOSVMRangeRule(conn, n.nft4.Filter, inputChain, tunname); err != nil {\n\t\t\treturn fmt.Errorf(\"add return chromeos vm range rule v4: %w\", err)\n\t\t}\n\t\tif err = addDropCGNATRangeRule(conn, n.nft4.Filter, inputChain, tunname); err != nil {\n\t\t\treturn fmt.Errorf(\"add drop cgnat range rule v4: %w\", err)\n\t\t}\n\tcase CGNATModeReturn:\n\t\tif err = addReturnCGNATRangeRule(conn, n.nft4.Filter, inputChain, tunname); err != nil {\n\t\t\treturn fmt.Errorf(\"add return cgnat range rule v4: %w\", err)\n\t\t}\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported cgnat mode %q\", mode)\n\t}\n\tif err = conn.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"flush cgnat rules v4: %w\", err)\n\t}\n\treturn nil\n}\n\n// DelExternalCGNATRules removes the rules created by AddExternalCGNATRules,\n// if they exist.\nfunc (n *nftablesRunner) DelExternalCGNATRules(mode CGNATMode, tunname string) error {\n\tconn := n.conn\n\n\tinputChain, err := getChainFromTable(conn, n.nft4.Filter, chainNameInput)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get input chain v4: %v\", err)","sourceCodeStart":1616,"sourceCodeEnd":1652,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/util/linuxfw/nftables_runner.go#L1616-L1652","documentation":"The CGNATModeReturn branch of AddExternalCGNATRules adds a rule that lets traffic sourced from the CGNAT range on non-Tailscale interfaces return to normal INPUT processing instead of being dropped, used when the host itself legitimately uses CGNAT space upstream. Failure comes from addReturnCGNATRangeRule: either createRangeRule cannot build the rule, or conn.Flush returns the kernel's rejection (\"add rule: <errno>\") for the queued insert.","triggerScenarios":"AddExternalCGNATRules(CGNATModeReturn, tunname) with missing capabilities, a vanished or stale chain handle after concurrent flush, unsupported kernel expressions, or a closed netlink conn.","commonSituations":"ISPs behind CGNAT (100.64.0.0/10 used on the WAN side) where Return mode is chosen; firewall reload races; unprivileged execution.","solutions":["Check CAP_NET_ADMIN and retry","Confirm INPUT and ts-input chains exist and are stable during the call","Coordinate with external firewall tooling to avoid mid-batch flushes","Validate kernel nftables support with a manual rule add"],"exampleFix":"// before\nif err := nfr.AddExternalCGNATRules(linuxfw.CGNATModeReturn, tunname); err != nil {\n    return err\n}\n\n// after\nif err := nfr.AddExternalCGNATRules(linuxfw.CGNATModeReturn, tunname); err != nil {\n    var opErr *netlink.OpError\n    if errors.As(err, &opErr) {\n        return fmt.Errorf(\"netlink op %q failed: %w\", opErr.Op, err)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isNetlinkOpError(err error) (*netlink.OpError, bool) {\n    var opErr *netlink.OpError\n    return opErr, errors.As(err, &opErr)\n}","tryCatchPattern":"if err := nfr.AddExternalCGNATRules(linuxfw.CGNATModeReturn, tunname); err != nil {\n    if opErr, ok := isNetlinkOpError(err); ok {\n        log.Printf(\"netlink op %s failed: %v\", opErr.Op, opErr.Err)\n    }\n    return err\n}","preventionTips":["Choose the mode deliberately: Drop unless the upstream network itself uses CGNAT space","Keep privileges and chain stability during configuration","Retry after external firewall churn settles"],"tags":["nftables","cgnat","rule-insertion","netlink","tailscale","go"],"backgroundTag":"nftables-rule-add-failed","analyzedSha":"6e0912f97994f927632b34ae9e63b53d6516a6ac","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}