package unit; import java.util.*; /** * openbsd-0001: pf_osfp_validate O(N²) fingerprint validation scan * * Models sys/net/pf_osfp.c pf_osfp_validate / pf_osfp_find: * - Slow: outer SLIST_FOREACH × inner SLIST_FOREACH (linear scan) → O(N²) * - Fast: HashMap keyed by tcpopts → O(N) validate, O(1) amortized find * * pf_osfp_validate is called on every pfctl ruleset reload (DIOCOSFPADD ioctl). * pf_osfp_find is called per TCP SYN packet in pf_osfp_fingerprint. * Default pf.os has 246 entries; N² = 60,516 comparisons per reload. */ public class PfOsfpAlgorithm { // Fingerprint (key fields from struct pf_os_fingerprint) static class OsFingerprint { final long tcpopts; // packed TCP options — primary discriminator final int wsize, psize, mss, flags, optcnt, wscale, ttl; final String name; OsFingerprint(long tcpopts, int wsize, int psize, int mss, int flags, int optcnt, int wscale, int ttl, String name) { this.tcpopts = tcpopts; this.wsize = wsize; this.psize = psize; this.mss = mss; this.flags = flags; this.optcnt = optcnt; this.wscale = wscale; this.ttl = ttl; this.name = name; } } // ---- SLOW: O(N²) ---- static class SlowOsfpList { final List list = new ArrayList<>(); long comparisons = 0; // operation counter void insert(OsFingerprint fp) { list.add(fp); } void resetCounters() { comparisons = 0; } /** pf_osfp_find: linear scan — O(N) */ OsFingerprint find(OsFingerprint needle) { for (OsFingerprint f : list) { comparisons++; if (f.tcpopts == needle.tcpopts && f.wsize == needle.wsize && f.psize == needle.psize && f.mss == needle.mss && f.optcnt == needle.optcnt && f.wscale == needle.wscale && f.ttl == needle.ttl) return f; } return null; } /** pf_osfp_validate: N calls to find — O(N²) total */ boolean validate() { for (OsFingerprint f : list) { OsFingerprint found = find(f); if (found != f) return false; } return true; } } // ---- FAST: O(N) validate, O(bucket) find ---- static class FastOsfpList { final List list = new ArrayList<>(); // Hash by tcpopts (first check in pf_osfp_find; buckets average ~4 entries) final Map> byTcpopts = new HashMap<>(); long comparisons = 0; void insert(OsFingerprint fp) { list.add(fp); byTcpopts.computeIfAbsent(fp.tcpopts, k -> new ArrayList<>()).add(fp); } void resetCounters() { comparisons = 0; } /** pf_osfp_find: bucket scan — O(N/buckets) ~O(1) */ OsFingerprint find(OsFingerprint needle) { List bucket = byTcpopts.get(needle.tcpopts); if (bucket == null) return null; for (OsFingerprint f : bucket) { comparisons++; if (f.wsize == needle.wsize && f.psize == needle.psize && f.mss == needle.mss && f.optcnt == needle.optcnt && f.wscale == needle.wscale && f.ttl == needle.ttl) return f; } return null; } /** pf_osfp_validate: O(N * bucket_avg) ~O(N) */ boolean validate() { for (OsFingerprint f : list) { OsFingerprint found = find(f); if (found != f) return false; } return true; } } static List generateFingerprints(int n, Random rng) { List fps = new ArrayList<>(n); Set usedKeys = new HashSet<>(); for (int i = 0; i < n; i++) { long tcpopts; int wsize, ttl, psize, optcnt, wscale, mss; String key; do { int opt0 = rng.nextInt(5), opt1 = rng.nextInt(5), opt2 = rng.nextInt(5); tcpopts = opt0 | (opt1 << 3) | (opt2 << 6) | ((long)(rng.nextInt(8)) << 9); wsize = 512 + rng.nextInt(65000); ttl = 32 + rng.nextInt(224); psize = 20 + rng.nextInt(1000); optcnt = rng.nextInt(8); wscale = rng.nextInt(15); mss = rng.nextBoolean() ? 1460 : 512; key = tcpopts + ":" + wsize + ":" + ttl + ":" + psize; } while (!usedKeys.add(key)); fps.add(new OsFingerprint(tcpopts, wsize, psize, mss, rng.nextInt(0x4000), optcnt, wscale, ttl, "OS-" + i)); } return fps; } public static void main(String[] args) { System.out.println("openbsd-0001: pf_osfp O(N²) validate vs O(N) hash validate"); System.out.println("================================================================"); int[] sizes = {246, 500, 1000, 2000}; boolean allPass = true; int passed = 0; int total = sizes.length; for (int n : sizes) { Random rng = new Random(n * 42L); List fps = generateFingerprints(n, rng); SlowOsfpList slow = new SlowOsfpList(); FastOsfpList fast = new FastOsfpList(); for (OsFingerprint fp : fps) { slow.insert(fp); fast.insert(fp); } // Correctness slow.resetCounters(); fast.resetCounters(); boolean slowOk = slow.validate(); boolean fastOk = fast.validate(); if (!slowOk || !fastOk) { System.out.printf(" N=%4d correctness FAIL (slow=%b fast=%b)%n", n, slowOk, fastOk); allPass = false; continue; } for (OsFingerprint fp : fps) { if (slow.find(fp) != fp || fast.find(fp) != fp) { System.out.printf(" N=%4d find mismatch FAIL%n", n); allPass = false; break; } } // Operation count (deterministic, JVM-noise immune) slow.resetCounters(); slow.validate(); long slowOps = slow.comparisons; fast.resetCounters(); fast.validate(); long fastOps = fast.comparisons; double ratio = (double) slowOps / Math.max(fastOps, 1); String verdict = ratio >= 10.0 ? "PASS" : "FAIL"; if (ratio < 10.0) allPass = false; else passed++; // Also benchmark wall time long t0 = System.nanoTime(); for (int it = 0; it < 100; it++) slow.validate(); long slowNs = (System.nanoTime() - t0) / 100; t0 = System.nanoTime(); for (int it = 0; it < 100; it++) fast.validate(); long fastNs = (System.nanoTime() - t0) / 100; System.out.printf(" N=%4d slow_ops=%,7d fast_ops=%,5d ops_ratio=%5.1fx wall=%,6dns/%,5dns [%s]%n", n, slowOps, fastOps, ratio, slowNs, fastNs, verdict); } System.out.println(); System.out.printf("%d/%d PASS%n", passed, total); if (!allPass) System.exit(1); } }