Files
codex/codex-rs/config/src/host_name.rs
T
Felipe Coury 40e7dda4d2 perf(config): defer remote sandbox hostname lookup (#28542)
## Why

[#18763](https://github.com/openai/codex/pull/18763) added canonical
hostname resolution for `remote_sandbox_config`. Requirements
composition currently performs that synchronous DNS lookup on every
fresh process, even when none of the loaded requirements layers contains
`[[remote_sandbox_config]]`. On hosts with slow local DNS resolution,
this can add several seconds to Codex startup.

## What

- defer hostname resolution until a parsed requirements layer actually
contains `remote_sandbox_config`
- cache the resolver result once per requirements composition,
preserving the existing single-lookup behavior across multiple layers
- keep the existing FQDN resolution and per-layer requirements
precedence unchanged
- cover both the ordinary no-lookup path and the multi-layer
single-lookup path

## How to Test

On a host where local canonical-name resolution is slow:

1. Start Codex without `[[remote_sandbox_config]]` in any managed
requirements layer and confirm startup no longer waits for hostname
resolution.
2. Add a matching `[[remote_sandbox_config]]` entry and confirm its
`allowed_sandbox_modes` still overrides the layer's top-level value.
3. Add remote sandbox entries to multiple requirements layers and
confirm precedence remains unchanged while the hostname is resolved only
once.

Targeted tests:

- `just test -p codex-config hostname_resolver`
- `just test -p codex-config` (181 passed)
2026-06-16 11:17:41 -04:00

100 lines
3.3 KiB
Rust

#[cfg(unix)]
use dns_lookup::AddrInfoHints;
#[cfg(unix)]
use dns_lookup::getaddrinfo;
use std::sync::LazyLock;
#[cfg(windows)]
use winapi_util::sysinfo::ComputerNameKind;
#[cfg(windows)]
use winapi_util::sysinfo::get_computer_name;
static HOST_NAME: LazyLock<Option<String>> = LazyLock::new(compute_host_name);
/// Returns a process-cached canonical hostname, falling back to the normalized
/// kernel hostname. The first call on Unix may perform blocking DNS resolution.
pub fn host_name() -> Option<String> {
HOST_NAME.clone()
}
fn compute_host_name() -> Option<String> {
let kernel_hostname = gethostname::gethostname();
let kernel_hostname = normalize_host_name(&kernel_hostname.to_string_lossy())?;
// Remote sandbox requirements are meant to target remote hosts by DNS name,
// so prefer the canonical FQDN when the local resolver can provide one.
// This is best-effort host classification, not authenticated device proof.
if let Some(fqdn) = local_fqdn_for_hostname(&kernel_hostname) {
return Some(fqdn);
}
// Some machines have only a short local hostname or resolver setup that
// does not return AI_CANONNAME. Keep matching behavior best-effort by
// falling back to the cleaned kernel hostname instead of returning None.
Some(kernel_hostname)
}
fn normalize_host_name(hostname: &str) -> Option<String> {
let hostname = hostname.trim().trim_end_matches('.');
(!hostname.is_empty()).then(|| hostname.to_ascii_lowercase())
}
#[cfg(unix)]
fn local_fqdn_for_hostname(hostname: &str) -> Option<String> {
let hints = AddrInfoHints {
flags: libc::AI_CANONNAME,
..AddrInfoHints::default()
};
getaddrinfo(Some(hostname), /*service*/ None, Some(hints))
.ok()?
.filter_map(Result::ok)
.filter_map(|addr| addr.canonname)
// getaddrinfo may return the short hostname as canonname when no FQDN
// is available. Treat only DNS-qualified names as an FQDN result.
.find_map(|hostname| normalize_fqdn_candidate(&hostname))
}
#[cfg(windows)]
fn local_fqdn_for_hostname(_hostname: &str) -> Option<String> {
get_computer_name(ComputerNameKind::PhysicalDnsFullyQualified)
.ok()
.and_then(|hostname| hostname.into_string().ok())
.and_then(|hostname| normalize_fqdn_candidate(&hostname))
}
#[cfg(not(any(unix, windows)))]
fn local_fqdn_for_hostname(_hostname: &str) -> Option<String> {
None
}
fn normalize_fqdn_candidate(hostname: &str) -> Option<String> {
normalize_host_name(hostname).filter(|hostname| hostname.contains('.'))
}
#[cfg(test)]
mod tests {
use super::normalize_fqdn_candidate;
use pretty_assertions::assert_eq;
#[test]
fn normalize_fqdn_candidate_accepts_dns_qualified_name() {
assert_eq!(
normalize_fqdn_candidate("runner-01.ci.example.com"),
Some("runner-01.ci.example.com".to_string())
);
}
#[test]
fn normalize_fqdn_candidate_rejects_short_name() {
assert_eq!(normalize_fqdn_candidate("runner-01"), None);
}
#[test]
fn normalize_fqdn_candidate_trims_trailing_dot_and_normalizes_case() {
assert_eq!(
normalize_fqdn_candidate("RUNNER-01.CI.EXAMPLE.COM."),
Some("runner-01.ci.example.com".to_string())
);
}
}