windowsselecttcpwinsockwinsock2

Windows sockets: How to immediately detect TCP RST on nonblocking connect()?


Our software (Nmap port scanner) needs to quickly determine the status of a non-blocking TCP socket connect(). We use select() to monitor a lot of sockets, and Windows is good at notifying us when one succeeds. But if the port is closed and the target sends a TCP RST, Windows will keep trying a few times before notifying the exceptfds, and the socket error is WSAECONNREFUSED as expected. Our application has its own timeout, though, and will usually mark the connection as timed-out before Windows gives up. We want to get as close as possible to the behavior of Linux, which is to notify with ECONNREFUSED immediately upon receipt of the first RST.

We have tried using the TCP_MAXRT socket option, and this works to get select() to signal us right away, but the result (for closed ports) is always WSAETIMEDOUT, which makes it impossible to distinguish closed (RST) from filtered/firewalled (network timeout), which puts us back at the original problem. Determining this distinction is a core feature of our application.

So what is the best way on Windows to find out if a non-blocking socket connect() has received a connection reset?

EDITED TO ADD: A core problem here is this line from Microsoft's documentation on the SO_ERROR socket option: "This per-socket error code is not always immediately set." If it were immediately set, we could check for it prior to the connect timeout.


Solution

  • After several years, we found the correct combination of socket options and ioctls to accomplish this:

    When a connection is attempted, Winsock begins sending SYN packets. If the first receives a RST, another SYN is sent after a brief wait. If instead it times out, the retransmission timeout is increased and another SYN is sent. When the last SYN retry has finished, the socket error code will be set to WSAECONNREFUSED if the response was RST or WSAETIMEDOUT if there was no response. If the connection timeout happens before the last SYN retry has finished, the socket error code is WSAETIMEDOUT regardless of whether any response was RST.

    To allow WSAECONNREFUSED to be set, the connection timeout has to be long enough to allow all the SYN retries to be sent. In most cases, each SYN will get a RST, so the time required is just that small wait (half a second, if I recall correctly) times the number of retries. However, it is possible for the first SYN attempts to time out due to packet loss, then a RST to arrive for the last attempt, so some calculation is necessary to accommodate all situations.