netbirdio/netbird · error

failed to check rule: %w

Error message

failed to check rule: %w

What it means

go-iptables' Exists runs `iptables -t filter -C NETBIRD-ACL-INPUT <specs>`; exit status 1 means 'no match' (returns false,nil), but any other failure is returned as this error. Non-1 failures include xtables lock contention, a missing chain or table, unparseable/unsupported match extensions in the spec (e.g. `-m set --match-set <name>` when the referenced set or the xt_set match module is missing), and running as non-root (iptables exit 4).

Source

Thrown at client/firewall/iptables/acl_linux.go:152

				log.Debugf("flush ipset %s before use: %v", ipsetName, err)
			} else {
				log.Errorf("flush ipset %s before use: %v", ipsetName, err)
			}
		}
		if err := m.createIPSet(ipsetName); err != nil {
			return nil, fmt.Errorf("create ipset: %w", err)
		}
		if err := m.addToIPSet(ipsetName, ip); err != nil {
			return nil, fmt.Errorf("add IP to ipset: %w", err)
		}

		ipList := newIpList(ip.String())
		m.ipsetStore.addIpList(ipsetName, ipList)
	}

	ok, err := m.iptablesClient.Exists(tableFilter, chain, specs...)
	if err != nil {
		return nil, fmt.Errorf("failed to check rule: %w", err)
	}
	if ok {
		return nil, fmt.Errorf("rule already exists")
	}

	// Insert DROP rules at the beginning, append ACCEPT rules at the end
	if action == firewall.ActionDrop {
		// Insert at the beginning of the chain (position 1)
		err = m.iptablesClient.Insert(tableFilter, chain, 1, specs...)
	} else {
		err = m.iptablesClient.Append(tableFilter, chain, specs...)
	}
	if err != nil {
		return nil, err
	}

	if err := m.iptablesClient.Append(tableMangle, chainRTPRE, mangleSpecs...); err != nil {
		log.Errorf("failed to add mangle rule: %v", err)

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Run the agent as root; exit code 4 from iptables means permission denied.
  2. Ensure the kernel set referenced by --match-set exists before rule creation (it is created earlier in the same function, so an external destroyer is the usual culprit).
  3. Load required modules: `modprobe xt_set ip_set ip_set_hash_net`.
  4. For xtables lock contention, retry the AddPeerFiltering call once after a short backoff, or reduce concurrent iptables users on the host.
Defensive patterns

Strategy: try-catch

Validate before calling

func checkIptablesEnv() error {
    if os.Geteuid() != 0 {
        return errors.New("iptables requires root (exit status 4 otherwise)")
    }
    for _, b := range []string{"iptables"} {
        if _, err := exec.LookPath(b); err != nil {
            return fmt.Errorf("%s not in PATH: %w", b, err)
        }
    }
    return nil
}

Try / catch

if err := mgr.AddPeerFiltering(...); err != nil {
    var ee *iptables.Error
    if errors.As(err, &iptables.Error{}) || errors.As(err, &ee) {
        // classify: exit 4 = permission, exit 2/3 = table/chain or lock issues
        log.Errorf("iptables check failed with exit %d", ee.ExitStatus())
    }
    if strings.Contains(err.Error(), "failed to check rule") && isXtablesLockHeld() {
        time.Sleep(250 * time.Millisecond) // then retry once
        err = mgr.AddPeerFiltering(...)
    }
}

Prevention

When it happens

Trigger: AddPeerFiltering when the spec references an ipset that does not exist in the kernel (set destroyed between addToIPSet and the Exists check); xt_set/xt_comment module not loaded on modular kernels; another process holding /run/xtables.lock while the agent checks; agent invoked without root so iptables refuses to list rules.

Common situations: Minimal container images without match extensions; iptables-legacy vs iptables-nft mixing where the rule lives in the other backend; hosts running heavy parallel iptables scripts (Docker, firewalld) causing transient lock errors.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/95d409b116a2f6e8. Report an issue: GitHub.