Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ffa07b3
add socket.bind_socket() function + tests
giampaolo Feb 7, 2019
5618b21
make ftplib use bind_socket() (reuse code)
giampaolo Feb 7, 2019
6b3e634
use bind_socket() in tests (reuse code)
giampaolo Feb 7, 2019
cbaa3c1
set default backlog to 128; provide a more informative message if SO_…
giampaolo Feb 7, 2019
9832435
add 'flags' parameter
giampaolo Feb 7, 2019
17a6b7b
use bind_socket() in more unit-tests
giampaolo Feb 7, 2019
29b1d69
add comment
giampaolo Feb 7, 2019
e4063f2
add NEWS entry
giampaolo Feb 7, 2019
057d831
add 'reuse_addr' arg
giampaolo Feb 8, 2019
b4883cb
rename method
giampaolo Feb 8, 2019
fb0e442
make family and type kw-only args
giampaolo Feb 8, 2019
3e876c5
raise ValueError if type is not SOCK_STREAM/DGRAM
giampaolo Feb 8, 2019
e9fb489
set IPV6_V6ONLY by default
giampaolo Feb 8, 2019
231455f
unittest: check IPV6_V6ONLY is set by default
giampaolo Feb 8, 2019
fbdce4e
fix test failures
giampaolo Feb 11, 2019
2e9e48c
adjust doc wording
giampaolo Feb 12, 2019
4f28c47
change var name
giampaolo Feb 12, 2019
281b914
set flags arg to None by default
giampaolo Feb 12, 2019
e003dfe
document that AF_INET is preferred if host's family is unclear
giampaolo Feb 12, 2019
0a893ca
introduce supports_hybrid_ipv46 and relative bind_socket arg
giampaolo Feb 12, 2019
2d247a2
various improvements:
giampaolo Feb 12, 2019
2c3c85c
update doc
giampaolo Feb 12, 2019
1931e7c
use 'localhost' in tests (safer)
giampaolo Feb 12, 2019
2364a89
raise error on Windows if reuse_addr=True and type != SOCK_DGRAM
giampaolo Feb 13, 2019
1d13a9c
update doc + provide better error message on bind()
giampaolo Feb 13, 2019
46a562e
rename bind_socket() to create_server()
giampaolo Feb 14, 2019
9cd6f01
get rid of reuse_addr arg
giampaolo Feb 14, 2019
d0e69bb
address @vstinner comments
giampaolo Feb 14, 2019
f93058b
fix NameError + add comment clarifying why we in case of ambiguous ho…
giampaolo Feb 14, 2019
63d762e
fix test failures
giampaolo Feb 14, 2019
e95da59
don't use getaddrinfo(), change function signature
giampaolo Feb 14, 2019
22ea974
rename arg hybrid_ipv46 -> dualstack_ipv6
giampaolo Feb 14, 2019
ad8a219
update doc
giampaolo Feb 14, 2019
3b3df83
fix ftplib bug, skip IPV6 tests
giampaolo Feb 15, 2019
d75a600
idlelib/rpc.py: add missing address argument
terryjreedy Feb 15, 2019
265b225
update doc
giampaolo Feb 15, 2019
426907d
fix doc example + remove UDP-related test code which is no longer used
giampaolo Feb 17, 2019
d62499a
merge from master
giampaolo Feb 17, 2019
7b75345
set backlog=0 instead of None; address doc-related review comments
giampaolo Feb 18, 2019
0cf3bb1
change unit-tests so that client does not rely on getaddrinfo() - bet…
giampaolo Feb 18, 2019
12bbf0c
fix wrong client addr + fix travis test due to extra whitespace
giampaolo Feb 18, 2019
caa7605
automatize -> automate
gpshead Feb 19, 2019
ecac919
Merge branch 'master' into bind-socket
giampaolo Mar 6, 2019
f786884
update doc; catch socket.error instead of Exception; remove idlelib i…
giampaolo Mar 6, 2019
e19b28f
Merge branch 'master' into bind-socket
giampaolo Mar 28, 2019
5b49125
update doc (remove ref to activestate recipe)
giampaolo Mar 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion Doc/library/socket.rst
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,50 @@ The following functions all create :ref:`socket objects <socket-objects>`.
.. versionchanged:: 3.2
*source_address* was added.

.. function:: create_server(address, *, family=AF_INET, backlog=0, reuse_port=False, dualstack_ipv6=False)

Convenience function which creates a TCP socket bound to *address* (a 2-tuple
``(host, port)``) and return the socket object.

*family* should be either :data:`AF_INET` or :data:`AF_INET6`.
*backlog* is the queue size passed to :meth:`socket.listen`; when ``0``
a default reasonable value is chosen.
*reuse_port* dictates whether to set the :data:`SO_REUSEPORT` socket option.

If *dualstack_ipv6* is true and the platform supports it the socket will
be able to accept both IPv4 and IPv6 connections, else it will raise
:exc:`ValueError`. Most POSIX platforms and Windows are supposed to support
this functionality.
When this functionality is enabled the address returned by
:meth:`socket.getpeername` when an IPv4 connection occurs will be an IPv6
address represented as an IPv4-mapped IPv6 address.
If *dualstack_ipv6* is false it will explicitly disable this functionality
on platforms that enable it by default (e.g. Linux).
This parameter can be used in conjunction with :func:`has_dualstack_ipv6`:

::

import socket

addr = ("", 8080) # all interfaces, port 8080
if socket.has_dualstack_ipv6():
s = socket.create_server(addr, family=socket.AF_INET6, dualstack_ipv6=True)
else:
s = socket.create_server(addr)

.. note::
On POSIX platforms the :data:`SO_REUSEADDR` socket option is set in order to
immediately reuse previous sockets which were bound on the same *address*
and remained in TIME_WAIT state.

.. versionadded:: 3.8

.. function:: has_dualstack_ipv6()

Return ``True`` if the platform supports creating a TCP socket which can
handle both IPv4 and IPv6 connections.

.. versionadded:: 3.8

.. function:: fromfd(fd, family, type, proto=0)

Expand Down Expand Up @@ -1778,7 +1822,6 @@ sends traffic to the first one connected successfully. ::
data = s.recv(1024)
print('Received', repr(data))


The next example shows how to write a very simple network sniffer with raw
sockets on Windows. The example requires administrator privileges to modify
the interface::
Expand Down
9 changes: 9 additions & 0 deletions Doc/whatsnew/3.8.rst
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,15 @@ contain characters unrepresentable at the OS level.
(Contributed by Serhiy Storchaka in :issue:`33721`.)


socket
------

Added :meth:`~socket.create_server()` and :meth:`~socket.has_dualstack_ipv6()`
convenience functions to automate the necessary tasks usually involved when
creating a server socket, including accepting both IPv4 and IPv6 connections
on the same socket. (Contributed by Giampaolo Rodola in :issue:`17561`.)


shutil
------

Expand Down
21 changes: 1 addition & 20 deletions Lib/ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,26 +302,7 @@ def sendeprt(self, host, port):

def makeport(self):
'''Create a new socket and send a PORT command for it.'''
err = None
sock = None
for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
af, socktype, proto, canonname, sa = res
try:
sock = socket.socket(af, socktype, proto)
sock.bind(sa)
except OSError as _:
err = _
if sock:
sock.close()
sock = None
continue
break
if sock is None:
if err is not None:
raise err
else:
raise OSError("getaddrinfo returns an empty list")
sock.listen(1)
sock = socket.create_server(("", 0), family=self.af, backlog=1)
port = sock.getsockname()[1] # Get proper port
host = self.sock.getsockname()[0] # Get proper host
if self.af == socket.AF_INET:
Expand Down
87 changes: 85 additions & 2 deletions Lib/socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@
EAGAIN = getattr(errno, 'EAGAIN', 11)
EWOULDBLOCK = getattr(errno, 'EWOULDBLOCK', 11)

__all__ = ["fromfd", "getfqdn", "create_connection",
"AddressFamily", "SocketKind"]
__all__ = ["fromfd", "getfqdn", "create_connection", "create_server",
"has_dualstack_ipv6", "AddressFamily", "SocketKind"]
__all__.extend(os._get_exports_list(_socket))

# Set up the socket.AF_* socket.SOCK_* constants as members of IntEnums for
Expand Down Expand Up @@ -728,6 +728,89 @@ def create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
else:
raise error("getaddrinfo returns an empty list")


def has_dualstack_ipv6():
"""Return True if the platform supports creating a SOCK_STREAM socket
which can handle both AF_INET and AF_INET6 (IPv4 / IPv6) connections.
"""
if not has_ipv6 \
or not hasattr(_socket, 'IPPROTO_IPV6') \
or not hasattr(_socket, 'IPV6_V6ONLY'):
return False
try:
with socket(AF_INET6, SOCK_STREAM) as sock:
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
return True
except error:
return False


