Skip to main content

dynamo_runtime/utils/
ip_resolver.rs

1// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Local IP address resolution for advertising endpoints.
5
6use crate::pipeline::network::tcp::server::{DefaultIpResolver, IpResolver};
7use local_ip_address::Error;
8use std::net::{IpAddr, Ipv4Addr};
9
10const FALLBACK: IpAddr = IpAddr::V4(Ipv4Addr::LOCALHOST);
11
12/// Resolve the local IP for advertising endpoints, falling back to 127.0.0.1.
13///
14/// IPv6 addresses are bracketed (e.g. `[::1]`) so the result is safe to
15/// interpolate into a `host:port` URL.
16pub fn local_ip_for_advertise() -> String {
17    resolve(DefaultIpResolver)
18}
19
20/// TCP RPC host: `DYN_TCP_RPC_HOST` if set, otherwise the resolved local IP.
21pub fn tcp_rpc_host_from_env() -> String {
22    std::env::var("DYN_TCP_RPC_HOST").unwrap_or_else(|_| local_ip_for_advertise())
23}
24
25fn resolve<R: IpResolver>(resolver: R) -> String {
26    let ip = resolver
27        .local_ip()
28        .or_else(|err| match err {
29            Error::LocalIpAddressNotFound => resolver.local_ipv6(),
30            _ => Err(err),
31        })
32        .unwrap_or(FALLBACK);
33
34    match ip {
35        IpAddr::V6(_) => format!("[{ip}]"),
36        IpAddr::V4(_) => ip.to_string(),
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    struct MockIpResolver {
45        v4: Result<IpAddr, Error>,
46        v6: Result<IpAddr, Error>,
47    }
48
49    impl IpResolver for MockIpResolver {
50        fn local_ip(&self) -> Result<IpAddr, Error> {
51            self.v4
52                .as_ref()
53                .copied()
54                .map_err(|_| Error::LocalIpAddressNotFound)
55        }
56
57        fn local_ipv6(&self) -> Result<IpAddr, Error> {
58            self.v6
59                .as_ref()
60                .copied()
61                .map_err(|_| Error::LocalIpAddressNotFound)
62        }
63    }
64
65    #[test]
66    fn ipv4_returned_unbracketed() {
67        let r = MockIpResolver {
68            v4: Ok(IpAddr::from([192, 168, 1, 100])),
69            v6: Err(Error::LocalIpAddressNotFound),
70        };
71        assert_eq!(resolve(r), "192.168.1.100");
72    }
73
74    #[test]
75    fn ipv6_fallback_is_bracketed() {
76        let r = MockIpResolver {
77            v4: Err(Error::LocalIpAddressNotFound),
78            v6: Ok(IpAddr::from([0x2001, 0xdb8, 0, 0, 0, 0, 0, 1])),
79        };
80        assert_eq!(resolve(r), "[2001:db8::1]");
81    }
82
83    #[test]
84    fn both_fail_uses_localhost() {
85        let r = MockIpResolver {
86            v4: Err(Error::LocalIpAddressNotFound),
87            v6: Err(Error::LocalIpAddressNotFound),
88        };
89        assert_eq!(resolve(r), "127.0.0.1");
90    }
91}