diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 727b964f..50809e19 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -561,7 +561,11 @@ class JupyterHub(Application): def _url_part_changed(self, change): """propagate deprecated ip/port/base_url config to the bind_url""" urlinfo = urlparse(self.bind_url) - urlinfo = urlinfo._replace(netloc='%s:%i' % (self.ip, self.port)) + if ':' in self.ip: + fmt = '[%s]:%i' + else: + fmt = '%s:%i' + urlinfo = urlinfo._replace(netloc=fmt % (self.ip, self.port)) urlinfo = urlinfo._replace(path=self.base_url) bind_url = urlunparse(urlinfo) if bind_url != self.bind_url: @@ -727,10 +731,10 @@ class JupyterHub(Application): help="""The ip or hostname for proxies and spawners to use for connecting to the Hub. - Use when the bind address (`hub_ip`) is 0.0.0.0 or otherwise different + Use when the bind address (`hub_ip`) is 0.0.0.0, :: or otherwise different from the connect address. - Default: when `hub_ip` is 0.0.0.0, use `socket.gethostname()`, otherwise use `hub_ip`. + Default: when `hub_ip` is 0.0.0.0 or ::, use `socket.gethostname()`, otherwise use `hub_ip`. Note: Some spawners or proxy implementations might not support hostnames. Check your spawner or proxy documentation to see if they have extra requirements. diff --git a/jupyterhub/objects.py b/jupyterhub/objects.py index de2ac7a6..e5aa9c1c 100644 --- a/jupyterhub/objects.py +++ b/jupyterhub/objects.py @@ -53,7 +53,7 @@ class Server(HasTraits): Never used in APIs, only logging, since it can be non-connectable value, such as '', meaning all interfaces. """ - if self.ip in {'', '0.0.0.0'}: + if self.ip in {'', '0.0.0.0', '::'}: return self.url.replace(self._connect_ip, self.ip or '*', 1) return self.url @@ -87,13 +87,13 @@ class Server(HasTraits): """The address to use when connecting to this server When `ip` is set to a real ip address, the same value is used. - When `ip` refers to 'all interfaces' (e.g. '0.0.0.0'), + When `ip` refers to 'all interfaces' (e.g. '0.0.0.0' or '::'), clients connect via hostname by default. Setting `connect_ip` explicitly overrides any default behavior. """ if self.connect_ip: return self.connect_ip - elif self.ip in {'', '0.0.0.0'}: + elif self.ip in {'', '0.0.0.0', '::'}: # if listening on all interfaces, default to hostname for connect return socket.gethostname() else: @@ -149,7 +149,12 @@ class Server(HasTraits): if self.connect_url: parsed = urlparse(self.connect_url) return "{proto}://{host}".format(proto=parsed.scheme, host=parsed.netloc) - return "{proto}://{ip}:{port}".format( + + if ':' in self._connect_ip: + fmt = "{proto}://[{ip}]:{port}" + else: + fmt = "{proto}://{ip}:{port}" + return fmt.format( proto=self.proto, ip=self._connect_ip, port=self._connect_port ) diff --git a/jupyterhub/services/service.py b/jupyterhub/services/service.py index fbf1f331..dcd28946 100644 --- a/jupyterhub/services/service.py +++ b/jupyterhub/services/service.py @@ -342,7 +342,7 @@ class Service(LoggingConfigurable): env['JUPYTERHUB_SERVICE_PREFIX'] = self.server.base_url hub = self.hub - if self.hub.ip in ('0.0.0.0', ''): + if self.hub.ip in ('', '0.0.0.0', '::'): # if the Hub is listening on all interfaces, # tell services to connect via localhost # since they are always local subprocesses diff --git a/jupyterhub/user.py b/jupyterhub/user.py index 71ceac4e..4f049817 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -566,7 +566,12 @@ class User: else: # >= 0.7 returns (ip, port) proto = 'https' if self.settings['internal_ssl'] else 'http' - url = '%s://%s:%i' % ((proto,) + url) + + # check if spawner returned an IPv6 address + if ':' in url[0]: + url = '%s://[%s]:%i' % ((proto,) + url) + else: + url = '%s://%s:%i' % ((proto,) + url) urlinfo = urlparse(url) server.proto = urlinfo.scheme server.ip = urlinfo.hostname diff --git a/jupyterhub/utils.py b/jupyterhub/utils.py index 851b4d53..82bcc0c8 100644 --- a/jupyterhub/utils.py +++ b/jupyterhub/utils.py @@ -66,7 +66,7 @@ def can_connect(ip, port): Return True if we can connect, False otherwise. """ - if ip in {'', '0.0.0.0'}: + if ip in {'', '0.0.0.0', '::'}: ip = '127.0.0.1' try: socket.create_connection((ip, port)).close() @@ -179,7 +179,7 @@ async def exponential_backoff( async def wait_for_server(ip, port, timeout=10): """Wait for any server to show up at ip:port.""" - if ip in {'', '0.0.0.0'}: + if ip in {'', '0.0.0.0', '::'}: ip = '127.0.0.1' await exponential_backoff( lambda: can_connect(ip, port),