use std::{collections::BTreeMap, sync::Arc};
use anyhow::Result;
use iroh_base::NodeId;
use n0_future::{
boxed::BoxFuture,
join_all,
task::{self, AbortOnDropHandle, JoinSet},
};
use tokio::sync::Mutex;
use tokio_util::sync::CancellationToken;
use tracing::{error, info_span, trace, warn, Instrument};
use crate::{
endpoint::{Connecting, Connection},
Endpoint,
};
#[derive(Clone, Debug)]
pub struct Router {
endpoint: Endpoint,
task: Arc<Mutex<Option<AbortOnDropHandle<()>>>>,
cancel_token: CancellationToken,
}
#[derive(Debug)]
pub struct RouterBuilder {
endpoint: Endpoint,
protocols: ProtocolMap,
}
pub trait ProtocolHandler: Send + Sync + std::fmt::Debug + 'static {
fn on_connecting(&self, connecting: Connecting) -> BoxFuture<Result<Connection>> {
Box::pin(async move {
let conn = connecting.await?;
Ok(conn)
})
}
fn accept(&self, connection: Connection) -> BoxFuture<Result<()>>;
fn shutdown(&self) -> BoxFuture<()> {
Box::pin(async move {})
}
}
impl<T: ProtocolHandler> ProtocolHandler for Arc<T> {
fn on_connecting(&self, conn: Connecting) -> BoxFuture<Result<Connection>> {
self.as_ref().on_connecting(conn)
}
fn accept(&self, conn: Connection) -> BoxFuture<Result<()>> {
self.as_ref().accept(conn)
}
fn shutdown(&self) -> BoxFuture<()> {
self.as_ref().shutdown()
}
}
impl<T: ProtocolHandler> ProtocolHandler for Box<T> {
fn on_connecting(&self, conn: Connecting) -> BoxFuture<Result<Connection>> {
self.as_ref().on_connecting(conn)
}
fn accept(&self, conn: Connection) -> BoxFuture<Result<()>> {
self.as_ref().accept(conn)
}
fn shutdown(&self) -> BoxFuture<()> {
self.as_ref().shutdown()
}
}
#[derive(Debug, Default)]
pub(crate) struct ProtocolMap(BTreeMap<Vec<u8>, Box<dyn ProtocolHandler>>);
impl ProtocolMap {
pub(crate) fn get(&self, alpn: &[u8]) -> Option<&dyn ProtocolHandler> {
self.0.get(alpn).map(|p| &**p)
}
pub(crate) fn insert(&mut self, alpn: Vec<u8>, handler: Box<dyn ProtocolHandler>) {
self.0.insert(alpn, handler);
}
pub(crate) fn alpns(&self) -> impl Iterator<Item = &Vec<u8>> {
self.0.keys()
}
pub(crate) async fn shutdown(&self) {
let handlers = self.0.values().map(|p| p.shutdown());
join_all(handlers).await;
}
}
impl Router {
pub fn builder(endpoint: Endpoint) -> RouterBuilder {
RouterBuilder::new(endpoint)
}
pub fn endpoint(&self) -> &Endpoint {
&self.endpoint
}
pub fn is_shutdown(&self) -> bool {
self.cancel_token.is_cancelled()
}
pub async fn shutdown(&self) -> Result<()> {
if self.is_shutdown() {
return Ok(());
}
self.cancel_token.cancel();
if let Some(task) = self.task.lock().await.take() {
task.await?;
}
Ok(())
}
}
impl RouterBuilder {
pub fn new(endpoint: Endpoint) -> Self {
Self {
endpoint,
protocols: ProtocolMap::default(),
}
}
pub fn accept<T: ProtocolHandler>(mut self, alpn: impl AsRef<[u8]>, handler: T) -> Self {
let handler = Box::new(handler);
self.protocols.insert(alpn.as_ref().to_vec(), handler);
self
}
pub fn endpoint(&self) -> &Endpoint {
&self.endpoint
}
pub fn spawn(self) -> Router {
let alpns = self
.protocols
.alpns()
.map(|alpn| alpn.to_vec())
.collect::<Vec<_>>();
let protocols = Arc::new(self.protocols);
self.endpoint.set_alpns(alpns);
let mut join_set = JoinSet::new();
let endpoint = self.endpoint.clone();
let cancel = CancellationToken::new();
let cancel_token = cancel.clone();
let run_loop_fut = async move {
let _cancel_guard = cancel_token.clone().drop_guard();
loop {
tokio::select! {
biased;
_ = cancel_token.cancelled() => {
break;
},
Some(res) = join_set.join_next() => {
match res {
Err(outer) => {
if outer.is_panic() {
error!("Task panicked: {outer:?}");
break;
} else if outer.is_cancelled() {
trace!("Task cancelled: {outer:?}");
} else {
error!("Task failed: {outer:?}");
break;
}
}
Ok(Some(())) => {
trace!("Task finished");
}
Ok(None) => {
trace!("Task cancelled");
}
}
},
incoming = endpoint.accept() => {
let Some(incoming) = incoming else {
break; };
let protocols = protocols.clone();
let token = cancel_token.child_token();
join_set.spawn(async move {
token.run_until_cancelled(handle_connection(incoming, protocols)).await
}.instrument(info_span!("router.accept")));
},
}
}
shutdown(&endpoint, protocols).await;
tracing::info!("Shutting down remaining tasks");
join_set.shutdown().await;
};
let task = task::spawn(run_loop_fut);
let task = AbortOnDropHandle::new(task);
Router {
endpoint: self.endpoint,
task: Arc::new(Mutex::new(Some(task))),
cancel_token: cancel,
}
}
}
async fn shutdown(endpoint: &Endpoint, protocols: Arc<ProtocolMap>) {
let _ = tokio::join!(
endpoint.close(),
protocols.shutdown(),
);
}
async fn handle_connection(incoming: crate::endpoint::Incoming, protocols: Arc<ProtocolMap>) {
let mut connecting = match incoming.accept() {
Ok(conn) => conn,
Err(err) => {
warn!("Ignoring connection: accepting failed: {err:#}");
return;
}
};
let alpn = match connecting.alpn().await {
Ok(alpn) => alpn,
Err(err) => {
warn!("Ignoring connection: invalid handshake: {err:#}");
return;
}
};
let Some(handler) = protocols.get(&alpn) else {
warn!("Ignoring connection: unsupported ALPN protocol");
return;
};
match handler.on_connecting(connecting).await {
Ok(connection) => {
if let Err(err) = handler.accept(connection).await {
warn!("Handling incoming connection ended with error: {err}");
}
}
Err(err) => {
warn!("Handling incoming connecting ended with error: {err}");
}
}
}
#[derive(derive_more::Debug, Clone)]
pub struct AccessLimit<P: ProtocolHandler + Clone> {
proto: P,
#[debug("limiter")]
limiter: Arc<dyn Fn(NodeId) -> bool + Send + Sync + 'static>,
}
impl<P: ProtocolHandler + Clone> AccessLimit<P> {
pub fn new<F>(proto: P, limiter: F) -> Self
where
F: Fn(NodeId) -> bool + Send + Sync + 'static,
{
Self {
proto,
limiter: Arc::new(limiter),
}
}
}
impl<P: ProtocolHandler + Clone> ProtocolHandler for AccessLimit<P> {
fn on_connecting(&self, conn: Connecting) -> BoxFuture<Result<Connection>> {
self.proto.on_connecting(conn)
}
fn accept(&self, conn: Connection) -> BoxFuture<Result<()>> {
let this = self.clone();
Box::pin(async move {
let remote = conn.remote_node_id()?;
let is_allowed = (this.limiter)(remote);
if !is_allowed {
conn.close(0u32.into(), b"not allowed");
anyhow::bail!("not allowed");
}
this.proto.accept(conn).await?;
Ok(())
})
}
fn shutdown(&self) -> BoxFuture<()> {
self.proto.shutdown()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_shutdown() -> Result<()> {
let endpoint = Endpoint::builder().bind().await?;
let router = Router::builder(endpoint.clone()).spawn();
assert!(!router.is_shutdown());
assert!(!endpoint.is_closed());
router.shutdown().await?;
assert!(router.is_shutdown());
assert!(endpoint.is_closed());
Ok(())
}
#[derive(Debug, Clone)]
struct Echo;
const ECHO_ALPN: &[u8] = b"/iroh/echo/1";
impl ProtocolHandler for Echo {
fn accept(&self, connection: Connection) -> BoxFuture<Result<()>> {
println!("accepting echo");
Box::pin(async move {
let (mut send, mut recv) = connection.accept_bi().await?;
let _bytes_sent = tokio::io::copy(&mut recv, &mut send).await?;
send.finish()?;
connection.closed().await;
Ok(())
})
}
}
#[tokio::test]
async fn test_limiter() -> Result<()> {
let e1 = Endpoint::builder().bind().await?;
let proto = AccessLimit::new(Echo, |_node_id| false);
let r1 = Router::builder(e1.clone()).accept(ECHO_ALPN, proto).spawn();
let addr1 = r1.endpoint().node_addr().await?;
let e2 = Endpoint::builder().bind().await?;
println!("connecting");
let conn = e2.connect(addr1, ECHO_ALPN).await?;
let (_send, mut recv) = conn.open_bi().await?;
let response = recv.read_to_end(1000).await.unwrap_err();
assert!(format!("{:#?}", response).contains("not allowed"));
r1.shutdown().await?;
e2.close().await;
Ok(())
}
}