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>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# 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))
|
||||
Reference in New Issue
Block a user