Add support for IPv6 hostnames

This commit is contained in:
Unrud 2018-09-06 09:12:53 +02:00
parent 2275ba4f93
commit dec2ad8bea
2 changed files with 57 additions and 6 deletions

View File

@ -47,6 +47,30 @@ if os.name == "posix":
else: else:
ParallelizationMixIn = socketserver.ThreadingMixIn ParallelizationMixIn = socketserver.ThreadingMixIn
HAS_IPV6 = socket.has_ipv6
if hasattr(socket, "EAI_NONAME"):
EAI_NONAME = socket.EAI_NONAME
else:
HAS_IPV6 = False
if hasattr(socket, "EAI_ADDRFAMILY"):
EAI_ADDRFAMILY = socket.EAI_ADDRFAMILY
elif os.name == "nt":
EAI_ADDRFAMILY = None
else:
HAS_IPV6 = False
if hasattr(socket, "IPPROTO_IPV6"):
IPPROTO_IPV6 = socket.IPPROTO_IPV6
elif os.name == "nt":
IPPROTO_IPV6 = 41
else:
HAS_IPV6 = False
if hasattr(socket, "IPV6_V6ONLY"):
IPV6_V6ONLY = socket.IPV6_V6ONLY
elif os.name == "nt":
IPV6_V6ONLY = 27
else:
HAS_IPV6 = False
class ParallelHTTPServer(ParallelizationMixIn, class ParallelHTTPServer(ParallelizationMixIn,
wsgiref.simple_server.WSGIServer): wsgiref.simple_server.WSGIServer):
@ -74,14 +98,18 @@ class ParallelHTTPServer(ParallelizationMixIn,
self.server_port = port self.server_port = port
self.setup_environ() self.setup_environ()
return return
ipv6 = ":" in self.server_address[0] try:
if ipv6 and self.address_family == socket.AF_INET: super().server_bind()
except socket.gaierror as e:
if (not HAS_IPV6 or self.address_family != socket.AF_INET or
e.errno not in (EAI_NONAME, EAI_ADDRFAMILY)):
raise
# Try again with IPv6
self.address_family = socket.AF_INET6 self.address_family = socket.AF_INET6
self.socket = socket.socket(self.address_family, self.socket_type) self.socket = socket.socket(self.address_family, self.socket_type)
if ipv6:
# Only allow IPv6 connections to the IPv6 socket # Only allow IPv6 connections to the IPv6 socket
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) self.socket.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
super().server_bind() super().server_bind()
def get_request(self): def get_request(self):
# Set timeout for client # Set timeout for client

View File

@ -19,12 +19,14 @@ Test the internal server.
""" """
import os
import shutil import shutil
import socket import socket
import ssl import ssl
import tempfile import tempfile
import threading import threading
import time import time
import warnings
from urllib import request from urllib import request
from urllib.error import HTTPError, URLError from urllib.error import HTTPError, URLError
@ -32,6 +34,8 @@ from radicale import config, server
from .helpers import get_file_path from .helpers import get_file_path
import pytest # isort:skip
class DisabledRedirectHandler(request.HTTPRedirectHandler): class DisabledRedirectHandler(request.HTTPRedirectHandler):
def http_error_302(self, req, fp, code, msg, headers): def http_error_302(self, req, fp, code, msg, headers):
@ -66,7 +70,10 @@ class TestBaseServerRequests:
def teardown(self): def teardown(self):
self.shutdown_socket.sendall(b" ") self.shutdown_socket.sendall(b" ")
self.thread.join() try:
self.thread.join()
except RuntimeError: # Thread never started
pass
shutil.rmtree(self.colpath) shutil.rmtree(self.colpath)
def request(self, method, path, data=None, **headers): def request(self, method, path, data=None, **headers):
@ -100,3 +107,19 @@ class TestBaseServerRequests:
self.thread.start() self.thread.start()
status, _, _ = self.request("GET", "/") status, _, _ = self.request("GET", "/")
assert status == 302 assert status == 302
def test_ipv6(self):
if not server.HAS_IPV6:
pytest.skip("IPv6 not support")
if os.name == "nt" and os.environ.get("WINE_PYTHON"):
warnings.warn("WORKAROUND: incomplete errno conversion in WINE")
server.EAI_ADDRFAMILY = -9
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as sock:
sock.setsockopt(server.IPPROTO_IPV6, server.IPV6_V6ONLY, 1)
# Find available port
sock.bind(("localhost", 0))
self.sockname = sock.getsockname()[:2]
self.configuration["server"]["hosts"] = "[%s]:%d" % self.sockname
self.thread.start()
status, _, _ = self.request("GET", "/")
assert status == 302