{"record":{"id":"aaa77b8064119d2d","repo":"netbirdio/netbird","slug":"update-v6-set-w","errorCode":null,"errorMessage":"update v6 set: %w","messagePattern":"update v6 set: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/firewall/iptables/manager_linux.go","lineNumber":458,"sourceCode":"\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tvar v4Prefixes, v6Prefixes []netip.Prefix\n\tfor _, p := range prefixes {\n\t\tif p.Addr().Is6() {\n\t\t\tv6Prefixes = append(v6Prefixes, p)\n\t\t} else {\n\t\t\tv4Prefixes = append(v4Prefixes, p)\n\t\t}\n\t}\n\n\tif err := m.router.UpdateSet(set, v4Prefixes); err != nil {\n\t\treturn err\n\t}\n\n\tif m.hasIPv6() && len(v6Prefixes) > 0 {\n\t\tif err := m.router6.UpdateSet(set, v6Prefixes); err != nil {\n\t\t\treturn fmt.Errorf(\"update v6 set: %w\", err)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// AddInboundDNAT adds an inbound DNAT rule redirecting traffic from NetBird peers to local services.\nfunc (m *Manager) AddInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {\n\tm.mutex.Lock()\n\tdefer m.mutex.Unlock()\n\n\tif localAddr.Is6() {\n\t\tif !m.hasIPv6() {\n\t\t\treturn fmt.Errorf(\"add inbound DNAT: %w\", firewall.ErrIPv6NotInitialized)\n\t\t}\n\t\treturn m.router6.AddInboundDNAT(localAddr, protocol, originalPort, translatedPort)\n\t}\n\treturn m.router.AddInboundDNAT(localAddr, protocol, originalPort, translatedPort)","sourceCodeStart":440,"sourceCodeEnd":476,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/iptables/manager_linux.go#L440-L476","documentation":"UpdateSet splits the prefix list by family; the v4 refresh (router.UpdateSet) succeeded, then the v6 refresh (router6.UpdateSet, an ipset member sync) failed and got this wrap. The result is partial state: v4 ipset members are current, v6 members are stale. The operation is idempotent - re-calling UpdateSet with the same set and full prefix list re-syncs both families.","triggerScenarios":"UpdateSet with at least one v6 prefix on a hasIPv6() manager where the v6 ipset operation fails - set destroyed underneath, kernel ipset module issues, or netfilter mutex contention.","commonSituations":"External ipset flush between calls; large dynamic route updates where DNS resolution changes membership mid-sync; hosts with ipset support but degraded v6 netfilter.","solutions":["Retry UpdateSet with the same set and complete prefix list - the sync is idempotent","Inspect the v6 set as root: ipset list <name> (v6 sets carry the -v6 suffix convention in the ACL layer)","If persistent, restart the agent to recreate the sets and re-apply membership"],"exampleFix":"// before\nif err := mgr.UpdateSet(set, prefixes); err != nil {\n\treturn err // v4 updated, v6 stale\n}\n\n// after\nif err := mgr.UpdateSet(set, prefixes); err != nil {\n\tlog.Warnf(\"set update failed, retrying once: %v\", err)\n\tif rerr := mgr.UpdateSet(set, prefixes); rerr != nil {\n\t\treturn fmt.Errorf(\"retry update set: %w\", rerr)\n\t}\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err := mgr.UpdateSet(set, prefixes); err != nil {\n    // v4 half applied; re-sync both families idempotently\n    if rerr := mgr.UpdateSet(set, prefixes); rerr != nil {\n        return fmt.Errorf(\"update set (retry): %w\", rerr)\n    }\n    log.Debugf(\"v6 set resynced after retry\")\n}","preventionTips":["Keep the full prefix list around so UpdateSet can be retried wholesale","Remember v6 sets are only touched when v6 prefixes exist and the manager has v6","Verify ipset membership after retries during large dynamic-route updates"],"tags":["iptables","ipv6","ipset","firewall","partial-state","netbird","go"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}