socket tests

$Id$

The UNIX socket API has been standardized for a decade. But the standards are vague on a lot of critical behavior. These tests exist to remove that ambiguity.

A summary of my test results is below. To know what happens on your exact systems, run the tests yourself!

Tested platforms

Test results

test Linux OS X
Non-blocking connects
Behaviors of various tests while performing a non-blocking connect. I've somewhat arbitrarily declared some to have passed and failed. The important thing is the aggregate: can I develop a consistent procedure that will determine the state of a connection across platforms?
connect after connected 0 EISCONN
connect after refused ECONNREFUSED EINVAL
connect while connecting ENOTCONN ENOTCONN
getsockopt(SO_ERROR) after connected 0 0
getsockopt(SO_ERROR) after refused ECONNREFUSED ECONNREFUSED
getsockopt(SO_ERROR) while connecting 0 0
getpeername after connected 0 0
getpeername after refused ENOTCONN ENOTCONN
getpeername while connecting ENOTCONN ENOTCONN
read(0) while connecting EWOULDBLOCK 0
read(0) after connected (no data) EWOULDBLOCK 0
read(0) after connected (data) EWOULDBLOCK 0
read(0) after refused ECONNREFUSED ECONNREFUSED
read(1) while connecting EWOULDBLOCK EWOULDBLOCK
read(1) after connected (no data) EWOULDBLOCK EWOULDBLOCK
read(1) after connected (data) 1 1
read(1) after refused ECONNREFUSED ECONNREFUSED
write(0) while connecting EWOULDBLOCK ENOTCONN
write(0) after connected 0 0
write(0) after refused ECONNREFUSED EPIPE
write(1) while connecting EWOULDBLOCK ENOTCONN
write(1) after connected 1 1
write(1) after refused ECONNREFUSED EPIPE
Closing sockets
Corner cases dealing with FIN vs RST
Default close type background background
read(1) from background-closed peer 0 0
write(1) to closed peer 1 1
shutdown(SHUT_WR) after peer close 0 0
getsockopt(SO_ERROR) ??? ???
shutdown(SHUT_WR) after peer reset ENOTCONN EINVAL
getsockopt(SO_ERROR) ENOTCONN ECONNRESET
shutdown(SHUT_WR) after peer close, local write ENOTCONN EINVAL
getsockopt(SO_ERROR) EPIPE ECONNRESET
read(1) after peer close, local write 0 ECONNRESET
read(1) after peer reset ECONNRESET ECONNRESET
write(1) after peer reset ECONNRESET EPIPE
getsockopt(SO_ERROR) after EPIPE (no EPIPE) ECONNRESET
Background close with unread data RST FIN
1-second lingering close with no ACK received 0 (after 1 sec) 0 (after 0.01 sec)[1]
1-second lingering close on non-blocking socket[2] 0 (after 1 sec) 0 (immediately)

[1] - OS X blocking linger uses 1/100th of the requested value, due to a missing factor of HZ. It also stores the value in a short, limiting linger to (2^15-1)/HZ sec ~= 5.5 minutes.

[2] - close is the only IO syscall with no EWOULDBLOCK return.

Conclusions

Non-blocking connects

We need a procedure for making a non-blocking connect. It should be tolerant of spurious readiness notifications from epoll(2) and the like. Thus, it needs a way to distinguish the connecting state, as well as the connected and failed states. Ideally, it should have no side effects (data read or written) and be the same on all platforms.

Armed with the information above, we can produce such a procedure:

Non-blocking connect procedure

(Note that by the result of getsockopt(SO_ERROR), I mean the resulting optval. The direct return value should always be 0.

Closing sockets

To correctly handle all closing cases, a portable program should: