From 282967dd27a1267bf8198ad91d0d8f98024c8ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Cholewi=C5=84ski?= Date: Sat, 24 Jan 2026 18:47:20 +0100 Subject: [PATCH 1/2] implement whitelisting --- src/transport/mod.rs | 1 + src/transport/tcp_listener.rs | 4 ++ src/transport/tls.rs | 7 ++++ src/transport/transport_layer.rs | 72 +++++++++++++++++++++++++++++++- src/transport/udp.rs | 16 +++++++ src/transport/websocket.rs | 4 ++ 6 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 4d206199..67e895f2 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -14,6 +14,7 @@ pub use sip_addr::SipAddr; pub use tcp_listener::TcpListenerConnection; pub use tls::{TlsConfig, TlsListenerConnection}; pub use transport_layer::TransportLayer; +pub use transport_layer::TransportWhitelist; pub use websocket::WebSocketListenerConnection; #[cfg(test)] diff --git a/src/transport/tcp_listener.rs b/src/transport/tcp_listener.rs index 3c8d5fd1..7aa80cf8 100644 --- a/src/transport/tcp_listener.rs +++ b/src/transport/tcp_listener.rs @@ -45,6 +45,10 @@ impl TcpListenerConnection { continue; } }; + if !transport_layer_inner.is_whitelisted(remote_addr.ip()).await { + debug!(remote = %remote_addr, "tcp connection rejected by whitelist"); + continue; + } let local_addr = SipAddr { r#type: Some(rsip::transport::Transport::Tcp), addr: remote_addr.into(), diff --git a/src/transport/tls.rs b/src/transport/tls.rs index 4d88b85e..0d4fef59 100644 --- a/src/transport/tls.rs +++ b/src/transport/tls.rs @@ -78,6 +78,13 @@ impl TlsListenerConnection { continue; } }; + if !transport_layer_inner + .is_whitelisted(remote_addr.ip()) + .await + { + debug!(remote = %remote_addr, "tls connection rejected by whitelist"); + continue; + } let acceptor_clone = acceptor.clone(); let transport_layer_inner_ref = transport_layer_inner.clone(); diff --git a/src/transport/transport_layer.rs b/src/transport/transport_layer.rs index caa1e871..46698b5c 100644 --- a/src/transport/transport_layer.rs +++ b/src/transport/transport_layer.rs @@ -11,6 +11,7 @@ use rsip_dns::trust_dns_resolver::TokioAsyncResolver; #[cfg(feature = "rsip-dns")] use rsip_dns::ResolvableExt; +use std::net::IpAddr; use std::sync::{Mutex, RwLock}; use std::{collections::HashMap, sync::Arc}; use tokio::select; @@ -23,6 +24,25 @@ pub trait DomainResolver: Send + Sync { async fn resolve(&self, target: &SipAddr) -> Result; } +#[async_trait] +pub trait TransportWhitelist: Send + Sync { + /// Return true to accept the packet/connection for the given peer IP. + async fn allow(&self, ip: IpAddr) -> bool; +} + +#[async_trait] +impl TransportWhitelist for F +where + F: Send + Sync + Fn(IpAddr) -> Fut, + Fut: std::future::Future + Send, +{ + async fn allow(&self, ip: IpAddr) -> bool { + (self)(ip).await + } +} + +pub(crate) type TransportWhitelistRef = Arc; + pub struct DefaultDomainResolver {} impl DefaultDomainResolver { @@ -107,6 +127,7 @@ pub struct TransportLayerInner { pub(crate) transport_tx: TransportSender, pub(crate) transport_rx: Mutex>, pub domain_resolver: Box, + whitelist: RwLock>, } pub(crate) type TransportLayerInnerRef = Arc; @@ -129,6 +150,7 @@ impl TransportLayer { transport_tx, transport_rx: Mutex::new(Some(transport_rx)), domain_resolver, + whitelist: RwLock::new(None), }; Self { outbound: None, @@ -196,9 +218,48 @@ impl TransportLayer { } } } + + /// Set an async whitelist callback invoked on incoming packets/connections. + pub fn set_whitelist(&self, whitelist: T) + where + T: TransportWhitelist + 'static, + { + self.inner.set_whitelist(Some(Arc::new(whitelist))); + } + + /// Remove the whitelist callback. + pub fn clear_whitelist(&self) { + self.inner.set_whitelist(None); + } } impl TransportLayerInner { + pub(super) fn set_whitelist(&self, whitelist: Option) { + match self.whitelist.write() { + Ok(mut guard) => { + *guard = whitelist; + } + Err(e) => { + warn!(error = ?e, "Failed to update whitelist"); + } + } + } + + pub(crate) async fn is_whitelisted(&self, ip: IpAddr) -> bool { + let whitelist = match self.whitelist.read() { + Ok(guard) => guard.clone(), + Err(e) => { + warn!(error = ?e, "Failed to read whitelist"); + return true; + } + }; + + match whitelist { + Some(whitelist) => whitelist.allow(ip).await, + None => true, + } + } + pub(super) fn add_listener(&self, connection: SipConnection) { match self.listens.write() { Ok(mut listens) => { @@ -349,7 +410,12 @@ impl TransportLayerInner { let sender = self.transport_tx.clone(); match transport { SipConnection::Udp(transport) => { - tokio::spawn(async move { transport.serve_loop(sender).await }); + let transport_layer_inner = self.clone(); + tokio::spawn(async move { + transport + .serve_loop_with_whitelist(sender, Some(transport_layer_inner)) + .await + }); Ok(()) } SipConnection::TcpListener(connection) => connection.serve_listener(self.clone()).await, @@ -383,7 +449,9 @@ impl TransportLayerInner { } select! { _ = sub_token.cancelled() => { } - _ = transport.serve_loop(sender_clone.clone()) => { + _ = async { + transport.serve_loop(sender_clone.clone()).await + } => { } } info!(addr=%transport.get_addr(), "transport serve_loop exited"); diff --git a/src/transport/udp.rs b/src/transport/udp.rs index af68f03b..d6dc7db5 100644 --- a/src/transport/udp.rs +++ b/src/transport/udp.rs @@ -1,5 +1,6 @@ use super::{connection::TransportSender, SipAddr, SipConnection}; use crate::{ + transport::transport_layer::TransportLayerInnerRef, transport::{ connection::{KEEPALIVE_REQUEST, KEEPALIVE_RESPONSE, MAX_UDP_BUF_SIZE}, TransportEvent, @@ -64,6 +65,14 @@ impl UdpConnection { } pub async fn serve_loop(&self, sender: TransportSender) -> Result<()> { + self.serve_loop_with_whitelist(sender, None).await + } + + pub async fn serve_loop_with_whitelist( + &self, + sender: TransportSender, + transport_layer_inner: Option, + ) -> Result<()> { let mut buf = BytesMut::with_capacity(MAX_UDP_BUF_SIZE); buf.resize(MAX_UDP_BUF_SIZE, 0); loop { @@ -92,6 +101,13 @@ impl UdpConnection { } }; + if let Some(transport_layer_inner) = &transport_layer_inner { + if !transport_layer_inner.is_whitelisted(addr.ip()).await { + debug!(src = %addr, "udp packet rejected by whitelist"); + continue; + } + } + match &buf[..len] { KEEPALIVE_REQUEST => { self.inner.conn.send_to(KEEPALIVE_RESPONSE, addr).await.ok(); diff --git a/src/transport/websocket.rs b/src/transport/websocket.rs index 30333361..2158f573 100644 --- a/src/transport/websocket.rs +++ b/src/transport/websocket.rs @@ -90,6 +90,10 @@ impl WebSocketListenerConnection { continue; } }; + if !transport_layer_inner.is_whitelisted(remote_addr.ip()).await { + debug!(remote = %remote_addr, "websocket connection rejected by whitelist"); + continue; + } debug!(remote = %remote_addr, "New WebSocket connection"); From 323bd132960501738cac59aacc4c430089d6df9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Cholewi=C5=84ski?= Date: Tue, 27 Jan 2026 14:22:18 +0100 Subject: [PATCH 2/2] format --- src/transport/tls.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/transport/tls.rs b/src/transport/tls.rs index 0d4fef59..52bd6117 100644 --- a/src/transport/tls.rs +++ b/src/transport/tls.rs @@ -78,10 +78,7 @@ impl TlsListenerConnection { continue; } }; - if !transport_layer_inner - .is_whitelisted(remote_addr.ip()) - .await - { + if !transport_layer_inner.is_whitelisted(remote_addr.ip()).await { debug!(remote = %remote_addr, "tls connection rejected by whitelist"); continue; }