netbirdio/netbird · error

add IP to ipset: %w

Error message

add IP to ipset: %w

What it means

Returned from the ACL manager's AddPeerFiltering fast path: the ipset name is already tracked in the in-memory ipsetStore, but the netlink ADD of the peer IP to that kernel set failed (ipset-go's ipset.Add with Replace:true). It wraps netlink errno failures such as the set no longer existing in the kernel, an address-family mismatch (IPv4 entry into an inet6 set), or missing CAP_NET_ADMIN. Because the memory store says the set exists while the kernel disagrees, it almost always indicates store/kernel desynchronization or a privilege problem rather than a bad IP.

Source

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

	// of silently leaving the chain empty.
	if ipsetName != "" && !m.ipsetSupported {
		ipsetName = ""
	}
	proto := protoForFamily(protocol, m.v6)
	specs := filterRuleSpecs(ip, proto, sPort, dPort, action, ipsetName)

	mangleSpecs := slices.Clone(specs)
	mangleSpecs = append(mangleSpecs,
		"-i", m.wgIface.Name(),
		"-m", "addrtype", "--dst-type", "LOCAL",
		"-j", "MARK", "--set-xmark", fmt.Sprintf("%#x", nbnet.PreroutingFwmarkRedirected),
	)

	specs = append(specs, "-j", actionToStr(action))
	if ipsetName != "" {
		if ipList, ipsetExists := m.ipsetStore.ipset(ipsetName); ipsetExists {
			if err := m.addToIPSet(ipsetName, ip); err != nil {
				return nil, fmt.Errorf("add IP to ipset: %w", err)
			}
			// if ruleset already exists it means we already have the firewall rule
			// so we need to update IPs in the ruleset and return new fw.Rule object for ACL manager.
			ipList.addIP(ip.String())
			return []firewall.Rule{&Rule{
				ruleID:    uuid.New().String(),
				ipsetName: ipsetName,
				ip:        ip.String(),
				chain:     chain,
				specs:     specs,
				v6:        m.v6,
			}}, nil
		}

		if err := m.flushIPSet(ipsetName); err != nil {
			if errors.Is(err, ipset.ErrSetNotExist) {
				log.Debugf("flush ipset %s before use: %v", ipsetName, err)
			} else {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Run the agent as root (or grant CAP_NET_ADMIN and CAP_NET_MODULE) so netlink ADD succeeds.
  2. On ipset.ErrSetNotExist, self-heal: drop the stale entry from ipsetStore and fall through to the flush/create path instead of returning the error.
  3. Verify family alignment: v6 managers must only receive v6 peer IPs, v4 managers only v4.
  4. Stop concurrent external mutation of the NETBIRD ipsets, or call aclManager.Reset() after such mutation to resynchronize.

Example fix

// before
if err := m.addToIPSet(ipsetName, ip); err != nil {
    return nil, fmt.Errorf("add IP to ipset: %w", err)
}

// after
if err := m.addToIPSet(ipsetName, ip); err != nil {
    if !errors.Is(err, ipset.ErrSetNotExist) {
        return nil, fmt.Errorf("add IP to ipset: %w", err)
    }
    m.ipsetStore.deleteIpset(ipsetName)
    // fall through to the flush/create/add path below to resync with the kernel
}
Defensive patterns

Strategy: try-catch

Validate before calling

// probe kernel set existence with the manager's own flush trick
if err := ipset.Flush(ipsetName); err != nil {
    if errors.Is(err, ipset.ErrSetNotExist) {
        // kernel set is gone although ipsetStore tracks it: recreate before Add
        if err := ipset.Create(ipsetName, ipset.TypeHashNet, opts); err != nil {
            return err
        }
    }
}
if os.Geteuid() != 0 {
    return errors.New("ipset ADD requires root (CAP_NET_ADMIN)")
}

Try / catch

if err := m.AddPeerFiltering(ip, proto, port, action, comment); err != nil {
    if strings.Contains(err.Error(), "add IP to ipset") && errors.Is(err, ipset.ErrSetNotExist) {
        // resync: reset the acl manager and retry once
        if rerr := m.Reset(); rerr == nil {
            err = m.AddPeerFiltering(ip, proto, port, action, comment)
        }
    }
    if err != nil { /* surface */ }
}

Prevention

When it happens

Trigger: Calling AddPeerFiltering for a peer whose ruleset ipset is in ipsetStore while the kernel set was destroyed out-of-band (external `ipset destroy`, `ipset flush -` + reload, netns teardown); running the agent without CAP_NET_ADMIN; a v6 aclManager (m.v6 true, set created with FamilyIPV6) receiving a v4 net.IP, producing a CIDR/32 entry in an inet6 set.

Common situations: Operators or parallel firewall tooling (fail2ban, k8s NetworkManager scripts) wiping ipsets while the agent runs; containers started without NET_ADMIN; leftover manager state after an agent crash where the kernel was cleaned but ipsetStore was repopulated from persisted state; mixed-family peer addresses on dual-stack setups.

Related errors


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