195 lines
7.3 KiB
Java
195 lines
7.3 KiB
Java
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);
|
||
}
|
||
}
|