mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-15 14:03:02 +00:00
Merge pull request #4988 from manics/ipv6
More IPv6: Use bare IPv6 for configuration, use `[ipv6]` when displaying IPv6 outputs
This commit is contained in:
@@ -1922,7 +1922,11 @@ class JupyterHub(Application):
|
||||
self.internal_ssl_components_trust
|
||||
)
|
||||
|
||||
default_alt_names = ["IP:127.0.0.1", "DNS:localhost"]
|
||||
default_alt_names = [
|
||||
"IP:127.0.0.1",
|
||||
"IP:0:0:0:0:0:0:0:1",
|
||||
"DNS:localhost",
|
||||
]
|
||||
if self.subdomain_host:
|
||||
default_alt_names.append(
|
||||
f"DNS:{urlparse(self.subdomain_host).hostname}"
|
||||
|
@@ -12,6 +12,7 @@ from . import orm
|
||||
from .traitlets import URLPrefix
|
||||
from .utils import (
|
||||
can_connect,
|
||||
fmt_ip_url,
|
||||
make_ssl_context,
|
||||
random_port,
|
||||
url_path_join,
|
||||
@@ -50,7 +51,7 @@ class Server(HasTraits):
|
||||
since it can be non-connectable value, such as '', meaning all interfaces.
|
||||
"""
|
||||
if self.ip in {'', '0.0.0.0', '::'}:
|
||||
return self.url.replace(self._connect_ip, self.ip or '*', 1)
|
||||
return self.url.replace(self._connect_ip, fmt_ip_url(self.ip) or '*', 1)
|
||||
return self.url
|
||||
|
||||
@observe('bind_url')
|
||||
@@ -216,4 +217,4 @@ class Hub(Server):
|
||||
return url_path_join(self.url, 'api')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__} {self.ip}:{self.port}>"
|
||||
return f"<{self.__class__.__name__} {fmt_ip_url(self.ip)}:{self.port}>"
|
||||
|
@@ -46,7 +46,7 @@ from sqlalchemy.pool import StaticPool
|
||||
from sqlalchemy.types import LargeBinary, Text, TypeDecorator
|
||||
from tornado.log import app_log
|
||||
|
||||
from .utils import compare_token, hash_token, new_token, random_port, utcnow
|
||||
from .utils import compare_token, fmt_ip_url, hash_token, new_token, random_port, utcnow
|
||||
|
||||
# top-level variable for easier mocking in tests
|
||||
utcnow = partial(utcnow, with_tz=False)
|
||||
@@ -157,7 +157,7 @@ class Server(Base):
|
||||
spawner = relationship("Spawner", back_populates="server", uselist=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Server({self.ip}:{self.port})>"
|
||||
return f"<Server({fmt_ip_url(self.ip)}:{self.port})>"
|
||||
|
||||
|
||||
# lots of things have roles
|
||||
|
@@ -436,7 +436,10 @@ class Service(LoggingConfigurable):
|
||||
# since they are always local subprocesses
|
||||
hub = copy.deepcopy(self.hub)
|
||||
hub.connect_url = ''
|
||||
hub.connect_ip = '127.0.0.1'
|
||||
if self.hub.ip and ":" in self.hub.ip:
|
||||
hub.connect_ip = "::1"
|
||||
else:
|
||||
hub.connect_ip = "127.0.0.1"
|
||||
|
||||
self.spawner = _ServiceSpawner(
|
||||
cmd=self.command,
|
||||
|
@@ -48,6 +48,7 @@ from .traitlets import ByteSpecification, Callable, Command
|
||||
from .utils import (
|
||||
AnyTimeoutError,
|
||||
exponential_backoff,
|
||||
fmt_ip_url,
|
||||
maybe_future,
|
||||
random_port,
|
||||
recursive_update,
|
||||
@@ -469,20 +470,40 @@ class Spawner(LoggingConfigurable):
|
||||
The IP address (or hostname) the single-user server should listen on.
|
||||
|
||||
Usually either '127.0.0.1' (default) or '0.0.0.0'.
|
||||
On IPv6 only networks use '::1' or '::'.
|
||||
|
||||
If the spawned singleuser server is running JupyterHub 5.3.0 later
|
||||
You can set this to the empty string '' to indicate both IPv4 and IPv6.
|
||||
|
||||
The JupyterHub proxy implementation should be able to send packets to this interface.
|
||||
|
||||
Subclasses which launch remotely or in containers
|
||||
should override the default to '0.0.0.0'.
|
||||
|
||||
.. versionchanged:: 5.3
|
||||
An empty string '' means all interfaces (IPv4 and IPv6). Prior to this
|
||||
the behaviour of '' was not defined.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Default changed to '127.0.0.1', from ''.
|
||||
In most cases, this does not result in a change in behavior,
|
||||
as '' was interpreted as 'unspecified',
|
||||
which used the subprocesses' own default, itself usually '127.0.0.1'.
|
||||
Default changed to '127.0.0.1', from unspecified.
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
@validate("ip")
|
||||
def _strip_ipv6(self, proposal):
|
||||
"""
|
||||
Prior to 5.3.0 it was necessary to use [] when specifying an
|
||||
[ipv6] due to the IP being concatenated with the port when forming URLs
|
||||
without [].
|
||||
|
||||
To avoid breaking existing workarounds strip [].
|
||||
"""
|
||||
v = proposal["value"]
|
||||
if v.startswith("[") and v.endswith("]"):
|
||||
self.log.warning("Removing '[' ']' from Spawner.ip %s", self.ip)
|
||||
v = v[1:-1]
|
||||
return v
|
||||
|
||||
port = Integer(
|
||||
0,
|
||||
help="""
|
||||
@@ -1093,7 +1114,7 @@ class Spawner(LoggingConfigurable):
|
||||
base_url = '/'
|
||||
|
||||
proto = 'https' if self.internal_ssl else 'http'
|
||||
bind_url = f"{proto}://{self.ip}:{self.port}{base_url}"
|
||||
bind_url = f"{proto}://{fmt_ip_url(self.ip)}:{self.port}{base_url}"
|
||||
env["JUPYTERHUB_SERVICE_URL"] = bind_url
|
||||
|
||||
# the public URLs of this server and the Hub
|
||||
|
@@ -108,8 +108,10 @@ 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'
|
||||
elif ip == "::":
|
||||
ip = "::1"
|
||||
try:
|
||||
socket.create_connection((ip, port)).close()
|
||||
except OSError as e:
|
||||
@@ -267,17 +269,20 @@ 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'
|
||||
app_log.debug("Waiting %ss for server at %s:%s", timeout, ip, port)
|
||||
elif ip == "::":
|
||||
ip = "::1"
|
||||
display_ip = fmt_ip_url(ip)
|
||||
app_log.debug("Waiting %ss for server at %s:%s", timeout, display_ip, port)
|
||||
tic = time.perf_counter()
|
||||
await exponential_backoff(
|
||||
lambda: can_connect(ip, port),
|
||||
f"Server at {ip}:{port} didn't respond in {timeout} seconds",
|
||||
f"Server at {display_ip}:{port} didn't respond in {timeout} seconds",
|
||||
timeout=timeout,
|
||||
)
|
||||
toc = time.perf_counter()
|
||||
app_log.debug("Server at %s:%s responded in %.2fs", ip, port, toc - tic)
|
||||
app_log.debug("Server at %s:%s responded in %.2fs", display_ip, port, toc - tic)
|
||||
|
||||
|
||||
async def wait_for_http_server(url, timeout=10, ssl_context=None):
|
||||
@@ -962,3 +967,13 @@ def recursive_update(target, new):
|
||||
|
||||
else:
|
||||
target[k] = v
|
||||
|
||||
|
||||
def fmt_ip_url(ip):
|
||||
"""
|
||||
Format an IP for use in URLs. IPv6 is wrapped with [], everything else is
|
||||
unchanged
|
||||
"""
|
||||
if ":" in ip:
|
||||
return f"[{ip}]"
|
||||
return ip
|
||||
|
Reference in New Issue
Block a user