Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 50 additions & 9 deletions docs/fern/pages/reference/components/runtime-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,48 @@ Unless a field is marked environment-only, it has both a CLI flag and an environ
See [Frontend Configuration](frontend-configuration.mdx) for `DYN_HTTP_BODY_LIMIT_MB`.
</ParamField>

<ParamField path="DYN_TCP_RPC_HOST" type="string" default="auto-detected local address">
Host for the TCP request-plane server. Accepts IPv4 and IPv6 literals, bracketed IPv6 literals,
wildcards, and interface names, including aliases such as `eth0:1`. If unset or empty, Dynamo
selects the first usable non-loopback IPv4 address, then IPv6, then IPv4 loopback, then IPv6
loopback. On Unix, automatic selection excludes interfaces that are down. Dynamo trims surrounding
whitespace from configured hosts; a value containing only whitespace still fails server startup.

For a named interface, Dynamo uses the same address selection order, restricted to that interface.
It selects the first usable address in each category in operating-system enumeration order. An
interface with only loopback addresses uses IPv4 loopback, then IPv6 loopback. Explicit interface
lookup does not filter by interface state.

A wildcard uses the same automatic interface inventory to select a usable non-loopback address in
its requested family for advertisement. If only the other family has one, Dynamo switches the bind
wildcard to that family. If neither family has one, Dynamo prefers loopback in the requested family
before trying the other family. If no
loopback address is found, it uses the requested family's standard loopback address. The bind
wildcard uses the selected address family. Wildcards are never advertised. IPv4 link-local
addresses are accepted. Multicast, broadcast, and IPv6 link-local addresses are rejected.

IPv4-mapped IPv6 addresses use IPv4 rules. For example, `::ffff:0.0.0.0` uses the `0.0.0.0`
wildcard rules and advertises a concrete address.

The server resolves the address once at startup. A loopback fallback persists until restart, and
an interface-enumeration error fails server startup.
</ParamField>

<ParamField path="DYN_TCP_RPC_PORT" type="integer" default="0">
Port used by the TCP request-plane server. Accepted values are integers from `0` to `65535`.
When the value is unset, empty, or `0`, the operating system assigns a free port. Dynamo does not
trim whitespace. An invalid value, including a value with surrounding whitespace, silently falls
back to an operating-system-assigned port.
</ParamField>

<ParamField path="DYN_TCP_RESPONSE_STREAM_HOST" type="string" default="auto-detected local address">
IPv4 or IPv6 address, or exact network interface name, used to bind and advertise the TCP response
stream server. If an interface name occurs more than once in the operating system's interface
list, Dynamo uses the last address. Dynamo trims whitespace and treats an empty value as unset.
Unspecified addresses such as `0.0.0.0` and `::` are invalid.
Host for the TCP response-stream server. It accepts the same address syntax and uses the same
address selection and wildcard-family rules as `DYN_TCP_RPC_HOST`. Dynamo trims whitespace and
treats an empty value as unset. With a wildcard host, the TCP listener binds the selected wildcard,
while the QUIC response listener binds only the selected concrete address.

The server resolves the address once at startup. A loopback fallback persists until restart, and
an interface-enumeration error fails server startup.
</ParamField>

<ParamField path="DYN_TCP_RESPONSE_STREAM_PORT" type="integer" default="0">
Expand All @@ -167,13 +204,17 @@ Unless a field is marked environment-only, it has both a CLI flag and an environ

<ParamField path="DYN_EVENT_PLANE_HOST" type="string" default="auto-detected local address">
IPv4 or IPv6 address, or exact network interface name, advertised by direct ZMQ event publishers.
If an interface name occurs more than once in the operating system's interface list, Dynamo uses
the last address. Dynamo trims whitespace and treats an empty value as unset. Unspecified addresses
such as `0.0.0.0` and `::` are invalid.
For an interface name, Dynamo selects the first usable IPv4 address, then IPv6. Dynamo trims
whitespace and treats an empty value as unset. Unspecified addresses such as `0.0.0.0` and `::` are
invalid, including IPv4-mapped wildcards such as `::ffff:0.0.0.0`. When unset, automatic
advertisement stays in the IPv4 bind family and, on Unix, excludes down interfaces. If no eligible
interface has a usable non-loopback IPv4 address, automatic selection advertises IPv4 loopback
(`127.0.0.1` when no IPv4 loopback address is found), and remote subscribers cannot reach the publisher.

This variable changes the discovery address, not the listener address. Direct ZMQ continues to bind
an IPv4 wildcard, so an IPv6 value does not enable IPv6 listening. This variable does not affect
NATS or ZMQ broker mode.
an IPv4 wildcard. For remote subscribers, an explicit override must identify a reachable IPv4
address served by that listener. An IPv6 value does not enable IPv6 listening. This variable does
not affect NATS or ZMQ broker mode.
</ParamField>

