feat(network-proxy): add a SOCKS5 proxy with policy enforcement (#9803)

### Summary
- Adds an optional SOCKS5 listener via `rama-socks5`
- SOCKS5 is disabled by default and gated by config
- Reuses existing policy enforcement and blocked-request recording
- Blocks SOCKS5 in limited mode to prevent method-policy bypass
- Applies bind clamping to the SOCKS5 listener

### Config
New/used fields under `network_proxy`:
- `enable_socks5`
- `socks_url`
- `enable_socks5_udp`

### Scope
- Changes limited to `codex-rs/network-proxy` (+ `codex-rs/Cargo.lock`)

### Testing
```bash
cd codex-rs
just fmt
cargo test -p codex-network-proxy --offline
This commit is contained in:
viyatb-oai 2026-01-27 10:09:39 -08:00 committed by GitHub
parent 538e1059a3
commit 877b76bb9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 452 additions and 19 deletions

28
codex-rs/Cargo.lock generated
View file

@ -1683,6 +1683,7 @@ dependencies = [
"rama-http",
"rama-http-backend",
"rama-net",
"rama-socks5",
"rama-tcp",
"rama-tls-boring",
"rama-unix",
@ -5959,6 +5960,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "rama-socks5"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5468b263516daaf258de32542c1974b7cbe962363ad913dcb669f5d46db0ef3e"
dependencies = [
"byteorder",
"rama-core",
"rama-net",
"rama-tcp",
"rama-udp",
"rama-utils",
"tokio",
]
[[package]]
name = "rama-tcp"
version = "0.3.0-alpha.4"
@ -5997,6 +6013,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "rama-udp"
version = "0.3.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36ed05e0ecac73e084e92a3a8b1fbf16fdae8958c506f0f0eada180a2d99eef4"
dependencies = [
"rama-core",
"rama-net",
"tokio",
"tokio-util",
]
[[package]]
name = "rama-unix"
version = "0.3.0-alpha.4"

View file

@ -34,6 +34,7 @@ rama-core = { version = "=0.3.0-alpha.4" }
rama-http = { version = "=0.3.0-alpha.4" }
rama-http-backend = { version = "=0.3.0-alpha.4", features = ["tls"] }
rama-net = { version = "=0.3.0-alpha.4", features = ["http", "tls"] }
rama-socks5 = { version = "=0.3.0-alpha.4" }
rama-tcp = { version = "=0.3.0-alpha.4", features = ["http"] }
rama-tls-boring = { version = "=0.3.0-alpha.4", features = ["http"] }

View file

@ -3,6 +3,7 @@
`codex-network-proxy` is Codex's local network policy enforcement proxy. It runs:
- an HTTP proxy (default `127.0.0.1:3128`)
- an optional SOCKS5 proxy (default `127.0.0.1:8081`, disabled by default)
- an admin HTTP API (default `127.0.0.1:8080`)
It enforces an allow/deny policy and a "limited" mode intended for read-only network access.
@ -20,6 +21,10 @@ Example config:
enabled = true
proxy_url = "http://127.0.0.1:3128"
admin_url = "http://127.0.0.1:8080"
# Optional SOCKS5 listener (disabled by default).
enable_socks5 = false
socks_url = "http://127.0.0.1:8081"
enable_socks5_udp = false
# When `enabled` is false, the proxy no-ops and does not bind listeners.
# When true, respect HTTP(S)_PROXY/ALL_PROXY for upstream requests (HTTP(S) proxies only),
# including CONNECT tunnels in full mode.
@ -28,7 +33,7 @@ allow_upstream_proxy = false
# If you want to expose these listeners beyond localhost, you must opt in explicitly.
dangerously_allow_non_loopback_proxy = false
dangerously_allow_non_loopback_admin = false
mode = "limited" # or "full"
mode = "full" # default when unset; use "limited" for read-only mode
[network_proxy.policy]
# Hosts must match the allowlist (unless denied).
@ -60,6 +65,12 @@ export HTTP_PROXY="http://127.0.0.1:3128"
export HTTPS_PROXY="http://127.0.0.1:3128"
```
For SOCKS5 traffic (when `enable_socks5 = true`):
```bash
export ALL_PROXY="socks5h://127.0.0.1:8081"
```
### 4) Understand blocks / debugging
When a request is blocked, the proxy responds with `403` and includes:
@ -70,8 +81,8 @@ When a request is blocked, the proxy responds with `403` and includes:
- `blocked-by-method-policy`
- `blocked-by-policy`
In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed for plain HTTP. HTTPS `CONNECT`
remains a transparent tunnel, so limited-mode method enforcement does not apply to HTTPS.
In "limited" mode, only `GET`, `HEAD`, and `OPTIONS` are allowed. HTTPS `CONNECT` and SOCKS5 are
blocked because they would bypass method enforcement.
## Library API

View file

@ -23,6 +23,12 @@ pub struct NetworkProxySettings {
#[serde(default = "default_admin_url")]
pub admin_url: String,
#[serde(default)]
pub enable_socks5: bool,
#[serde(default = "default_socks_url")]
pub socks_url: String,
#[serde(default)]
pub enable_socks5_udp: bool,
#[serde(default)]
pub allow_upstream_proxy: bool,
#[serde(default)]
pub dangerously_allow_non_loopback_proxy: bool,
@ -40,6 +46,9 @@ impl Default for NetworkProxySettings {
enabled: false,
proxy_url: default_proxy_url(),
admin_url: default_admin_url(),
enable_socks5: false,
socks_url: default_socks_url(),
enable_socks5_udp: false,
allow_upstream_proxy: false,
dangerously_allow_non_loopback_proxy: false,
dangerously_allow_non_loopback_admin: false,
@ -90,6 +99,10 @@ fn default_admin_url() -> String {
"http://127.0.0.1:8080".to_string()
}
fn default_socks_url() -> String {
"http://127.0.0.1:8081".to_string()
}
/// Clamp non-loopback bind addresses to loopback unless explicitly allowed.
fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) -> SocketAddr {
if addr.ip().is_loopback() {
@ -110,21 +123,27 @@ fn clamp_non_loopback(addr: SocketAddr, allow_non_loopback: bool, name: &str) ->
pub(crate) fn clamp_bind_addrs(
http_addr: SocketAddr,
socks_addr: SocketAddr,
admin_addr: SocketAddr,
cfg: &NetworkProxySettings,
) -> (SocketAddr, SocketAddr) {
) -> (SocketAddr, SocketAddr, SocketAddr) {
let http_addr = clamp_non_loopback(
http_addr,
cfg.dangerously_allow_non_loopback_proxy,
"HTTP proxy",
);
let socks_addr = clamp_non_loopback(
socks_addr,
cfg.dangerously_allow_non_loopback_proxy,
"SOCKS5 proxy",
);
let admin_addr = clamp_non_loopback(
admin_addr,
cfg.dangerously_allow_non_loopback_admin,
"admin API",
);
if cfg.policy.allow_unix_sockets.is_empty() {
return (http_addr, admin_addr);
return (http_addr, socks_addr, admin_addr);
}
// `x-unix-socket` is intentionally a local escape hatch. If the proxy (or admin API) is
@ -136,6 +155,11 @@ pub(crate) fn clamp_bind_addrs(
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_proxy and clamping HTTP proxy to loopback"
);
}
if cfg.dangerously_allow_non_loopback_proxy && !socks_addr.ip().is_loopback() {
warn!(
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_proxy and clamping SOCKS5 proxy to loopback"
);
}
if cfg.dangerously_allow_non_loopback_admin && !admin_addr.ip().is_loopback() {
warn!(
"unix socket proxying is enabled; ignoring dangerously_allow_non_loopback_admin and clamping admin API to loopback"
@ -143,12 +167,14 @@ pub(crate) fn clamp_bind_addrs(
}
(
SocketAddr::from(([127, 0, 0, 1], http_addr.port())),
SocketAddr::from(([127, 0, 0, 1], socks_addr.port())),
SocketAddr::from(([127, 0, 0, 1], admin_addr.port())),
)
}
pub struct RuntimeConfig {
pub http_addr: SocketAddr,
pub socks_addr: SocketAddr,
pub admin_addr: SocketAddr,
}
@ -159,16 +185,24 @@ pub fn resolve_runtime(cfg: &NetworkProxyConfig) -> Result<RuntimeConfig> {
cfg.network_proxy.proxy_url
)
})?;
let socks_addr = resolve_addr(&cfg.network_proxy.socks_url, 8081).with_context(|| {
format!(
"invalid network_proxy.socks_url: {}",
cfg.network_proxy.socks_url
)
})?;
let admin_addr = resolve_addr(&cfg.network_proxy.admin_url, 8080).with_context(|| {
format!(
"invalid network_proxy.admin_url: {}",
cfg.network_proxy.admin_url
)
})?;
let (http_addr, admin_addr) = clamp_bind_addrs(http_addr, admin_addr, &cfg.network_proxy);
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg.network_proxy);
Ok(RuntimeConfig {
http_addr,
socks_addr,
admin_addr,
})
}
@ -403,11 +437,14 @@ mod tests {
..Default::default()
};
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
let (http_addr, admin_addr) = clamp_bind_addrs(http_addr, admin_addr, &cfg);
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
assert_eq!(http_addr, "0.0.0.0:3128".parse::<SocketAddr>().unwrap());
assert_eq!(socks_addr, "0.0.0.0:8081".parse::<SocketAddr>().unwrap());
assert_eq!(admin_addr, "0.0.0.0:8080".parse::<SocketAddr>().unwrap());
}
@ -423,11 +460,14 @@ mod tests {
..Default::default()
};
let http_addr = "0.0.0.0:3128".parse::<SocketAddr>().unwrap();
let socks_addr = "0.0.0.0:8081".parse::<SocketAddr>().unwrap();
let admin_addr = "0.0.0.0:8080".parse::<SocketAddr>().unwrap();
let (http_addr, admin_addr) = clamp_bind_addrs(http_addr, admin_addr, &cfg);
let (http_addr, socks_addr, admin_addr) =
clamp_bind_addrs(http_addr, socks_addr, admin_addr, &cfg);
assert_eq!(http_addr, "127.0.0.1:3128".parse::<SocketAddr>().unwrap());
assert_eq!(socks_addr, "127.0.0.1:8081".parse::<SocketAddr>().unwrap());
assert_eq!(admin_addr, "127.0.0.1:8080".parse::<SocketAddr>().unwrap());
}
}

