netbirdio/netbird · error
error adding set %s: %w
Error message
error adding set %s: %w
What it means
Returned by createIpSet (router_linux.go:561) when conn.AddSet fails while creating the interval set that backs a multi-prefix network. AddSet validates and queues the NEWSET message plus up to 3000 initial elements (maxPrefixesSet=1500 prefixes x 2 half-open interval endpoints, lines 558-559); construction-time failures include a nil Table, a KeyType whose byte length does not match the table family (r.af.setKeyType), or malformed set flags. Kernel rejections for the queued message surface one line later at Flush (error 683).
Source
Thrown at client/firewall/nftables/router_linux.go:562
prefixes := firewall.MergeIPRanges(input.prefixes)
nfset := &nftables.Set{
Name: setName,
Comment: input.set.Comment(),
Table: r.workTable,
// required for prefixes
Interval: true,
KeyType: r.af.setKeyType,
}
elements := r.convertPrefixesToSet(prefixes)
nElements := len(elements)
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)
}View on GitHub (pinned to 93e97f4bf1)
Solutions
- Confirm the set's KeyType matches the router's address family (4-byte keys for the ip table, 16-byte for ip6) — check r.af.setKeyType wiring.
- If a stale set with the same HashedName exists from a previous run, delete the work table or DelSet it before re-adding (the manager recreates the table on init).
- Verify set invariants the library requires: non-nil Table (r.workTable), Interval=true for prefixes, and a non-empty unique Name.
- Check the wrapped error before Flush-related codes — if it is a marshaling/validation error it will reproduce deterministically, so fix construction instead of retrying.
Example fix
// before
nfset := &nftables.Set{Name: setName, Table: r.workTable, Interval: true, KeyType: r.af.setKeyType}
if err := r.conn.AddSet(nfset, initialElements); err != nil {
return nil, fmt.Errorf("error adding set %s: %w", setName, err)
}
// after
nfset := &nftables.Set{Name: setName, Table: r.workTable, Interval: true, KeyType: r.af.setKeyType}
if nfset.KeyType == 0 || nfset.KeyType.Length != r.af.addrLen {
return nil, fmt.Errorf("set %s key length %d does not match family %d", setName, nfset.KeyType.Length, r.af.addrLen)
}
if err := r.conn.AddSet(nfset, initialElements); err != nil {
return nil, fmt.Errorf("error adding set %s: %w", setName, err)
} Defensive patterns
Strategy: fallback
Validate before calling
// Validate set construction matches the table family before AddSet
wantLen := uint32(4)
if r.af.tableFamily == nftables.TableFamilyIPv6 {
wantLen = 16
}
if nfset.Table == nil || nfset.KeyType.Length != wantLen || !nfset.Interval {
return nil, fmt.Errorf("invalid set definition %s: table=%v keyLen=%d interval=%v", setName, nfset.Table, nfset.KeyType.Length, nfset.Interval)
} Type guard
func validSetForFamily(s *nftables.Set, family nftables.TableFamily) bool {
if s == nil || s.Table == nil || !s.Interval {
return false
}
want := uint32(4)
if family == nftables.TableFamilyIPv6 {
want = 16
}
return s.KeyType != nil && s.KeyType.Length == want
} Try / catch
if err := r.conn.AddSet(nfset, initialElements); err != nil {
// construction error: deterministic, do not retry blindly
if existing, gerr := r.conn.GetSetByName(r.workTable, setName); gerr == nil {
return existing, nil // fall back to the existing set
}
return nil, fmt.Errorf("error adding set %s: %w", setName, err)
} Prevention
- Assert set key length equals the router's address family length at creation time, in tests covering both families.
- Recreate the netbird work table on agent start so leftover sets cannot collide with new definitions.
- Unit-test set construction for v4 and v6 routers with the same prefix fixtures to catch family mix-ups.
- Keep set names derived from content hashes (HashedName) so identical inputs always map to one set.
When it happens
Trigger: Calling AddRouteFiltering/getIpSet with a firewall.Set whose hashed name collides with an incompatible leftover set, an IPv6 set created under an IPv4 router (or vice versa via familyForAddr mismatch), or an element key length that disagrees with KeyType during marshaling.
Common situations: Agent restart after an unclean shutdown leaving a set with the same hash name but different key type; dual-stack peers where the v4 and v6 routers build sets from the same prefix list; embedded/minimal kernels missing interval-set support (flags rejected).
Related errors
- flush error: %w
- error adding prefixes (%d) to set %s: %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/a0e1fb6ea856889e.
Report an issue: GitHub.