From 9c3227b689e44270213b8a12723af35d4eb4c71e Mon Sep 17 00:00:00 2001 From: Lianmin Zheng Date: Fri, 26 Jun 2026 16:05:36 -0700 Subject: [PATCH] Fix IPv6 wildcard bootstrap address resolution in disagg (#29459) --- python/sglang/srt/disaggregation/common/conn.py | 11 ++++++----- .../disaggregation/test_register_to_bootstrap.py | 16 ++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/python/sglang/srt/disaggregation/common/conn.py b/python/sglang/srt/disaggregation/common/conn.py index 26ba9c9a0..2eb2dbc18 100644 --- a/python/sglang/srt/disaggregation/common/conn.py +++ b/python/sglang/srt/disaggregation/common/conn.py @@ -394,11 +394,12 @@ class CommonKVManager(BaseKVManager): else: # Single-node case: bootstrap server's host is the same as http server's host host = self.bootstrap_host - # If the server was bound to the wildcard address (0.0.0.0 / ::), use the - # actual local IP instead — a PUT to http://0.0.0.0:/route is rejected - # with 403 by aiohttp ≥3.9 because 0.0.0.0 is not a valid HTTP Host value. - if host in ("0.0.0.0", "::"): - host = self.local_ip + # A wildcard bind address (0.0.0.0 / ::) is not a valid HTTP Host + # and can't be connected to; rewrite it to the same-family loopback, + # which the wildcard listener also binds. (self.local_ip is wrong + # here — it can resolve to a different family than the listener, + # e.g. IPv6 while the server is bound to 0.0.0.0.) + host = {"0.0.0.0": "127.0.0.1", "::": "::1"}.get(host, host) bootstrap_na = NetworkAddress(host, self.bootstrap_port) url = f"{bootstrap_na.to_url()}/route" diff --git a/test/registered/unit/disaggregation/test_register_to_bootstrap.py b/test/registered/unit/disaggregation/test_register_to_bootstrap.py index 362cefb38..3fbad2380 100644 --- a/test/registered/unit/disaggregation/test_register_to_bootstrap.py +++ b/test/registered/unit/disaggregation/test_register_to_bootstrap.py @@ -185,8 +185,8 @@ class TestRegisterToBootstrap(CustomTestCase): @patch("sglang.srt.disaggregation.common.conn.time") @patch("sglang.srt.disaggregation.common.conn.requests.put") - def test_wildcard_host_0000_uses_local_ip(self, mock_put, mock_time): - """When --host 0.0.0.0 is used, the PUT must target local_ip not 0.0.0.0. + def test_wildcard_host_0000_uses_ipv4_loopback(self, mock_put, mock_time): + """When --host 0.0.0.0 is used, the PUT must target IPv4 loopback. Scenario: cross-node P/D disagg where each role runs on a single node (tp=1). Each machine runs its own SGLang instance with --host 0.0.0.0 @@ -196,7 +196,7 @@ class TestRegisterToBootstrap(CustomTestCase): aiohttp >=3.9 rejects that with HTTP 403 because 0.0.0.0 is not a valid Host header value. - Fix: substitute self.local_ip when bootstrap_host is a wildcard. + Fix: substitute same-family loopback when bootstrap_host is a wildcard. """ mock_time.monotonic.return_value = 0.0 success_resp = MagicMock() @@ -210,12 +210,12 @@ class TestRegisterToBootstrap(CustomTestCase): url_used = mock_put.call_args[0][0] self.assertNotIn("0.0.0.0", url_used) - self.assertIn("192.168.1.10", url_used) + self.assertIn("127.0.0.1", url_used) @patch("sglang.srt.disaggregation.common.conn.time") @patch("sglang.srt.disaggregation.common.conn.requests.put") - def test_wildcard_host_ipv6_uses_local_ip(self, mock_put, mock_time): - """Same fix for the IPv6 wildcard \"::\": must use local_ip instead.""" + def test_wildcard_host_ipv6_uses_ipv6_loopback(self, mock_put, mock_time): + """Same fix for the IPv6 wildcard \"::\": must use IPv6 loopback.""" mock_time.monotonic.return_value = 0.0 success_resp = MagicMock() success_resp.status_code = 200 @@ -227,9 +227,9 @@ class TestRegisterToBootstrap(CustomTestCase): mgr.register_to_bootstrap() url_used = mock_put.call_args[0][0] - # "::" bracketed as "[::]:port" should not appear; local_ip should + # "::" bracketed as "[::]:port" should not appear; loopback should. self.assertNotIn("[::]", url_used) - self.assertIn("fd00", url_used) + self.assertIn("[::1]", url_used) def _make_manager(self, dist_init_addr=None): """Create a lightweight mock manager that has the attributes needed