Each decky base container now receives a set of Linux kernel sysctls (net.ipv4.ip_default_ttl, net.ipv4.tcp_syn_retries, etc.) tuned to match the claimed OS family, making nmap OS detection return the expected OS rather than the Linux host. - decnet/os_fingerprint.py: OS profile table (linux/windows/bsd/embedded/cisco) keyed by TTL and TCP tuning knobs - decnet/archetypes.py: Archetype gains nmap_os field; windows-* → "windows", printer/iot/industrial → "embedded", rest → "linux" - decnet/config.py: DeckyConfig gains nmap_os field (default "linux") - decnet/cli.py: nmap_os resolved from archetype → DeckyConfig in both CLI and INI build paths - decnet/composer.py: base container gets sysctls + cap_add: [NET_ADMIN]; service containers inherit via shared network namespace - tests/test_os_fingerprint.py: 48 new tests covering profiles, compose injection, archetype coverage, and CLI propagation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
56 lines
1.7 KiB
Python
56 lines
1.7 KiB
Python
"""
|
||
OS TCP/IP fingerprint profiles for DECNET deckies.
|
||
|
||
Maps an nmap OS family slug to a dict of Linux kernel sysctls that, when applied
|
||
to a container's network namespace, make its TCP/IP stack behaviour resemble the
|
||
claimed OS as closely as possible within the Linux kernel's constraints.
|
||
|
||
Primary discriminator leveraged by nmap: net.ipv4.ip_default_ttl (TTL)
|
||
Linux → 64
|
||
Windows → 128
|
||
BSD (FreeBSD/macOS)→ 64 (different TCP options, but same TTL as Linux)
|
||
Embedded / network → 255
|
||
|
||
Secondary tuning (TCP behaviour):
|
||
net.ipv4.tcp_syn_retries – SYN retransmits before giving up
|
||
net.core.rmem_default – initial receive buffer → affects SYN-ACK window field
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
OS_SYSCTLS: dict[str, dict[str, str]] = {
|
||
"linux": {
|
||
"net.ipv4.ip_default_ttl": "64",
|
||
"net.ipv4.tcp_syn_retries": "6",
|
||
},
|
||
"windows": {
|
||
"net.ipv4.ip_default_ttl": "128",
|
||
"net.ipv4.tcp_syn_retries": "2",
|
||
"net.core.rmem_default": "8388608", # 8 MB → large initial window like Windows
|
||
},
|
||
"bsd": {
|
||
"net.ipv4.ip_default_ttl": "64",
|
||
"net.ipv4.tcp_syn_retries": "6",
|
||
},
|
||
"embedded": {
|
||
"net.ipv4.ip_default_ttl": "255",
|
||
"net.ipv4.tcp_syn_retries": "3",
|
||
},
|
||
"cisco": {
|
||
"net.ipv4.ip_default_ttl": "255",
|
||
"net.ipv4.tcp_syn_retries": "2",
|
||
},
|
||
}
|
||
|
||
_DEFAULT_OS = "linux"
|
||
|
||
|
||
def get_os_sysctls(nmap_os: str) -> dict[str, str]:
|
||
"""Return the sysctl dict for *nmap_os*. Falls back to Linux on unknown slugs."""
|
||
return dict(OS_SYSCTLS.get(nmap_os, OS_SYSCTLS[_DEFAULT_OS]))
|
||
|
||
|
||
def all_os_families() -> list[str]:
|
||
"""Return all registered nmap OS family slugs."""
|
||
return list(OS_SYSCTLS.keys())
|