Part 2: The inetd Daemon Concept, Operation & Configuration |

 

Part 2: The inetd Daemon
Concept, Operation & Configuration | Chapter 60 – TLPI Series

The Problem: Too Many Idle Daemons

If you look at /etc/services on a Linux system, you will see hundreds of different network services listed — FTP, Telnet, SMTP, POP3, echo, discard, daytime, and many more.

In theory, a system could be running a separate server process for each one of these. But think about what that means:

  • Most of these services are rarely used — they sit idle 99% of the time, just waiting for a connection that may never come
  • Every idle process still occupies a slot in the kernel’s process table
  • Every idle process still consumes memory and swap space
  • The total resource drain from hundreds of idle server processes is significant
The Solution: Instead of one daemon per service, run one single daemon that monitors all service ports and starts the correct server on demand. This is what inetd does.

What is inetd?

inetd (pronounced “inet-dee”) stands for Internet Daemon. Because it oversees a wide range of services and acts as their supervisor, it is also known as the Internet Superserver.

inetd provides two main benefits:

Benefit 1
Reduced Process Count

One inetd process monitors all service ports. Individual servers are started only when a connection/datagram actually arrives. No more hundreds of idle daemons wasting memory.

Benefit 2
Simplified Server Programming

inetd handles all the boilerplate socket setup (socket, bind, listen, accept) on behalf of every service. The server program can go straight to reading from stdin/writing to stdout.

An extended version of inetd called xinetd is provided in some Linux distributions. xinetd adds security enhancements like access control lists, rate limiting, and better logging. For xinetd details, see: http://www.xinetd.org/

How inetd Works: Step-by-Step Operation

inetd is normally started during system boot. After going through the normal daemon startup process (closing file descriptors, detaching from terminal, etc.), inetd performs the following sequence of steps:

inetd Lifecycle Flow
1
Read /etc/inetd.conf
For each service listed, create a socket of the right type (stream for TCP, dgram for UDP) and bind it to the specified port. TCP sockets are additionally put into listen mode.
2
select() on all sockets
Call select() to monitor all created sockets simultaneously. inetd blocks here, consuming almost no CPU, waiting for any activity on any of the sockets.
3
select() wakes up
When a UDP datagram arrives or a TCP connection request comes in, select() returns. For TCP connections, inetd also calls accept() to accept the connection before continuing.
4
fork() + exec() the server
inetd calls fork() to create a child process. The child performs setup steps (see below) and then calls exec() to replace itself with the actual server program.
5
Parent closes connected socket
For TCP, inetd (the parent) closes the connected socket file descriptor — it is only needed in the child (the execed server).
6
Loop back to Step 2
inetd goes back to calling select() to wait for the next connection or datagram on any of its sockets.

Child Process Setup Steps (Step 4 detail)

Before the child process calls exec() to run the actual server, it does the following:

  1. Close all inherited file descriptors except the one socket that has activity (the connected TCP socket or the UDP socket with data)
  2. Duplicate the socket onto fds 0, 1, and 2 (stdin, stdout, stderr) using dup2(). This means the server program can simply use read()/write() or even fgets()/printf() on standard file descriptors.
  3. Close the original socket fd (since it is now duplicated on 0, 1, 2 and no longer needed at its original number)
  4. Optionally change UID/GID to values specified in /etc/inetd.conf, so the server runs as a less-privileged user rather than as root

Code: What the Child Does Before exec()

/* Simplified pseudocode showing what inetd's child process does
   before exec()-ing the actual server */

int conn_fd = /* the accepted TCP socket or UDP socket with data */;

/* Step a: Close all other file descriptors */
int max_fd = sysconf(_SC_OPEN_MAX);
for (int fd = 0; fd < max_fd; fd++) {
    if (fd != conn_fd) {
        close(fd);   /* ignore errors — fd may not be open */
    }
}

/* Step b: Duplicate socket onto stdin(0), stdout(1), stderr(2) */
if (conn_fd != STDIN_FILENO) {
    dup2(conn_fd, STDIN_FILENO);   /* fd 0 = socket */
    dup2(conn_fd, STDOUT_FILENO);  /* fd 1 = socket */
    dup2(conn_fd, STDERR_FILENO);  /* fd 2 = socket */
    close(conn_fd);  /* original no longer needed */
}

/* Step c: Optionally drop privileges */
setgid(server_gid);   /* set group ID first */
setuid(server_uid);   /* then set user ID */

