Why does the socket connect () to its own ephemeral port?

I can reliably connect Winsock to connect() myself if I connect to localhost with a port in the range of automatically assigned ephemeral ports (5000-65534). In particular, Windows appears to have a system-wide mobile port number, which is the next port that it will try to assign as the local port number for the client socket. If I create sockets until the assigned number falls below my destination port number, and then re-creates the socket and tries to connect to this port number, I can usually connect it to myself.

I first got this in an application that repeatedly tries to connect to a specific port on localhost, and when the service does not listen on it, it very rarely successfully establishes a connection and receives the message that it originally sent (which happens to be a Redis PING command).

An example in Python (starting with nothing listening on the target port):

 import socket TARGET_PORT = 49400 def mksocket(): return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) while True: sock = mksocket() sock.bind(('127.0.0.1', 0)) host, port = sock.getsockname() if port > TARGET_PORT - 10 and port < TARGET_PORT: break print port while port < TARGET_PORT: sock = mksocket() err = None try: sock.connect(('127.0.0.1', TARGET_PORT)) except socket.error, e: err = e host, port = sock.getsockname() if err: print 'Unable to connect to port %d, used local port %d: %s' % (TARGET_PORT, port, err) else: print 'Connected to port %d, used local port %d' (TARGET_PORT, port) 

On my Mac machine, this ends with Unable to connect to port 49400, used local port 49400 . On my Windows 7 machine, the connection is successful and prints Connected to port 49400, used local port 49400 . The resulting socket accepts any data that is sent to it.

Is this a bug in Winsock? Is this a bug in my code?

Edit: Here is a screenshot of TcpView with the abusive connection shown:

python.exe 8108 TCP (my HOSTNAME) 49400 localhost 49400 ESTABLISHED

+6
source share
2 answers

This is similar to โ€œsimultaneous initiation,โ€ as described in RFC 793 No. 3.4. See Figure 8. Note that neither side is in LISTEN state at any stage. In your case, both ends are the same: this will lead to the fact that it will work exactly as described in the RFC.

+2
source

This is a logical error in your code.

First of all, only newer versions of Windows use 5000-65534 as ephemeral ports. In older versions, 1025-5000 was used instead.

You create several sockets that are explicitly tied to random ephemeral ports until you have connected a socket that is 10 ports smaller than your target port. However, if any of these sockets is actually bound to the actual target port, you ignore this and continue the loop. That way, you can or can get the socket bound to the target port, and you can or can't get the final port value, which is actually smaller than the target port.

After that, if port turns out to be smaller than your target port (which is not guaranteed), then you create more sockets that are implicitly tied to various random available ephemeral ports when you call connect() (it implicitly bind() internally if bind() is not already was called), none of which will be the same ephemeral port with which you are explicitly tied, since these ports are already in use and can no longer be used.

In no case do you have any socket connecting the ephemeral port to the same ephemeral port. And if another application is not tied to your target port and is actively listening on this port, then there is no way that connect() can successfully connect to the target port on any of the created sockets, since none of them are in the listening state. And getsockname() not valid for an unconnected socket, and the connecting socket is not guaranteed to be bound if connect() fails. Thus, the symptoms that you think are occurring are actually physically impossible given the code you showed. Your journal simply makes the wrong assumptions and thus logs the wrong things, giving you a false state of being.

Try something more like this and you will see what the real ports are:

 import socket TARGET_PORT = 49400 def mksocket(): return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) while True: sock = mksocket() sock.bind(('127.0.0.1', 0)) host, port = sock.getsockname() print 'Bound to local port %d' % (port) if port > TARGET_PORT - 10 and port < TARGET_PORT: break if port >= TARGET_PORT: print 'Bound port %d exceeded target port %d' % (port, TARGET_PORT) else: while port < TARGET_PORT: sock = mksocket() # connect() would do this internal anyway, so this is just to ensure a port is available for logging even if connect() fails sock.bind(('127.0.0.1', 0)) err = None try: sock.connect(('127.0.0.1', TARGET_PORT)) except socket.error, e: err = e host, port = sock.getsockname() if err: print 'Unable to connect to port %d using local port %d' % (TARGET_PORT, port) else: print 'Connected to port %d using local port %d' % (TARGET_PORT, port) 
0
source

Source: https://habr.com/ru/post/949180/


All Articles