{"record":{"id":"099a45291f59b0ab","repo":"netbirdio/netbird","slug":"error-adding-prefixes-d-to-set-s-w","errorCode":null,"errorMessage":"error adding prefixes (%d) to set %s: %w","messagePattern":"error adding prefixes \\((.+?)\\) to set (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":576,"sourceCode":"\tmaxElements := maxPrefixesSet * 2\n\tinitialElements := elements[:min(maxElements, nElements)]\n\n\tif err := r.conn.AddSet(nfset, initialElements); err != nil {\n\t\treturn nil, fmt.Errorf(\"error adding set %s: %w\", setName, err)\n\t}\n\tif err := r.conn.Flush(); err != nil {\n\t\treturn nil, fmt.Errorf(\"flush error: %w\", err)\n\t}\n\tlog.Debugf(\"Created new ipset: %s with %d initial prefixes (total prefixes %d)\", setName, len(initialElements)/2, len(prefixes))\n\n\tvar subEnd int\n\tfor subStart := maxElements; subStart < nElements; subStart += maxElements {\n\t\tsubEnd = min(subStart+maxElements, nElements)\n\t\tsubElement := elements[subStart:subEnd]\n\t\tnSubPrefixes := len(subElement) / 2\n\t\tlog.Tracef(\"Adding new prefixes (%d) in ipset: %s\", nSubPrefixes, setName)\n\t\tif err := r.conn.SetAddElements(nfset, subElement); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"error adding prefixes (%d) to set %s: %w\", nSubPrefixes, setName, err)\n\t\t}\n\t\tif err := r.conn.Flush(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"flush error: %w\", err)\n\t\t}\n\t\tlog.Debugf(\"Added new prefixes (%d) in ipset: %s\", nSubPrefixes, setName)\n\t}\n\n\tlog.Infof(\"Created new ipset: %s with %d prefixes\", setName, len(prefixes))\n\treturn nfset, nil\n}\n\nfunc (r *router) convertPrefixesToSet(prefixes []netip.Prefix) []nftables.SetElement {\n\tvar elements []nftables.SetElement\n\tfor _, prefix := range prefixes {\n\t\t// nftables needs half-open intervals [firstIP, lastIP) for prefixes\n\t\t// 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\n\t\tfirstIP := prefix.Addr()\n\t\tlastIP := calculateLastIP(prefix).Next()","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L558-L594","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nsubElement := elements[subStart:subEnd]\nif err := r.conn.SetAddElements(nfset, subElement); err != nil {\n    return nil, fmt.Errorf(\"error adding prefixes (%d) to set %s: %w\", nSubPrefixes, setName, err)\n}\n\n// after\nsubElement := coalesceAdjacent(elements[subStart:subEnd])\nif err := r.conn.SetAddElements(nfset, subElement); err != nil {\n    if isErrno(err, unix.EEXIST) {\n        // partially populated set from an earlier attempt: rebuild it\n        r.conn.DelSet(nfset)\n        _ = r.conn.Flush()\n    }\n    return nil, fmt.Errorf(\"error adding prefixes (%d) to set %s: %w\", nSubPrefixes, setName, err)\n}","handlingStrategy":"validation","validationCode":"// Validate prefixes are merged, non-overlapping, and under the batch limit before adding\nmerged := firewall.MergeIPRanges(input.prefixes)\nif len(merged) > maxPrefixesSet*4 { // hard ceiling well above the 1500-per-batch budget\n    return nil, fmt.Errorf(\"set %s would need %d prefixes, exceeding practical limit\", setName, len(merged))\n}\nfor i := 1; i < len(merged); i++ {\n    if merged[i].Overlaps(merged[i-1]) {\n        return nil, fmt.Errorf(\"overlapping prefixes %s / %s survived merge\", merged[i-1], merged[i])\n    }\n}","typeGuard":"func intervalElementsAreDisjoint(elements []nftables.SetElement) bool {\n\t// each prefix yields [start, end); verify no start sorts before the previous end\n\tfor i := 2; i < len(elements); i += 2 {\n\t\tif bytes.Compare(elements[i].Key, elements[i-1].Key) < 0 {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"if err := r.conn.SetAddElements(nfset, subElement); err != nil {\n    if isErrno(err, unix.EEXIST) {\n        // overlap or partially populated set: rebuild once from clean state\n        r.conn.DelSet(nfset)\n        _ = r.conn.Flush()\n    }\n    return nil, fmt.Errorf(\"error adding prefixes (%d) to set %s: %w\", nSubPrefixes, setName, err)\n}","preventionTips":["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."],"tags":["go","nftables","netlink","ipset","firewall","scalability","linux"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}