16.6 Nonblocking accept
We stated in Chapter 6 that a listening socket
is returned as readable by select when a completed
connection is ready to be accepted. Therefore, if we are
using select to wait for incoming connections, we should
not need to set the listening socket to nonblocking because if
select tells us that the connection is ready,
accept should not block.
Unfortunately, there is a timing problem that
can trip us up here [Gierth 1996]. To see this problem, we modify
our TCP echo client (Figure 5.4) to
establish the connection and then send an RST to the server.
Figure 16.21 shows this
new version.
Figure 16.21
TCP echo client that creates connection and sends an RST.
nonblock/tcpcli03.c
1 #include "unp.h"
2 int
3 main(int argc, char **argv)
4 {
5 int sockfd;
6 struct linger ling;
7 struct sockaddr_in servaddr;
8 if (argc != 2)
9 err_quit("usage: tcpcli <IPaddress>");
10 sockfd = Socket(AF_INET, SOCK_STREAM, 0);
11 bzero(&servaddr, sizeof(servaddr));
12 servaddr.sin_family = AF_INET;
13 servaddr.sin_port = htons(SERV_PORT);
14 Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
15 Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
16 ling.l_onoff = 1; /* cause RST to be sent on close() */
17 ling.l_linger = 0;
18 Setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
19 Close(sockfd);
20 exit(0);
21 }
Set SO_LINGER socket option
16鈥?9
Once the connection is established, we set the SO_LINGER
socket option, setting the l_onoff flag to 1 and the
l_linger time to 0. As stated in Section 7.5, this
causes an RST to be sent on a TCP socket when the connection is
closed. We then close the socket.
Next, we modify our TCP server from Figures
6.21 and 6.22 to pause after
select returns that the listening socket is readable, but
before calling accept. In the following code from the
beginning of Figure 6.22, the two
lines preceded by a plus sign are new:
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
+ printf("listening socket readable\n");
+ sleep(5);
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
What we are simulating here is a busy server
that cannot call accept as soon as select returns
that the listening socket is readable. Normally, this slowness on
the part of the server is not a problem (indeed, this is why a
queue of completed connections is maintained), but when combined
with the RST from the client, after the connection is established,
we can have a problem.
In Section 5.11, we
noted that when the client aborts the connection before the server
calls accept, Berkeley-derived implementations do not
return the aborted connection to the server, while other
implementations should return ECONNABORTED but often
return EPROTO instead. Consider the following example of a
Berkeley-derived implementation:
-
The client establishes the connection and then
aborts it as in Figure
16.21.
-
select returns readable to the server
process, but it takes the server a short time to call
accept.
-
Between the server's return from select
and its calling accept, the RST is received from the
client.
-
The completed connection is removed from the
queue and we assume that no other completed connections exist.
-
The server calls accept, but since
there are no completed connections, it blocks.
The server will remain blocked in the call to
accept until some other client establishes a connection.
But in the meantime, assuming a server like Figure 6.22, the
server is blocked in the call to accept and will not
handle any other ready descriptors.
This problem is somewhat similar to the
denial-of-service attack described in Section 6.8, but
with this new bug, the server breaks out of the blocked
accept as soon as another client establishes a
connection.
The fix for this problem is as follows:
-
Always set a
listening socket to nonblocking when you use select to
indicate when a connection is ready to be accepted.
-
Ignore the
following errors on the subsequent call to accept:
EWOULDBLOCK (for Berkeley-derived implementations, when
the client aborts the connection), ECONNABORTED (for POSIX
implementations, when the client aborts the connection),
EPROTO (for SVR4 implementations, when the client aborts
the connection), and EINTR (if signals are being
caught).
|