Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cpaelzer/80331eb7b2a74836b52522bd076a5296 to your computer and use it in GitHub Desktop.
Save cpaelzer/80331eb7b2a74836b52522bd076a5296 to your computer and use it in GitHub Desktop.
Debug for hardening systemd socket activation on failing services

README

The example below creates a Unix Socket server listening on a stream (i.e. SOCK_STREAM) socket. A similar approach can be followed to create a UDP server on a datagram (i.e. SOCK_DGRAM) socket. See man systemd.socket for details.

An example server

Create an simple echo server at /opt/foo/serve.py.

#!/usr/bin/python

from SocketServer import UnixStreamServer, StreamRequestHandler

import logging
import os
import sys

class Handler(StreamRequestHandler):
    def handle(self):
        self.data = self.rfile.readline().strip()
        logging.info("From <%s>: %s" % (self.client_address, self.data))
        self.wfile.write(self.data.upper() + "\r\n")

class Server(UnixStreamServer):

    # The constant would be better initialized by a systemd module
    SYSTEMD_FIRST_SOCKET_FD = 3

    def __init__(self, server_address, handler_cls):
        # Invoke base but omit bind/listen steps (performed by systemd activation!)
        UnixStreamServer.__init__(
            self, server_address, handler_cls, bind_and_activate=False)
        # Override socket
        self.socket = socket.fromfd(
            self.SYSTEMD_FIRST_SOCKET_FD, self.address_family, self.socket_type)

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    HOST, PORT = "localhost", 9999 # not really needed here
    CHECKPATH='/opt/foo/wouldbesysfsfile'
    server = Server((HOST, PORT), Handler)
    if not os.path.isfile(CHECKPATH):
        logging.info("%s does not exist consume and reject message" % CHECKPATH)
        server.handle_request()
        logging.info("Handled, but going out of service now")
        sys.exit(0);
    server.serve_forever()

Define the systemd service unit

Create the service definition unit file at /etc/systemd/system/foo.service (note that this unit refers to foo.socket):

[Unit]
Description=Foo Service
After=network.target foo.socket
Requires=foo.socket

[Service]
Type=simple
ExecStart=/usr/bin/python /opt/foo/serve.py
TimeoutStopSec=5

[Install]
WantedBy=multi-user.target

Define the systemd socket unit

Create the socket definition unit file at /etc/systemd/system/foo.socket:

[Unit]
Description=Foo Socket
PartOf=foo.service

[Socket]
ListenStream=/run/foo.sock

[Install]
WantedBy=sockets.target

Test it

Send a message to the listening service:

echo "Hello World"| netcat -U /run/foo.sock

Modifications

This is based on another gist It contains further Modifications:

  • Let the service handle the initial message (to Nack it in the real case)
  • Let the service then go "out of service" (like a one-shot)
  • This allows to be restarted on further socket activity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment