Returns an unused port that should be suitable for binding. This is achieved by creating a temporary socket with the same family and type as the 'sock' parameter (default is AF_INET, SOCK_STREAM), and binding it to the specified host address (defaults to 0.0.0.0) with the port set to 0,
(family=socket.AF_INET, socktype=socket.SOCK_STREAM)
| 18 | |
| 19 | |
| 20 | def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM): |
| 21 | """Returns an unused port that should be suitable for binding. This is |
| 22 | achieved by creating a temporary socket with the same family and type as |
| 23 | the 'sock' parameter (default is AF_INET, SOCK_STREAM), and binding it to |
| 24 | the specified host address (defaults to 0.0.0.0) with the port set to 0, |
| 25 | eliciting an unused ephemeral port from the OS. The temporary socket is |
| 26 | then closed and deleted, and the ephemeral port is returned. |
| 27 | |
| 28 | Either this method or bind_port() should be used for any tests where a |
| 29 | server socket needs to be bound to a particular port for the duration of |
| 30 | the test. Which one to use depends on whether the calling code is creating |
| 31 | a python socket, or if an unused port needs to be provided in a constructor |
| 32 | or passed to an external program (i.e. the -accept argument to openssl's |
| 33 | s_server mode). Always prefer bind_port() over find_unused_port() where |
| 34 | possible. Hard coded ports should *NEVER* be used. As soon as a server |
| 35 | socket is bound to a hard coded port, the ability to run multiple instances |
| 36 | of the test simultaneously on the same host is compromised, which makes the |
| 37 | test a ticking time bomb in a buildbot environment. On Unix buildbots, this |
| 38 | may simply manifest as a failed test, which can be recovered from without |
| 39 | intervention in most cases, but on Windows, the entire python process can |
| 40 | completely and utterly wedge, requiring someone to log in to the buildbot |
| 41 | and manually kill the affected process. |
| 42 | |
| 43 | (This is easy to reproduce on Windows, unfortunately, and can be traced to |
| 44 | the SO_REUSEADDR socket option having different semantics on Windows versus |
| 45 | Unix/Linux. On Unix, you can't have two AF_INET SOCK_STREAM sockets bind, |
| 46 | listen and then accept connections on identical host/ports. An EADDRINUSE |
| 47 | OSError will be raised at some point (depending on the platform and |
| 48 | the order bind and listen were called on each socket). |
| 49 | |
| 50 | However, on Windows, if SO_REUSEADDR is set on the sockets, no EADDRINUSE |
| 51 | will ever be raised when attempting to bind two identical host/ports. When |
| 52 | accept() is called on each socket, the second caller's process will steal |
| 53 | the port from the first caller, leaving them both in an awkwardly wedged |
| 54 | state where they'll no longer respond to any signals or graceful kills, and |
| 55 | must be forcibly killed via OpenProcess()/TerminateProcess(). |
| 56 | |
| 57 | The solution on Windows is to use the SO_EXCLUSIVEADDRUSE socket option |
| 58 | instead of SO_REUSEADDR, which effectively affords the same semantics as |
| 59 | SO_REUSEADDR on Unix. Given the propensity of Unix developers in the Open |
| 60 | Source world compared to Windows ones, this is a common mistake. A quick |
| 61 | look over OpenSSL's 0.9.8g source shows that they use SO_REUSEADDR when |
| 62 | openssl.exe is called with the 's_server' option, for example. See |
| 63 | http://bugs.python.org/issue2550 for more info. The following site also |
| 64 | has a very thorough description about the implications of both REUSEADDR |
| 65 | and EXCLUSIVEADDRUSE on Windows: |
| 66 | https://learn.microsoft.com/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse |
| 67 | |
| 68 | XXX: although this approach is a vast improvement on previous attempts to |
| 69 | elicit unused ports, it rests heavily on the assumption that the ephemeral |
| 70 | port returned to us by the OS won't immediately be dished back out to some |
| 71 | other process when we close and delete our temporary socket but before our |
| 72 | calling code has a chance to bind the returned port. We can deal with this |
| 73 | issue if/when we come across it. |
| 74 | """ |
| 75 | |
| 76 | with socket.socket(family, socktype) as tempsock: |
| 77 | port = bind_port(tempsock) |
searching dependent graphs…