{"record":{"id":"c8d497f70a1f9a6e","repo":"cilium/cilium","slug":"unable-to-mark-orphaned-cidr-s-still-used-by-node","errorCode":null,"errorMessage":"unable to mark orphaned CIDR %s still used by node %s as allocated: %w","messagePattern":"unable to mark orphaned CIDR (.+?) still used by node (.+?) as allocated: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"operator/pkg/ipam/allocator/multipool/pool_allocator.go","lineNumber":188,"sourceCode":"\tcase len(p.orphans[node][pool].v6) == 0:\n\t\tcidrs := p.orphans[node][pool]\n\t\tp.orphans[node][pool] = cidrSets{\n\t\t\tv4:           cidrs.v4,\n\t\t\tallowFirstIP: cidrs.allowFirstIP,\n\t\t\tallowLastIP:  cidrs.allowLastIP,\n\t\t}\n\t}\n}\n\nfunc (p *PoolAllocator) unorphanCIDR(isV6 bool, node, pool string, cidr netip.Prefix) error {\n\tp.logger.Info(\n\t\t\"CIDR from pool already in use by node, marking it as allocated\",\n\t\tlogfields.CIDR, cidr,\n\t\tlogfields.PoolName, pool,\n\t\tlogfields.Node, node,\n\t)\n\tif err := p.occupyCIDR(node, pool, cidr); err != nil {\n\t\treturn fmt.Errorf(\"unable to mark orphaned CIDR %s still used by node %s as allocated: %w\", cidr, node, err)\n\t}\n\tif isV6 {\n\t\tdelete(p.orphans[node][pool].v6, cidr)\n\t} else {\n\t\tdelete(p.orphans[node][pool].v4, cidr)\n\t}\n\treturn nil\n}\n\nfunc (p *PoolAllocator) reconcileOrphanCIDRs(pool string, v4, v6 []cidralloc.CIDRAllocator) error {\n\tvar errs []error\n\tfor node, cidrs := range p.orphans {\n\t\tfor pool, cidrSets := range cidrs {\n\t\t\tfor cidr := range cidrSets.v4 {\n\t\t\t\tif containsCIDR(v4, cidr) {\n\t\t\t\t\terrs = append(errs, p.unorphanCIDR(false, node, pool, cidr))\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/cilium/cilium/blob/ac7b90affa4baf0642e6685319d56907b3a73a6d/operator/pkg/ipam/allocator/multipool/pool_allocator.go#L170-L206","documentation":"During reconcileOrphanCIDRs, unorphanCIDR reclaims a CIDR that a node still uses but which the allocator lost track of (orphaned). It calls p.occupyCIDR to mark the orphaned CIDR as allocated again; if that fails (e.g. the CIDR is now allocated to a different node, or it is outside the pool's ranges), the error is wrapped with this message naming the CIDR and node.","triggerScenarios":"unorphanCIDR(node, pool, cidr) is invoked and occupyCIDR returns errAlreadyAllocated (another node took the CIDR while it was orphaned), 'cidr is not part of the requested pool' (pool CIDR config changed), or an allocator error. reconcileOrphanCIDRs then surfaces the wrapped error.","commonSituations":"Operator restarted with lost in-memory allocation state while nodes kept their CIDRs; a competing operator instance re-allocated the same CIDR; pool CIDRs were edited during the orphan window so the orphaned prefix is no longer in range.","solutions":["Check which node now owns the CIDR (node specs / pool allocations) — if reassigned, clear the orphan record for the old node instead of forcing occupy.","Confirm the pool's CIDR config still contains the orphaned prefix; restore it if it was changed mid-flight.","Ensure single-operator operation (leader election) to prevent a second allocator from claiming orphaned CIDRs concurrently.","Retry reconciliation: once the conflicting allocation is resolved, the next reconcileOrphanCIDRs pass can mark the CIDR allocated."],"exampleFix":"// before\nif err := p.occupyCIDR(node, pool, cidr); err != nil {\n\treturn fmt.Errorf(\"unable to mark orphaned CIDR %s still used by node %s as allocated: %w\", cidr, node, err)\n}\n// after: tolerate already-owned by same node, fail loudly on foreign owner\nif err := p.occupyCIDR(node, pool, cidr); err != nil {\n\tif errors.Is(err, errAlreadyAllocated) {\n\t\t// drop stale orphan entry; CIDR owner changed\n\t\tdelete(p.orphans, node)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unable to mark orphaned CIDR %s still used by node %s as allocated: %w\", cidr, node, err)\n}","handlingStrategy":"try-catch","validationCode":"// verify the orphaned CIDR is still in the pool before re-occupying\nif !containsCIDR(allocators, cidr) {\n\tdelete(p.orphans, node) // stale orphan, drop it\n\treturn nil\n}","typeGuard":"func orphanOwnedByOtherNode(p *PoolAllocator, cidr netip.Prefix, node string) bool {\n\towner := p.cidrOwner(cidr)\n\treturn owner != \"\" && owner != node\n}","tryCatchPattern":"if err := reconcileOrphanCIDRs(ctx, p, nodes); err != nil {\n\tvar owned *OwnershipConflict\n\tif strings.Contains(err.Error(), \"orphaned CIDR\") && strings.Contains(err.Error(), \"already been allocated\") {\n\t\t// CIDR reassigned while orphaned; drop stale record and requeue\n\t\tdropStaleOrphans(p)\n\t\treturn nil\n\t}\n\treturn err\n}","preventionTips":["Persist allocation state (or run a single leader-elected operator) so restarts don't orphan CIDRs.","After operator restart, compare node-reported CIDRs with allocator state before reclaiming.","Freeze pool CIDR edits while orphans are outstanding."],"tags":["ipam","cidr","cilium","state-sync"],"backgroundTag":"orphaned-cidr-conflict","analyzedSha":"ac7b90affa4baf0642e6685319d56907b3a73a6d","analyzedAt":"2026-08-31T18:27:15.868Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}