fix(network): preserve remote DNS semantics for SOCKS proxies

This commit is contained in:
elky
2026-09-04 21:10:55 +08:00
parent 018af84d7d
commit b08fa3bdb6
3 changed files with 86 additions and 10 deletions
@@ -1272,7 +1272,25 @@ async fn test_manual_proxy_connectivity_with_probe_url(
timeout_secs: u64,
) -> Value {
let started_at = Instant::now();
let proxy = match reqwest::Proxy::all(proxy_url) {
// reqwest resolves the destination locally for `socks5://`, which would
// bypass the gateway's private-address/DNS-rebinding guard. Normalize
// legacy SOCKS URLs to the remote-DNS form before probing; HTTP/HTTPS and
// already-normalized `socks5h://` URLs are unchanged.
let proxy_url =
match crate::execution_runtime::transport::normalize_execution_proxy_url(proxy_url) {
Ok(proxy_url) => proxy_url,
Err(_) => {
return build_proxy_connectivity_result(
probe_url,
timeout_secs,
false,
None,
None,
Some("代理 URL 无效".to_string()),
);
}
};
let proxy = match reqwest::Proxy::all(&proxy_url) {
Ok(proxy) => proxy,
Err(error) => {
return build_proxy_connectivity_result(
@@ -23,7 +23,8 @@ use wreq::ws::message::{CloseFrame as WreqCloseFrame, Message as WreqWsMessage};
use crate::ai_serving::AiExecutionDecision;
use crate::execution_runtime::transport::{
build_browser_wreq_client, build_request_headers, ExecutionTransportControls,
build_browser_wreq_client, build_request_headers, normalize_execution_proxy_url,
ExecutionTransportControls,
};
use crate::frontdoor_loop_guard::gateway_frontdoor_self_loop_guard_error;
use crate::handlers::proxy::websocket::session::{
@@ -287,10 +288,10 @@ async fn build_websocket_client(
builder.build().map_err(|_| errors.client_build_failed)
}
fn resolve_websocket_proxy_url<'a>(
proxy: Option<&'a ProxySnapshot>,
fn resolve_websocket_proxy_url(
proxy: Option<&ProxySnapshot>,
errors: UpstreamWebSocketErrorCodes,
) -> Result<Option<&'a str>, &'static str> {
) -> Result<Option<String>, &'static str> {
let Some(proxy) = proxy else {
return Ok(None);
};
@@ -323,7 +324,9 @@ fn resolve_websocket_proxy_url<'a>(
{
return Err(errors.proxy_invalid);
}
return Ok(Some(proxy_url));
let normalized =
normalize_execution_proxy_url(proxy_url).map_err(|_| errors.proxy_invalid)?;
return Ok(Some(normalized));
}
if proxy.node_id.is_some() || proxy.mode.as_deref() == Some("tunnel") {
return Err(errors.tunnel_proxy_unsupported);
@@ -976,7 +979,19 @@ mod tests {
};
assert_eq!(
resolve_websocket_proxy_url(Some(&authenticated_node), errors),
Ok(Some("http://alice:[email protected]:8080"))
Ok(Some(
"http://alice:[email protected]:8080/".to_string()
))
);
let socks = ProxySnapshot {
enabled: Some(true),
url: Some("socks5://proxy.example:1080".to_string()),
..ProxySnapshot::default()
};
assert_eq!(
resolve_websocket_proxy_url(Some(&socks), errors),
Ok(Some("socks5h://proxy.example:1080".to_string()))
);
}