fix(collector): strip port from remote_addr before attacker identity resolution
host:port in remote_addr was creating a distinct Attacker row per TCP connection instead of per IP. Split on the last ':' in parse_rfc5424; preserve the port as fields['remote_port'] so repeated source ports are retained as fingerprint signal in bounty payloads.
This commit is contained in:
@@ -472,7 +472,14 @@ def parse_rfc5424(line: str) -> Optional[dict[str, Any]]:
|
||||
attacker_ip = "Unknown"
|
||||
for fname in _IP_FIELDS:
|
||||
if fname in fields:
|
||||
attacker_ip = fields[fname]
|
||||
raw = fields[fname]
|
||||
# remote_addr may be "host:port" — split so identity keys on IP only.
|
||||
host, _, port = raw.rpartition(":")
|
||||
if host and port.isdigit():
|
||||
attacker_ip = host.strip("[]") # handle [::1]:port IPv6 form
|
||||
fields.setdefault("remote_port", port)
|
||||
else:
|
||||
attacker_ip = raw
|
||||
break
|
||||
|
||||
# Fallback for plain `logger` callers that don't use SD params (notably
|
||||
|
||||
@@ -640,6 +640,7 @@ async def _extract_bounty(
|
||||
"protocol": _fields.get("proto") or _fields.get("protocol", "h1"),
|
||||
"method": _fields.get("method"),
|
||||
"path": _fields.get("path"),
|
||||
"remote_port": _fields.get("remote_port"),
|
||||
},
|
||||
})
|
||||
|
||||
@@ -656,6 +657,7 @@ async def _extract_bounty(
|
||||
"settings": _fields.get("settings"),
|
||||
"frame_order": _fields.get("frame_order"),
|
||||
"protocol": "h2" if _evt_type == "http2_settings" else "h3",
|
||||
"remote_port": _fields.get("remote_port"),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -156,6 +156,21 @@ class TestParseRfc5424:
|
||||
assert result["fields"]["src_ip"] == "1.2.3.4"
|
||||
assert "src" not in result["fields"]
|
||||
|
||||
def test_remote_addr_with_port_strips_port(self):
|
||||
"""remote_addr="1.2.3.4:40838" — attacker_ip must be the host only."""
|
||||
line = self._make_line('remote_addr="192.168.1.5:40838"')
|
||||
result = parse_rfc5424(line)
|
||||
assert result["attacker_ip"] == "192.168.1.5"
|
||||
assert result["fields"]["remote_port"] == "40838"
|
||||
|
||||
def test_remote_addr_plain_ip_no_port(self):
|
||||
"""remote_addr="1.2.3.4" without port — attacker_ip is the full value,
|
||||
no remote_port key injected."""
|
||||
line = self._make_line('remote_addr="192.168.1.5"')
|
||||
result = parse_rfc5424(line)
|
||||
assert result["attacker_ip"] == "192.168.1.5"
|
||||
assert "remote_port" not in result["fields"]
|
||||
|
||||
def test_parses_msg(self):
|
||||
line = self._make_line(msg="hello world")
|
||||
result = parse_rfc5424(line)
|
||||
|
||||
Reference in New Issue
Block a user