The TIME_WAITs on Mac OS X

mac-osxnetstatsockettcptcpip

No TIME_WAITs on Mac OS X

Normally, when a TCP connection is closed, the socket on the side where close() is called first is left in the TIME_WAIT state.

When one of the peers is a Mac OS X (Lion) machine, no TIME_WAIT is listed by netstat -an on the Mac if close() is called first on the Mac side. However, it seems that the socket is actually in TIME_WAIT state, because trying to call listen() again (without using the socket option SO_REUSEADDR) causes listen() to fail.

Waiting for 2*MSL (Maximum Segment Lifetime which is 15 seconds on Mac OS X Lion as reported by sysctl net.inet.tcp.msl) clears the TIME_WAIT state, and listen() can be called again without error.

Why can't I see the socket in TIME_WAIT?

Testing

Here are two simple test programs in Python.

Server

#!/usr/bin/env python

import socket

HOST = ''
PORT = 50007
l = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
l.bind((HOST, PORT))
l.listen(1)
print("Listening on %d" % PORT)
(s, _) = l.accept()
print("Connected")
raw_input("Press <enter> to close...")
l.close()
s.close()
print("Closed")

Client

#!/usr/bin/env python

import socket
import sys

HOST = sys.argv[1]
PORT = 50007

print("Opening connection to server")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
raw_input("Press <enter> to close...")
s.close()
print("Closed")

When running both the server and the client on two different Linux machines, the peer that press <enter> to call close() first gets a TIME_WAIT as expected:

$ ./server-timewait.py 
Listening on 50007
Connected
Press <enter> to close...
Closed
$ netstat -an | grep 50007
tcp        0      0 172.16.185.219:50007    172.16.185.42:49818     TIME_WAIT  
$ 

When one of the peers is a Mac (running OS X Lion) I never see a TIME_WAIT when running netstat -an | grep 50007 after closing first on the Mac.

Best Answer

This bug report claims that the problem is in the netstat implementation. The code attached to the bug report shows sockets in TIME_WAIT state correctly. You need to remove the following lines

if (lip == INADDR_LOCALHOST ||
  lip == INADDR_ANY
  ) { continue; }

to make it show sockets bound to localhost.