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
- 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.
- Lower the per-batch element count below 1500 prefixes (maxPrefixesSet) to stay under the observed kernel failure point at ~1638.
- Split very large networks into multiple sets/rules, or collapse the route to a shorter prefix where acceptable.
- 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
- Normalize ranges to merged, sorted, adjacency-coalesced prefixes before they reach the firewall layer.
- Cap route networks at ~1500 prefixes per set; split larger ones across multiple sets or shorten prefixes.
- Reproduce production prefix lists in tests with the real convertPrefixesToSet to catch half-open interval collisions.
- On any mid-creation failure, delete the partial set so retries start from a known state.
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
- error adding set %s: %w
- flush error: %w
- create ipset: %w
- delete ip from ipset: %w
- add IP to ipset %s: %w
AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16).
Data as JSON: /api/errors/099a45291f59b0ab.
Report an issue: GitHub.