java-topology/defects/openbsd/unit/PfOsfpAlgorithm.java

195 lines
7.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<OsFingerprint> 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<OsFingerprint> list = new ArrayList<>();
// Hash by tcpopts (first check in pf_osfp_find; buckets average ~4 entries)
final Map<Long, List<OsFingerprint>> 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<OsFingerprint> 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<OsFingerprint> generateFingerprints(int n, Random rng) {
List<OsFingerprint> fps = new ArrayList<>(n);
Set<String> 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<OsFingerprint> 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);
}
}