Understanding the
TCP Wrapper Concept
Figure 16.1 shows
how you can visualize the role of tcpd as it interacts with inetd and the resulting
server.
Figure 16.1:
This graphical representation of the TCP
wrapper concept
illustrates the relationship of the processes
involved.
Let's review the
process of a remote client connecting to your in.telnetd server:
- The client uses his telnet client command to issue a connect request to your machine's telnet daemon.
- Your Linux host is using inetd, which has been configured to listen on port 23 for telnet requests. It accepts the connection request from step 1.
- The /etc/inetd.conf configuration file directs your inetd server to fork(2) a new process. The parent process goes back to listening for more connects.
- The child process from step 3 now calls exec(2) to execute the /usr/sbin/tcpd TCP wrapper program.
- The tcpd program determines whether the client should be given access or not. This is determined by the combination of the socket addresses involved and the configuration files /etc/hosts.deny and /etc/hosts.allow.
- If access is to be denied, tcpd simply terminates (this causes file units 0, 1, and 2 to be closed, which are the socket file descriptors).
- If access is to be granted, the executable that is to be started is determined by tcpd's argv [0] value. In this example, the name is in.telnetd. This specifies the executable pathname /usr/sbin/in.telnetd, which is passed to the exec(2) function to load and execute.
- The server now runs in place of tcpd with the same process ID that tcpd formerly had. The server now performs input and output on the sockets (file units 0, 1, and 2).
Step 7 is
important— it is where the server process is started by the exec(2) function
call from within tcpd. This maintains the important parent/child relationship
between inetd and the (child)
server process.
When the wait flag word is used, the inetd daemon can start the next server
only when it detects that the current child process has ended. This works
correctly only when the server process is a
direct child process of the parent inetd. Numbers might help make this easier
to digest:
- The inetd daemon has process ID 124 for this example.
- The inetd daemon calls fork(2) to start a child process. This child process ID is now 1243 for this example.
- The inetd child process (PID 1243) now calls exec(2) to start /usr/sbin/tcpd.
- Note that tcpd is now running as PID 1243 (recall that exec(2) uses the same process resources to start a new program, while discarding the original program that called exec(2)).
- The tcpd eventually calls exec(2) again, when access is to be granted. This starts the new server, which is /usr/sbin/in.telnetd in this example.
- Note that the server /usr/sbin/in.telnetd still is PID 1243 because exec(2) does not create a new process (see notes in step 4).
- Server in.telnetd eventually exits (PID 1243 terminates).
- Parent process inetd (PID 124) receives a SIGCHLD signal to indicate that its child process ID 1243 has terminated. This will cause inetd to call upon wait (2) to determine which child process has terminated.
From this list of
steps, you can see how cleverly inserted the tcpd wrapper program is. This program
never actually performs I/O on the sockets— this would disturb the protocol
being used (telnet or otherwise).
Determining Access
You might still have two questions at this point:
- How does the TCP wrapper program determine what service it is securing (telnet, ftp, and so on)?
- How does it determine who the client is?
Determining the
Service
The tcpd program
can determine the service it is protecting by calling upon the getsockname (2) function.
Remember that function? It not only returns the socket address that the client
was connecting to,
but it indicates the port number of the service. In the previous examples, the
port number was 23 (the telnet service).
Determining the
Client Identity
Because the tcpd program
was not the one that executed the accept(2) function call (this was done by inetd),
it must determine who the client is. As you've probably guessed, this is done
with the getpeername(2) function. You will recall that this function retrieves
the address and port number of the remote client, in the same manner as getsockname(2).
Determining the
Datagram Client Identity
Determining the
identity of a datagram client is a bit trickier. The astute reader might have wondered
about this in the previous section, because datagrams do not use the accept(2) function
call. It is also not possible to use getpeername(2) on datagram sockets because
each datagram can potentially come from different clients. The client's address
is returned by the recvfrom(2) function call. How, then, can tcpd determine the
client's identity without actually reading the
server's datagram?
It turns out that
tcpd is able to cheat. The client's address and port number can be determined
by calling recvfrom(2) using the flag option MSG_PEEK. Example code is shown as
follows:
Example
int z;
struct
sockaddr_in adr_clnt;/* AF_INET */
int len_inet; /* length */
int s; /* Socket */
char dgram[512];
/* Recv buffer */
len_inet = sizeof
adr_clnt;
z = recvfrom(s, /* Socket */
dgram, /* Receiving buffer */
sizeof dgram, /* Max recv buf size */
MSG_PEEK, /* Flags: Peek at data */
(struct sockaddr *)&adr_clnt,/*
Addr */
&len_inet); /* Addr len, in & out */
Notice the flag
option MSG_PEEK. This option directs the kernel to carry out the recvfrom(2) call
as normal except that the datagram is not to be removed from the queue as
"read." This allows the tcpd program to "peek" at the
datagram that the server will subsequently read, if access is granted.
Notice that the
data itself is not important here. What this MSG_PEEK operation accomplishes is
that it returns the client's IP address (in the example, this is placed into adr_clnt).
The wrapper program can
determine from the variable adr_clnt whether this datagram should be processed
by the server or not.
No comments:
Post a Comment