use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use subtle::ConstantTimeEq;
use tokio::net::{TcpListener, TcpStream};
use tokio::sync::{mpsc, oneshot, Mutex};
use super::header::{BridgeHeader, Direction};
use crate::network::{IncomingConnection, NetworkError};
const MAX_CONCURRENT_CONNECTIONS: usize = 256;
const HEADER_READ_TIMEOUT: Duration = Duration::from_secs(2);
pub(crate) const DIAL_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) struct Bridge {
listener: TcpListener,
session_token: [u8; 32],
pending_dials: Arc<Mutex<HashMap<String, oneshot::Sender<TcpStream>>>>,
incoming_channels: Arc<Mutex<HashMap<u16, mpsc::Sender<IncomingConnection>>>>,
semaphore: Arc<tokio::sync::Semaphore>,
}
impl Bridge {
pub async fn bind(session_token: [u8; 32]) -> Result<Self, NetworkError> {
let listener = TcpListener::bind("127.0.0.1:0").await?;
Ok(Self {
listener,
session_token,
pending_dials: Arc::new(Mutex::new(HashMap::new())),
incoming_channels: Arc::new(Mutex::new(HashMap::new())),
semaphore: Arc::new(tokio::sync::Semaphore::new(MAX_CONCURRENT_CONNECTIONS)),
})
}
pub fn local_port(&self) -> Result<u16, NetworkError> {
Ok(self
.listener
.local_addr()
.map_err(|e| NetworkError::Internal(format!("bridge listener local_addr: {e}")))?
.port())
}
pub async fn register_dial(&self, request_id: String) -> oneshot::Receiver<TcpStream> {
let (tx, rx) = oneshot::channel();
self.pending_dials.lock().await.insert(request_id, tx);
rx
}
pub async fn remove_dial(&self, request_id: &str) {
self.pending_dials.lock().await.remove(request_id);
}
pub async fn register_listener(
&self,
port: u16,
tx: mpsc::Sender<IncomingConnection>,
) {
self.incoming_channels.lock().await.insert(port, tx);
}
pub async fn remove_listener(&self, port: u16) {
self.incoming_channels.lock().await.remove(&port);
}
pub async fn run(&self, shutdown: tokio::sync::watch::Receiver<bool>) {
let mut shutdown = shutdown;
loop {
tokio::select! {
result = self.listener.accept() => {
match result {
Ok((stream, _addr)) => {
let permit = match self.semaphore.clone().try_acquire_owned() {
Ok(p) => p,
Err(_) => {
tracing::warn!("bridge: max connections reached, dropping");
drop(stream);
continue;
}
};
let session_token = self.session_token;
let pending_dials = self.pending_dials.clone();
let incoming_channels = self.incoming_channels.clone();
tokio::spawn(async move {
let _permit = permit;
if let Err(e) = Self::handle_connection(
stream,
session_token,
pending_dials,
incoming_channels,
).await {
tracing::debug!("bridge connection error: {e}");
}
});
}
Err(e) => {
tracing::error!("bridge accept error: {e}");
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
}
_ = shutdown.changed() => {
tracing::info!("bridge shutting down");
break;
}
}
}
}
async fn handle_connection(
mut stream: TcpStream,
expected_token: [u8; 32],
pending_dials: Arc<Mutex<HashMap<String, oneshot::Sender<TcpStream>>>>,
incoming_channels: Arc<Mutex<HashMap<u16, mpsc::Sender<IncomingConnection>>>>,
) -> Result<(), NetworkError> {
let header = tokio::time::timeout(
HEADER_READ_TIMEOUT,
BridgeHeader::read_from(&mut stream),
)
.await
.map_err(|_| NetworkError::BridgeError("header read timeout".into()))?
.map_err(|e| NetworkError::BridgeError(format!("header parse error: {e}")))?;
if header.session_token.ct_eq(&expected_token).unwrap_u8() != 1 {
tracing::warn!("bridge: rejected connection with invalid session token");
return Err(NetworkError::BridgeError("invalid session token".into()));
}
match header.direction {
Direction::Outgoing => {
let request_id = &header.request_id;
if request_id.is_empty() {
return Err(NetworkError::BridgeError(
"outgoing connection has no request_id".into(),
));
}
let sender = pending_dials.lock().await.remove(request_id);
match sender {
Some(tx) => {
tracing::debug!("bridge: delivering dial result for {request_id}");
let _ = tx.send(stream);
}
None => {
tracing::warn!(
"bridge: no pending dial for request_id={request_id}, dropping"
);
}
}
}
Direction::Incoming => {
let port = header.service_port;
let channels = incoming_channels.lock().await;
if let Some(tx) = channels.get(&port) {
let conn = IncomingConnection {
stream,
remote_addr: header.remote_addr,
remote_identity: header.remote_dns_name,
port,
};
if tx.send(conn).await.is_err() {
tracing::warn!("bridge: listener channel for port {port} closed");
}
} else {
tracing::warn!(
"bridge: no listener registered for port {port}, dropping connection"
);
}
}
}
Ok(())
}
}