{"record":{"id":"286c624f9d7a5035","repo":"tailscale/tailscale","slug":"find-rule-w","errorCode":null,"errorMessage":"find rule: %w","messagePattern":"find rule: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/linuxfw/nftables_runner.go","lineNumber":837,"sourceCode":"\n// insertLoopbackRule inserts the TS loop back rule into\n// the given chain as the first rule if it does not exist.\nfunc insertLoopbackRule(\n\tconn *nftables.Conn, proto nftables.TableFamily,\n\ttable *nftables.Table, chain *nftables.Chain, addr netip.Addr) error {\n\n\tloopBackRule, err := createLoopbackRule(proto, table, chain, addr)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create loopback rule: %w\", err)\n\t}\n\n\t// If TestDial is set, we are running in test mode and we should not\n\t// find rule because header will mismatch.\n\tif conn.TestDial == nil {\n\t\t// Check if the rule already exists.\n\t\trule, err := findRule(conn, loopBackRule)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"find rule: %w\", err)\n\t\t}\n\t\tif rule != nil {\n\t\t\t// Rule already exists, no need to insert.\n\t\t\treturn nil\n\t\t}\n\t}\n\n\t// This inserts the rule to the top of the chain\n\t_ = conn.InsertRule(loopBackRule)\n\n\tif err = conn.Flush(); err != nil {\n\t\treturn fmt.Errorf(\"insert rule: %w\", err)\n\t}\n\treturn nil\n}\n\n// getNFTByAddr returns the nftables with correct IP family\n// that we will be using for the given address.","sourceCodeStart":819,"sourceCodeEnd":855,"githubUrl":"https://github.com/tailscale/tailscale/blob/6e0912f97994f927632b34ae9e63b53d6516a6ac/util/linuxfw/nftables_runner.go#L819-L855","documentation":"Before inserting the loopback rule, insertLoopbackRule checks whether an identical rule already exists (skipped only in test mode where conn.TestDial is set). This error means that dedup lookup failed: findRule's netlink GetRules dump errored, so the code aborts rather than risk inserting a duplicate. The wrapped cause is the same 'get nftables rules' failure family.","triggerScenarios":"AddLoopbackRule reaching findRule when conn.GetRules fails: EPERM without CAP_NET_ADMIN, netlink socket errors, or ENOBUFS while a large/concurrently-mutated ruleset is being dumped.","commonSituations":"Unprivileged process adding loopback rules (common with tsnet in containers); busy hosts where other firewall managers rewrite rules mid-dump.","solutions":["Grant CAP_NET_ADMIN / run as root.","Confirm `nft list chain ip filter input` works as the same user.","On transient netlink dumps failures (ENOBUFS), retry AddLoopbackRule once; the operation is idempotent because of this very dedup check.","Reduce concurrent ruleset churn or serialize firewall writes."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"func loopbackInsertSafe(conn *nftables.Conn, t *nftables.Table, ch *nftables.Chain) bool {\n\t_, err := conn.GetRules(t, ch)\n\treturn err == nil\n}","typeGuard":"func isTransientNetlink(err error) bool {\n\tvar errno syscall.Errno\n\treturn errors.As(err, &errno) && (errno == syscall.ENOBUFS || errno == syscall.EAGAIN)\n}","tryCatchPattern":"err := fw.AddLoopbackRule(addr)\nif err != nil && isTransientNetlink(err) {\n\ttime.Sleep(100 * time.Millisecond)\n\terr = fw.AddLoopbackRule(addr) // safe: insert path dedups via findRule\n}","preventionTips":["Rely on AddLoopbackRule's built-in idempotency — retrying it never duplicates the rule.","Fix capability/kernel issues before relying on retries; EPERM is permanent.","Minimize concurrent nftables rewrites during loopback rule programming."],"tags":["go","linux","nftables","netlink","netfilter","tailscale","loopback"],"backgroundTag":"nftables-netlink-error","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"}