{"record":{"id":"01c31708a77c9662","repo":"netbirdio/netbird","slug":"add-elements-to-set-s-w","errorCode":null,"errorMessage":"add elements to set %s: %w","messagePattern":"add elements to set (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/nftables/router_linux.go","lineNumber":1862,"sourceCode":"\t\tdelete(r.rules, ruleKey+snatSuffix)\n\n\t\tif err := r.ipFwdState.ReleaseForwarding(r.af.tableFamily == nftables.TableFamilyIPv6); err != nil {\n\t\t\tlog.Errorf(\"%v\", err)\n\t\t}\n\t}\n\n\treturn nberrors.FormatErrorOrNil(merr)\n}\n\nfunc (r *router) UpdateSet(set firewall.Set, prefixes []netip.Prefix) error {\n\tnfset, err := r.conn.GetSetByName(r.workTable, set.HashedName())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get set %s: %w\", set.HashedName(), err)\n\t}\n\n\telements := r.convertPrefixesToSet(prefixes)\n\tif err := r.conn.SetAddElements(nfset, elements); err != nil {\n\t\treturn fmt.Errorf(\"add elements to set %s: %w\", set.HashedName(), err)\n\t}\n\n\tif err := r.conn.Flush(); err != nil {\n\t\treturn fmt.Errorf(flushError, err)\n\t}\n\n\tlog.Debugf(\"updated set %s with prefixes %v\", set.HashedName(), prefixes)\n\n\treturn nil\n}\n\n// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services.\nfunc (r *router) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {\n\truleID := fmt.Sprintf(\"inbound-dnat-%s-%s-%d-%d\", localAddr.String(), protocol, originalPort, translatedPort)\n\n\tif _, exists := r.rules[ruleID]; exists {\n\t\treturn nil\n\t}","sourceCodeStart":1844,"sourceCodeEnd":1880,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/nftables/router_linux.go#L1844-L1880","documentation":"UpdateSet converts the prefixes to set elements and adds them with SetAddElements, which writes NFT_MSG_NEWSETELEM to the kernel immediately. Failures include ENOENT (the set vanished between GetSetByName and the add), netlink message-size limits with very large element batches (the package documents that about 1638 prefixes start to fail and caps usage at maxPrefixesSet=1500), and permission errors.","triggerScenarios":"One UpdateSet call carrying hundreds or thousands of prefixes; the set deleted concurrently mid-update; unprivileged netlink access.","commonSituations":"Large network routes (big CIDR lists) pushed in a single update; races with external nft flush; hosts with small netlink buffers.","solutions":["Chunk element additions below the netlink limit (the codebase treats 1500 as the safe bound).","Retry after confirming the set still exists with 'sudo nft list set <table> <name>'.","Prefer adding only new elements (diff) over re-adding the full prefix list each update.","Verify CAP_NET_ADMIN when the error is not size-related."],"exampleFix":"// before\nelements := r.convertPrefixesToSet(prefixes)\nif err := r.conn.SetAddElements(nfset, elements); err != nil {\n    return fmt.Errorf(\"add elements to set %s: %w\", set.HashedName(), err)\n}\n\n// after: chunk to stay under netlink message limits\nconst chunkSize = 512\nelements := r.convertPrefixesToSet(prefixes)\nfor i := 0; i < len(elements); i += chunkSize {\n    end := min(i+chunkSize, len(elements))\n    if err := r.conn.SetAddElements(nfset, elements[i:end]); err != nil {\n        return fmt.Errorf(\"add elements to set %s (chunk at %d): %w\", set.HashedName(), i, err)\n    }\n}","handlingStrategy":"validation","validationCode":"// Stay under the netlink element limit before adding\nconst maxPrefixesSet = 1500 // ~1638 starts to fail in practice\n\nfunc prefixBatchSafe(n int) error {\n    if n > maxPrefixesSet {\n        return fmt.Errorf(\"%d prefixes exceeds the safe netlink batch limit %d; split the update\", n, maxPrefixesSet)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"elements := r.convertPrefixesToSet(prefixes)\nif err := r.conn.SetAddElements(nfset, elements); err != nil {\n    if errors.Is(err, unix.EMSGSIZE) || errors.Is(err, unix.ENOMEM) {\n        // batch too large: caller should chunk and retry\n        return fmt.Errorf(\"add elements to set %s: batch of %d too large: %w\", set.HashedName(), len(elements), err)\n    }\n    return fmt.Errorf(\"add elements to set %s: %w\", set.HashedName(), err)\n}","preventionTips":["Chunk prefix updates (for example 512 elements per call) to stay clear of netlink message limits.","Diff prefix lists so unchanged elements are not re-added every update.","Track set sizes in monitoring to catch growth toward the ~1500-element danger zone."],"tags":["nftables","nft-set","netlink","scalability"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}