View file

@ -9,6 +9,7 @@ mod proxy;
mod reasons;
mod responses;
mod runtime;
mod socks5;
mod state;
mod upstream;

View file

@ -3,6 +3,7 @@ use crate::config;
use crate::http_proxy;
use crate::network_policy::NetworkPolicyDecider;
use crate::runtime::unix_socket_permissions_supported;
use crate::socks5;
use crate::state::NetworkProxyState;
use anyhow::Context;
use anyhow::Result;
@ -61,8 +62,9 @@ impl NetworkProxyBuilder {
let current_cfg = state.current_cfg().await?;
let runtime = config::resolve_runtime(&current_cfg)?;
// Reapply bind clamping for caller overrides so unix-socket proxying stays loopback-only.
let (http_addr, admin_addr) = config::clamp_bind_addrs(
let (http_addr, socks_addr, admin_addr) = config::clamp_bind_addrs(
self.http_addr.unwrap_or(runtime.http_addr),
runtime.socks_addr,
self.admin_addr.unwrap_or(runtime.admin_addr),
&current_cfg.network_proxy,
);
@ -70,6 +72,7 @@ impl NetworkProxyBuilder {
Ok(NetworkProxy {
state,
http_addr,
socks_addr,
admin_addr,
policy_decider: self.policy_decider,
})
@ -80,6 +83,7 @@ impl NetworkProxyBuilder {
pub struct NetworkProxy {
state: Arc<NetworkProxyState>,
http_addr: SocketAddr,
socks_addr: SocketAddr,
admin_addr: SocketAddr,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
}
@ -105,10 +109,21 @@ impl NetworkProxy {
self.http_addr,
self.policy_decider.clone(),
));
let socks_task = if current_cfg.network_proxy.enable_socks5 {
Some(tokio::spawn(socks5::run_socks5(
self.state.clone(),
self.socks_addr,
self.policy_decider.clone(),
current_cfg.network_proxy.enable_socks5_udp,
)))
} else {
None
};
let admin_task = tokio::spawn(admin::run_admin_api(self.state.clone(), self.admin_addr));
Ok(NetworkProxyHandle {
http_task: Some(http_task),
socks_task,
admin_task: Some(admin_task),
completed: false,
})
@ -117,6 +132,7 @@ impl NetworkProxy {
pub struct NetworkProxyHandle {
http_task: Option<JoinHandle<Result<()>>>,
socks_task: Option<JoinHandle<Result<()>>>,
admin_task: Option<JoinHandle<Result<()>>>,
completed: bool,
}
@ -125,6 +141,7 @@ impl NetworkProxyHandle {
fn noop() -> Self {
Self {
http_task: Some(tokio::spawn(async { Ok(()) })),
socks_task: None,
admin_task: Some(tokio::spawn(async { Ok(()) })),
completed: true,
}
@ -133,33 +150,49 @@ impl NetworkProxyHandle {
pub async fn wait(mut self) -> Result<()> {
let http_task = self.http_task.take().context("missing http proxy task")?;
let admin_task = self.admin_task.take().context("missing admin proxy task")?;
let socks_task = self.socks_task.take();
let http_result = http_task.await;
let admin_result = admin_task.await;
let socks_result = match socks_task {
Some(task) => Some(task.await),
None => None,
};
self.completed = true;
http_result??;
admin_result??;
if let Some(socks_result) = socks_result {
socks_result??;
}
Ok(())
}
pub async fn shutdown(mut self) -> Result<()> {
abort_tasks(self.http_task.take(), self.admin_task.take()).await;
abort_tasks(
self.http_task.take(),
self.socks_task.take(),
self.admin_task.take(),
)
.await;
self.completed = true;
Ok(())
}
}
async fn abort_task(task: Option<JoinHandle<Result<()>>>) {
if let Some(task) = task {
task.abort();
let _ = task.await;
}
}
async fn abort_tasks(
http_task: Option<JoinHandle<Result<()>>>,
socks_task: Option<JoinHandle<Result<()>>>,
admin_task: Option<JoinHandle<Result<()>>>,
) {
if let Some(http_task) = http_task {
http_task.abort();
let _ = http_task.await;
}
if let Some(admin_task) = admin_task {
admin_task.abort();
let _ = admin_task.await;
}
abort_task(http_task).await;
abort_task(socks_task).await;
abort_task(admin_task).await;
}
impl Drop for NetworkProxyHandle {
@ -168,9 +201,10 @@ impl Drop for NetworkProxyHandle {
return;
}
let http_task = self.http_task.take();
let socks_task = self.socks_task.take();
let admin_task = self.admin_task.take();
tokio::spawn(async move {
abort_tasks(http_task, admin_task).await;
abort_tasks(http_task, socks_task, admin_task).await;
});
}
}

View file

@ -0,0 +1,318 @@
use crate::config::NetworkMode;
use crate::network_policy::NetworkDecision;
use crate::network_policy::NetworkPolicyDecider;
use crate::network_policy::NetworkPolicyRequest;
use crate::network_policy::NetworkProtocol;
use crate::network_policy::evaluate_host_policy;
use crate::policy::normalize_host;
use crate::reasons::REASON_METHOD_NOT_ALLOWED;
use crate::reasons::REASON_PROXY_DISABLED;
use crate::state::BlockedRequest;
use crate::state::NetworkProxyState;
use anyhow::Context as _;
use anyhow::Result;
use rama_core::Layer;
use rama_core::Service;
use rama_core::error::BoxError;
use rama_core::extensions::ExtensionsRef;
use rama_core::layer::AddInputExtensionLayer;
use rama_core::service::service_fn;
use rama_net::client::EstablishedClientConnection;
use rama_net::stream::SocketInfo;
use rama_socks5::Socks5Acceptor;
use rama_socks5::server::DefaultConnector;
use rama_socks5::server::DefaultUdpRelay;
use rama_socks5::server::udp::RelayRequest;
use rama_socks5::server::udp::RelayResponse;
use rama_tcp::TcpStream;
use rama_tcp::client::Request as TcpRequest;
use rama_tcp::client::service::TcpConnector;
use rama_tcp::server::TcpListener;
use std::io;
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::error;
use tracing::info;
use tracing::warn;
pub async fn run_socks5(
state: Arc<NetworkProxyState>,
addr: SocketAddr,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
enable_socks5_udp: bool,
) -> Result<()> {
let listener = TcpListener::build()
.bind(addr)
.await
// See `http_proxy.rs` for details on why we wrap `BoxError` before converting to anyhow.
.map_err(rama_core::error::OpaqueError::from)
.map_err(anyhow::Error::from)
.with_context(|| format!("bind SOCKS5 proxy: {addr}"))?;
info!("SOCKS5 proxy listening on {addr}");
match state.network_mode().await {
Ok(NetworkMode::Limited) => {
info!("SOCKS5 is blocked in limited mode; set mode=\"full\" to allow SOCKS5");
}
Ok(NetworkMode::Full) => {}
Err(err) => {
warn!("failed to read network mode: {err}");
}
}
let tcp_connector = TcpConnector::default();
let policy_tcp_connector = service_fn({
let policy_decider = policy_decider.clone();
move |req: TcpRequest| {
let tcp_connector = tcp_connector.clone();
let policy_decider = policy_decider.clone();
async move { handle_socks5_tcp(req, tcp_connector, policy_decider).await }
}
});
let socks_connector = DefaultConnector::default().with_connector(policy_tcp_connector);
let base = Socks5Acceptor::new().with_connector(socks_connector);
if enable_socks5_udp {
let udp_state = state.clone();
let udp_decider = policy_decider.clone();
let udp_relay = DefaultUdpRelay::default().with_async_inspector(service_fn({
move |request: RelayRequest| {
let udp_state = udp_state.clone();
let udp_decider = udp_decider.clone();
async move { inspect_socks5_udp(request, udp_state, udp_decider).await }
}
}));
let socks_acceptor = base.with_udp_associator(udp_relay);
listener
.serve(AddInputExtensionLayer::new(state).into_layer(socks_acceptor))
.await;
} else {
listener
.serve(AddInputExtensionLayer::new(state).into_layer(base))
.await;
}
Ok(())
}
async fn handle_socks5_tcp(
req: TcpRequest,
tcp_connector: TcpConnector,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
) -> Result<EstablishedClientConnection<TcpStream, TcpRequest>, BoxError> {
let app_state = req
.extensions()
.get::<Arc<NetworkProxyState>>()
.cloned()
.ok_or_else(|| io::Error::other("missing state"))?;
let host = normalize_host(&req.authority.host.to_string());
let port = req.authority.port;
if host.is_empty() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid host").into());
}
let client = req
.extensions()
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string());
match app_state.enabled().await {
Ok(true) => {}
Ok(false) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
REASON_PROXY_DISABLED.to_string(),
client.clone(),
None,
None,
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!("SOCKS blocked; proxy disabled (client={client}, host={host})");
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "proxy disabled").into());
}
Err(err) => {
error!("failed to read enabled state: {err}");
return Err(io::Error::other("proxy error").into());
}
}
match app_state.network_mode().await {
Ok(NetworkMode::Limited) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
REASON_METHOD_NOT_ALLOWED.to_string(),
client.clone(),
None,
Some(NetworkMode::Limited),
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!(
"SOCKS blocked by method policy (client={client}, host={host}, mode=limited, allowed_methods=GET, HEAD, OPTIONS)"
);
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into());
}
Ok(NetworkMode::Full) => {}
Err(err) => {
error!("failed to evaluate method policy: {err}");
return Err(io::Error::other("proxy error").into());
}
}
let request = NetworkPolicyRequest::new(
NetworkProtocol::Socks5Tcp,
host.clone(),
port,
client.clone(),
None,
None,
None,
);
match evaluate_host_policy(&app_state, policy_decider.as_ref(), &request).await {
Ok(NetworkDecision::Deny { reason }) => {
let _ = app_state
.record_blocked(BlockedRequest::new(
host.clone(),
reason.clone(),
client.clone(),
None,
None,
"socks5".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!("SOCKS blocked (client={client}, host={host}, reason={reason})");
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "blocked").into());
}
Ok(NetworkDecision::Allow) => {
let client = client.as_deref().unwrap_or_default();
info!("SOCKS allowed (client={client}, host={host}, port={port})");
}
Err(err) => {
error!("failed to evaluate host: {err}");
return Err(io::Error::other("proxy error").into());
}
}
tcp_connector.serve(req).await
}
async fn inspect_socks5_udp(
request: RelayRequest,
state: Arc<NetworkProxyState>,
policy_decider: Option<Arc<dyn NetworkPolicyDecider>>,
) -> io::Result<RelayResponse> {
let RelayRequest {
server_address,
payload,
extensions,
..
} = request;
let host = normalize_host(&server_address.ip_addr.to_string());
let port = server_address.port;
if host.is_empty() {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid host"));
}
let client = extensions
.get::<SocketInfo>()
.map(|info| info.peer_addr().to_string());
match state.enabled().await {
Ok(true) => {}
Ok(false) => {
let _ = state
.record_blocked(BlockedRequest::new(
host.clone(),
REASON_PROXY_DISABLED.to_string(),
client.clone(),
None,
None,
"socks5-udp".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!("SOCKS UDP blocked; proxy disabled (client={client}, host={host})");
return Ok(RelayResponse {
maybe_payload: None,
extensions,
});
}
Err(err) => {
error!("failed to read enabled state: {err}");
return Err(io::Error::other("proxy error"));
}
}
match state.network_mode().await {
Ok(NetworkMode::Limited) => {
let _ = state
.record_blocked(BlockedRequest::new(
host.clone(),
REASON_METHOD_NOT_ALLOWED.to_string(),
client.clone(),
None,
Some(NetworkMode::Limited),
"socks5-udp".to_string(),
))
.await;
return Ok(RelayResponse {
maybe_payload: None,
extensions,
});
}
Ok(NetworkMode::Full) => {}
Err(err) => {
error!("failed to evaluate method policy: {err}");
return Err(io::Error::other("proxy error"));
}
}
let request = NetworkPolicyRequest::new(
NetworkProtocol::Socks5Udp,
host.clone(),
port,
client.clone(),
None,
None,
None,
);
match evaluate_host_policy(&state, policy_decider.as_ref(), &request).await {
Ok(NetworkDecision::Deny { reason }) => {
let _ = state
.record_blocked(BlockedRequest::new(
host.clone(),
reason.clone(),
client.clone(),
None,
None,
"socks5-udp".to_string(),
))
.await;
let client = client.as_deref().unwrap_or_default();
warn!("SOCKS UDP blocked (client={client}, host={host}, reason={reason})");
Ok(RelayResponse {
maybe_payload: None,
extensions,
})
}
Ok(NetworkDecision::Allow) => Ok(RelayResponse {
maybe_payload: Some(payload),
extensions,
}),
Err(err) => {
error!("failed to evaluate UDP host: {err}");
Err(io::Error::other("proxy error"))
}
}
}