ActiveProbe.run/syslog_fields/publish_payload now accept port=None so
non-port-iterating probes can live in the registry. Ipv6LeakProbe replaces
the hand-rolled _ipv6_leak_phase special case in worker.py; it runs last
via priority=999. _probe_cycle no longer has an ad-hoc phase call.
Fixes three stale test files (test_prober_bus, test_prober_rotation,
test_prober_worker) that were broken since the 916b21b6 registry refactor.
63 lines
2.2 KiB
Python
63 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from decnet.logging import get_logger
|
|
from decnet.prober.base import ActiveProbe
|
|
|
|
_log = get_logger("prober.ipv6_leak_probe")
|
|
|
|
|
|
class Ipv6LeakProbe(ActiveProbe):
|
|
"""Port-free active probe that solicits a fe80:: response from the attacker.
|
|
|
|
Sends ICMPv6 Echo Request to ff02::1 on the attacker's reachable iface
|
|
to reveal the attacker's IPv6 IID / MAC-derived address.
|
|
|
|
Only fires when the attacker is directly reachable on L2 (no gateway).
|
|
Runs last (priority=999) so all TCP-level probes complete first.
|
|
"""
|
|
|
|
probe_name = "ipv6_leak"
|
|
default_ports: list[int | None] = [None]
|
|
event_type = "ipv6_link_local_leak"
|
|
priority = 999
|
|
|
|
def run(self, ip: str, port: int | None, timeout: float) -> dict[str, Any] | None:
|
|
from decnet.prober.ipv6_leak import _route_info, solicit_ipv6_leak
|
|
on_link, iface = _route_info(ip)
|
|
if not on_link:
|
|
_log.debug("ipv6_leak_probe: %s is not on-link — skip", ip)
|
|
return None
|
|
if iface is None:
|
|
_log.debug("ipv6_leak_probe: cannot determine iface for %s — skip", ip)
|
|
return None
|
|
return solicit_ipv6_leak(ip, iface, timeout=timeout)
|
|
|
|
def syslog_fields(
|
|
self, ip: str, port: int | None, result: dict[str, Any]
|
|
) -> tuple[dict[str, Any], str]:
|
|
addr = result.get("addr", "")
|
|
iid_kind = result.get("iid_kind", "")
|
|
fields = {
|
|
"ipv6_addr": addr,
|
|
"iid_kind": iid_kind,
|
|
"mac_oui": result.get("mac_oui", ""),
|
|
"on_iface": result.get("on_iface", ""),
|
|
"vector": result.get("vector", ""),
|
|
}
|
|
return fields, f"IPv6 leak {ip} → {addr} ({iid_kind})"
|
|
|
|
def publish_payload(
|
|
self, ip: str, port: int | None, result: dict[str, Any]
|
|
) -> dict[str, Any]:
|
|
return {
|
|
"attacker_ip": ip,
|
|
"addr": result.get("addr", ""),
|
|
"iid_kind": result.get("iid_kind", ""),
|
|
"mac_oui": result.get("mac_oui", ""),
|
|
"vector": result.get("vector", ""),
|
|
"on_iface": result.get("on_iface", ""),
|
|
"observed_at": result.get("observed_at", ""),
|
|
}
|