diff -r 6c6c873c0059 Lib/asyncio/base_events.py --- a/Lib/asyncio/base_events.py Tue Mar 10 19:40:37 2015 -0700 +++ b/Lib/asyncio/base_events.py Wed Mar 11 14:18:10 2015 +0100 @@ -786,14 +786,23 @@ class BaseEventLoop(events.AbstractEvent if reuse_address is None: reuse_address = os.name == 'posix' and sys.platform != 'cygwin' sockets = [] - if host == '': - host = None - infos = yield from self.getaddrinfo( - host, port, family=family, - type=socket.SOCK_STREAM, proto=0, flags=flags) - if not infos: - raise OSError('getaddrinfo() returned empty list') + if (isinstance(host, str) + or not isinstance(host, collections.Iterable)): + host = [host] + if None in host or '' in host: + # if we bind to all addresses, do not attempt to use specific + # ones (this would cause an "Address already in use" error) + host = [None] + + infos = set() + for h in host: + infos1 = yield from self.getaddrinfo( + h, port, family=family, + type=socket.SOCK_STREAM, proto=0, flags=flags) + if not infos1: + raise OSError('getaddrinfo() returned empty list') + infos.update(infos1) completed = False try: diff -r 6c6c873c0059 Lib/asyncio/events.py --- a/Lib/asyncio/events.py Tue Mar 10 19:40:37 2015 -0700 +++ b/Lib/asyncio/events.py Wed Mar 11 14:18:10 2015 +0100 @@ -296,7 +296,8 @@ class AbstractEventLoop: If host is an empty string or None all interfaces are assumed and a list of multiple sockets will be returned (most likely - one for IPv4 and another one for IPv6). + one for IPv4 and another one for IPv6). host can also be an + iterable (e.g. list) of hosts to bind to. family can be set to either AF_INET or AF_INET6 to force the socket to use IPv4 or IPv6. If not set it will be determined diff -r 6c6c873c0059 Lib/test/test_asyncio/test_events.py --- a/Lib/test/test_asyncio/test_events.py Tue Mar 10 19:40:37 2015 -0700 +++ b/Lib/test/test_asyncio/test_events.py Wed Mar 11 14:18:10 2015 +0100 @@ -53,6 +53,13 @@ def osx_tiger(): return version < (10, 5) +def get_local_ip_address(): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("42.42.42.42", 80)) + ipaddr = s.getsockname()[0] + s.close() + return ipaddr + ONLYCERT = data_file('ssl_cert.pem') ONLYKEY = data_file('ssl_key.pem') SIGNED_CERTFILE = data_file('keycert3.pem') @@ -67,13 +74,14 @@ class MyBaseProto(asyncio.Protocol): self.transport = None self.state = 'INITIAL' self.nbytes = 0 + self.loop = loop if loop is not None: self.connected = asyncio.Future(loop=loop) self.done = asyncio.Future(loop=loop) def connection_made(self, transport): self.transport = transport - assert self.state == 'INITIAL', self.state + assert self.state in ('INITIAL', 'CLOSED'), self.state self.state = 'CONNECTED' if self.connected: self.connected.set_result(None) @@ -91,6 +99,9 @@ class MyBaseProto(asyncio.Protocol): self.state = 'CLOSED' if self.done: self.done.set_result(None) + self.done = asyncio.Future(loop=self.loop) + if self.connected: + self.connected = asyncio.Future(loop=self.loop) class MyProto(MyBaseProto): @@ -696,6 +707,48 @@ class EventLoopTestsMixin: self.assertEqual(cm.exception.errno, errno.EADDRINUSE) self.assertIn(str(httpd.address), cm.exception.strerror) + @unittest.skipIf(get_local_ip_address().startswith('127'), + 'No non-loopback IP address') + def test_create_server_multiple_hosts(self): + ips = ['127.0.0.1', get_local_ip_address()] + proto = MyProto(self.loop) + f = self.loop.create_server(lambda: proto, ips, 0) + server = self.loop.run_until_complete(f) + self.assertEqual(len(server.sockets), len(ips)) + payload = b'xxx' + nbytes_sent = 0 + for sock in server.sockets: + host, port = sock.getsockname() + self.assertTrue(host in ips) + + client = socket.socket() + client.connect((host, port)) + client.sendall(payload) + + self.loop.run_until_complete(proto.connected) + self.assertEqual('CONNECTED', proto.state) + + test_utils.run_until(self.loop, lambda: proto.nbytes > nbytes_sent) + nbytes_sent += len(payload) + self.assertEqual(nbytes_sent, proto.nbytes) + + # extra info is available + self.assertIsNotNone(proto.transport.get_extra_info('sockname')) + self.assertEqual(host, + proto.transport.get_extra_info('peername')[0]) + + # close connection + proto.transport.close() + self.loop.run_until_complete(proto.done) + + self.assertEqual('CLOSED', proto.state) + + # the client socket must be closed after to avoid ECONNRESET upon + # recv()/send() on the serving socket + client.close() + # close server + server.close() + def test_create_server(self): proto = MyProto(self.loop) f = self.loop.create_server(lambda: proto, '0.0.0.0', 0)