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>
61 lines
2.0 KiB
Python
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))
|