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, # * 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 # rather than 1024-bit RSA-with-MD5. This also allows us to work
# with a FIPS build of OpenSSL. # 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", "foolscap >= 0.12.3",
# Needed for SFTP. # Needed for SFTP.

View File

@ -1,5 +1,5 @@
# from the Python Standard Library # from the Python Standard Library
import os, sys, re, socket, subprocess, errno import os, re, socket, subprocess, errno
from sys import platform from sys import platform
@ -8,9 +8,10 @@ from twisted.internet import defer, threads, reactor
from twisted.internet.protocol import DatagramProtocol from twisted.internet.protocol import DatagramProtocol
from twisted.internet.error import CannotListenError from twisted.internet.error import CannotListenError
from twisted.python.procutils import which from twisted.python.procutils import which
from twisted.python.runtime import platformType
from twisted.python import log from twisted.python import log
from foolscap.util import allocate_tcp_port # re-exported
try: try:
import resource import resource
def increase_rlimits(): def increase_rlimits():
@ -237,70 +238,9 @@ def _cygwin_hack_find_addresses():
return defer.succeed(addresses) return defer.succeed(addresses)
def allocate_tcp_port(): __all__ = ["allocate_tcp_port",
"""Return an (integer) available TCP port on localhost. This briefly "increase_rlimits",
listens on the port in question, then closes it right away.""" "get_local_addresses_sync",
"get_local_addresses_async",
# Making this work correctly on multiple OSes is non-trivial: "get_local_ip_for",
# * 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