inetd is a daemon that conviently launches TCP/UDP services upon incoming IP packets, redirecting the socket to the stdio (file descriptors 0, 1, 2) of specified user’s code for launching the service. FreeBSD has a documentation page on inetd.

Let’s see a few examples. Below the configuration file $HOME/etc/inetd.conf configures one TCP service and two UDP services,

21000 stream tcp nowait alpine python3 python3 /home/alpine/bin/lower.py
27000 dgram udp wait alpine python3 python3 /home/alpine/bin/udpclient.py
28000 dgram udp wait alpine python3 python3 /home/alpine/bin/udpclient2.py

Run in the shell

inetd etc/inetd.conf

to start the inetd daemon with the given configuration.

As configured, the first is a TCP service. The user code $HOME/bin/lower.py does standard I/O agnostic of the internet data:

# convert each line in input to lower case

from sys import stdin

for line in stdin:
    print(line.lower(), end='')

One may test with

printf 'wEjLu\nUikO\n'|nc 127.0.0.1 21000

With UDP service, the UDP socket will be passed as user’s stdin, but the user code cannot work as agnostically as the code behind the TCP service does. One has to recreate the UDP socket in order to do I/O.

The first $HOME/bin/udpclient.py receives tuple data as “2024-04-01,GS.US,58” on each line and insert the tuples into a database table:


from socket import socket
from sys import stdin
import sqlite3

with socket(fileno=stdin.fileno()) as sock:
    # bytes to str
    received = sock.recv(1024).decode('utf8')

    # convert to records
    lines = [_.strip() for _ in received.split()]
    records = [_.split(',') for _ in lines]
    records = [(time, name, float(value))
                for time, name, value in records]

    # write to db
    with sqlite3.connect('/home/alpine/fin.db') as conn:
        conn.executemany('INSERT INTO ts VALUES (?, ?, ?)', records)

where $HOME/fin.db is created with schema:

CREATE TABLE IF NOT EXISTS ts (
  time TEXT,
  name TEXT,
  value REAL,
  PRIMARY KEY(time, name)
);

Test the UDP service with

printf '2024-04-13,GS.US,450.3\n2024-04-13,AAPL.US,140.25\n'|nc -u 127.0.0.1 27000

The second UDP service illustrates how to do output on UDP. Unlike the TCP service, simply print() won’t work, one has to use socket.sendto() on UDP. Note unless socket.connect() had been called, a UDP socket can receive data from any peer thus has no fixed peer. Generally calling socket.getpeername() on an UDP socket would have an exception raised. But we can read out the peer’s name (host and port) when receiving a particular message with socket.recvfrom().

$HOME/bin/udpclient2.py:


from socket import socket
from sys import stdin

with socket(fileno=stdin.fileno()) as sock:
    recv, peer = sock.recvfrom(1024)
    recv = recv.decode('utf8')
    sock.sendto(recv.upper().encode('utf8'), peer)

In C, call getsockname to get the socket name details, in order to recreate the UDP socket. As stdin’s file descriptor is 0 by default, call getsockname(0, .., ..).

Check the system log for example /var/log/messages, inetd may write error messages from the services there.