mirror of
https://github.com/openai/codex.git
synced 2026-04-25 23:24:55 +00:00
feat: reserve loopback ephemeral listeners for managed proxy (#11269)
Codex may run many per-thread proxy instances, so hardcoded proxy ports are brittle and conflict-prone. The previous "ephemeral" approach still had a race: `build()` read `local_addr()` from temporary listeners and dropped them before `run()` rebound the ports. That left a [TOCTOU](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) window where the OS (or another process) could reuse the same port, causing intermittent `EADDRINUSE` and partial proxy startup. Change the managed proxy path to reserve real listener sockets up front and keep them alive until startup: - add `ReservedListeners` on `NetworkProxy` to hold HTTP/SOCKS/admin std listeners allocated during `build()` - in managed mode, bind `127.0.0.1:0` for each listener and carry those bound sockets into `run()` instead of rebinding by address later - add `run_*_with_std_listener` entry points for HTTP, SOCKS5, and admin servers so `run()` can start services from already-reserved sockets - keep static/configured ports only when `managed_by_codex(false)`, including explicit `socks_addr` override support - remove fallback synthetic port allocation and add tests for managed ephemeral loopback binding and unmanaged configured-port behavior This makes managed startup deterministic, avoids port collisions, and preserves the intended distinction between Codex-managed ephemeral ports and externally managed fixed ports.
This commit is contained in:
@@ -64,6 +64,7 @@ use rama_tls_rustls::client::TlsConnectorLayer;
|
||||
use serde::Serialize;
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
use std::sync::Arc;
|
||||
use tracing::error;
|
||||
use tracing::info;
|
||||
@@ -85,6 +86,28 @@ pub async fn run_http_proxy(
|
||||
.map_err(anyhow::Error::from)
|
||||
.with_context(|| format!("bind HTTP proxy: {addr}"))?;
|
||||
|
||||
run_http_proxy_with_listener(state, listener, policy_decider).await
|
||||
}
|
||||
|
||||
pub async fn run_http_proxy_with_std_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: StdTcpListener,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
) -> Result<()> {
|
||||
let listener =
|
||||
TcpListener::try_from(listener).context("convert std listener to HTTP proxy listener")?;
|
||||
run_http_proxy_with_listener(state, listener, policy_decider).await
|
||||
}
|
||||
|
||||
async fn run_http_proxy_with_listener(
|
||||
state: Arc<NetworkProxyState>,
|
||||
listener: TcpListener,
|
||||
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
|
||||
) -> Result<()> {
|
||||
let addr = listener
|
||||
.local_addr()
|
||||
.context("read HTTP proxy listener local addr")?;
|
||||
|
||||
let http_service = HttpServer::auto(Executor::new()).service(
|
||||
(
|
||||
UpgradeLayer::new(
|
||||
|
||||
Reference in New Issue
Block a user