wave14b: openbsd-0001/2 + 526/240 PDF
This commit is contained in:
parent
ac0e1e091e
commit
c4026333ba
9 changed files with 695 additions and 5 deletions
195
defects/openbsd/unit/PfOsfpAlgorithm.java
Normal file
195
defects/openbsd/unit/PfOsfpAlgorithm.java
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue