use std::{
collections::BTreeMap,
future::Future,
pin::Pin,
sync::{Arc, Mutex},
};
use iroh_base::NodeId;
use n0_future::{
join_all,
task::{self, AbortOnDropHandle, JoinSet},
};
use snafu::{Backtrace, Snafu};
use tokio_util::sync::CancellationToken;
use tracing::{Instrument, error, field::Empty, info_span, trace, warn};
use crate::{
Endpoint,
endpoint::{Connecting, Connection, RemoteNodeIdError},
};
#[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,
}
#[allow(missing_docs)]
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum AcceptError {
#[snafu(transparent)]
Connection {
source: crate::endpoint::ConnectionError,
backtrace: Option<Backtrace>,
#[snafu(implicit)]
span_trace: n0_snafu::SpanTrace,
},
#[snafu(transparent)]
MissingRemoteNodeId { source: RemoteNodeIdError },
#[snafu(display("Not allowed."))]
NotAllowed {},
#[snafu(transparent)]
User {
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
}
impl AcceptError {
pub fn from_err<T: std::error::Error + Send + Sync + 'static>(value: T) -> Self {
Self::User {
source: Box::new(value),
}
}
}
impl From<std::io::Error> for AcceptError {
fn from(err: std::io::Error) -> Self {
Self::from_err(err)
}
}
impl From<quinn::ClosedStream> for AcceptError {
fn from(err: quinn::ClosedStream) -> Self {
Self::from_err(err)
}
}
pub trait ProtocolHandler: Send + Sync + std::fmt::Debug + 'static {
fn on_connecting(
&self,
connecting: Connecting,
) -> impl Future<Output = Result<Connection, AcceptError>> + Send {
async move {
let conn = connecting.await?;
Ok(conn)
}
}
fn accept(
&self,
connection: Connection,
) -> impl Future<Output = Result<(), AcceptError>> + Send;
fn shutdown(&self) -> impl Future<Output = ()> + Send {
async move {}
}
}
impl<T: ProtocolHandler> ProtocolHandler for Arc<T> {
async fn on_connecting(&self, conn: Connecting) -> Result<Connection, AcceptError> {
self.as_ref().on_connecting(conn).await
}
async fn accept(&self, conn: Connection) -> Result<(), AcceptError> {
self.as_ref().accept(conn).await
}
async fn shutdown(&self) {
self.as_ref().shutdown().await
}
}
impl<T: ProtocolHandler> ProtocolHandler for Box<T> {
async fn on_connecting(&self, conn: Connecting) -> Result<Connection, AcceptError> {
self.as_ref().on_connecting(conn).await
}
async fn accept(&self, conn: Connection) -> Result<(), AcceptError> {
self.as_ref().accept(conn).await
}
async fn shutdown(&self) {
self.as_ref().shutdown().await
}
}
impl<T: ProtocolHandler> From<T> for Box<dyn DynProtocolHandler> {
fn from(value: T) -> Self {
Box::new(value)
}
}
pub trait DynProtocolHandler: Send + Sync + std::fmt::Debug + 'static {
fn on_connecting(
&self,
connecting: Connecting,
) -> Pin<Box<dyn Future<Output = Result<Connection, AcceptError>> + Send + '_>> {
Box::pin(async move {
let conn = connecting.await?;
Ok(conn)
})
}
fn accept(
&self,
connection: Connection,
) -> Pin<Box<dyn Future<Output = Result<(), AcceptError>> + Send + '_>>;
fn shutdown(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(async move {})
}
}
impl<P: ProtocolHandler> DynProtocolHandler for P {
fn accept(
&self,
connection: Connection,
) -> Pin<Box<dyn Future<Output = Result<(), AcceptError>> + Send + '_>> {
Box::pin(<Self as ProtocolHandler>::accept(self, connection))
}
fn on_connecting(
&self,
connecting: Connecting,
) -> Pin<Box<dyn Future<Output = Result<Connection, AcceptError>> + Send + '_>> {
Box::pin(<Self as ProtocolHandler>::on_connecting(self, connecting))
}
fn shutdown(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
Box::pin(<Self as ProtocolHandler>::shutdown(self))
}
}
#[derive(Debug, Default)]
pub(crate) struct ProtocolMap(BTreeMap<Vec<u8>, Box<dyn DynProtocolHandler>>);
impl ProtocolMap {
pub(crate) fn get(&self, alpn: &[u8]) -> Option<&dyn DynProtocolHandler> {
self.0.get(alpn).map(|p| &**p)
}
pub(crate) fn insert(&mut self, alpn: Vec<u8>, handler: Box<dyn DynProtocolHandler>) {
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<(), n0_future::task::JoinError> {
if self.is_shutdown() {
return Ok(());
}
self.cancel_token.cancel();
let task = self.task.lock().expect("poisoned").take();
if let Some(task) = task {
task.await?;
}
Ok(())
}
}
impl RouterBuilder {
pub fn new(endpoint: Endpoint) -> Self {
Self {
endpoint,
protocols: ProtocolMap::default(),
}
}
pub fn accept(
mut self,
alpn: impl AsRef<[u8]>,
handler: impl Into<Box<dyn DynProtocolHandler>>,
) -> Self {
self.protocols
.insert(alpn.as_ref().to_vec(), handler.into());
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();
let handler_cancel_token = CancellationToken::new();
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 = handler_cancel_token.child_token();
let span = info_span!("router.accept", me=%endpoint.node_id().fmt_short(), remote=Empty, alpn=Empty);
join_set.spawn(async move {
token.run_until_cancelled(handle_connection(incoming, protocols)).await
}.instrument(span));
},
}
}
protocols.shutdown().await;
handler_cancel_token.cancel();
endpoint.close().await;
tracing::debug!("Shutting down remaining tasks");
join_set.abort_all();
while let Some(res) = join_set.join_next().await {
match res {
Err(err) if err.is_panic() => error!("Task panicked: {err:?}"),
_ => {}
}
}
};
let task = task::spawn(run_loop_fut.instrument(tracing::Span::current()));
let task = AbortOnDropHandle::new(task);
Router {
endpoint: self.endpoint,
task: Arc::new(Mutex::new(Some(task))),
cancel_token: cancel,
}
}
}
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;
}
};
tracing::Span::current().record("alpn", String::from_utf8_lossy(&alpn).to_string());
let Some(handler) = protocols.get(&alpn) else {
warn!("Ignoring connection: unsupported ALPN protocol");
return;
};
match handler.on_connecting(connecting).await {
Ok(connection) => {
if let Ok(remote) = connection.remote_node_id() {
tracing::Span::current()
.record("remote", tracing::field::display(remote.fmt_short()));
};
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,
) -> impl Future<Output = Result<Connection, AcceptError>> + Send {
self.proto.on_connecting(conn)
}
async fn accept(&self, conn: Connection) -> Result<(), AcceptError> {
let remote = conn.remote_node_id()?;
let is_allowed = (self.limiter)(remote);
if !is_allowed {
conn.close(0u32.into(), b"not allowed");
return Err(NotAllowedSnafu.build());
}
self.proto.accept(conn).await?;
Ok(())
}
fn shutdown(&self) -> impl Future<Output = ()> + Send {
self.proto.shutdown()
}
}
#[cfg(test)]
mod tests {
use std::{sync::Mutex, time::Duration};
use n0_snafu::{Result, ResultExt};
use n0_watcher::Watcher;
use quinn::ApplicationClose;
use super::*;
use crate::{RelayMode, endpoint::ConnectionError};
#[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.e()?;
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 {
async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
println!("accepting echo");
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()
.relay_mode(RelayMode::Disabled)
.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().initialized().await;
dbg!(&addr1);
let e2 = Endpoint::builder()
.relay_mode(RelayMode::Disabled)
.bind()
.await?;
println!("connecting");
let conn = e2.connect(addr1, ECHO_ALPN).await?;
let (_send, mut recv) = conn.open_bi().await.e()?;
let response = recv.read_to_end(1000).await.unwrap_err();
assert!(format!("{response:#?}").contains("not allowed"));
r1.shutdown().await.e()?;
e2.close().await;
Ok(())
}
#[tokio::test]
async fn test_graceful_shutdown() -> Result {
#[derive(Debug, Clone, Default)]
struct TestProtocol {
connections: Arc<Mutex<Vec<Connection>>>,
}
const TEST_ALPN: &[u8] = b"/iroh/test/1";
impl ProtocolHandler for TestProtocol {
async fn accept(&self, connection: Connection) -> Result<(), AcceptError> {
self.connections.lock().expect("poisoned").push(connection);
Ok(())
}
async fn shutdown(&self) {
tokio::time::sleep(Duration::from_millis(100)).await;
let mut connections = self.connections.lock().expect("poisoned");
for conn in connections.drain(..) {
conn.close(42u32.into(), b"shutdown");
}
}
}
eprintln!("creating ep1");
let endpoint = Endpoint::builder()
.relay_mode(RelayMode::Disabled)
.bind()
.await?;
let router = Router::builder(endpoint)
.accept(TEST_ALPN, TestProtocol::default())
.spawn();
eprintln!("waiting for node addr");
let addr = router.endpoint().node_addr().initialized().await;
eprintln!("creating ep2");
let endpoint2 = Endpoint::builder()
.relay_mode(RelayMode::Disabled)
.bind()
.await?;
eprintln!("connecting to {addr:?}");
let conn = endpoint2.connect(addr, TEST_ALPN).await?;
eprintln!("starting shutdown");
router.shutdown().await.e()?;
eprintln!("waiting for closed conn");
let reason = conn.closed().await;
assert_eq!(
reason,
ConnectionError::ApplicationClosed(ApplicationClose {
error_code: 42u32.into(),
reason: b"shutdown".to_vec().into()
})
);
Ok(())
}
}