- test_sniffer_p0f.py: p0f passive OS fingerprinting tests - test_sniffer_tcp_fingerprint.py: TCP fingerprinting accuracy tests - test_sniffer_retransmit.py: retransmission detection and analysis
118 lines
3.9 KiB
Python
118 lines
3.9 KiB
Python
"""
|
|
Unit tests for the passive p0f-lite OS fingerprint lookup.
|
|
|
|
Covers:
|
|
- initial_ttl() TTL → bucket rounding
|
|
- hop_distance() upper-bound clamping
|
|
- guess_os() signature matching for Linux, Windows, macOS, nmap,
|
|
embedded, and the unknown fallback
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from decnet.sniffer.p0f import guess_os, hop_distance, initial_ttl
|
|
|
|
|
|
# ─── initial_ttl ────────────────────────────────────────────────────────────
|
|
|
|
class TestInitialTtl:
|
|
def test_linux_bsd(self):
|
|
assert initial_ttl(64) == 64
|
|
assert initial_ttl(59) == 64
|
|
assert initial_ttl(33) == 64
|
|
|
|
def test_windows(self):
|
|
assert initial_ttl(128) == 128
|
|
assert initial_ttl(120) == 128
|
|
assert initial_ttl(65) == 128
|
|
|
|
def test_embedded(self):
|
|
assert initial_ttl(255) == 255
|
|
assert initial_ttl(254) == 255
|
|
assert initial_ttl(200) == 255
|
|
|
|
def test_very_short(self):
|
|
# anything <= 32 rounds to 32
|
|
assert initial_ttl(32) == 32
|
|
assert initial_ttl(1) == 32
|
|
|
|
def test_out_of_range(self):
|
|
# Packets with TTL > 255 (should never happen) still bucket.
|
|
assert initial_ttl(300) == 255
|
|
|
|
|
|
# ─── hop_distance ───────────────────────────────────────────────────────────
|
|
|
|
class TestHopDistance:
|
|
def test_zero_when_local(self):
|
|
assert hop_distance(64) == 0
|
|
assert hop_distance(128) == 0
|
|
assert hop_distance(255) == 0
|
|
|
|
def test_typical(self):
|
|
assert hop_distance(60) == 4 # 4 hops from Linux
|
|
assert hop_distance(120) == 8 # 8 hops from Windows
|
|
|
|
def test_negative_or_weird_still_bucketed(self):
|
|
# TTL=0 is anomalous but we still return a non-negative distance.
|
|
# TTL 0 bucket is 32 → distance = 32 - 0 = 32.
|
|
assert hop_distance(0) == 32
|
|
|
|
|
|
# ─── guess_os ───────────────────────────────────────────────────────────────
|
|
|
|
class TestGuessOs:
|
|
def test_linux_default(self):
|
|
# Modern Linux: TTL 64, window 29200+, WScale 7, full options
|
|
result = guess_os(
|
|
ttl=64, window=29200, mss=1460, wscale=7,
|
|
options_sig="M,S,T,N,W",
|
|
)
|
|
assert result == "linux"
|
|
|
|
def test_windows_default(self):
|
|
# Windows 10: TTL 128, window 64240, WScale 8, MSS 1460
|
|
result = guess_os(
|
|
ttl=128, window=64240, mss=1460, wscale=8,
|
|
options_sig="M,N,W,N,N,T,S",
|
|
)
|
|
assert result == "windows"
|
|
|
|
def test_macos_ios(self):
|
|
# macOS default: TTL 64, window 65535, WScale 6, ends with EOL
|
|
result = guess_os(
|
|
ttl=64, window=65535, mss=1460, wscale=6,
|
|
options_sig="M,N,W,N,N,T,S,E",
|
|
)
|
|
assert result == "macos_ios"
|
|
|
|
def test_nmap_sYn(self):
|
|
# nmap -sS uses tiny/distinctive windows like 1024 or 4096
|
|
result = guess_os(
|
|
ttl=64, window=1024, mss=1460, wscale=10,
|
|
options_sig="M,W,T,S,S",
|
|
)
|
|
assert result == "nmap"
|
|
|
|
def test_nmap_alt_window(self):
|
|
result = guess_os(
|
|
ttl=64, window=31337, mss=1460, wscale=10,
|
|
options_sig="M,W,T,S,S",
|
|
)
|
|
assert result == "nmap"
|
|
|
|
def test_embedded_ttl255(self):
|
|
# Any TTL bucket 255 → embedded
|
|
result = guess_os(
|
|
ttl=250, window=4128, mss=536, wscale=None,
|
|
options_sig="M",
|
|
)
|
|
assert result == "embedded"
|
|
|
|
def test_unknown(self):
|
|
# Bizarre combo nothing matches
|
|
result = guess_os(
|
|
ttl=50, window=100, mss=0, wscale=None, options_sig="",
|
|
)
|
|
assert result == "unknown"
|