Files
FabledSteward/plugins/syslog/network.py
T
bvandeusenandClaude Opus 5.5 ed9da56efa
CI / lint (push) Successful in 2s
CI / unit (push) Successful in 1m29s
CI / integration (push) Successful in 1m38s
CI / publish (push) Successful in 20s
feat(syslog): set-up help on the page, port 5514 published, Docker-hidden senders called out (#6003)
The Syslog page says where to send (this server, its port and protocols) and
where the setting is on Synology, QNAP, TrueNAS, OpenWrt, UniFi and rsyslog,
open until a device sends. A sender whose address is on Steward's own
container network (Swarm ingress publishing hides the real sender) is
flagged, with the host-mode ports to publish instead. compose files and the
Dockerfile publish 5514/udp and /tcp; docs/plugins/syslog.md describes the
plugin and the deploy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 07:01:56 -04:00

61 lines
2.0 KiB
Python

# plugins/syslog/network.py
"""Whether a sender's address is really the sender's (#6003).
A source is told apart by the address its lines come from. Docker can hide
that address: a port published through Swarm's ingress (the default) hands
every packet to Steward from an address on the ingress network, so every
device reads as one sender. Such an address lies on one of the container's
own networks. The page says so beside it and how to fix it: publish the
port in host mode."""
from __future__ import annotations
import ipaddress
import socket
import struct
from functools import lru_cache
ROUTES = "/proc/net/route"
def subnets_from(route_table: str) -> list[ipaddress.IPv4Network]:
"""The networks a host is directly on, from /proc/net/route's text: each
route with no gateway, the default route left out."""
out = []
for line in route_table.splitlines()[1:]:
fields = line.split()
if len(fields) < 8:
continue
try:
dest, gateway, mask = (int(fields[i], 16) for i in (1, 2, 7))
except ValueError:
continue
if dest == 0 or gateway != 0:
continue
address = socket.inet_ntoa(struct.pack("<I", dest))
netmask = socket.inet_ntoa(struct.pack("<I", mask))
try:
out.append(ipaddress.IPv4Network(f"{address}/{netmask}", strict=False))
except ValueError:
continue
return out
@lru_cache(maxsize=1)
def own_subnets() -> tuple[ipaddress.IPv4Network, ...]:
"""This container's own networks, read once; none off Linux."""
try:
with open(ROUTES) as f:
return tuple(subnets_from(f.read()))
except OSError:
return ()
def on_own_network(address: str, subnets=None) -> bool:
"""Whether a sender's address is on one of Steward's own networks: most
likely Docker standing in for the real sender."""
try:
ip = ipaddress.ip_address(address)
except ValueError:
return False
return any(ip in net for net in (own_subnets() if subnets is None else subnets))