mirror of
https://github.com/awfufu/traudit
synced 2026-03-01 05:29:44 +08:00
fix: resolve https xff deadlock and proxy chain test port collisions
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user