use std::io;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use bytes::Bytes;
use http::Response;
use log::{debug, info};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::mpsc;
use tokio::task::{AbortHandle, JoinSet};
use tokio::time::{Duration, interval, timeout};
use crate::client_proxy_selector::{ClientProxySelector, ConnectDecision};
use crate::copy_bidirectional::copy_bidirectional;
use crate::resolver::Resolver;
use crate::routing::{ServerStream, run_udp_routing};
use crate::tcp::tcp_server::run_udp_copy;
use crate::uot::SocksPacketAddrStream;
use crate::util::{SHUTDOWN_TIMEOUT, shutdown_stream, timeout_stream_setup};
use crate::vless::VlessMessageStream;
use super::MuxProtocol;
use super::activity_tracked_stream::ActivityTrackedStream;
use super::activity_tracker::{ActivityTracker, IDLE_TIMEOUT, SHUTDOWN_DRAIN_TIMEOUT};
use super::h2mux_padding::H2MuxPaddingStream;
use super::h2mux_protocol::{SessionRequest, StreamRequest};
use super::h2mux_server_stream::H2MuxServerStream;
use super::h2mux_stream::H2MuxStream;
use crate::prepend_stream::PrependStream;
const STREAM_WINDOW_SIZE: u32 = 256 * 1024; const CONNECTION_WINDOW_SIZE: u32 = 1 << 20; const MAX_FRAME_SIZE: u32 = 16 * 1024;
const INBOUND_BUFFER: usize = 128;
pub struct InboundStream {
_permit: crate::resources::BudgetPermit,
_local_permit: crate::resources::BudgetPermit,
pub stream: H2MuxServerStream,
pub request: StreamRequest,
}
pub struct H2MuxServerSession {
driver: AbortHandle,
inbound_rx: mpsc::Receiver<InboundStream>,
#[allow(dead_code)]
is_closed: Arc<AtomicBool>,
protocol: MuxProtocol,
padding_enabled: bool,
}
impl Drop for H2MuxServerSession {
fn drop(&mut self) {
self.close();
}
}
impl H2MuxServerSession {
pub async fn new<IO>(mut conn: IO) -> io::Result<Self>
where
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let session_req = SessionRequest::decode(&mut conn).await?;
if session_req.protocol != MuxProtocol::H2Mux {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("unsupported mux protocol: {:?}", session_req.protocol),
));
}
debug!(
"H2MuxServerSession: received session request (version={}, protocol={:?}, padding={})",
session_req.version, session_req.protocol, session_req.padding
);
if session_req.padding {
let padded = H2MuxPaddingStream::new(conn);
Self::handshake_and_spawn(padded, session_req.protocol, session_req.padding).await
} else {
Self::handshake_and_spawn(conn, session_req.protocol, session_req.padding).await
}
}
async fn handshake_and_spawn<IO>(
conn: IO,
protocol: MuxProtocol,
padding_enabled: bool,
) -> io::Result<Self>
where
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
let (inbound_tx, inbound_rx) = mpsc::channel(INBOUND_BUFFER);
let is_closed = Arc::new(AtomicBool::new(false));
let activity = ActivityTracker::new();
let conn = ActivityTrackedStream::new(conn, activity.clone());
let connection = h2::server::Builder::new()
.initial_window_size(STREAM_WINDOW_SIZE)
.initial_connection_window_size(CONNECTION_WINDOW_SIZE)
.max_frame_size(MAX_FRAME_SIZE)
.max_concurrent_streams(
crate::resources::limits()
.max_streams_per_connection
.unwrap_or(u32::MAX as usize) as u32,
)
.handshake(conn)
.await
.map_err(|e| io::Error::other(format!("H2 server handshake failed: {}", e)))?;
debug!("H2MuxServerSession: H2 handshake complete");
let is_closed_clone = Arc::clone(&is_closed);
let driver = tokio::spawn(async move {
Self::accept_loop(connection, inbound_tx, is_closed_clone, activity).await;
});
Ok(Self {
driver: driver.abort_handle(),
inbound_rx,
is_closed,
protocol,
padding_enabled,
})
}
async fn accept_loop(
mut connection: h2::server::Connection<impl AsyncRead + AsyncWrite + Unpin, Bytes>,
inbound_tx: mpsc::Sender<InboundStream>,
is_closed: Arc<AtomicBool>,
activity: ActivityTracker,
) {
let check_interval = IDLE_TIMEOUT / 6;
let mut idle_timer = interval(check_interval);
idle_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
idle_timer.tick().await;
let mut tasks = JoinSet::new();
let slots = Arc::new(crate::resources::Budget::new(
crate::resources::limits().max_streams_per_connection,
));
loop {
tokio::select! {
_ = tasks.join_next(), if !tasks.is_empty() => {}
result = connection.accept() => {
match result {
Some(Ok((request, respond))) => {
let inbound_tx = inbound_tx.clone();
let slots = slots.clone();
tasks.spawn(async move {
if let Err(e) = Self::handle_stream(request, respond, inbound_tx, slots).await {
debug!("H2MuxServerSession: stream error: {}", e);
}
});
}
Some(Err(e)) => {
debug!("H2MuxServerSession: accept error: {}", e);
break;
}
None => break,
}
}
_ = idle_timer.tick() => {
if is_closed.load(Ordering::Relaxed) {
break;
}
if activity.is_idle(IDLE_TIMEOUT) {
debug!("H2MuxServerSession: idle timeout, initiating graceful shutdown");
connection.graceful_shutdown();
let drain_result = tokio::time::timeout(SHUTDOWN_DRAIN_TIMEOUT, async {
loop {
let result = tokio::select! {
result = connection.accept() => result,
_ = tasks.join_next(), if !tasks.is_empty() => continue,
};
let Some(result) = result else { break };
match result {
Ok((request, respond)) => {
let inbound_tx = inbound_tx.clone();
let slots = slots.clone();
tasks.spawn(async move {
if let Err(e) = Self::handle_stream(request, respond, inbound_tx, slots).await {
debug!("H2MuxServerSession: stream error during drain: {}", e);
}
});
}
Err(e) => {
debug!("H2MuxServerSession: error during drain, stopping: {}", e);
break;
}
}
}
}).await;
if drain_result.is_err() {
debug!("H2MuxServerSession: drain timeout, forcing close");
}
break;
}
}
}
}
is_closed.store(true, Ordering::Relaxed);
debug!("H2MuxServerSession: accept loop ended");
}
async fn handle_stream(
request: http::Request<h2::RecvStream>,
mut respond: h2::server::SendResponse<Bytes>,
inbound_tx: mpsc::Sender<InboundStream>,
slots: Arc<crate::resources::Budget>,
) -> io::Result<()> {
let Some(local_permit) = slots.acquire(1) else {
respond.send_reset(h2::Reason::REFUSED_STREAM);
return Ok(());
};
let permit = match crate::resources::try_stream() {
Some(permit) => permit,
None => {
respond.send_reset(h2::Reason::REFUSED_STREAM);
return Ok(());
}
};
let response = Response::builder()
.status(http::StatusCode::OK)
.body(())
.map_err(|e| io::Error::other(format!("failed to build H2 response: {e}")))?;
let send_stream = respond
.send_response(response, false)
.map_err(|e| io::Error::other(format!("failed to send H2 response: {e}")))?;
let recv_stream = request.into_body();
let mut stream = H2MuxStream::new(send_stream, recv_stream);
let stream_request = timeout(
Duration::from_secs(30),
StreamRequest::decode_async(&mut stream),
)
.await??;
debug!(
"H2MuxServerSession: new stream to {} ({})",
stream_request.destination, stream_request.network
);
let server_stream = H2MuxServerStream::new(stream);
let inbound = InboundStream {
_permit: permit,
_local_permit: local_permit,
stream: server_stream,
request: stream_request,
};
inbound_tx
.send(inbound)
.await
.map_err(|_| io::Error::new(io::ErrorKind::BrokenPipe, "inbound channel closed"))?;
Ok(())
}
pub async fn accept(&mut self) -> Option<InboundStream> {
self.inbound_rx.recv().await
}
#[allow(dead_code)]
pub fn is_closed(&self) -> bool {
self.is_closed.load(Ordering::Relaxed)
}
#[allow(dead_code)]
pub fn close(&self) {
self.is_closed.store(true, Ordering::Relaxed);
self.driver.abort();
}
pub fn protocol(&self) -> MuxProtocol {
self.protocol
}
pub fn padding_enabled(&self) -> bool {
self.padding_enabled
}
}
#[allow(dead_code)]
pub async fn run_h2mux_server<IO, F, Fut>(conn: IO, handler: F) -> io::Result<()>
where
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
F: Fn(InboundStream) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = io::Result<()>> + Send + 'static,
{
let mut session = timeout(Duration::from_secs(30), H2MuxServerSession::new(conn)).await??;
let mut tasks = JoinSet::new();
loop {
let inbound = tokio::select! {
inbound = session.accept() => inbound,
_ = tasks.join_next(), if !tasks.is_empty() => continue,
};
let Some(inbound) = inbound else { break };
let fut = handler(inbound);
tasks.spawn(async move {
if let Err(e) = fut.await {
debug!("H2MUX stream handler error: {}", e);
}
});
}
Ok(())
}
pub async fn handle_h2mux_session<S>(
stream: S,
initial_data: Option<Box<[u8]>>,
udp_enabled: bool,
proxy_selector: Arc<ClientProxySelector>,
resolver: Arc<dyn Resolver>,
) -> io::Result<()>
where
S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
info!("H2MUX: Starting server session");
let stream = PrependStream::new(stream, initial_data);
let mut session = timeout(Duration::from_secs(30), H2MuxServerSession::new(stream)).await??;
let mut tasks = JoinSet::new();
info!(
"H2MUX: Session established (protocol={:?}, padding={})",
session.protocol(),
session.padding_enabled()
);
loop {
let inbound = tokio::select! {
inbound = session.accept() => inbound,
_ = tasks.join_next(), if !tasks.is_empty() => continue,
};
let Some(inbound) = inbound else { break };
let proxy_selector = proxy_selector.clone();
let resolver = resolver.clone();
tasks.spawn(async move {
if let Err(e) =
handle_h2mux_stream(inbound, udp_enabled, proxy_selector, resolver).await
{
debug!("H2MUX stream error: {}", e);
}
});
}
debug!("H2MUX: Session ended");
Ok(())
}
async fn handle_h2mux_stream(
inbound: InboundStream,
udp_enabled: bool,
proxy_selector: Arc<ClientProxySelector>,
resolver: Arc<dyn Resolver>,
) -> io::Result<()> {
let InboundStream {
_permit,
_local_permit,
mut stream,
request,
} = inbound;
let is_udp = request.is_udp();
let packet_addr = request.packet_addr;
let destination = request.destination;
info!(
"H2MUX stream: {} -> {} (udp={}, packet_addr={})",
if is_udp { "UDP" } else { "TCP" },
destination,
is_udp,
packet_addr
);
if !is_udp {
return handle_h2mux_tcp(stream, destination, proxy_selector, resolver).await;
}
if !udp_enabled {
reject_h2mux_stream(&mut stream, "UDP not enabled").await;
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"UDP not enabled",
));
}
if packet_addr {
handle_h2mux_udp_packet_addr(stream, proxy_selector, resolver).await
} else {
handle_h2mux_udp(stream, destination, proxy_selector, resolver).await
}
}
async fn reject_h2mux_stream(stream: &mut H2MuxServerStream, message: &str) {
let _ = timeout(SHUTDOWN_TIMEOUT, stream.write_error_response(message)).await;
shutdown_stream(stream).await;
}
async fn handle_h2mux_tcp(
mut stream: H2MuxServerStream,
destination: crate::address::NetLocation,
proxy_selector: Arc<ClientProxySelector>,
resolver: Arc<dyn Resolver>,
) -> io::Result<()> {
let action =
timeout_stream_setup(proxy_selector.judge(destination.clone().into(), &resolver)).await?;
match action {
ConnectDecision::Allow {
chain_group,
remote_location,
} => {
debug!("H2MUX TCP: connecting to {} via chain", remote_location);
let client_result =
timeout_stream_setup(chain_group.connect_tcp(remote_location, &resolver)).await?;
let mut client_stream = client_result.client_stream;
let result = copy_bidirectional(&mut stream, &mut *client_stream, false, false).await;
futures::join!(
shutdown_stream(&mut stream),
shutdown_stream(&mut client_stream),
);
result
}
ConnectDecision::Block => {
debug!("H2MUX TCP: blocked by rules: {}", destination);
reject_h2mux_stream(&mut stream, "Connection blocked by rules").await;
Err(io::Error::new(
io::ErrorKind::ConnectionRefused,
format!("Connection to {} blocked", destination),
))
}
}
}
async fn handle_h2mux_udp(
mut stream: H2MuxServerStream,
destination: crate::address::NetLocation,
proxy_selector: Arc<ClientProxySelector>,
resolver: Arc<dyn Resolver>,
) -> io::Result<()> {
debug!("H2MUX UDP fixed: {}", destination);
let action = timeout_stream_setup(proxy_selector.judge(destination.into(), &resolver)).await?;
match action {
ConnectDecision::Allow {
chain_group,
remote_location,
} => {
let client_stream = timeout_stream_setup(
chain_group.connect_udp_bidirectional(&resolver, remote_location),
)
.await?;
let server_stream = VlessMessageStream::new(Box::new(stream));
run_udp_copy(Box::new(server_stream), client_stream, false, false).await
}
ConnectDecision::Block => {
reject_h2mux_stream(&mut stream, "Connection blocked by rules").await;
Err(io::Error::new(
io::ErrorKind::ConnectionRefused,
"UDP connection blocked by rules",
))
}
}
}
async fn handle_h2mux_udp_packet_addr(
stream: H2MuxServerStream,
proxy_selector: Arc<ClientProxySelector>,
resolver: Arc<dyn Resolver>,
) -> io::Result<()> {
debug!("H2MUX UDP packet_addr mode - entering");
let packet_stream = SocksPacketAddrStream::new_socks(Box::new(stream));
debug!("H2MUX UDP packet_addr mode - starting routing");
let result = run_udp_routing(
ServerStream::Targeted(Box::new(packet_stream)),
proxy_selector,
resolver,
false,
)
.await;
debug!("H2MUX UDP packet_addr mode - routing ended: {:?}", result);
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::h2mux::{H2MuxClientSession, H2MuxOptions};
use tokio::io::AsyncWriteExt;
#[tokio::test]
async fn dropping_server_closes_driver_and_waiting_streams() {
let (client, server) = tokio::io::duplex(65536);
let options = H2MuxOptions::default();
let (client, server) = tokio::join!(
H2MuxClientSession::new(client, &options),
H2MuxServerSession::new(server),
);
let mut client = client.unwrap();
let mut server = server.unwrap();
let driver = server.driver.clone();
let location = crate::address::NetLocation::from_str("example.com:443", None).unwrap();
let mut stream = client.open_tcp(&location).await.unwrap();
stream.write_all(b"probe").await.unwrap();
stream.flush().await.unwrap();
let inbound = timeout(Duration::from_secs(1), server.accept())
.await
.unwrap()
.unwrap();
drop(server);
timeout(Duration::from_secs(1), async {
while !driver.is_finished() {
tokio::task::yield_now().await;
}
})
.await
.unwrap();
drop(inbound);
drop(stream);
}
}