ostp/ostp-server/src/fallback.rs

88 lines
2.8 KiB
Rust

//! Fallback TCP server for anti-DPI camouflage.
//!
//! When a connection arrives that doesn't match the OSTP protocol
//! (e.g., a DPI probe, web spider, or direct HTTP request),
//! it gets transparently proxied to a fallback web server (e.g., nginx).
//!
//! This makes the OSTP server indistinguishable from a regular web server
//! during active probing.
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
/// Fallback server configuration.
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct FallbackConfig {
/// Enable fallback TCP listener
pub enabled: bool,
/// TCP listen address (e.g., "0.0.0.0:443" or "0.0.0.0:80")
pub listen: String,
/// Target to proxy to (e.g., "127.0.0.1:8080" for local nginx)
pub target: String,
}
/// Start the fallback TCP proxy server.
pub async fn start_fallback_server(config: FallbackConfig) {
let listener = match TcpListener::bind(&config.listen).await {
Ok(l) => l,
Err(e) => {
tracing::error!("fallback server failed to bind on {}: {}", config.listen, e);
return;
}
};
tracing::info!("fallback server listening on {} -> {}", config.listen, config.target);
loop {
match listener.accept().await {
Ok((client, peer_addr)) => {
let target = config.target.clone();
tokio::spawn(async move {
if let Err(e) = proxy_connection(client, &target).await {
tracing::debug!(peer = %peer_addr, "fallback proxy error: {}", e);
}
});
}
Err(e) => {
tracing::warn!("fallback accept error: {}", e);
}
}
}
}
async fn proxy_connection(mut client: TcpStream, target: &str) -> anyhow::Result<()> {
let mut upstream = TcpStream::connect(target).await?;
let (mut client_read, mut client_write) = client.split();
let (mut upstream_read, mut upstream_write) = upstream.split();
let client_to_upstream = async {
let mut buf = vec![0u8; 8192];
loop {
let n = client_read.read(&mut buf).await?;
if n == 0 { break; }
upstream_write.write_all(&buf[..n]).await?;
}
upstream_write.shutdown().await?;
Ok::<_, anyhow::Error>(())
};
let upstream_to_client = async {
let mut buf = vec![0u8; 8192];
loop {
let n = upstream_read.read(&mut buf).await?;
if n == 0 { break; }
client_write.write_all(&buf[..n]).await?;
}
client_write.shutdown().await?;
Ok::<_, anyhow::Error>(())
};
tokio::select! {
r = client_to_upstream => { r?; }
r = upstream_to_client => { r?; }
}
Ok(())
}