My company releases a special TCP stack for special purposes and I’m tasked with implementing RFC793 compliant closing sequence. One of the unit tests has a server working on top of the special TCP stack talking to a normal Linux TCP client, and I’m running into some strange behaviour that I’m not sure whether is caused by programming error on my part or is to be expected.
Before my work started, we used to send a RST packet when the user application calls close(). I’ve implemented the FIN handshake, but I noticed that in the case of simultaneous TCP termination (FIN_WAIT_1 -> CLOSING -> TIME_WAIT on both ends, see the picture), the standard Linux TCP client cannot connect to the same destination address and port again, with connect() returning with EADDRNOTAVAIL, until after TIME_WAIT passes into CLOSED.
Now, the standard Linux client application sets the option SO_REUSEADDR, binds the socket to port 8888 each time, and connects to destination port 6666. My question is, why does bind() succeed and why does connect() fail? I would have thought SO_REUSEADDR could take over a local TIME_WAIT port, which it did, but what does connect() have against talking to the destination-ip:6666 again?
Is my code doing something it shouldn’t or is this expected behaviour?
I can confirm no SYN packet for the failed connect() makes it out of the client machine at all. I’ve attached a screenshot of the FIN handshake for the above session.
Advertisement
Answer
Your previous implementation used RST
to end the connection. Receipt of an RST
packet immediately removes the connection from the active connection table. That’s because there is no further possibility of receiving a valid packet on that connection: the peer has just told your system that that session is not valid.
If you do a proper session termination with FIN
, on the other hand, there is the last packet problem: how do you know for sure whether the peer actually received the last acknowledgment you sent to their FIN
(this is the definition of TCP’s TIME_WAIT
state)? And if the peer didn’t receive it, they might validly send another copy of the FIN
packet which your machine should then re-ACK
.
Now, your bind
succeeds because you’re using SO_REUSEADDR
, but you still cannot create a new connection with the exact same ports on both sides because that entry is still in your active connection table (in TIME_WAIT
state). The 4-tuple (IP1, port1, IP2, port2) must always be unique.
As @EJP suggested in the comment, it is unusual for the client to specify a port, and there is typically no reason to. I would change your test.