Skip to content

Commit

Permalink
feat: Add socket-activated systemd service for discovery
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aleasto committed Oct 9, 2024
1 parent 006a799 commit bb33f4a
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 1 deletion.
16 changes: 16 additions & 0 deletions etc/systemd/wsdd-discovery.service
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions etc/systemd/wsdd-discovery.socket
Original file line number Diff line number Diff line change
@@ -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
18 changes: 17 additions & 1 deletion src/wsdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit bb33f4a

Please sign in to comment.