• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import contextlib
2import errno
3import socket
4import unittest
5import sys
6
7from .. import support
8
9
10HOST = "localhost"
11HOSTv4 = "127.0.0.1"
12HOSTv6 = "::1"
13
14
15def find_unused_port(family=socket.AF_INET, socktype=socket.SOCK_STREAM):
16    """Returns an unused port that should be suitable for binding.  This is
17    achieved by creating a temporary socket with the same family and type as
18    the 'sock' parameter (default is AF_INET, SOCK_STREAM), and binding it to
19    the specified host address (defaults to 0.0.0.0) with the port set to 0,
20    eliciting an unused ephemeral port from the OS.  The temporary socket is
21    then closed and deleted, and the ephemeral port is returned.
22
23    Either this method or bind_port() should be used for any tests where a
24    server socket needs to be bound to a particular port for the duration of
25    the test.  Which one to use depends on whether the calling code is creating
26    a python socket, or if an unused port needs to be provided in a constructor
27    or passed to an external program (i.e. the -accept argument to openssl's
28    s_server mode).  Always prefer bind_port() over find_unused_port() where
29    possible.  Hard coded ports should *NEVER* be used.  As soon as a server
30    socket is bound to a hard coded port, the ability to run multiple instances
31    of the test simultaneously on the same host is compromised, which makes the
32    test a ticking time bomb in a buildbot environment. On Unix buildbots, this
33    may simply manifest as a failed test, which can be recovered from without
34    intervention in most cases, but on Windows, the entire python process can
35    completely and utterly wedge, requiring someone to log in to the buildbot
36    and manually kill the affected process.
37
38    (This is easy to reproduce on Windows, unfortunately, and can be traced to
39    the SO_REUSEADDR socket option having different semantics on Windows versus
40    Unix/Linux.  On Unix, you can't have two AF_INET SOCK_STREAM sockets bind,
41    listen and then accept connections on identical host/ports.  An EADDRINUSE
42    OSError will be raised at some point (depending on the platform and
43    the order bind and listen were called on each socket).
44
45    However, on Windows, if SO_REUSEADDR is set on the sockets, no EADDRINUSE
46    will ever be raised when attempting to bind two identical host/ports. When
47    accept() is called on each socket, the second caller's process will steal
48    the port from the first caller, leaving them both in an awkwardly wedged
49    state where they'll no longer respond to any signals or graceful kills, and
50    must be forcibly killed via OpenProcess()/TerminateProcess().
51
52    The solution on Windows is to use the SO_EXCLUSIVEADDRUSE socket option
53    instead of SO_REUSEADDR, which effectively affords the same semantics as
54    SO_REUSEADDR on Unix.  Given the propensity of Unix developers in the Open
55    Source world compared to Windows ones, this is a common mistake.  A quick
56    look over OpenSSL's 0.9.8g source shows that they use SO_REUSEADDR when
57    openssl.exe is called with the 's_server' option, for example. See
58    http://bugs.python.org/issue2550 for more info.  The following site also
59    has a very thorough description about the implications of both REUSEADDR
60    and EXCLUSIVEADDRUSE on Windows:
61    http://msdn2.microsoft.com/en-us/library/ms740621(VS.85).aspx)
62
63    XXX: although this approach is a vast improvement on previous attempts to
64    elicit unused ports, it rests heavily on the assumption that the ephemeral
65    port returned to us by the OS won't immediately be dished back out to some
66    other process when we close and delete our temporary socket but before our
67    calling code has a chance to bind the returned port.  We can deal with this
68    issue if/when we come across it.
69    """
70
71    with socket.socket(family, socktype) as tempsock:
72        port = bind_port(tempsock)
73    del tempsock
74    return port
75
76def bind_port(sock, host=HOST):
77    """Bind the socket to a free port and return the port number.  Relies on
78    ephemeral ports in order to ensure we are using an unbound port.  This is
79    important as many tests may be running simultaneously, especially in a
80    buildbot environment.  This method raises an exception if the sock.family
81    is AF_INET and sock.type is SOCK_STREAM, *and* the socket has SO_REUSEADDR
82    or SO_REUSEPORT set on it.  Tests should *never* set these socket options
83    for TCP/IP sockets.  The only case for setting these options is testing
84    multicasting via multiple UDP sockets.
85
86    Additionally, if the SO_EXCLUSIVEADDRUSE socket option is available (i.e.
87    on Windows), it will be set on the socket.  This will prevent anyone else
88    from bind()'ing to our host/port for the duration of the test.
89    """
90
91    if sock.family == socket.AF_INET and sock.type == socket.SOCK_STREAM:
92        if hasattr(socket, 'SO_REUSEADDR'):
93            if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) == 1:
94                raise support.TestFailed("tests should never set the "
95                                         "SO_REUSEADDR socket option on "
96                                         "TCP/IP sockets!")
97        if hasattr(socket, 'SO_REUSEPORT'):
98            try:
99                if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1:
100                    raise support.TestFailed("tests should never set the "
101                                             "SO_REUSEPORT socket option on "
102                                             "TCP/IP sockets!")
103            except OSError:
104                # Python's socket module was compiled using modern headers
105                # thus defining SO_REUSEPORT but this process is running
106                # under an older kernel that does not support SO_REUSEPORT.
107                pass
108        if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'):
109            sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
110
111    sock.bind((host, 0))
112    port = sock.getsockname()[1]
113    return port
114
115def bind_unix_socket(sock, addr):
116    """Bind a unix socket, raising SkipTest if PermissionError is raised."""
117    assert sock.family == socket.AF_UNIX
118    try:
119        sock.bind(addr)
120    except PermissionError:
121        sock.close()
122        raise unittest.SkipTest('cannot bind AF_UNIX sockets')
123
124def _is_ipv6_enabled():
125    """Check whether IPv6 is enabled on this host."""
126    if socket.has_ipv6:
127        sock = None
128        try:
129            sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
130            sock.bind((HOSTv6, 0))
131            return True
132        except OSError:
133            pass
134        finally:
135            if sock:
136                sock.close()
137    return False
138
139IPV6_ENABLED = _is_ipv6_enabled()
140
141
142_bind_nix_socket_error = None
143def skip_unless_bind_unix_socket(test):
144    """Decorator for tests requiring a functional bind() for unix sockets."""
145    if not hasattr(socket, 'AF_UNIX'):
146        return unittest.skip('No UNIX Sockets')(test)
147    global _bind_nix_socket_error
148    if _bind_nix_socket_error is None:
149        from .os_helper import TESTFN, unlink
150        path = TESTFN + "can_bind_unix_socket"
151        with socket.socket(socket.AF_UNIX) as sock:
152            try:
153                sock.bind(path)
154                _bind_nix_socket_error = False
155            except OSError as e:
156                _bind_nix_socket_error = e
157            finally:
158                unlink(path)
159    if _bind_nix_socket_error:
160        msg = 'Requires a functional unix bind(): %s' % _bind_nix_socket_error
161        return unittest.skip(msg)(test)
162    else:
163        return test
164
165
166def get_socket_conn_refused_errs():
167    """
168    Get the different socket error numbers ('errno') which can be received
169    when a connection is refused.
170    """
171    errors = [errno.ECONNREFUSED]
172    if hasattr(errno, 'ENETUNREACH'):
173        # On Solaris, ENETUNREACH is returned sometimes instead of ECONNREFUSED
174        errors.append(errno.ENETUNREACH)
175    if hasattr(errno, 'EADDRNOTAVAIL'):
176        # bpo-31910: socket.create_connection() fails randomly
177        # with EADDRNOTAVAIL on Travis CI
178        errors.append(errno.EADDRNOTAVAIL)
179    if hasattr(errno, 'EHOSTUNREACH'):
180        # bpo-37583: The destination host cannot be reached
181        errors.append(errno.EHOSTUNREACH)
182    if not IPV6_ENABLED:
183        errors.append(errno.EAFNOSUPPORT)
184    return errors
185
186
187_NOT_SET = object()
188
189@contextlib.contextmanager
190def transient_internet(resource_name, *, timeout=_NOT_SET, errnos=()):
191    """Return a context manager that raises ResourceDenied when various issues
192    with the internet connection manifest themselves as exceptions."""
193    import nntplib
194    import urllib.error
195    if timeout is _NOT_SET:
196        timeout = support.INTERNET_TIMEOUT
197
198    default_errnos = [
199        ('ECONNREFUSED', 111),
200        ('ECONNRESET', 104),
201        ('EHOSTUNREACH', 113),
202        ('ENETUNREACH', 101),
203        ('ETIMEDOUT', 110),
204        # socket.create_connection() fails randomly with
205        # EADDRNOTAVAIL on Travis CI.
206        ('EADDRNOTAVAIL', 99),
207    ]
208    default_gai_errnos = [
209        ('EAI_AGAIN', -3),
210        ('EAI_FAIL', -4),
211        ('EAI_NONAME', -2),
212        ('EAI_NODATA', -5),
213        # Encountered when trying to resolve IPv6-only hostnames
214        ('WSANO_DATA', 11004),
215    ]
216
217    denied = support.ResourceDenied("Resource %r is not available" % resource_name)
218    captured_errnos = errnos
219    gai_errnos = []
220    if not captured_errnos:
221        captured_errnos = [getattr(errno, name, num)
222                           for (name, num) in default_errnos]
223        gai_errnos = [getattr(socket, name, num)
224                      for (name, num) in default_gai_errnos]
225
226    def filter_error(err):
227        n = getattr(err, 'errno', None)
228        if (isinstance(err, TimeoutError) or
229            (isinstance(err, socket.gaierror) and n in gai_errnos) or
230            (isinstance(err, urllib.error.HTTPError) and
231             500 <= err.code <= 599) or
232            (isinstance(err, urllib.error.URLError) and
233                 (("ConnectionRefusedError" in err.reason) or
234                  ("TimeoutError" in err.reason) or
235                  ("EOFError" in err.reason))) or
236            n in captured_errnos):
237            if not support.verbose:
238                sys.stderr.write(denied.args[0] + "\n")
239            raise denied from err
240
241    old_timeout = socket.getdefaulttimeout()
242    try:
243        if timeout is not None:
244            socket.setdefaulttimeout(timeout)
245        yield
246    except nntplib.NNTPTemporaryError as err:
247        if support.verbose:
248            sys.stderr.write(denied.args[0] + "\n")
249        raise denied from err
250    except OSError as err:
251        # urllib can wrap original socket errors multiple times (!), we must
252        # unwrap to get at the original error.
253        while True:
254            a = err.args
255            if len(a) >= 1 and isinstance(a[0], OSError):
256                err = a[0]
257            # The error can also be wrapped as args[1]:
258            #    except socket.error as msg:
259            #        raise OSError('socket error', msg).with_traceback(sys.exc_info()[2])
260            elif len(a) >= 2 and isinstance(a[1], OSError):
261                err = a[1]
262            else:
263                break
264        filter_error(err)
265        raise
266    # XXX should we catch generic exceptions and look for their
267    # __cause__ or __context__?
268    finally:
269        socket.setdefaulttimeout(old_timeout)
270