From 1196363d0beb551fd82518a93288bc84226b5dae Mon Sep 17 00:00:00 2001 From: anti Date: Fri, 10 Apr 2026 16:41:23 -0400 Subject: [PATCH] =?UTF-8?q?feat(os=5Ffingerprint):=20Phase=202=20=E2=80=94?= =?UTF-8?q?=20add=20icmp=5Fratelimit=20+=20icmp=5Fratemask=20sysctls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Windows: both 0 (no ICMP rate limiting — matches real Windows behavior) Linux: 1000ms / mask 6168 (kernel defaults) BSD: 250ms / mask 6168 (FreeBSD default is faster than Linux) Embedded/Cisco: both 0 (most firmware doesn't rate-limit ICMP) These affect nmap's IE and U1 probe groups which measure ICMP error response timing to closed UDP ports. Windows responds to all probes instantly while Linux throttles to ~1/sec. Tests: 10 new cases (5 per sysctl). Suite: 822 passed. --- decnet/os_fingerprint.py | 18 ++++++++++++-- tests/test_os_fingerprint.py | 47 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/decnet/os_fingerprint.py b/decnet/os_fingerprint.py index aa17b86..094bbc0 100644 --- a/decnet/os_fingerprint.py +++ b/decnet/os_fingerprint.py @@ -23,9 +23,13 @@ Secondary discriminators (nmap OPS / WIN / ECN / T2–T6 probe groups): net.ipv4.ip_no_pmtu_disc – DF bit in ICMP replies (IE probes); embedded on net.ipv4.tcp_fin_timeout – FIN_WAIT_2 seconds (T2–T6 timing); Windows shorter +ICMP tuning (nmap IE / U1 probe groups): + net.ipv4.icmp_ratelimit – Min ms between ICMP error replies; Windows = 0 (none) + net.ipv4.icmp_ratemask – Bitmask of ICMP types subject to rate limiting + Note: net.core.rmem_default is a global (non-namespaced) sysctl and cannot be -set per-container without --privileged; TCP window size mangling is deferred to -Phase 2 (iptables entrypoint). +set per-container without --privileged; TCP window size is already correct for +Windows (64240) from the kernel's default tcp_rmem settings. """ from __future__ import annotations @@ -40,6 +44,8 @@ OS_SYSCTLS: dict[str, dict[str, str]] = { "net.ipv4.tcp_ecn": "2", "net.ipv4.ip_no_pmtu_disc": "0", "net.ipv4.tcp_fin_timeout": "60", + "net.ipv4.icmp_ratelimit": "1000", + "net.ipv4.icmp_ratemask": "6168", }, "windows": { "net.ipv4.ip_default_ttl": "128", @@ -50,6 +56,8 @@ OS_SYSCTLS: dict[str, dict[str, str]] = { "net.ipv4.tcp_ecn": "0", "net.ipv4.ip_no_pmtu_disc": "0", "net.ipv4.tcp_fin_timeout": "30", + "net.ipv4.icmp_ratelimit": "0", + "net.ipv4.icmp_ratemask": "0", }, "bsd": { "net.ipv4.ip_default_ttl": "64", @@ -60,6 +68,8 @@ OS_SYSCTLS: dict[str, dict[str, str]] = { "net.ipv4.tcp_ecn": "0", "net.ipv4.ip_no_pmtu_disc": "0", "net.ipv4.tcp_fin_timeout": "60", + "net.ipv4.icmp_ratelimit": "250", + "net.ipv4.icmp_ratemask": "6168", }, "embedded": { "net.ipv4.ip_default_ttl": "255", @@ -70,6 +80,8 @@ OS_SYSCTLS: dict[str, dict[str, str]] = { "net.ipv4.tcp_ecn": "0", "net.ipv4.ip_no_pmtu_disc": "1", "net.ipv4.tcp_fin_timeout": "15", + "net.ipv4.icmp_ratelimit": "0", + "net.ipv4.icmp_ratemask": "0", }, "cisco": { "net.ipv4.ip_default_ttl": "255", @@ -80,6 +92,8 @@ OS_SYSCTLS: dict[str, dict[str, str]] = { "net.ipv4.tcp_ecn": "0", "net.ipv4.ip_no_pmtu_disc": "1", "net.ipv4.tcp_fin_timeout": "15", + "net.ipv4.icmp_ratelimit": "0", + "net.ipv4.icmp_ratemask": "0", }, } diff --git a/tests/test_os_fingerprint.py b/tests/test_os_fingerprint.py index 970655b..79e3438 100644 --- a/tests/test_os_fingerprint.py +++ b/tests/test_os_fingerprint.py @@ -164,6 +164,53 @@ def test_embedded_tcp_fin_timeout_is_15(): def test_cisco_tcp_fin_timeout_is_15(): assert get_os_sysctls("cisco")["net.ipv4.tcp_fin_timeout"] == "15" +# --------------------------------------------------------------------------- +# os_fingerprint module — icmp_ratelimit +# --------------------------------------------------------------------------- + +def test_linux_icmp_ratelimit_is_1000(): + assert get_os_sysctls("linux")["net.ipv4.icmp_ratelimit"] == "1000" + + +def test_windows_icmp_ratelimit_is_0(): + assert get_os_sysctls("windows")["net.ipv4.icmp_ratelimit"] == "0" + + +def test_bsd_icmp_ratelimit_is_250(): + assert get_os_sysctls("bsd")["net.ipv4.icmp_ratelimit"] == "250" + + +def test_embedded_icmp_ratelimit_is_0(): + assert get_os_sysctls("embedded")["net.ipv4.icmp_ratelimit"] == "0" + + +def test_cisco_icmp_ratelimit_is_0(): + assert get_os_sysctls("cisco")["net.ipv4.icmp_ratelimit"] == "0" + + +# --------------------------------------------------------------------------- +# os_fingerprint module — icmp_ratemask +# --------------------------------------------------------------------------- + +def test_linux_icmp_ratemask_is_6168(): + assert get_os_sysctls("linux")["net.ipv4.icmp_ratemask"] == "6168" + + +def test_windows_icmp_ratemask_is_0(): + assert get_os_sysctls("windows")["net.ipv4.icmp_ratemask"] == "0" + + +def test_bsd_icmp_ratemask_is_6168(): + assert get_os_sysctls("bsd")["net.ipv4.icmp_ratemask"] == "6168" + + +def test_embedded_icmp_ratemask_is_0(): + assert get_os_sysctls("embedded")["net.ipv4.icmp_ratemask"] == "0" + + +def test_cisco_icmp_ratemask_is_0(): + assert get_os_sysctls("cisco")["net.ipv4.icmp_ratemask"] == "0" + # --------------------------------------------------------------------------- # os_fingerprint module — structural / completeness