#![warn(missing_docs)]
mod tcp;
mod udp;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use bytes::Bytes;
use rings_core::dht::Did;
use tokio::net::TcpStream;
use tokio::net::UdpSocket;
use tokio::sync::mpsc;
use tokio::time::timeout;
use tokio_util::sync::CancellationToken;
use crate::error::Error;
use crate::error::Result;
use crate::extension::ext::Scope;
use crate::extension::protocols::relay::RelayCommand;
use crate::extension::transport::Frame;
use crate::extension::transport::Initiator;
use crate::extension::transport::SessionKey;
use crate::extension::transport::TransportKind;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
const TCP_BUF: usize = 30_000;
const UDP_BUF: usize = 65_536;
enum Outbound {
Data(Bytes),
Shutdown,
}
struct SessionHandle {
outbound: mpsc::Sender<Outbound>,
cancel: CancellationToken,
src: Option<SocketAddr>,
generation: u64,
}
enum Pending {
Tcp(TcpStream),
Udp {
socket: Arc<UdpSocket>,
src: SocketAddr,
first: Bytes,
},
}
#[derive(Default)]
pub(crate) struct TransportSessions {
map: Mutex<HashMap<SessionKey, SessionHandle>>,
tokens: AtomicU64,
generations: AtomicU64,
pending: Mutex<HashMap<u64, Pending>>,
udp_flows: Mutex<HashMap<SocketAddr, SessionKey>>,
}
impl TransportSessions {
pub fn new() -> Self {
Self::default()
}
fn next_token(&self) -> u64 {
self.tokens.fetch_add(1, Ordering::Relaxed)
}
pub async fn connect(
self: Arc<Self>,
scope: Scope,
key: SessionKey,
addr: SocketAddr,
kind: TransportKind,
) {
debug_assert_eq!(
scope.namespace(),
key.namespace.as_str(),
"relay engine acted with a scope outside the session's namespace"
);
let task = RelayTask::register(self.clone(), scope, key);
tokio::spawn(async move {
match kind {
TransportKind::Tcp => {
match timeout(CONNECT_TIMEOUT, TcpStream::connect(addr)).await {
Ok(Ok(stream)) => tcp::relay_tcp(task, stream).await,
_ => task.refuse().await,
}
}
TransportKind::Udp => match udp::bind_connected_udp(addr).await {
Some(socket) => udp::relay_udp_connected(task, socket).await,
None => task.refuse().await,
},
}
});
}
pub async fn listen(
self: Arc<Self>,
scope: Scope,
local_addr: SocketAddr,
peer: Did,
service: String,
kind: TransportKind,
) {
match kind {
TransportKind::Tcp => self.listen_tcp(scope, local_addr, peer, service).await,
TransportKind::Udp => self.listen_udp(scope, local_addr, peer, service).await,
}
}
pub async fn write(&self, key: &SessionKey, bytes: Bytes) {
if let Some(tx) = self.sender(key) {
let _ = tx.send(Outbound::Data(bytes)).await;
}
}
pub async fn shutdown(&self, key: &SessionKey) {
if let Some(tx) = self.sender(key) {
let _ = tx.send(Outbound::Shutdown).await;
}
}
pub async fn close(&self, scope: &Scope, key: &SessionKey) {
let removed = self.map.lock().ok().and_then(|mut map| map.remove(key));
self.finish_close(scope, key, removed).await;
}
async fn close_if_current(&self, scope: &Scope, key: &SessionKey, generation: u64) -> bool {
let removed = self.map.lock().ok().and_then(|mut map| {
let current = map.get(key).map(|h| h.generation);
(current == Some(generation))
.then(|| map.remove(key))
.flatten()
});
self.finish_close(scope, key, removed).await
}
async fn finish_close(
&self,
scope: &Scope,
key: &SessionKey,
removed: Option<SessionHandle>,
) -> bool {
let Some(handle) = removed else {
return false;
};
handle.cancel.cancel();
if let Some(src) = handle.src {
if let Ok(mut flows) = self.udp_flows.lock() {
flows.remove(&src);
}
}
inject_untrack(scope, key).await;
true
}
pub async fn bind_accepted(
self: Arc<Self>,
scope: Scope,
token: u64,
key: SessionKey,
service: String,
) {
debug_assert_eq!(
scope.namespace(),
key.namespace.as_str(),
"relay engine bound a session under a foreign namespace scope"
);
let Some(pending) = self.pending.lock().ok().and_then(|mut p| p.remove(&token)) else {
return; };
match pending {
Pending::Tcp(stream) => {
let task = RelayTask::register(self.clone(), scope.clone(), key.clone());
if open(&scope, &key, service.as_str()).await.is_err() {
task.refuse().await;
return;
}
tokio::spawn(async move { tcp::relay_tcp(task, stream).await });
}
Pending::Udp { socket, src, first } => {
let (outbound_rx, cancel, generation) = self.register(key.clone(), Some(src));
if let Ok(mut flows) = self.udp_flows.lock() {
flows.insert(src, key.clone());
}
udp::spawn_udp_sendto(socket, src, outbound_rx, cancel);
if open(&scope, &key, service.as_str()).await.is_err() {
self.close_if_current(&scope, &key, generation).await;
return;
}
let from_opener = opened_by_us(&key);
let _ = send_frame(&scope, key.peer, Frame::Data {
session: key.session,
from_opener,
bytes: first,
})
.await;
}
}
}
fn udp_flow(&self, src: &SocketAddr) -> Option<SessionKey> {
let key = self.udp_flows.lock().ok()?.get(src).cloned()?;
self.is_live(&key).then_some(key)
}
fn stash_pending(&self, pending: Pending) -> Option<u64> {
let token = self.next_token();
self.pending.lock().ok()?.insert(token, pending);
Some(token)
}
fn evict_pending(&self, token: u64) {
if let Ok(mut pending) = self.pending.lock() {
pending.remove(&token);
}
}
fn register(
&self,
key: SessionKey,
src: Option<SocketAddr>,
) -> (mpsc::Receiver<Outbound>, CancellationToken, u64) {
let (outbound, outbound_rx) = mpsc::channel::<Outbound>(1024);
let cancel = CancellationToken::new();
let generation = self.generations.fetch_add(1, Ordering::Relaxed);
self.insert(key, SessionHandle {
outbound,
cancel: cancel.clone(),
src,
generation,
});
(outbound_rx, cancel, generation)
}
fn sender(&self, key: &SessionKey) -> Option<mpsc::Sender<Outbound>> {
self.map
.lock()
.ok()
.and_then(|map| map.get(key).map(|handle| handle.outbound.clone()))
}
fn is_live(&self, key: &SessionKey) -> bool {
self.map
.lock()
.map(|map| map.contains_key(key))
.unwrap_or(false)
}
fn insert(&self, key: SessionKey, handle: SessionHandle) {
if let Ok(mut map) = self.map.lock() {
if let Some(old) = map.insert(key, handle) {
old.cancel.cancel();
}
}
}
}
struct RelayTask {
sessions: Arc<TransportSessions>,
scope: Scope,
key: SessionKey,
outbound_rx: mpsc::Receiver<Outbound>,
cancel: CancellationToken,
generation: u64,
}
impl RelayTask {
fn register(sessions: Arc<TransportSessions>, scope: Scope, key: SessionKey) -> Self {
let (outbound_rx, cancel, generation) = sessions.register(key.clone(), None);
Self {
sessions,
scope,
key,
outbound_rx,
cancel,
generation,
}
}
async fn refuse(self) {
if self
.sessions
.close_if_current(&self.scope, &self.key, self.generation)
.await
{
let _ = send_frame(&self.scope, self.key.peer, Frame::Close {
session: self.key.session,
from_opener: opened_by_us(&self.key),
})
.await;
}
}
}
fn opened_by_us(key: &SessionKey) -> bool {
matches!(key.initiator, Initiator::Local)
}
async fn open(scope: &Scope, key: &SessionKey, service: &str) -> Result<()> {
send_frame(scope, key.peer, Frame::Open {
session: key.session,
service: service.to_string(),
})
.await
}
async fn send_frame(scope: &Scope, peer: Did, frame: Frame) -> Result<()> {
let payload = bincode::serialize(&frame).map_err(|_| Error::EncodeError)?;
scope.send(peer, Bytes::from(payload)).await
}
async fn inject_accepted(scope: &Scope, token: u64, peer: Did, service: String) {
let command = RelayCommand::<SocketAddr>::Accepted {
token,
peer,
service,
};
if let Ok(bytes) = bincode::serialize(&command) {
let _ = scope.inject(Bytes::from(bytes)).await;
}
}
async fn inject_untrack(scope: &Scope, key: &SessionKey) {
let command = RelayCommand::<SocketAddr>::Untrack {
peer: key.peer,
session: key.session,
initiator: key.initiator,
};
if let Ok(bytes) = bincode::serialize(&command) {
if let Err(e) = scope.inject(Bytes::from(bytes)).await {
tracing::warn!(
"relay Untrack inject failed for {key:?}: {e:?}; pure state may still list \
this (now dropped) session"
);
}
}
}