netbirdio/netbird · error

error adding prefixes (%d) to set %s: %w

Error message

error adding prefixes (%d) to set %s: %w

What it means

Returned inside createIpSet's batching loop (router_linux.go:575) when conn.SetAddElements fails while appending prefixes beyond the initial 1500. Each prefix becomes two SetElements (half-open interval [firstIP, lastIP), convertPrefixesToSet lines 588-604), so each batch pushes 3000 elements. SetAddElements fails while marshaling when elements overlap (kernel rejects overlapping/intersecting intervals with EEXIST) — the reason line 543 merges prefixes first — or when a batch exceeds netlink message limits. The comment at line 58 records that kernels start failing around 1638 prefixes, hence the 1500 margin.

Source

Thrown at client/firewall/nftables/router_linux.go:576

	maxElements := maxPrefixesSet * 2
	initialElements := elements[:min(maxElements, nElements)]

	if err := r.conn.AddSet(nfset, initialElements); err != nil {
		return nil, fmt.Errorf("error adding set %s: %w", setName, err)
	}
	if err := r.conn.Flush(); err != nil {
		return nil, fmt.Errorf("flush error: %w", err)
	}
	log.Debugf("Created new ipset: %s with %d initial prefixes (total prefixes %d)", setName, len(initialElements)/2, len(prefixes))

	var subEnd int
	for subStart := maxElements; subStart < nElements; subStart += maxElements {
		subEnd = min(subStart+maxElements, nElements)
		subElement := elements[subStart:subEnd]
		nSubPrefixes := len(subElement) / 2
		log.Tracef("Adding new prefixes (%d) in ipset: %s", nSubPrefixes, setName)
		if err := r.conn.SetAddElements(nfset, subElement); err != nil {
			return nil, fmt.Errorf("error adding prefixes (%d) to set %s: %w", nSubPrefixes, setName, err)
		}
		if err := r.conn.Flush(); err != nil {
			return nil, fmt.Errorf("flush error: %w", err)
		}
		log.Debugf("Added new prefixes (%d) in ipset: %s", nSubPrefixes, setName)
	}

	log.Infof("Created new ipset: %s with %d prefixes", setName, len(prefixes))
	return nfset, nil
}

func (r *router) convertPrefixesToSet(prefixes []netip.Prefix) []nftables.SetElement {
	var elements []nftables.SetElement
	for _, prefix := range prefixes {
		// nftables needs half-open intervals [firstIP, lastIP) for prefixes
		// e.g. 10.0.0.0/24 becomes [10.0.0.0, 10.0.1.0), 10.1.1.1/32 becomes [10.1.1.1, 10.1.1.2) etc
		firstIP := prefix.Addr()
		lastIP := calculateLastIP(prefix).Next()

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Merge overlapping AND adjacent prefixes before conversion — adjacent intervals (10.0.1.0/24 next to 10.0.2.0/24 encoded half-open) can still collide, so normalize with MergeIPRanges plus adjacency coalescing.
  2. Lower the per-batch element count below 1500 prefixes (maxPrefixesSet) to stay under the observed kernel failure point at ~1638.
  3. Split very large networks into multiple sets/rules, or collapse the route to a shorter prefix where acceptable.
  4. If the error is EEXIST from a partially populated set (earlier failed run), delete the set and retry creation from scratch.

Example fix

// before
subElement := elements[subStart:subEnd]
if err := r.conn.SetAddElements(nfset, subElement); err != nil {
    return nil, fmt.Errorf("error adding prefixes (%d) to set %s: %w", nSubPrefixes, setName, err)
}

// after
subElement := coalesceAdjacent(elements[subStart:subEnd])
if err := r.conn.SetAddElements(nfset, subElement); err != nil {
    if isErrno(err, unix.EEXIST) {
        // partially populated set from an earlier attempt: rebuild it
        r.conn.DelSet(nfset)
        _ = r.conn.Flush()
    }
    return nil, fmt.Errorf("error adding prefixes (%d) to set %s: %w", nSubPrefixes, setName, err)
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate prefixes are merged, non-overlapping, and under the batch limit before adding
merged := firewall.MergeIPRanges(input.prefixes)
if len(merged) > maxPrefixesSet*4 { // hard ceiling well above the 1500-per-batch budget
    return nil, fmt.Errorf("set %s would need %d prefixes, exceeding practical limit", setName, len(merged))
}
for i := 1; i < len(merged); i++ {
    if merged[i].Overlaps(merged[i-1]) {
        return nil, fmt.Errorf("overlapping prefixes %s / %s survived merge", merged[i-1], merged[i])
    }
}

Type guard

func intervalElementsAreDisjoint(elements []nftables.SetElement) bool {
	// each prefix yields [start, end); verify no start sorts before the previous end
	for i := 2; i < len(elements); i += 2 {
		if bytes.Compare(elements[i].Key, elements[i-1].Key) < 0 {
			return false
		}
	}
	return true
}

Try / catch

if err := r.conn.SetAddElements(nfset, subElement); err != nil {
    if isErrno(err, unix.EEXIST) {
        // overlap or partially populated set: rebuild once from clean state
        r.conn.DelSet(nfset)
        _ = r.conn.Flush()
    }
    return nil, fmt.Errorf("error adding prefixes (%d) to set %s: %w", nSubPrefixes, setName, err)
}

Prevention

When it happens

Trigger: A routed network whose range expansion produces more than 1500 prefixes (e.g. a large discontiguous range list), unmerged overlapping ranges reaching the loop, or a single batch whose serialized size exceeds the netlink/message ceiling.

Common situations: Management distributing a network route backed by many small CIDRs; geo-IP style allow-lists turned into routes; IPv6 ranges expanding into thousands of /64s; rules computed from user-defined address groups.

Related errors


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