def create_server(address, *, family=AF_INET, backlog=0, reuse_port=False,
dualstack_ipv6=False):
"""Convenience function which creates a SOCK_STREAM type socket
bound to *address* (a 2-tuple (host, port)) and return the socket
object.

*family* should be either AF_INET or AF_INET6.
*backlog* is the queue size passed to socket.listen().
*reuse_port* dictates whether to use the SO_REUSEPORT socket option.
*dualstack_ipv6*: if true and the platform supports it, it will
create an AF_INET6 socket able to accept both IPv4 or IPv6
connections. When false it will explicitly disable this option on
platforms that enable it by default (e.g. Linux).

>>> with create_server((None, 8000)) as server:
... while True:
... conn, addr = server.accept()
... # handle new connection
"""
if reuse_port and not hasattr(_socket, "SO_REUSEPORT"):
raise ValueError("SO_REUSEPORT not supported on this platform")
if dualstack_ipv6:
if not has_dualstack_ipv6():
raise ValueError("dualstack_ipv6 not supported on this platform")
if family != AF_INET6:
raise ValueError("dualstack_ipv6 requires AF_INET6 family")
sock = socket(family, SOCK_STREAM)
try:
# Note about Windows. We don't set SO_REUSEADDR because:
# 1) It's unnecessary: bind() will succeed even in case of a
# previous closed socket on the same address and still in
# TIME_WAIT state.
# 2) If set, another socket is free to bind() on the same
# address, effectively preventing this one from accepting
# connections. Also, it may set the process in a state where
# it'll no longer respond to any signals or graceful kills.
# See: msdn2.microsoft.com/en-us/library/ms740621(VS.85).aspx
if os.name not in ('nt', 'cygwin') and \
hasattr(_socket, 'SO_REUSEADDR'):
try:
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
except error:
# Fail later on bind(), for platforms which may not
# support this option.
pass
if reuse_port:
sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
if has_ipv6 and family == AF_INET6:
if dualstack_ipv6:
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 0)
elif hasattr(_socket, "IPV6_V6ONLY") and \
hasattr(_socket, "IPPROTO_IPV6"):
sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
try:
sock.bind(address)
except error as err:
msg = '%s (while attempting to bind on address %r)' % \
(err.strerror, address)
raise error(err.errno, msg) from None
sock.listen(backlog)
return sock
except error:
sock.close()
raise


def getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
"""Resolve host and port into list of address info entries.

Expand Down
8 changes: 2 additions & 6 deletions Lib/test/_test_multiprocessing.py
Original file line number Diff line number Diff line change
Expand Up @@ -3334,9 +3334,7 @@ def _listener(cls, conn, families):
new_conn.close()
l.close()

l = socket.socket()
l.bind((test.support.HOST, 0))
l.listen()
l = socket.create_server((test.support.HOST, 0))
conn.send(l.getsockname())
new_conn, addr = l.accept()
conn.send(new_conn)
Expand Down Expand Up @@ -4345,9 +4343,7 @@ def _child_test_wait_socket(cls, address, slow):

def test_wait_socket(self, slow=False):
from multiprocessing.connection import wait
l = socket.socket()
l.bind((test.support.HOST, 0))
l.listen()
l = socket.create_server((test.support.HOST, 0))
addr = l.getsockname()
readers = []
procs = []
Expand Down
5 changes: 1 addition & 4 deletions Lib/test/eintrdata/eintr_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,9 @@ def test_sendmsg(self):
self._test_send(lambda sock, data: sock.sendmsg([data]))

def test_accept(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock = socket.create_server((support.HOST, 0))
self.addCleanup(sock.close)

sock.bind((support.HOST, 0))
port = sock.getsockname()[1]
sock.listen()

code = '\n'.join((
'import socket, time',
Expand Down
10 changes: 1 addition & 9 deletions Lib/test/test_asyncio/functional.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,21 +60,13 @@ def tcp_server(self, server_prog, *,
else:
addr = ('127.0.0.1', 0)

sock = socket.socket(family, socket.SOCK_STREAM)

sock = socket.create_server(addr, family=family, backlog=backlog)
if timeout is None:
raise RuntimeError('timeout is required')
if timeout <= 0:
raise RuntimeError('only blocking sockets are supported')
sock.settimeout(timeout)

try:
sock.bind(addr)
sock.listen(backlog)
except OSError as ex:
sock.close()
raise ex

return TestThreadedServer(
self, sock, server_prog, timeout, max_clients)

Expand Down
12 changes: 3 additions & 9 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -667,9 +667,7 @@ def data_received(self, data):
super().data_received(data)
self.transport.write(expected_response)

lsock = socket.socket()
lsock.bind(('127.0.0.1', 0))
lsock.listen(1)
lsock = socket.create_server(('127.0.0.1', 0), backlog=1)
addr = lsock.getsockname()

message = b'test data'
Expand Down Expand Up @@ -1118,9 +1116,7 @@ def connection_made(self, transport):
super().connection_made(transport)
proto.set_result(self)

sock_ob = socket.socket(type=socket.SOCK_STREAM)
sock_ob.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock_ob.bind(('0.0.0.0', 0))
sock_ob = socket.create_server(('0.0.0.0', 0))

f = self.loop.create_server(TestMyProto, sock=sock_ob)
server = self.loop.run_until_complete(f)
Expand All @@ -1136,9 +1132,7 @@ def connection_made(self, transport):
server.close()

def test_create_server_addr_in_use(self):
sock_ob = socket.socket(type=socket.SOCK_STREAM)
sock_ob.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock_ob.bind(('0.0.0.0', 0))
sock_ob = socket.create_server(('0.0.0.0', 0))

f = self.loop.create_server(MyProto, sock=sock_ob)
server = self.loop.run_until_complete(f)
Expand Down
11 changes: 3 additions & 8 deletions Lib/test/test_asyncio/test_streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,7 @@ async def handle_client(self, client_reader, client_writer):
await client_writer.wait_closed()

def start(self):
sock = socket.socket()
sock.bind(('127.0.0.1', 0))
sock = socket.create_server(('127.0.0.1', 0))
self.server = self.loop.run_until_complete(
asyncio.start_server(self.handle_client,
sock=sock,
Expand All @@ -605,8 +604,7 @@ def handle_client_callback(self, client_reader, client_writer):
client_writer))

def start_callback(self):
sock = socket.socket()
sock.bind(('127.0.0.1', 0))
sock = socket.create_server(('127.0.0.1', 0))
addr = sock.getsockname()
sock.close()
self.server = self.loop.run_until_complete(
Expand Down Expand Up @@ -796,10 +794,7 @@ def test_drain_raises(self):

def server():
# Runs in a separate thread.
sock = socket.socket()
with sock:
sock.bind(('localhost', 0))
sock.listen(1)
with socket.create_server(('localhost', 0)) as sock:
addr = sock.getsockname()
q.put(addr)
clt, _ = sock.accept()
Expand Down
4 changes: 1 addition & 3 deletions Lib/test/test_epoll.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@
class TestEPoll(unittest.TestCase):

def setUp(self):
self.serverSocket = socket.socket()
self.serverSocket.bind(('127.0.0.1', 0))
self.serverSocket.listen()
self.serverSocket = socket.create_server(('127.0.0.1', 0))
self.connections = [self.serverSocket]

def tearDown(self):
Expand Down
9 changes: 3 additions & 6 deletions Lib/test/test_ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,7 @@ def cmd_port(self, arg):
self.push('200 active data connection established')

def cmd_pasv(self, arg):
with socket.socket() as sock:
sock.bind((self.socket.getsockname()[0], 0))
sock.listen()
with socket.create_server((self.socket.getsockname()[0], 0)) as sock:
sock.settimeout(TIMEOUT)
ip, port = sock.getsockname()[:2]
ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256
Expand All @@ -150,9 +148,8 @@ def cmd_eprt(self, arg):
self.push('200 active data connection established')

def cmd_epsv(self, arg):
with socket.socket(socket.AF_INET6) as sock:
sock.bind((self.socket.getsockname()[0], 0))
sock.listen()
with socket.create_server((self.socket.getsockname()[0], 0),
family=socket.AF_INET6) as sock:
sock.settimeout(TIMEOUT)
port = sock.getsockname()[1]
self.push('229 entering extended passive mode (|||%d|)' %port)
Expand Down
5 changes: 1 addition & 4 deletions Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,11 +1116,8 @@ def test_read1_bound_content_length(self):

def test_response_fileno(self):
# Make sure fd returned by fileno is valid.
serv = socket.socket(
socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
serv = socket.create_server((HOST, 0))
self.addCleanup(serv.close)
serv.bind((HOST, 0))
serv.listen()

result = None
def run_server():
Expand Down
4 changes: 1 addition & 3 deletions Lib/test/test_kqueue.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@ def test_create_event(self):


def test_queue_event(self):
serverSocket = socket.socket()
serverSocket.bind(('127.0.0.1', 0))
serverSocket.listen()
serverSocket = socket.create_server(('127.0.0.1', 0))
client = socket.socket()
client.setblocking(False)
try:
Expand Down
Loading