Unverified Commit 9b8b35a8 authored by ishandhanani's avatar ishandhanani Committed by GitHub
Browse files

fix: ipv6 support for sglang disaggregation (#5521)


Co-authored-by: default avatarClaude Opus 4.5 <noreply@anthropic.com>
parent 2e8c4447
...@@ -88,11 +88,55 @@ def _get_bootstrap_info_for_config( ...@@ -88,11 +88,55 @@ def _get_bootstrap_info_for_config(
return None, None return None, None
if inner_tm.server_args.dist_init_addr: if inner_tm.server_args.dist_init_addr:
bootstrap_host = socket.gethostbyname( # IPv6-ready host extraction and resolution:
inner_tm.server_args.dist_init_addr.split(":")[0] # 1) Extract raw host from "host:port" or "[IPv6]:port"/"[IPv6]".
) # 2) Resolve via AF_UNSPEC to accept A/AAAA and literals.
# 3) Bracket-wrap IPv6 for safe "{host}:{port}" URL formatting.
addr = inner_tm.server_args.dist_init_addr.strip()
if addr.startswith("["):
end = addr.find("]")
host_core = addr[1:end] if end != -1 else addr.strip("[]")
else:
# Only treat single ':' with numeric suffix as host:port; otherwise it's an IPv6/FQDN host.
if addr.count(":") == 1:
host_candidate, maybe_port = addr.rsplit(":", 1)
host_core = host_candidate if maybe_port.isdigit() else addr
else:
host_core = addr
try:
infos = socket.getaddrinfo(
host_core,
None,
family=socket.AF_UNSPEC,
type=socket.SOCK_STREAM,
)
resolved = infos[0][4][0] # let OS policy pick v4/v6
bootstrap_host = resolved
addr_family = infos[0][0]
logging.info(
f"Resolved bootstrap host '{host_core}' -> '{resolved}' "
f"({'IPv6' if addr_family == socket.AF_INET6 else 'IPv4'})"
)
except socket.gaierror as e:
# Fallback: keep literal/FQDN as-is (still wrap IPv6 below)
bootstrap_host = host_core
logging.warning(
f"Failed to resolve bootstrap host '{host_core}': {e}, using as-is"
)
else: else:
# get_local_ip_auto() tries IPv4 first, then IPv6. For explicit control,
# set SGLANG_HOST_IP env var (use bracketed format for IPv6: [addr])
bootstrap_host = get_local_ip_auto() bootstrap_host = get_local_ip_auto()
is_ipv6 = ":" in bootstrap_host
logging.info(
f"Using auto-detected local IP: {bootstrap_host} "
f"({'IPv6' if is_ipv6 else 'IPv4'})"
)
# Wrap IPv6 literal with brackets so f"{host}:{port}" stays valid.
if ":" in bootstrap_host and not bootstrap_host.startswith("["):
bootstrap_host = f"[{bootstrap_host}]"
logging.info(f"Wrapped IPv6 address with brackets: {bootstrap_host}")
return bootstrap_host, bootstrap_port return bootstrap_host, bootstrap_port
except Exception as e: except Exception as e:
......
...@@ -12,23 +12,31 @@ use std::net::IpAddr; ...@@ -12,23 +12,31 @@ use std::net::IpAddr;
/// This function attempts to resolve the local IP address using the provided resolver. /// This function attempts to resolve the local IP address using the provided resolver.
/// If resolution fails, it falls back to 127.0.0.1 (localhost). /// If resolution fails, it falls back to 127.0.0.1 (localhost).
/// ///
/// IPv6 addresses are wrapped with brackets for safe URL construction (e.g., `[::1]`).
///
/// # Arguments /// # Arguments
/// * `resolver` - An implementation of IpResolver trait for getting local IP addresses /// * `resolver` - An implementation of IpResolver trait for getting local IP addresses
/// ///
/// # Returns /// # Returns
/// A string representation of the resolved IP address /// A string representation of the resolved IP address (IPv6 addresses are bracketed)
pub fn get_http_rpc_host_with_resolver<R: IpResolver>(resolver: R) -> String { pub fn get_http_rpc_host_with_resolver<R: IpResolver>(resolver: R) -> String {
let resolved_ip = resolver.local_ip().or_else(|err| match err { let resolved_ip = resolver.local_ip().or_else(|err| match err {
Error::LocalIpAddressNotFound => resolver.local_ipv6(), Error::LocalIpAddressNotFound => resolver.local_ipv6(),
_ => Err(err), _ => Err(err),
}); });
match resolved_ip { let addr = match resolved_ip {
Ok(addr) => addr, Ok(addr) => addr,
Err(Error::LocalIpAddressNotFound) => IpAddr::from([127, 0, 0, 1]), Err(Error::LocalIpAddressNotFound) => IpAddr::from([127, 0, 0, 1]),
Err(_) => IpAddr::from([127, 0, 0, 1]), // Fallback for any other error Err(_) => IpAddr::from([127, 0, 0, 1]), // Fallback for any other error
};
// Wrap IPv6 addresses with brackets for safe URL construction
// e.g., "2001:db8::1" becomes "[2001:db8::1]" so that "{host}:{port}" is valid
match addr {
IpAddr::V6(_) => format!("[{}]", addr),
IpAddr::V4(_) => addr.to_string(),
} }
.to_string()
} }
/// Get the local IP address for HTTP RPC host binding using the default resolver /// Get the local IP address for HTTP RPC host binding using the default resolver
...@@ -112,7 +120,8 @@ mod tests { ...@@ -112,7 +120,8 @@ mod tests {
}; };
let result = get_http_rpc_host_with_resolver(resolver); let result = get_http_rpc_host_with_resolver(resolver);
assert_eq!(result, "2001:db8::1"); // IPv6 addresses should be bracketed for safe URL construction
assert_eq!(result, "[2001:db8::1]");
} }
#[test] #[test]
...@@ -151,7 +160,35 @@ mod tests { ...@@ -151,7 +160,35 @@ mod tests {
// Should return some IP address (either resolved or fallback) // Should return some IP address (either resolved or fallback)
assert!(!result.is_empty()); assert!(!result.is_empty());
// Should be a valid IP address // Should be a valid IP address (strip brackets for IPv6 before parsing)
let _: IpAddr = result.parse().expect("Should be a valid IP address"); let ip_str = result.trim_start_matches('[').trim_end_matches(']');
let _: IpAddr = ip_str.parse().expect("Should be a valid IP address");
}
#[test]
fn test_ipv6_address_is_bracketed() {
let resolver = MockIpResolver {
ipv4_result: Err(Error::LocalIpAddressNotFound),
ipv6_result: Ok(IpAddr::from([0xfd00, 0xdead, 0xbeef, 0, 0, 0, 0, 2])),
};
let result = get_http_rpc_host_with_resolver(resolver);
// IPv6 must be bracketed for URLs like http://{host}:{port}/path
assert!(result.starts_with('['), "IPv6 should start with '['");
assert!(result.ends_with(']'), "IPv6 should end with ']'");
assert_eq!(result, "[fd00:dead:beef::2]");
}
#[test]
fn test_ipv4_address_not_bracketed() {
let resolver = MockIpResolver {
ipv4_result: Ok(IpAddr::from([10, 0, 0, 1])),
ipv6_result: Err(Error::LocalIpAddressNotFound),
};
let result = get_http_rpc_host_with_resolver(resolver);
// IPv4 should NOT be bracketed
assert!(!result.contains('['), "IPv4 should not contain '['");
assert_eq!(result, "10.0.0.1");
} }
} }
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