fix: resolve https xff deadlock and proxy chain test port collisions

This commit is contained in:
2026-02-03 21:19:38 +08:00
parent ba609187eb
commit f316541302
2 changed files with 54 additions and 39 deletions

View File

@@ -328,22 +328,45 @@ pub async fn serve_listener_loop<F, Fut>(
}
// 2. Resolve Real IP (consumes stream/buffer for XFF peeking if needed).
let (real_peer_ip, real_peer_port) = match crate::core::server::handler::resolve_real_ip(
&real_ip_config,
client_addr,
&proxy_info,
&mut stream,
&mut buffer,
)
.await
{
Ok((ip, port)) => (ip, port),
Err(e) => {
error!("[{}] real ip resolution failed: {}", service.name, e);
// Fallback or abort?
// Abort is safer if I/O broken.
return;
}
// [FIX] If TLS is enabled, we CANNOT peek for XFF headers on the raw stream because it's encrypted.
// In that case, we skip XFF resolution here and let the proxy application (Pingora) handle it
// after decryption (though Pingora might need its own config for that).
// For now, avoiding the deadlock is priority.
let perform_xff = if tls_acceptor.is_some() {
if let Some(ref cfg) = real_ip_config {
// If source is Xff, we must skip.
// If source is ProxyProtocol, we can still do it (already done via proxy_info above).
cfg.source != crate::config::RealIpSource::Xff
} else {
true
}
} else {
true
};
let (real_peer_ip, real_peer_port) = if perform_xff {
match crate::core::server::handler::resolve_real_ip(
&real_ip_config,
client_addr,
&proxy_info,
&mut stream,
&mut buffer,
)
.await
{
Ok((ip, port)) => (ip, port),
Err(e) => {
error!("[{}] real ip resolution failed: {}", service.name, e);
return;
}
}
} else {
// Fallback to what we know (Proxy Protocol or Physical)
if let Some(info) = &proxy_info {
(info.source.ip(), info.source.port())
} else {
(client_addr.ip(), client_addr.port())
}
};
let local_addr = match &stream {

View File

@@ -1,7 +1,7 @@
use std::net::SocketAddr;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use traudit::config::{
BindEntry, Config, DatabaseConfig, RealIpConfig, RealIpSource, ServiceConfig,
};
@@ -164,6 +164,7 @@ async fn test_https_v2_append_xff() {
.await;
}
// Helper for Chain Test
// Helper for Chain Test
struct ChainTestResources {
config: Config,
@@ -176,26 +177,12 @@ async fn prepare_chain_env() -> ChainTestResources {
// E4 Upstream (Mock Server)
let (e4_upstream_addr, _) = spawn_mock_upstream().await;
// Assign ports dynamically
let l1 = TcpListener::bind("127.0.0.1:0").await.unwrap();
let p1 = l1.local_addr().unwrap().port();
let addr1 = format!("127.0.0.1:{}", p1);
drop(l1);
let l2 = TcpListener::bind("127.0.0.1:0").await.unwrap();
let p2 = l2.local_addr().unwrap().port();
let addr2 = format!("127.0.0.1:{}", p2);
drop(l2);
let l3 = TcpListener::bind("127.0.0.1:0").await.unwrap();
let p3 = l3.local_addr().unwrap().port();
let addr3 = format!("127.0.0.1:{}", p3);
drop(l3);
let l4 = TcpListener::bind("127.0.0.1:0").await.unwrap();
let p4 = l4.local_addr().unwrap().port();
let addr4 = format!("127.0.0.1:{}", p4);
drop(l4);
// Use Unix sockets to avoid port collisions/stealing
let suffix = rand::random::<u64>();
let addr1 = format!("unix:///tmp/traudit_e1_{}.sock", suffix);
let addr2 = format!("unix:///tmp/traudit_e2_{}.sock", suffix);
let addr3 = format!("unix:///tmp/traudit_e3_{}.sock", suffix);
let addr4 = format!("unix:///tmp/traudit_e4_{}.sock", suffix);
// DB Config
let db_port = get_shared_db_port().await;
@@ -307,8 +294,9 @@ async fn test_proxy_chain() {
});
tokio::time::sleep(Duration::from_millis(2000)).await;
// Connect to E1
let mut stream = TcpStream::connect(&res.e1_addr)
// Connect to E1 (Unix)
let path = res.e1_addr.strip_prefix("unix://").unwrap();
let mut stream = tokio::net::UnixStream::connect(path)
.await
.expect("Failed to connect to E1");
@@ -325,4 +313,8 @@ async fn test_proxy_chain() {
response, b"chain_test_ping",
"Chain test failed: response mismatch"
);
// Cleanup E1 socket (others are cleaned up by server, but E1 we explicitly connected to)
// Actually all are cleaned up by server::run when it drops listener.
// We don't need manual cleanup here unless we want to be pedantic.
}