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

  1. Grant CAP_NET_ADMIN / run as root.
  2. Confirm `nft list chain ip filter input` works as the same user.
  3. On transient netlink dumps failures (ENOBUFS), retry AddLoopbackRule once; the operation is idempotent because of this very dedup check.
  4. 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

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.