Skip to content

Commit

Permalink
Switch to using port=None consistently to mean "any port"
Browse files Browse the repository at this point in the history
Slightly more readable than port=0, which also still works.
  • Loading branch information
njsmith committed Aug 14, 2017
1 parent ba1b940 commit 6a578f0
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 17 deletions.
28 changes: 16 additions & 12 deletions trio/_highlevel_open_tcp_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,31 @@ async def open_tcp_listeners(port, *, host=None, backlog=None):
Args:
port (int): The port to listen on. If you pass 0, the kernel will
automatically pick an arbitrary open port. But be careful: if you
use ``port=0`` when binding to multiple IP address, then each IP
address will be assigned a different port. An example of when this
happens is when ``host=None``, which means to bind to both the IPv4
wildcard address (``0.0.0.0``) and also the IPv6 wildcard address
(``::``).
port (int or None): The port to listen on. If you pass ``None`` or 0,
the kernel will automatically pick an arbitrary open port. (It
doesn't matter whether you pass ``None`` or 0; they're exactly
equivalent.) But be careful: if you use this feature when binding to
multiple IP addresses, then the random port selection will be done
separately for each IP address, so the returned listeners will
probably be listening on different ports. One situation where this
commonly occurs is when ``host=None`` (the default), which means to
bind to both the IPv4 wildcard address (``0.0.0.0``) and also the
IPv6 wildcard address (``::``).
host (str or None): The local interface to bind to. This is passed to
:func:`~socket.getaddrinfo` with the ``AI_PASSIVE`` flag set.
If you want to bind to bind to the wildcard address on both IPv4 and
IPv6, in order to accept connections on all available interfaces
then, pass ``None``. This is the default.
If you have a specific interface you want to bind to, pass its IP
address or hostname here. If a hostname resolves to multiple IP
addresses, this function will bind one listener to each of them.
If you want to bind to all available interfaces (the wildcard
address) for both IPv4 and IPv6, pass ``None`` (the default).
addresses, this function will one listener to each of them.
If you want to use only IPv4, or only IPv6, but want to accept on
all interfaces, pass the family-specific wildcard address:
``"0.0.0.0"`` or ``"::"``.
``"0.0.0.0"`` or ``"::"``, respectively.
backlog (int or None): The listen backlog to use. If you leave this as
``None`` then Trio will pick a good default.
Expand Down
3 changes: 2 additions & 1 deletion trio/_highlevel_ssl_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ async def open_ssl_over_tcp_listeners(
"""Start listening for SSL/TLS-encrypted TCP connections to the given port.
Args:
port (int): The port to listen on. See :func:`open_tcp_listeners`.
port (int or None): The port to listen on. See
:func:`open_tcp_listeners`.
ssl_context (~ssl.SSLContext): The SSL context to use for all incoming
connections.
host (str or None): The address to bind to; use ``None`` to bind to the
Expand Down
4 changes: 4 additions & 0 deletions trio/_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ async def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
"""

# Work around https://bugs.python.org/issue31198
if port is None:
port = 0

# If host and port are numeric, then getaddrinfo doesn't block and we can
# skip the whole thread thing, which seems worthwhile. So we try first
# with the _NUMERIC_ONLY flags set, and then only spawn a thread if that
Expand Down
17 changes: 13 additions & 4 deletions trio/tests/test_highlevel_open_tcp_listeners.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


async def test_open_tcp_listeners_basic():
listeners = await open_tcp_listeners(0)
listeners = await open_tcp_listeners(None)
assert isinstance(listeners, list)
for obj in listeners:
assert isinstance(obj, SocketListener)
Expand Down Expand Up @@ -77,7 +77,7 @@ async def measure_backlog(listener):
async def test_open_tcp_listeners_backlog():
# Operating systems don't necessarily use the exact backlog you pass
async def check_backlog(nominal, required_min, required_max):
listeners = await open_tcp_listeners(0, backlog=nominal)
listeners = await open_tcp_listeners(None, backlog=nominal)
actual = await measure_backlog(listeners[0])
for listener in listeners:
await listener.aclose()
Expand All @@ -90,15 +90,15 @@ async def check_backlog(nominal, required_min, required_max):

async def test_open_tcp_listeners_ipv6_v6only():
# Check IPV6_V6ONLY is working properly
(ipv6_listener,) = await open_tcp_listeners(0, host="::1")
(ipv6_listener,) = await open_tcp_listeners(None, host="::1")
_, port, *_ = ipv6_listener.socket.getsockname()

with pytest.raises(OSError):
await open_tcp_stream("127.0.0.1", port)


async def test_open_tcp_listeners_rebind():
(l1,) = await open_tcp_listeners(0, host="127.0.0.1")
(l1,) = await open_tcp_listeners(None, host="127.0.0.1")
sockaddr1 = l1.socket.getsockname()

# Plain old rebinding while it's still there should fail, even if we have
Expand Down Expand Up @@ -210,3 +210,12 @@ async def test_open_tcp_listeners_multiple_host_cleanup_on_error():
assert len(fsf.sockets) == 3
for sock in fsf.sockets:
assert sock.closed


async def test_open_tcp_listeners_accepts_port_None_and_0():
for port in [None, 0]:
# https://bugs.python.org/issue31198
for host in ["127.0.0.1", None]:
listeners = await open_tcp_listeners(port, host=host)
for l in listeners:
await l.aclose()
11 changes: 11 additions & 0 deletions trio/tests/test_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ def without_proto(gai_tup):
await tsocket.getaddrinfo("asdf", "12345")


async def test_getaddrinfo_port_None():
# https://bugs.python.org/issue31198
res = await tsocket.getaddrinfo("127.0.0.1", None)
assert res[0][-1] == ("127.0.0.1", 0)

res = await tsocket.getaddrinfo(
None, None, family=tsocket.AF_INET, flags=tsocket.AI_PASSIVE
)
assert res[0][-1] == ("0.0.0.0", 0)


async def test_getnameinfo():
# Trivial test:
ni_numeric = stdlib_socket.NI_NUMERICHOST | stdlib_socket.NI_NUMERICSERV
Expand Down

0 comments on commit 6a578f0

Please sign in to comment.