tailscale/tailscale · error
find rule: %w
Error message
find rule: %w
What it means
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.
Source
Thrown at util/linuxfw/nftables_runner.go:837
// insertLoopbackRule inserts the TS loop back rule into
// the given chain as the first rule if it does not exist.
func insertLoopbackRule(
conn *nftables.Conn, proto nftables.TableFamily,
table *nftables.Table, chain *nftables.Chain, addr netip.Addr) error {
loopBackRule, err := createLoopbackRule(proto, table, chain, addr)
if err != nil {
return fmt.Errorf("create loopback rule: %w", err)
}
// If TestDial is set, we are running in test mode and we should not
// find rule because header will mismatch.
if conn.TestDial == nil {
// Check if the rule already exists.
rule, err := findRule(conn, loopBackRule)
if err != nil {
return fmt.Errorf("find rule: %w", err)
}
if rule != nil {
// Rule already exists, no need to insert.
return nil
}
}
// This inserts the rule to the top of the chain
_ = conn.InsertRule(loopBackRule)
if err = conn.Flush(); err != nil {
return fmt.Errorf("insert rule: %w", err)
}
return nil
}
// getNFTByAddr returns the nftables with correct IP family
// that we will be using for the given address.View on GitHub (pinned to 6e0912f979)
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.
Defensive patterns
Strategy: retry
Validate before calling
func loopbackInsertSafe(conn *nftables.Conn, t *nftables.Table, ch *nftables.Chain) bool {
_, err := conn.GetRules(t, ch)
return err == nil
} Type guard
func isTransientNetlink(err error) bool {
var errno syscall.Errno
return errors.As(err, &errno) && (errno == syscall.ENOBUFS || errno == syscall.EAGAIN)
} Try / catch
err := fw.AddLoopbackRule(addr)
if err != nil && isTransientNetlink(err) {
time.Sleep(100 * time.Millisecond)
err = fw.AddLoopbackRule(addr) // safe: insert path dedups via findRule
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: Unprivileged process adding loopback rules (common with tsnet in containers); busy hosts where other firewall managers rewrite rules mid-dump.
Related errors
AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18).
Data as JSON: /api/errors/286c624f9d7a5035.
Report an issue: GitHub.