Commit f289084c authored by Jason R. Coombs's avatar Jason R. Coombs Committed by GitHub

bpo-24209: In http.server script, rely on getaddrinfo to bind to preferred...

bpo-24209: In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter. (#11767)

In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter.

As a result, now IPv6 is used as the default (including IPv4 on dual-stack systems). Enhanced tests.
parent 2848d9d2
...@@ -1224,24 +1224,34 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler): ...@@ -1224,24 +1224,34 @@ class CGIHTTPRequestHandler(SimpleHTTPRequestHandler):
self.log_message("CGI script exited OK") self.log_message("CGI script exited OK")
def _get_best_family(*address):
infos = socket.getaddrinfo(
*address,
type=socket.SOCK_STREAM,
flags=socket.AI_PASSIVE,
)
family, type, proto, canonname, sockaddr = next(iter(infos))
return family, sockaddr
def test(HandlerClass=BaseHTTPRequestHandler, def test(HandlerClass=BaseHTTPRequestHandler,
ServerClass=ThreadingHTTPServer, ServerClass=ThreadingHTTPServer,
protocol="HTTP/1.0", port=8000, bind=""): protocol="HTTP/1.0", port=8000, bind=None):
"""Test the HTTP request handler class. """Test the HTTP request handler class.
This runs an HTTP server on port 8000 (or the port argument). This runs an HTTP server on port 8000 (or the port argument).
""" """
server_address = (bind, port) ServerClass.address_family, addr = _get_best_family(bind, port)
if ':' in bind:
ServerClass.address_family = socket.AF_INET6
HandlerClass.protocol_version = protocol HandlerClass.protocol_version = protocol
with ServerClass(server_address, HandlerClass) as httpd: with ServerClass(addr, HandlerClass) as httpd:
sa = httpd.socket.getsockname() host, port = httpd.socket.getsockname()[:2]
serve_message = "Serving HTTP on {host} port {port} (http://{host}:{port}/) ..." url_host = f'[{host}]' if ':' in host else host
print(serve_message.format(host=sa[0], port=sa[1])) print(
f"Serving HTTP on {host} port {port} "
f"(http://{url_host}:{port}/) ..."
)
try: try:
httpd.serve_forever() httpd.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
...@@ -1254,7 +1264,7 @@ if __name__ == '__main__': ...@@ -1254,7 +1264,7 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--cgi', action='store_true', parser.add_argument('--cgi', action='store_true',
help='Run as CGI Server') help='Run as CGI Server')
parser.add_argument('--bind', '-b', default='', metavar='ADDRESS', parser.add_argument('--bind', '-b', metavar='ADDRESS',
help='Specify alternate bind address ' help='Specify alternate bind address '
'[default: all interfaces]') '[default: all interfaces]')
parser.add_argument('--directory', '-d', default=os.getcwd(), parser.add_argument('--directory', '-d', default=os.getcwd(),
......
...@@ -1118,21 +1118,63 @@ class MiscTestCase(unittest.TestCase): ...@@ -1118,21 +1118,63 @@ class MiscTestCase(unittest.TestCase):
class ScriptTestCase(unittest.TestCase): class ScriptTestCase(unittest.TestCase):
def mock_server_class(self):
return mock.MagicMock(
return_value=mock.MagicMock(
__enter__=mock.MagicMock(
return_value=mock.MagicMock(
socket=mock.MagicMock(
getsockname=lambda: ('', 0),
),
),
),
),
)
@mock.patch('builtins.print')
def test_server_test_unspec(self, _):
mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind=None)
self.assertIn(
mock_server.address_family,
(socket.AF_INET6, socket.AF_INET),
)
@mock.patch('builtins.print')
def test_server_test_localhost(self, _):
mock_server = self.mock_server_class()
server.test(ServerClass=mock_server, bind="localhost")
self.assertIn(
mock_server.address_family,
(socket.AF_INET6, socket.AF_INET),
)
ipv6_addrs = (
"::",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"::1",
)
ipv4_addrs = (
"0.0.0.0",
"8.8.8.8",
"127.0.0.1",
)
@mock.patch('builtins.print') @mock.patch('builtins.print')
def test_server_test_ipv6(self, _): def test_server_test_ipv6(self, _):
mock_server = mock.MagicMock() for bind in self.ipv6_addrs:
server.test(ServerClass=mock_server, bind="::") mock_server = self.mock_server_class()
self.assertEqual(mock_server.address_family, socket.AF_INET6) server.test(ServerClass=mock_server, bind=bind)
self.assertEqual(mock_server.address_family, socket.AF_INET6)
mock_server.reset_mock()
server.test(ServerClass=mock_server, @mock.patch('builtins.print')
bind="2001:0db8:85a3:0000:0000:8a2e:0370:7334") def test_server_test_ipv4(self, _):
self.assertEqual(mock_server.address_family, socket.AF_INET6) for bind in self.ipv4_addrs:
mock_server = self.mock_server_class()
mock_server.reset_mock() server.test(ServerClass=mock_server, bind=bind)
server.test(ServerClass=mock_server, self.assertEqual(mock_server.address_family, socket.AF_INET)
bind="::1")
self.assertEqual(mock_server.address_family, socket.AF_INET6)
def test_main(verbose=None): def test_main(verbose=None):
......
In http.server script, rely on getaddrinfo to bind to preferred address based on the bind parameter. Now default bind or binding to a name may bind to IPv6 or dual-stack, depending on the environment.
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment