package unit; import java.util.*; /** * nginx-0004 — CWE-407: ngx_http_upstream_keepalive_get_peer O(C) per upstream request * * Models src/http/modules/ngx_http_upstream_keepalive_module.c * ngx_http_upstream_keepalive_get_peer() (~line 212): * Slow: iterates free[] queue scanning for matching sockaddr → O(C) per request * where C = keepalive cache size (default 0, but configured 100–10000 in production) * Fast: HashMap → O(1) per request * * Hot path: called on every upstream HTTP request that could reuse a keepalive connection. * With `keepalive 1000` configured and many distinct upstreams, degrades to O(1000) per request. */ public class NginxKeepaliveCacheLinearScanTest { // Simulates ngx_connection_t / ngx_peer_connection_t with a sockaddr key static class KeepaliveConn { final String sockaddrKey; // e.g., "192.168.1.1:8080" KeepaliveConn(String sockaddrKey) { this.sockaddrKey = sockaddrKey; } } // --- SLOW: O(C) linear scan (defect) --- // Models: for (q = ngx_queue_head(cache); q != ngx_queue_sentinel(cache); q = ngx_queue_next(q)) // item = ngx_queue_data(q, ...); if (ngx_memn2cmp(sockaddr, item->sockaddr) == 0) found static long keepaliveGetPeerSlow(List cache, String target) { long ops = 0; for (KeepaliveConn conn : cache) { ops++; if (conn.sockaddrKey.equals(target)) break; } return ops; } // --- FAST: O(1) hash lookup (fix) --- // Models: pre-built HashMap keyed by sockaddr at connection cache time static long keepaliveGetPeerFast(Map index, String target) { index.get(target); return 1; } // Build a keepalive cache with C connections to distinct upstream addresses static List buildCache(int C) { List cache = new ArrayList<>(C); for (int i = 0; i < C; i++) cache.add(new KeepaliveConn("10.0." + (i / 256) + "." + (i % 256) + ":8080")); return cache; } static Map buildIndex(List cache) { Map map = new HashMap<>(cache.size() * 2); for (KeepaliveConn c : cache) map.put(c.sockaddrKey, c); return map; } public static void main(String[] args) { System.out.println("nginx-0004 CWE-407: keepalive_get_peer O(C) vs O(1)"); System.out.println("======================================================"); // R = number of upstream requests, C = keepalive cache size // target is the last entry (worst-case scan) int[][] params = { {100, 100}, {500, 500}, {1000, 1000} }; for (int[] p : params) { int R = p[0], C = p[1]; List cache = buildCache(C); Map index = buildIndex(cache); // Worst-case: target is the last connection in the cache String target = cache.get(C - 1).sockaddrKey; long slowTotal = 0, fastTotal = 0; for (int r = 0; r < R; r++) { slowTotal += keepaliveGetPeerSlow(cache, target); fastTotal += keepaliveGetPeerFast(index, target); } double ratio = (double) slowTotal / fastTotal; System.out.printf(" C=%4d cache, R=%4d requests (worst-case): slow=%,8d ops fast=%,5d ops speedup=%.0fx%n", C, R, slowTotal, fastTotal, ratio); assert ratio >= (double) C / 2 : "Expected speedup >= " + (C/2) + "x but got " + ratio + " (C=" + C + ")"; } System.out.println("\nPASS"); } }