import/delegate-to foolscap's allocate_tcp_port

(instead of using a copy). Foolscap-0.12.3 fixes a problem with
allocate_tcp_port() that was causing intermittent test failures. I think
it makes more sense to use Foolscap's copy (and fixes) than to keep
re-copying it into Tahoe each time it changes.

If/when we manage to stop depending upon foolscap for server RPC, we can
re-copy this back into tahoe's source tree.

refs ticket:2795
This commit is contained in:
Brian Warner 2016-09-01 22:39:03 -07:00
parent 076b3895dc
commit 57e7f7bb7c
2 changed files with 11 additions and 70 deletions

View File

@ -39,7 +39,8 @@ install_requires = [
# * foolscap 0.8.0 generates 2048-bit RSA-with-SHA-256 signatures,
# rather than 1024-bit RSA-with-MD5. This also allows us to work
# with a FIPS build of OpenSSL.
# * foolscap >= 0.12.3 provides tcp/tor/i2p connection handlers we need
# * foolscap >= 0.12.3 provides tcp/tor/i2p connection handlers we need,
# and allocate_tcp_port
"foolscap >= 0.12.3",
# Needed for SFTP.

View File

@ -1,5 +1,5 @@
# from the Python Standard Library
import os, sys, re, socket, subprocess, errno
import os, re, socket, subprocess, errno
from sys import platform
@ -8,9 +8,10 @@ from twisted.internet import defer, threads, reactor
from twisted.internet.protocol import DatagramProtocol
from twisted.internet.error import CannotListenError
from twisted.python.procutils import which
from twisted.python.runtime import platformType
from twisted.python import log
from foolscap.util import allocate_tcp_port # re-exported
try:
import resource
def increase_rlimits():
@ -237,70 +238,9 @@ def _cygwin_hack_find_addresses():
return defer.succeed(addresses)
def allocate_tcp_port():
"""Return an (integer) available TCP port on localhost. This briefly
listens on the port in question, then closes it right away."""
# Making this work correctly on multiple OSes is non-trivial:
# * on OS-X:
# * Binding the test socket to 127.0.0.1 lets the kernel give us a
# LISTEN port that some other process is using, if they bound it to
# ANY (0.0.0.0). These will fail when we attempt to
# listen(bind=0.0.0.0) ourselves
# * Binding the test socket to 0.0.0.0 lets the kernel give us LISTEN
# ports bound to 127.0.0.1, although then our subsequent listen()
# call usually succeeds.
# * In both cases, the kernel can give us a port that's in use by the
# near side of an ESTABLISHED socket. If the process which owns that
# socket is not owned by the same user as us, listen() will fail.
# * Doing a listen() right away (on the kernel-allocated socket)
# succeeds, but a subsequent listen() on a new socket (bound to
# the same port) will fail.
# * on Linux:
# * The kernel never gives us a port in use by a LISTEN socket, whether
# we bind the test socket to 127.0.0.1 or 0.0.0.0
# * Binding it to 127.0.0.1 does let the kernel give us ports used in
# an ESTABLISHED connection. Our listen() will fail regardless of who
# owns that socket. (note that we are using SO_REUSEADDR but not
# SO_REUSEPORT, which would probably affect things).
#
# So to make this work properly everywhere, allocate_tcp_port() needs two
# phases: first we allocate a port (with 0.0.0.0), then we close that
# socket, then we open a second socket, bind the second socket to the
# same port, then try to listen. If the listen() fails, we loop back and
# try again.
# Ideally we'd refrain from doing listen(), to minimize impact on the
# system, and we'd bind the port to 127.0.0.1, to avoid making it look
# like we're accepting data from the outside world (in situations where
# we're going to end up binding the port to 127.0.0.1 anyways). But for
# the above reasons, neither would work. We *do* add SO_REUSEADDR, to
# make sure our lingering socket won't prevent our caller from opening it
# themselves in a few moments (note that Twisted's
# tcp.Port.createInternetSocket sets SO_REUSEADDR, among other flags).
count = 0
while True:
s = _make_socket()
s.bind(("0.0.0.0", 0))
port = s.getsockname()[1]
s.close()
s = _make_socket()
try:
s.bind(("0.0.0.0", port))
s.listen(5) # this is what sometimes fails
s.close()
return port
except socket.error:
s.close()
count += 1
if count > 100:
raise
# try again
def _make_socket():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if platformType == "posix" and sys.platform != "cygwin":
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
return s
__all__ = ["allocate_tcp_port",
"increase_rlimits",
"get_local_addresses_sync",
"get_local_addresses_async",
"get_local_ip_for",
]