https://bugs.winehq.org/show_bug.cgi?id=47560
Bug ID: 47560 Summary: ws2_32:WS_getpeername behavior for on non-blocking tcp sockets during handshake Product: Wine Version: 4.12.1 Hardware: x86 OS: Linux Status: UNCONFIRMED Severity: minor Priority: P2 Component: winsock Assignee: wine-bugs@winehq.org Reporter: oysstu@gmail.com Distribution: ---
Created attachment 64948 --> https://bugs.winehq.org/attachment.cgi?id=64948 Winedebug output
This bug report deals with a difference in behavior of ws2_32:getpeername. It was discovered in the game Titan Quest Anniversary Edition when connection to a multiplayer lobby, but can be reproduced quite easily elsewhere.
Once a non-blocking socket is connected to using ws2_32:WS_connect, and ws2_32:getpeername is called before the TCP handshake is completed, the resulting behavior is different on Windows and Linux.
On Linux; before the TCP handshake is completed WSAENOTCONN is returned by ws2_32:getpeername. I.e. a socket is not connected before the handshake succeeds, hence one cannot get the peer name
On Windows; from msdn the documentation clearly states that "The getpeername function can be used only on a connected socket.". Apparently, once the ws2_32:connect call has been made, it's defined as "connected" until the handshake times out because Windows returns successfully without an error and gives the peer info of the remote address it's trying to connect to. The error can be reproduced by using connect to an IP/port that does not reply followed by the call to getpeername
Now, what happens in Titan Quest is that the developers has handled the WSAENOTCONN error by recreating the socket and doing the exact thing again instead of waiting for the socket to connect. Obviously, that's an error on their part, but it does work consistently on Windows.
During the handshake, the tcpi_state field of TCP_INFO will show as TCP_SYN_SENT/TCP_SYN_RECV, which can be used to detect when this happens. I don't know the best way to get the peer name without calling getpeername though. I suppose it can be retrieved by re-implementing af_inet:inetgetname() without the check for TCP_SYN_SENT, but this probably affects portability (?). Another more portable way is to cache the address/port in wine, which obviously only works for outgoing connections (don't see why the behavior has to change for incoming connections anyway).
af_inet:inetgetname(): https://github.com/torvalds/linux/blob/master/net/ipv4/af_inet.c#L760
I've attached a wine debug log, which shows the connect() call followed by a getpeername() that fails and raises an exception.