From bb33f4a3562b799360c8023c96dbc9d8ddfd18f5 Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Wed, 9 Oct 2024 16:49:31 +0200 Subject: [PATCH] feat: Add socket-activated systemd service for discovery Introduce a system-wide daemon listening on /run/wsdd.socket, which is managed by systemd. When a client first connects to the socket the daemon is started. Multiple clients can connect and safely use the API simultaneously. The service runs in discovery-only mode. Uses multicast source-port 37020. --- etc/systemd/wsdd-discovery.service | 16 ++++++++++++++++ etc/systemd/wsdd-discovery.socket | 9 +++++++++ src/wsdd.py | 18 +++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 etc/systemd/wsdd-discovery.service create mode 100644 etc/systemd/wsdd-discovery.socket diff --git a/etc/systemd/wsdd-discovery.service b/etc/systemd/wsdd-discovery.service new file mode 100644 index 0000000..cae86d5 --- /dev/null +++ b/etc/systemd/wsdd-discovery.service @@ -0,0 +1,16 @@ +[Unit] +Description=Web Services Dynamic Discovery service +Documentation=man:wsdd(8) +Requires=wsdd-discovery.socket +RefuseManualStart=true + +[Service] +Type=simple +; The service is put into an empty runtime directory chroot, +; i.e. the runtime directory which usually resides under /run +ExecStart=/usr/bin/wsdd --shortlog --chroot=/run/wsdd --source-port=37020 --no-host --discovery +DynamicUser=yes +User=wsdd +Group=wsdd +RuntimeDirectory=wsdd +AmbientCapabilities=CAP_SYS_CHROOT diff --git a/etc/systemd/wsdd-discovery.socket b/etc/systemd/wsdd-discovery.socket new file mode 100644 index 0000000..dce8ea8 --- /dev/null +++ b/etc/systemd/wsdd-discovery.socket @@ -0,0 +1,9 @@ +[Unit] +Description=Web Services Dynamic Discovery API socket +Documentation=man:wsdd(8) + +[Socket] +ListenStream=%t/wsdd.socket + +[Install] +WantedBy=sockets.target diff --git a/src/wsdd.py b/src/wsdd.py index e102422..4be1f82 100755 --- a/src/wsdd.py +++ b/src/wsdd.py @@ -42,6 +42,11 @@ except ModuleNotFoundError: from xml.etree.ElementTree import fromstring as ETfromString +try: + import systemd.daemon +except Exception: + # Non-systemd host + pass WSDD_VERSION: str = '0.8' @@ -1137,7 +1142,10 @@ async def create_server(self, aio_loop: asyncio.AbstractEventLoop, listen_addres # It appears mypy is not able to check the argument to create_task and the return value of start_server # correctly. The docs say start_server returns a coroutine and the create_task takes a coro. And: It works. # Thus, we ignore type errors here. - if isinstance(listen_address, int) or listen_address.isnumeric(): + if isinstance(listen_address, socket.SocketType): + self.server = await aio_loop.create_task(asyncio.start_unix_server( # type: ignore + self.on_connect, sock=listen_address)) + elif isinstance(listen_address, int) or listen_address.isnumeric(): self.server = await aio_loop.create_task(asyncio.start_server( # type: ignore self.on_connect, host='localhost', port=int(listen_address), reuse_address=True, reuse_port=True)) @@ -2010,6 +2018,14 @@ def main() -> int: api_server = None if args.listen: api_server = ApiServer(aio_loop, args.listen, nm) + else: + try: + fds = systemd.daemon.listen_fds() + if fds: + api_server = ApiServer(aio_loop, socket.socket(fileno=fds[0]), nm) + except Exception: + # Non-systemd host + pass # get uid:gid before potential chroot'ing if args.user is not None: