minetd: A Modern, Minimalist Superdaemon for Unix/Linux

At Hamkee, we've developed minetd to address a foundational challenge in Unix/Linux environments: the efficient management of network services. Modern service managers, while powerful, often introduce significant complexity for simple, single-purpose network daemons. We sought a return to the Unix philosophy of "doing one thing well," leading us to re-imagine the classic superdaemon for the modern era. minetd is a minimalist, secure, and highly portable solution for launching network services on demand, conserving system resources without the overhead of complex frameworks.

The Unix Superdaemon: A Principle of Efficiency

In Unix-like operating systems, a daemon is a process that runs in the background, detached from any interactive user's control terminal. Its purpose is to provide services that must be constantly available. A common pattern is for a system to run numerous daemons—one for FTP, one for SSH, one for a time-of-day service, and so on.

However, if a service is used infrequently, keeping its corresponding daemon process resident in memory at all times is inefficient. This problem was solved decades ago by the introduction of the "superdaemon," most famously inetd (the "internet daemon").

A superdaemon operates on a simple yet powerful principle:

It reads a configuration file that maps network ports to specific service programs. It creates, binds, and listens on all of these network sockets simultaneously. When a connection arrives on a port, the superdaemon accepts it and forks a new child process. This child process then executes the corresponding service program, with the network socket connected to its standard input, output, and error streams. This model provides immense resource savings. Instead of dozens of idle daemons consuming memory, a single inetd process waits for connections and only launches a service when it is actually needed. It is a testament to the power and elegance of Unix process management.

minetd: Hamkee's Modern Approach

While inetd and its successor xinetd were revolutionary, they carry the weight of legacy designs. At Hamkee, we identified the need for a superdaemon built with modern best practices in mind—one that is simple, auditable, and secure by design. This led us to develop minetd.

minetd is a single-file C implementation that adheres strictly to POSIX APIs, ensuring portability across Linux, BSD, macOS, and embedded systems. It has no external dependencies, no complex threading models, and a minimal, auditable codebase.

Our design goals were clear:

  • Minimal but Correct: Implement only the core superdaemon functionality, but do so robustly. Secure by Design: Employ best practices for process and file descriptor management to minimize the attack surface.
  • Simple but Powerful: Provide a straightforward configuration and execution model that remains highly effective.
  • Auditable: The entire implementation should be understandable by a single engineer. Architectural Deep Dive

The architecture of minetd is centered around a main event loop and clean process separation. Let's examine the core components of its implementation.

Configuration and Service Initialization

minetd uses a simple, line-oriented configuration file, typically located at /etc/minetd.conf. Each line defines a service with a name, a listening address and port, a rate limit, and the path to the executable.

A typical configuration looks like this:

# service <name> <host:port> <max_conn_per_min> <program> [args...]
service echo    0.0.0.0:7000 60 /usr/local/bin/echo-server
service daytime 0.0.0.0:7001 30 /usr/local/bin/daytime-server

Upon startup or reload, minetd parses this file, and for each service, it creates a non-blocking TCP socket, binds it to the specified address and port, and begins listening for connections. This core logic is fundamental to any network daemon. The C code to establish a listening socket for a single service involves a standard sequence of socket, setsockopt, bind, and listen calls.

The Main Event Loop: Handling Connections with select()

The heart of minetd is its event loop, which must monitor all listening sockets simultaneously. We chose to use the select() system call for this purpose due to its universal portability and simplicity, which perfectly aligns with our project goals.

The logic is straightforward:

An fd_set is created to hold the file descriptors of all active listening sockets. select() is called, blocking the process until a connection arrives on any of the monitored sockets. When select() returns, minetd iterates through the file descriptors to find which one is ready. For each ready socket, it calls accept() to establish the new client connection. This model allows a single, lightweight process to manage an arbitrary number of services with minimal CPU overhead.

Service Execution: The fork-exec Model

Once a connection is accepted, minetd performs the classic Unix fork-exec routine to launch the service. This is where the elegance of the superdaemon model becomes apparent.

Step 1: Forking the Process

The fork() system call creates an exact copy of the minetd process. The parent process gets the child's process ID and continues its event loop, ready to accept more connections. The child process begins its own execution.

pid_t pid = fork();

if (pid < 0) {
    perror("fork");
    close(client_fd);
    return;
}

if (pid > 0) {
    close(client_fd);
    return;
}

Step 2: Connecting the Socket in the Child

Inside the child process, we sever its ties to the parent's file descriptors and connect the accepted client socket to its standard streams. This is accomplished using dup2(), which duplicates the client file descriptor (client_fd) onto the standard input (0), output (1), and error (2) file descriptors.

dup2(client_fd, STDIN_FILENO);
dup2(client_fd, STDOUT_FILENO);
dup2(client_fd, STDERR_FILENO);
close(client_fd);

After these calls, any reads from standard input by the service program will receive data from the client, and any writes to standard output or error will send data to the client. This powerful redirection means the service program itself needs zero networking code.

Step 3: Executing the Service

Finally, the child process uses execvp() to replace its current memory image with that of the target service program.

execvp(service->program_path, service->argv);

perror("execvp");
_exit(127);

The _exit() call is a safeguard; execvp() should never return on success.

This clean separation ensures that each service runs in its own isolated process, and any crash or error in a service program cannot affect minetd or any other service.

An Example Service

To illustrate the simplicity this model affords, here is a complete "daytime" server, which simply writes the current time to the client and exits. Note the complete absence of any socket or networking code.

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t now = time(NULL);
    struct tm tm;

    gmtime_r(&now, &tm);

    char buf[128];
    strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S UTC\n", &tm);

    fputs(buf, stdout);
    return 0;
}

When this program is executed by minetd, its stdout is the network socket, so fputs sends the formatted date directly to the connected client.

Safe Hot-Reloading via SIGHUP

A critical feature for any modern daemon is the ability to reload its configuration without downtime. We implemented a safe, robust hot-reload mechanism in minetd triggered by the SIGHUP signal. When minetd receives SIGHUP, it parses the new configuration file into a temporary data structure. If parsing fails, it logs an error and aborts the reload, leaving the old configuration active. This prevents a malformed configuration from taking down the server. If successful, it gracefully transitions to the new service list.

New services are started, and services removed from the configuration have their listening sockets closed. Crucially, existing connections and running child processes are entirely unaffected. This atomicity ensures that configuration changes are predictable and safe to perform on a running system.

Conclusion

minetd is a reflection of Hamkee's core engineering principles: creating solutions that are simple, robust, and purpose-built. It demonstrates a deep understanding of the Unix process model and network programming, offering a modern, secure alternative to legacy superdaemons. By focusing on a minimal feature set and a clear, auditable codebase, minetd provides a powerful tool for anyone needing to manage simple network services efficiently, from embedded systems to complex server environments.

minetd was developed by the engineering team at Hamkee, where we specialize in high-performance unix/linux solutions. We invite you to explore the repository, examine the implementation, and discover the power of doing one thing well.