package unit; import java.util.*; /** * Cilium0003Algorithm — CWE-407 unit test for cilium-0003 * * cilium-0003: node/manager/manager.go:977-1073 * for _, address := range oldNode.IPAddresses { // O(A) addresses * if slices.Contains(nodeIPsAdded, prefix) { // O(A) linear scan * * nodeIPsAdded is a []netip.Prefix built by appending one entry per address — * scanning it per-address is O(A²) total. Five such slices are scanned. * * Called from NodeUpdated() on every node add/update event. * In large ClusterMesh deployments (500+ nodes), every node event pays O(A²). * * SLOW: slices.Contains([]Prefix, p) — O(A) linear scan per address * FAST: map[Prefix]struct{} pre-built — O(1) per address * * No JUnit. Run: javac -d . Cilium0003Algorithm.java && java -ea unit.Cilium0003Algorithm */ public class Cilium0003Algorithm { // ------------------------------------------------------------------------- // Data model — mirrors netip.Prefix (modeled as String for simplicity) // ------------------------------------------------------------------------- // Each "address" is a string like "10.0.0.1/32" or "fd00::1/128" // "nodeIPsAdded" is a list of such strings (the new addresses) // "oldIPAddresses" is the old list to diff against static long slowOps = 0; static long fastOps = 0; // ------------------------------------------------------------------------- // SLOW: O(A²) — models removeNodeFromIPCache with slices.Contains per address // ------------------------------------------------------------------------- /** * For each old address, check if it's in the added set. * Models: for _, addr := range oldIPAddresses { slices.Contains(nodeIPsAdded, addr) } * Returns the list of addresses to remove (not in added set). */ static List computeRemovedSlow(List oldAddresses, List addedPrefixes) { List toRemove = new ArrayList<>(); for (String addr : oldAddresses) { // O(A) boolean found = false; for (String added : addedPrefixes) { // O(A) linear scan — defect slowOps++; if (added.equals(addr)) { found = true; break; } } if (!found) { toRemove.add(addr); } } return toRemove; } /** * Models the full removeNodeFromIPCache pattern: * five separate slices each scanned linearly. */ static int removeNodeFromIPCacheSlow( List oldAddresses, List nodeIPsAdded, List ipsetEntries, List healthIPsAdded, List ingressIPsAdded, List podCIDRsAdded ) { int removeOps = 0; for (String addr : oldAddresses) { // O(A) // slices.Contains(nodeIPsAdded, prefix) boolean inNodeIPs = false; for (String p : nodeIPsAdded) { slowOps++; if (p.equals(addr)) { inNodeIPs = true; break; } } // slices.Contains(ipsetEntries, ...) boolean inIpset = false; for (String p : ipsetEntries) { slowOps++; if (p.equals(addr)) { inIpset = true; break; } } if (!inNodeIPs) { removeOps++; } } // pod CIDR removals for (String cidr : podCIDRsAdded) { // O(CIDR) boolean inPodCIDRs = false; for (String p : podCIDRsAdded) { slowOps++; if (p.equals(cidr)) { inPodCIDRs = true; break; } } } // health/ingress IPs for (String addr : oldAddresses) { for (String p : healthIPsAdded) { slowOps++; if (p.equals(addr)) break; } for (String p : ingressIPsAdded) { slowOps++; if (p.equals(addr)) break; } } return removeOps; } // ------------------------------------------------------------------------- // FAST: O(A) — pre-build map[string]struct{} for each slice // ------------------------------------------------------------------------- static List computeRemovedFast(List oldAddresses, List addedPrefixes) { Set addedSet = new HashSet<>(addedPrefixes.size() * 2); for (String p : addedPrefixes) { fastOps++; addedSet.add(p); } // O(A) one-time build List toRemove = new ArrayList<>(); for (String addr : oldAddresses) { // O(A) fastOps++; if (!addedSet.contains(addr)) { // O(1) lookup toRemove.add(addr); } } return toRemove; } static int removeNodeFromIPCacheFast( List oldAddresses, List nodeIPsAdded, List ipsetEntries, List healthIPsAdded, List ingressIPsAdded, List podCIDRsAdded ) { // Pre-build all five sets — O(A) total one-time cost Set nodeIPsSet = new HashSet<>(nodeIPsAdded); Set ipsetSet = new HashSet<>(ipsetEntries); Set healthSet = new HashSet<>(healthIPsAdded); Set ingressSet = new HashSet<>(ingressIPsAdded); Set podCIDRsSet = new HashSet<>(podCIDRsAdded); int removeOps = 0; for (String addr : oldAddresses) { // O(A) fastOps++; if (!nodeIPsSet.contains(addr)) { // O(1) removeOps++; } if (!ipsetSet.contains(addr)) fastOps++; if (!healthSet.contains(addr)) fastOps++; if (!ingressSet.contains(addr)) fastOps++; } return removeOps; } // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- /** Build A addresses. First half overlap with 'added', second half are old-only. */ static List buildAddresses(int A, String prefix) { List addrs = new ArrayList<>(A); for (int i = 0; i < A; i++) { addrs.add(prefix + "10." + (i / 256) + "." + (i % 256) + ".1/32"); } return addrs; } // ------------------------------------------------------------------------- // Tests // ------------------------------------------------------------------------- static void testCorrectness() { List old = Arrays.asList("10.0.0.1/32", "10.0.0.2/32", "10.0.0.3/32"); List added = Arrays.asList("10.0.0.1/32", "10.0.0.3/32"); // 2 is removed List slowResult = computeRemovedSlow(old, added); List fastResult = computeRemovedFast(old, added); assert slowResult.size() == 1 : "slow: expected 1 removal, got " + slowResult.size(); assert fastResult.size() == 1 : "fast: expected 1 removal, got " + fastResult.size(); assert slowResult.get(0).equals("10.0.0.2/32") : "slow: wrong removal " + slowResult.get(0); assert fastResult.get(0).equals("10.0.0.2/32") : "fast: wrong removal " + fastResult.get(0); System.out.println("PASS correctness: removal diff verified"); } static void testOpsCount_A50() { int A = 50; List old = buildAddresses(A, "old-"); List added = buildAddresses(A / 2, "old-"); // first half overlap slowOps = 0; List slowResult = computeRemovedSlow(old, added); long measuredSlowOps = slowOps; fastOps = 0; List fastResult = computeRemovedFast(old, added); long measuredFastOps = fastOps; assert slowResult.size() == fastResult.size() : "sizes differ: " + slowResult.size() + " vs " + fastResult.size(); double ratio = (double) measuredSlowOps / Math.max(measuredFastOps, 1); System.out.printf("PASS ops_count A=%d: slowOps=%d fastOps=%d ratio=%.1fx%n", A, measuredSlowOps, measuredFastOps, ratio); assert measuredSlowOps > measuredFastOps * 5 : "expected slowOps >> fastOps, got slow=" + measuredSlowOps + " fast=" + measuredFastOps; } static void testPerf_A100_NodeUpdate() { int A = 100; List oldAddrs = buildAddresses(A, ""); List nodeIPsAdded = buildAddresses(A / 2, ""); List ipsetEntries = buildAddresses(A / 4, ""); List healthIPs = buildAddresses(2, "h-"); List ingressIPs = buildAddresses(2, "i-"); List podCIDRs = buildAddresses(10, "c-"); long t0 = System.nanoTime(); int slowResult = 0; for (int i = 0; i < 5000; i++) { slowResult += removeNodeFromIPCacheSlow( oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs); } long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); int fastResult = 0; for (int i = 0; i < 5000; i++) { fastResult += removeNodeFromIPCacheFast( oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs); } long fastMs = (System.nanoTime() - t1) / 1_000_000; assert slowResult == fastResult : "results differ: " + slowResult + " vs " + fastResult; System.out.printf("PASS perf A=%d 5000 node-updates: slow=%dms fast=%dms ratio=%.1fx%n", A, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1)); assert slowMs >= fastMs : "expected slow >= fast, got slow=" + slowMs + "ms fast=" + fastMs + "ms"; } static void testPerf_A300_ClusterMesh_stress() { int A = 300; List oldAddrs = buildAddresses(A, ""); List nodeIPsAdded = buildAddresses(A, ""); // all added (worst-case scan) List ipsetEntries = buildAddresses(A / 2, ""); List healthIPs = buildAddresses(2, "h-"); List ingressIPs = buildAddresses(2, "i-"); List podCIDRs = buildAddresses(A / 3, "c-"); long t0 = System.nanoTime(); int slowResult = 0; for (int i = 0; i < 1000; i++) { slowResult += removeNodeFromIPCacheSlow( oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs); } long slowMs = (System.nanoTime() - t0) / 1_000_000; long t1 = System.nanoTime(); int fastResult = 0; for (int i = 0; i < 1000; i++) { fastResult += removeNodeFromIPCacheFast( oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs); } long fastMs = (System.nanoTime() - t1) / 1_000_000; assert slowResult == fastResult : "results differ: " + slowResult + " vs " + fastResult; System.out.printf("PASS stress A=%d 1000 node-updates: slow=%dms fast=%dms ratio=%.1fx%n", A, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1)); assert slowMs >= fastMs : "expected slow >= fast, got slow=" + slowMs + "ms fast=" + fastMs + "ms"; } // ------------------------------------------------------------------------- // Main // ------------------------------------------------------------------------- public static void main(String[] args) { System.out.println("=== Cilium0003Algorithm: node manager IP address dedup (cilium-0003) ==="); testCorrectness(); testOpsCount_A50(); testPerf_A100_NodeUpdate(); testPerf_A300_ClusterMesh_stress(); System.out.println("4/4 PASS"); } }