/* Step d: exec the actual server — it reads/writes via fds 0,1,2 */
execv("/usr/sbin/in.ftpd", argv);
/* If execv returns, it failed */
_exit(127);
Why dup2 onto 0, 1, 2? After exec(), the new server program has no knowledge that it is running under inetd. By placing the socket on standard file descriptors, the server can simply use normal I/O — read(0, buf, n) reads from the network, write(1, buf, n) writes to the network. Many simple services like echo or daytime are written this way for maximum simplicity.

The Configuration File: /etc/inetd.conf

inetd’s behavior is controlled entirely by its configuration file at /etc/inetd.conf. Each line in this file describes one service. Lines beginning with # are comments.

Example Lines from /etc/inetd.conf

# echo stream tcp nowait root internal
# echo dgram  udp wait   root internal
ftp    stream tcp nowait root /usr/sbin/tcpd in.ftpd
telnet stream tcp nowait root /usr/sbin/tcpd in.telnetd
login  stream tcp nowait root /usr/sbin/tcpd in.rlogind

The first two lines (commented out) are the TCP and UDP echo services implemented internally by inetd itself. The remaining three are real services that invoke the tcpd TCP wrapper.

Field-by-Field Explanation

Each line has the following whitespace-separated fields:

ftp    stream   tcp   nowait   root   /usr/sbin/tcpd   in.ftpd
1
Service
Name
2
Socket
Type
3
Protocol
4
Flags
(wait/nowait)
5
Login
Name
6
Server
Program
7
Server
Arguments
Field Meaning
Service name Name from /etc/services (e.g., ftp, telnet, echo). Combined with the protocol field to look up the port number.
Socket type stream for TCP (connection-oriented), dgram for UDP (datagram).
Protocol tcp or udp. Must match an entry in /etc/protocols.
Flags wait or nowait. Controls whether inetd temporarily hands off socket management to the execed server. Critical for correct TCP vs UDP behavior — covered in Part 3.
Login name Username (and optionally group) from /etc/passwd under which the execed server will run. Can optionally include a group: user.group
Server program Full pathname of the server executable to run when this service is triggered. Specify internal for services handled by inetd itself.
Server arguments argv[0], argv[1], … for the server. First argument is typically the program basename (argv[0]). Omitted for internal services.

Reloading inetd Configuration

When you modify /etc/inetd.conf, inetd is not restarted — you simply send it a SIGHUP signal to ask it to re-read the configuration file:

# Send SIGHUP to reload /etc/inetd.conf
killall -HUP inetd

# Alternatively, find inetd's PID and signal it directly
kill -HUP $(pidof inetd)

Interview Questions & Answers
Q1. What problem does inetd solve and what are its two main benefits?
inetd solves the problem of having hundreds of idle server processes consuming system resources while waiting for infrequent connections. Its two benefits are: (1) reduced process count — one inetd monitors all ports and starts servers only on demand, and (2) simplified server programming — inetd handles all socket boilerplate so servers just read/write standard file descriptors.
Q2. What system call does inetd use to monitor multiple sockets simultaneously?
inetd uses the select() system call (see Section 63.2.1 in TLPI). select() allows monitoring multiple file descriptors simultaneously and blocks until any of them becomes ready. This way, one process can watch all service sockets without spinning or polling.
Q3. How does inetd pass the connection to the server it exec()s?
The child process (created by fork()) closes all inherited file descriptors except the active socket, then uses dup2() to copy the socket onto file descriptors 0, 1, and 2 (stdin, stdout, stderr). After exec(), the server program communicates with the client simply by reading from fd 0 and writing to fd 1 — it does not need to know anything about sockets.
Q4. How do you reload inetd configuration without restarting it?
Send a SIGHUP signal to the inetd process: killall -HUP inetd or kill -HUP $(pidof inetd). inetd has a SIGHUP handler that closes all existing sockets, re-reads /etc/inetd.conf, and creates new sockets for the updated configuration.
Q5. Why does inetd call setuid() and setgid() before exec()ing the server? And why must setgid() come first?
inetd runs as root (effective UID 0), so its forked children are also privileged. To run the server with least privilege, inetd calls setgid() then setuid() to drop to the user and group configured in /etc/inetd.conf. setgid() must come before setuid() because once setuid() drops root privilege, the process can no longer call setgid().
Q6. What is xinetd and how does it differ from inetd?
xinetd is an extended version of inetd available in some Linux distributions. It adds security enhancements including per-service access control lists (allow/deny based on IP), rate limiting (max connections per second), better logging, and support for IPv6. Its configuration uses individual files per service under /etc/xinetd.d/ rather than one monolithic /etc/inetd.conf.

Leave a Reply

Your email address will not be published. Required fields are marked *