The inetd (Internet Superserver) Daemon

If we look through the contents of /etc/services, we see literally hundreds of different services listed. This implies that a system could theoretically be running a large number of server processes. However, most of these servers would usually be doing nothing but waiting for infrequent connection requests or datagrams. All of these server processes would nevertheless occupy slots in the kernel process table, and consume some memory and swap space, thus placing a load on the system.

The inetd daemon is designed to eliminate the need to run large numbers of infrequently used servers. Using inetd provides two main benefits:

Since it oversees a range of services, invoking other servers as required, inetd is sometimes known as the Internet superserver.

Note

An extended version of inetd, xinetd, is provided in some Linux distributions. Among other things, xinetd adds a number of security enhancements. Information about xinetd can be found at http://www.xinetd.org/.

The inetd daemon is normally started during system boot. After becoming a daemon process (Creating a Daemon), inetd performs the following steps:

  1. For each of the services specified in its configuration file, /etc/inetd.conf, inetd creates a socket of the appropriate type (i.e., stream or datagram) and binds it to the specified port. Each TCP socket is additionally marked to permit incoming connections via a call to listen().

  2. Using the select() system call (The select() System Call), inetd monitors all of the sockets created in the preceding step for datagrams or incoming connection requests.

  3. The select() call blocks until either a UDP socket has a datagram available to read or a connection request is received on a TCP socket. In the case of a TCP connection, inetd performs an accept() for the connection before proceeding to the next step.

  4. To start the server specified for this socket, inetd() calls fork() to create a new process that then does an exec() to start the server program. Before performing the exec(), the child process performs the following steps:

    1. Close all of the file descriptors inherited from its parent, except the one for the socket on which the UDP datagram is available or the TCP connection has been accepted.

    2. Use the techniques described in Duplicating File Descriptors to duplicate the socket file descriptor on file descriptors 0, 1, and 2, and close the socket file descriptor itself (since it is no longer required). After this step, the execed server is able to communicate on the socket by using the three standard file descriptors.

    3. Optionally, set the user and group IDs for the execed server to values specified in /etc/inetd.conf.

  5. If a connection was accepted on a TCP socket in step 3, inetd closes the connected socket (since it is needed only in the execed server).

  6. The inetd server returns to step 2.

The operation of the inetd daemon is controlled by a configuration file, normally /etc/inetd.conf. Each line in this file describes one of the services to be handled by inetd. Example 60-5 shows some examples of entries in the /etc/inetd.conf file that comes with one Linux distribution.

The first two lines of Example 60-5 are commented out by the initial # character; we show them now since we’ll refer to the echo service shortly.

Each line of /etc/inetd.conf consists of the following fields, delimited by white space:

  • Service name: This specifies the name of a service from the /etc/services file. In conjunction with the protocol field, this is used to look up /etc/services to determine which port number inetd should monitor for this service.

  • Socket type: This specifies the type of socket used by this service—for example, stream or dgram.

  • Protocol: This specifies the protocol to be used by this socket. This field can contain any of the Internet protocols listed in the file /etc/protocols (documented in the protocols(5) manual page), but almost every service specifies either tcp (for TCP) or udp (for UDP).

  • Flags: This field contains either wait or nowait. This field specifies whether or not the server execed by inetd (temporarily) takes over management of the socket for this service. If the execed server manages the socket, then this field is specified as wait. This causes inetd to remove this socket from the file descriptor set that it monitors using select() until the execed server exits (inetd detects this via a handler for SIGCHLD). We say some more about this field below.

  • Login name: This field consists of a username from /etc/passwd, optionally followed by a period (.) and a group name from /etc/group. These determine the user and group IDs under which the execed server is run. (Since inetd runs with an effective user ID of root, its children are also privileged and can thus use calls to setuid() and setgid() to change process credentials if desired.)

  • Server program: This specifies the pathname of the server program to be execed.

  • Server program arguments: This field specifies one or more arguments, separated by white space, to be used as the argument list when execing the server program. The first of these corresponds to argv[0] in the execed program and is thus usually the same as the basename part of the server program name. The next argument corresponds to argv[1], and so on.

Note

In the example lines shown in Example 60-5 for the ftp, telnet, and login services, we see the server program and arguments are set up differently than just described. All three of these services cause inetd to invoke the same program, tcpd(8) (the TCP daemon wrapper), which performs some logging and access-control checks before in turn execing the appropriate program, based on the value specified as the first server program argument (which is available to tcpd via argv[0]). Further information about tcpd can be found in the tcpd(8) manual page and in [Mann & Mitchell, 2003].

Stream socket (TCP) servers invoked by inetd are normally designed to handle just a single client connection and then terminate, leaving inetd with the job of listening for further connections. For such servers, flags should be specified as nowait. (If, instead, the execed server is to accept connections, then wait should be specified, in which case inetd does not accept the connection, but instead passes the file descriptor for the listening socket to the execed server as descriptor 0.)

For most UDP servers, the flags field should be specified as wait. A UDP server invoked by inetd is normally designed to read and process all outstanding datagrams on the socket and then terminate. (This usually requires some sort of timeout when reading the socket, so that the server terminates when no new datagrams arrive within a specified interval.) By specifying wait, we prevent the inetd daemon from simultaneously trying to select() on the socket, which would have the unintended consequence that inetd would race the UDP server to check for datagrams and, if it won the race, start another instance of the UDP server.

Note

Because the operation of inetd and the format of its configuration file are not specified by SUSv3, there are some (generally small) variations in the values that can be specified in the fields of /etc/inetd.conf. Most versions of inetd provide at least the syntax that we describe in the main text. For further details, see the inetd.conf(8) manual page.

As an efficiency measure, inetd implements a few simple services itself, instead of execing separate servers to perform the task. The UDP and TCP echo services are examples of services that inetd implements. For such services, the server program field of the corresponding /etc/inetd.conf record is specified as internal, and the server program arguments are omitted. (In the example lines in Example 60-5, we saw that the echo service entries were commented out. To enable the echo service, we need to remove the # character at the start of these lines.)

Whenever we change the /etc/inetd.conf file, we need to send a SIGHUP signal to inetd to request it to reread the file:

# killall -HUP inetd

We noted earlier that inetd simplifies the programming of servers, especially concurrent (usually TCP) servers. It does this by carrying out the following steps on behalf of the servers it invokes:

(In the description of the above steps, we assume the usual cases that the flags field of the service entry in /etc/inetd.conf is specified as nowait for TCP services and wait for UDP services.)

As an example of how inetd simplifies the programming of a TCP service, in Example 60-6, we show the inetd-invoked equivalent of the TCP echo server from Example 60-4. Since inetd performs all of the above steps, all that remains of the server is the code executed by the child process to handle the client request, which can be read from file descriptor 0 (STDIN_FILENO).

If the server resides in the directory /bin (for example), then we would need to create the following entry in /etc/inetd.conf in order to have inetd invoke the server:

echo stream tcp nowait root /bin/is_echo_inetd_sv is_echo_inetd_sv