<ParamField path="DYN_ZMQ_IO_THREADS" type="integer" default="4">
Expand Down
8 changes: 1 addition & 7 deletions lib/backend-common/src/rl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,7 @@ pub(crate) async fn serve_endpoint(

fn self_host_base_url(drt: &dynamo_runtime::DistributedRuntime) -> Option<String> {
let info = drt.system_status_server_info()?;
let socket_addr = info.socket_addr;
if socket_addr.ip().is_unspecified() {
let host = dynamo_runtime::utils::local_ip_for_advertise();
Some(format!("http://{host}:{}", socket_addr.port()))
} else {
Some(format!("http://{socket_addr}"))
}
Some(format!("http://{}", info.advertised_socket_addr()))
}

fn resolve_endpoint_name(primary_name: &str) -> anyhow::Result<String> {
Expand Down
2 changes: 2 additions & 0 deletions lib/bindings/kvbm/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions lib/bindings/python/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 3 additions & 9 deletions lib/bindings/python/rust/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1470,15 +1470,9 @@ impl DistributedRuntime {
/// Workers use this in their RL request-plane route descriptor so the
/// frontend does not need to derive worker system URLs from static env vars.
fn system_status_server_url(&self) -> Option<String> {
self.inner.system_status_server_info().map(|info| {
let socket_addr = info.socket_addr;
if socket_addr.ip().is_unspecified() {
let host = dynamo_runtime::utils::ip_resolver::local_ip_for_advertise();
format!("http://{host}:{}", socket_addr.port())
} else {
format!("http://{socket_addr}")
}
})
self.inner
.system_status_server_info()
.map(|info| format!("http://{}", info.advertised_socket_addr()))
}

/// Register an async Python callback for /engine/{route_name}
Expand Down
19 changes: 11 additions & 8 deletions lib/llm/src/local_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -888,15 +888,11 @@ pub(crate) fn self_host_base_url(
return Ok(None);
};

let configured = dynamo_runtime::RuntimeConfig::from_settings()
.unwrap_or_default()
.system_host;
let host = match configured.as_str() {
"0.0.0.0" | "::" | "[::]" => dynamo_runtime::utils::local_ip_for_advertise(),
_ => configured,
};
Ok(Some(system_status_base_url(&info)))
}

Ok(Some(format!("http://{host}:{}", info.port())))
fn system_status_base_url(info: &dynamo_runtime::SystemStatusServerInfo) -> String {
format!("http://{}", info.advertised_socket_addr())
}

/// Scan `model_dir` for files to advertise alongside the typed MDC slots.
Expand Down Expand Up @@ -951,6 +947,13 @@ mod self_host_metadata_default_tests {
assert!(self_host_metadata_default(Some("garbage"))); // unrecognized
assert!(!self_host_metadata_default(Some("false"))); // explicit opt-out
}

#[test]
fn self_host_url_uses_the_bound_status_address() {
let info = dynamo_runtime::SystemStatusServerInfo::new("[::1]:8080".parse().unwrap(), None);

assert_eq!(system_status_base_url(&info), "http://[::1]:8080");
}
}

#[cfg(test)]
Expand Down
3 changes: 3 additions & 0 deletions lib/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ tokio-rayon = { version = "2.1" }
kube = { version = "2.0.1", default-features = false, features = ["runtime", "derive", "client", "rustls-tls", "aws-lc-rs"] }
k8s-openapi = { version = "0.26.0", features = ["v1_32"] }

[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", default-features = false, features = ["net"] }

[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rcgen = { workspace = true }
Expand Down
23 changes: 23 additions & 0 deletions lib/runtime/examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 39 additions & 35 deletions lib/runtime/src/component/endpoint.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use std::net::SocketAddr;
use std::sync::Arc;

use anyhow::Result;
Expand Down Expand Up @@ -327,25 +328,8 @@ fn build_transport_type_inner(
) -> Result<TransportType> {
match mode {
RequestPlaneMode::Tcp => {
let tcp_host = crate::utils::tcp_rpc_host_from_env();
// If a fixed port is explicitly configured, use it directly (no init ordering dependency).
// Otherwise, use the actual bound port (set by TCP server after binding when port 0 is used).
let tcp_port = std::env::var("DYN_TCP_RPC_PORT")
.ok()
.and_then(|p| p.parse::<u16>().ok())
.filter(|&p| p != 0)
.unwrap_or(crate::pipeline::network::manager::get_actual_tcp_rpc_port()?);

// Include instance_id and endpoint name for proper TCP routing.
// Format: host:port/instance_id_hex/endpoint_name
// This ensures each worker has a unique routing key when multiple workers
// share the same TCP server (e.g., --num-workers > 1).
let tcp_endpoint = format!(
"{}:{}/{:x}/{}",
tcp_host, tcp_port, connection_id, endpoint_id.name
);

Ok(TransportType::Tcp(tcp_endpoint))
let address = crate::pipeline::network::manager::get_actual_tcp_rpc_address()?;
Ok(tcp_transport_type(address, endpoint_id, connection_id))
}
RequestPlaneMode::Nats => Ok(TransportType::Nats(nats::instance_subject(
endpoint_id,
Expand All @@ -354,31 +338,27 @@ fn build_transport_type_inner(
}
}

fn tcp_transport_type(
address: SocketAddr,
endpoint_id: &EndpointId,
connection_id: u64,
) -> TransportType {
TransportType::Tcp(format!("{address}/{connection_id:x}/{}", endpoint_id.name))
}

/// Build transport type, ensuring TCP server is initialized when needed.
///
/// In TCP mode with an OS-assigned port (`DYN_TCP_RPC_PORT` unset or invalid), the server must bind
/// before we can construct a correct transport address. This helper ensures that initialization
/// occurs, then delegates to the internal builder.
/// In TCP mode the server must bind before discovery can publish the concrete
/// advertised address. This helper ensures that initialization occurs, then
/// delegates to the internal builder.
pub async fn build_transport_type(
endpoint: &Endpoint,
endpoint_id: &EndpointId,
connection_id: u64,
) -> Result<TransportType> {
let mode = endpoint.drt().request_plane();

// For TCP with OS-assigned ports, we must ensure the server is initialized
// (bound to a port) before we can construct a correct transport address.
let has_fixed_port = match mode {
RequestPlaneMode::Tcp => std::env::var("DYN_TCP_RPC_PORT")
.ok()
.and_then(|p| p.parse::<u16>().ok())
.filter(|&p| p != 0)
.is_some(),
RequestPlaneMode::Nats => true, // NATS doesn't need port init
};

if !has_fixed_port {
// Ensure request plane server is initialized before building transport.
if mode == RequestPlaneMode::Tcp {
let _ = endpoint.drt().request_plane_server().await?;
}

Expand Down Expand Up @@ -473,3 +453,27 @@ impl Endpoint {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn tcp_transport_uses_concrete_ipv4_and_bracketed_ipv6_addresses() {
let endpoint_id = EndpointId {
namespace: "ns".to_string(),
component: "worker".to_string(),
name: "generate".to_string(),
};

for (address, expected) in [
("192.0.2.10:1234", "192.0.2.10:1234/2a/generate"),
("[2001:db8::10]:1234", "[2001:db8::10]:1234/2a/generate"),
] {
let transport = tcp_transport_type(address.parse().unwrap(), &endpoint_id, 0x2a);
assert_eq!(transport.address(), expected);
assert!(!transport.address().starts_with("0.0.0.0"));
assert!(!transport.address().starts_with("[::]"));
}
}
}
26 changes: 22 additions & 4 deletions lib/runtime/src/config/environment_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,15 @@ pub mod request_plane {

/// Buffer size above which the TCP decoder shrinks an empty buffer, in bytes.
pub const DYN_TCP_SHRINK_MESSAGE_SIZE: &str = "DYN_TCP_SHRINK_MESSAGE_SIZE";

/// Host or interface for the TCP request-plane server.
/// The server resolves the value once at startup. A loopback fallback
/// persists until restart, and an enumeration error fails server startup.
pub const DYN_TCP_RPC_HOST: &str = "DYN_TCP_RPC_HOST";

/// Port for the TCP request-plane server.
/// If unset, the OS assigns a free ephemeral port.
pub const DYN_TCP_RPC_PORT: &str = "DYN_TCP_RPC_PORT";
}

/// Response plane transport configuration.
Expand All @@ -813,10 +822,17 @@ pub mod tcp_response_stream {
/// If unset or 0, the OS assigns a free ephemeral port.
pub const DYN_TCP_RESPONSE_STREAM_PORT: &str = "DYN_TCP_RESPONSE_STREAM_PORT";

/// IP address or exact interface shared by the TCP request callback and QUIC response
/// listeners.
/// Unspecified addresses are rejected.
/// If unset, the server auto-detects a routable local IP.
/// Host or interface for the TCP response stream server and QUIC response listener.
///
/// Accepts IPv4 and IPv6 literals, bracketed IPv6 literals, IPv4 or IPv6
/// wildcards, and interface names. Interface aliases such as `eth0:1` are
/// also accepted. If unset, the server selects the first usable
/// non-loopback IPv4 address, then IPv6, then IPv4 loopback, then IPv6
/// loopback. A wildcard uses a reachable address in its requested family.
/// If only the other family has a usable non-loopback address, the server
/// switches the bind wildcard to that family. The server resolves the value
/// once at startup. A loopback fallback persists until restart, and an
/// enumeration error fails server startup.
pub const DYN_TCP_RESPONSE_STREAM_HOST: &str = "DYN_TCP_RESPONSE_STREAM_HOST";

/// TCP request-plane TLS configuration
Expand Down Expand Up @@ -1142,6 +1158,8 @@ mod tests {
response_plane::DYN_RESPONSE_PLANE,
request_plane::DYN_TCP_MAX_MESSAGE_SIZE,
request_plane::DYN_TCP_SHRINK_MESSAGE_SIZE,
request_plane::DYN_TCP_RPC_HOST,
request_plane::DYN_TCP_RPC_PORT,
// TCP Response Stream
tcp_response_stream::DYN_TCP_RESPONSE_STREAM_PORT,
tcp_response_stream::DYN_TCP_RESPONSE_STREAM_HOST,
Expand Down
Loading
Loading