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:
Instead of running a separate daemon for each service, a single process—the inetd daemon—monitors a specified set of socket ports and starts other servers as required. Thus, the number of processes running on the system is reduced.
The programming of the servers started by inetd is simplified, because inetd performs several of the steps that are commonly required by all network servers on startup.
Since it oversees a range of services, invoking other servers as required, inetd is sometimes known as the Internet superserver.
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:
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().
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.
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.
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:
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.
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.
Optionally, set the user and group IDs for the execed server to values specified in /etc/inetd.conf
.
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).
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.
Example 60-5. 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 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.
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.
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:
Perform all socket-related initialization, calling socket(), bind(), and (for TCP servers) listen().
For a TCP service, perform an accept() for the new connection.
Create a new process to handle the incoming UDP datagram or TCP connection. The process is automatically set up as a daemon. The inetd program handles all details of process creation via fork() and the reaping of dead children via a handler for SIGCHLD
.
Duplicate the file descriptor of the UDP socket or the connected TCP socket on file descriptors 0, 1, and 2, and close all other file descriptors (since they are unused in the execed server).
Exec the server program.
(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
Example 60-6. TCP echo server designed to be invoked via inetd
sockets/is_echo_inetd_sv.c
#include <syslog.h> #include "tlpi_hdr.h" #define BUF_SIZE 4096 int main(int argc, char *argv[]) { char buf[BUF_SIZE]; ssize_t numRead; while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) { if (write(STDOUT_FILENO, buf, numRead) != numRead) { syslog(LOG_ERR, "write() failed: %s", strerror(errno)); exit(EXIT_FAILURE); } } if (numRead == -1) { syslog(LOG_ERR, "Error from read(): %s", strerror(errno)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); }sockets/is_echo_inetd_sv.c