Search code examples
socketsdatagramsystemd

systemd-activate socket activation for UDP daemons


I like using systemd-activate(8) for testing socket-activated daemons during development, however, it seems it only listens for TCP connections:

% /usr/lib/systemd/systemd-activate -l 5700 ./prog
Listening on [::]:5700 as 3.

% netstat -nl |grep 5700
tcp6       0      0 :::5700             :::*             LISTEN 

I am using a program that handles datagrams (UDP). How can I make systemd-activate listen on a UDP port? Or is there a simple way to do this using other tools, without going to the trouble of crafting and installing a systemd unit file?


Solution

  • I ended up writing a simple C program to do this; code below (public domain).

    The usage is:

    ./a.out <port-number> <prog> [<arg1> ...]
    

    The program opens a UDP socket on <port-number>, sets the environment variables that systemd socket-activated daemons expect, then executes <prog> with whatever arguments follow.

    #include <errno.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <netinet/in.h>
    
    int main(int argc, char **argv) {
        if (argc < 2) {
            printf("no port specified\n");
            return -1;
        }
        if (argc < 3) {
            printf("no program specified\n");
            return -1;
        }
    
        uint16_t port = htons((uint16_t) strtoul(argv[1], NULL, 10));
        if (port == 0 || errno) {
            printf("failed to parse port: %s\n", argv[1]);
            return -1;
        }
    
        /* create datagram socket */
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (fd < 0) {
            printf("failed to open socket; errno: %d\n", errno);
            return -1;
        }
    
        struct sockaddr_in sa;
        sa.sin_family = AF_INET;
        sa.sin_port = port;
        sa.sin_addr.s_addr = INADDR_ANY;
    
        /* bind socket to port */
        int r = bind(fd, (struct sockaddr *) &sa, sizeof(struct sockaddr_in));
        if (r < 0) {
            printf("bind failed; errno: %d\n", errno);
            return -1;
        }
    
        /* execute subprocess */
        setenv("LISTEN_FDS", "1", 0);
        execvp(argv[2], argv + 2);
    }