use std::{
future::Future,
pin::Pin,
sync::{Arc, Mutex, mpsc},
thread::{JoinHandle, ThreadId},
};
use futures::{
StreamExt,
future::{self, Either},
};
use crate::{
IpEndpoint, L2tpEvent, L2tpIfName, NetNs, SessionConfig, SessionId,
SessionInfo, SessionModify, SessionStats, TunnelConfig, TunnelId,
TunnelInfo, TunnelSocket, TunnelStats, UdpEndpoint,
};
type BoxFuture = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
type Task = Box<dyn FnOnce(crate::L2tpHandle) -> BoxFuture + Send + 'static>;
struct Worker {
tasks: Option<tokio::sync::mpsc::UnboundedSender<Task>>,
thread: Mutex<Option<JoinHandle<()>>>,
thread_id: ThreadId,
}
impl Worker {
fn send(&self, task: Task) -> crate::Result<()> {
self.tasks
.as_ref()
.and_then(|tasks| tasks.send(task).ok())
.ok_or_else(|| crate::Error::other("the l2tp worker has stopped"))
}
}
impl Drop for Worker {
fn drop(&mut self) {
drop(self.tasks.take());
if std::thread::current().id() != self.thread_id
&& let Ok(mut thread) = self.thread.lock()
&& let Some(thread) = thread.take()
{
let _ = thread.join();
}
}
}
#[derive(Clone)]
pub struct L2tpHandle {
worker: Arc<Worker>,
}
impl std::fmt::Debug for L2tpHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("blocking::L2tpHandle")
.field("thread_id", &self.worker.thread_id)
.finish_non_exhaustive()
}
}
impl L2tpHandle {
pub fn new() -> crate::Result<Self> {
Self::start(None)
}
pub fn new_in(netns: &NetNs) -> crate::Result<Self> {
Self::start(Some(netns.try_clone()?))
}
fn start(netns: Option<NetNs>) -> crate::Result<Self> {
let (task_tx, mut task_rx) =
tokio::sync::mpsc::unbounded_channel::<Task>();
let (ready_tx, ready_rx) = mpsc::sync_channel::<crate::Result<()>>(1);
let thread = std::thread::Builder::new()
.name("l2tp-blocking".to_string())
.spawn(move || {
let runtime =
match tokio::runtime::Builder::new_current_thread()
.enable_io()
.build()
{
Ok(runtime) => runtime,
Err(err) => {
let _ = ready_tx.send(Err(err.into()));
return;
}
};
runtime.block_on(async move {
let handle = match &netns {
None => crate::L2tpHandle::new().await,
Some(netns) => crate::L2tpHandle::new_in(netns).await,
};
let handle = match handle {
Ok(handle) => handle,
Err(err) => {
let _ = ready_tx.send(Err(err));
return;
}
};
if ready_tx.send(Ok(())).is_err() {
return;
}
let mut tasks = tokio::task::JoinSet::new();
loop {
let task = if tasks.is_empty() {
task_rx.recv().await
} else {
let recv = task_rx.recv();
futures::pin_mut!(recv);
let join = tasks.join_next();
futures::pin_mut!(join);
match future::select(recv, join).await {
Either::Left((task, _)) => task,
Either::Right(_) => continue,
}
};
match task {
Some(task) => {
tasks.spawn(task(handle.clone()));
}
None => break,
}
}
while tasks.join_next().await.is_some() {}
});
})?;
let thread_id = thread.thread().id();
ready_rx.recv().map_err(|_| {
crate::Error::other("the l2tp worker stopped during startup")
})??;
Ok(Self {
worker: Arc::new(Worker {
tasks: Some(task_tx),
thread: Mutex::new(Some(thread)),
thread_id,
}),
})
}
fn call<T, F, Fut>(&self, f: F) -> crate::Result<T>
where
T: Send + 'static,
F: FnOnce(crate::L2tpHandle) -> Fut + Send + 'static,
Fut: Future<Output = crate::Result<T>> + Send + 'static,
{
let (tx, rx) = mpsc::sync_channel(1);
self.worker.send(Box::new(move |handle| {
Box::pin(async move {
let _ = tx.send(f(handle).await);
})
}))?;
rx.recv().unwrap_or_else(|_| {
Err(crate::Error::other("the l2tp worker dropped the request"))
})
}
fn detach<F, Fut>(&self, f: F)
where
F: FnOnce(crate::L2tpHandle) -> Fut + Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let _ = self
.worker
.send(Box::new(move |handle| Box::pin(f(handle))));
}
pub fn create_tunnel(
&self,
config: TunnelConfig,
socket: TunnelSocket,
) -> crate::Result<TunnelHandle> {
let inner = self.call(move |handle| async move {
handle.create_tunnel(config, socket).await
})?;
Ok(TunnelHandle::new(inner, self.clone()))
}
pub fn create_unmanaged_tunnel(
&self,
config: TunnelConfig,
) -> crate::Result<TunnelHandle> {
let inner = self.call(move |handle| async move {
handle.create_unmanaged_tunnel(config).await
})?;
Ok(TunnelHandle::new(inner, self.clone()))
}
pub fn open_tunnel(
&self,
tunnel_id: TunnelId,
) -> crate::Result<TunnelHandle> {
let inner = self.call(move |handle| async move {
handle.open_tunnel(tunnel_id).await
})?;
Ok(TunnelHandle::new(inner, self.clone()))
}
pub fn create_session(
&self,
config: SessionConfig,
) -> crate::Result<SessionHandle> {
let inner = self.call(move |handle| async move {
handle.create_session(config).await
})?;
Ok(SessionHandle::new(inner, self.clone()))
}
pub fn open_session(
&self,
tunnel_id: TunnelId,
session_id: SessionId,
) -> crate::Result<SessionHandle> {
let inner = self.call(move |handle| async move {
handle.open_session(tunnel_id, session_id).await
})?;
Ok(SessionHandle::new(inner, self.clone()))
}
pub fn open_session_by_ifname(
&self,
ifname: L2tpIfName,
) -> crate::Result<SessionHandle> {
let inner = self.call(move |handle| async move {
handle.open_session_by_ifname(&ifname).await
})?;
Ok(SessionHandle::new(inner, self.clone()))
}
pub fn get_tunnel(&self, tunnel_id: TunnelId) -> crate::Result<TunnelInfo> {
self.call(
move |handle| async move { handle.get_tunnel(tunnel_id).await },
)
}
pub fn get_session(
&self,
tunnel_id: TunnelId,
session_id: SessionId,
) -> crate::Result<SessionInfo> {
self.call(move |handle| async move {
handle.get_session(tunnel_id, session_id).await
})
}
pub fn get_session_by_ifname(
&self,
ifname: L2tpIfName,
) -> crate::Result<SessionInfo> {
self.call(move |handle| async move {
handle.get_session_by_ifname(&ifname).await
})
}
pub fn list_tunnels(&self) -> crate::Result<Vec<TunnelInfo>> {
self.call(|handle| async move { handle.list_tunnels().await })
}
pub fn list_sessions(
&self,
tunnel_id: TunnelId,
) -> crate::Result<Vec<SessionInfo>> {
self.call(
move |handle| async move { handle.list_sessions(tunnel_id).await },
)
}
pub fn list_all_sessions(&self) -> crate::Result<Vec<SessionInfo>> {
self.call(|handle| async move { handle.list_all_sessions().await })
}
pub fn delete_tunnel(&self, tunnel_id: TunnelId) -> crate::Result<()> {
self.call(
move |handle| async move { handle.delete_tunnel(tunnel_id).await },
)
}
pub fn delete_session(
&self,
tunnel_id: TunnelId,
session_id: SessionId,
) -> crate::Result<()> {
self.call(move |handle| async move {
handle.delete_session(tunnel_id, session_id).await
})
}
pub fn delete_session_by_ifname(
&self,
ifname: L2tpIfName,
) -> crate::Result<()> {
self.call(move |handle| async move {
handle.delete_session_by_ifname(&ifname).await
})
}
pub fn tunnel_stats(
&self,
tunnel_id: TunnelId,
) -> crate::Result<TunnelStats> {
self.call(
move |handle| async move { handle.tunnel_stats(tunnel_id).await },
)
}
pub fn session_stats(
&self,
tunnel_id: TunnelId,
session_id: SessionId,
) -> crate::Result<SessionStats> {
self.call(move |handle| async move {
handle.session_stats(tunnel_id, session_id).await
})
}
pub fn session_stats_by_ifname(
&self,
ifname: L2tpIfName,
) -> crate::Result<SessionStats> {
self.call(move |handle| async move {
handle.session_stats_by_ifname(&ifname).await
})
}
pub fn modify_session(
&self,
tunnel_id: TunnelId,
session_id: SessionId,
params: SessionModify,
) -> crate::Result<()> {
self.call(move |handle| async move {
handle.modify_session(tunnel_id, session_id, params).await
})
}
pub fn modify_session_by_ifname(
&self,
ifname: L2tpIfName,
params: SessionModify,
) -> crate::Result<()> {
self.call(move |handle| async move {
handle.modify_session_by_ifname(&ifname, params).await
})
}
pub fn events(&self) -> crate::Result<EventIter> {
let (event_tx, event_rx) = mpsc::channel();
let (stop_tx, mut stop_rx) = tokio::sync::oneshot::channel::<()>();
let (ready_tx, ready_rx) = mpsc::sync_channel::<crate::Result<()>>(1);
self.detach(move |handle| async move {
let mut events = match handle.events().await {
Ok(events) => events,
Err(err) => {
let _ = ready_tx.send(Err(err));
return;
}
};
if ready_tx.send(Ok(())).is_err() {
return;
}
loop {
match future::select(&mut stop_rx, events.next()).await {
Either::Left(_) => break,
Either::Right((Some(item), _)) => {
if event_tx.send(item).is_err() {
break;
}
}
Either::Right((None, _)) => break,
}
}
});
ready_rx.recv().map_err(|_| {
crate::Error::other("the l2tp worker dropped the subscription")
})??;
Ok(EventIter {
receiver: event_rx,
_stop: stop_tx,
_worker: self.clone(),
})
}
}
#[derive(Debug)]
pub struct EventIter {
receiver: mpsc::Receiver<crate::Result<L2tpEvent>>,
_stop: tokio::sync::oneshot::Sender<()>,
_worker: L2tpHandle,
}
impl Iterator for EventIter {
type Item = crate::Result<L2tpEvent>;
fn next(&mut self) -> Option<Self::Item> {
self.receiver.recv().ok()
}
}
#[derive(Debug)]
pub struct TunnelHandle {
tunnel_id: TunnelId,
inner: Option<crate::TunnelHandle>,
l2tp: L2tpHandle,
}
impl TunnelHandle {
fn new(inner: crate::TunnelHandle, l2tp: L2tpHandle) -> Self {
Self {
tunnel_id: inner.tunnel_id(),
inner: Some(inner),
l2tp,
}
}
pub fn tunnel_id(&self) -> TunnelId {
self.tunnel_id
}
pub fn socket(&self) -> Option<&TunnelSocket> {
self.inner.as_ref().and_then(|inner| inner.socket())
}
pub fn socket_mut(&mut self) -> Option<&mut TunnelSocket> {
self.inner.as_mut().and_then(|inner| inner.socket_mut())
}
pub fn auto_delete(&self) -> bool {
self.inner.as_ref().is_some_and(|inner| inner.auto_delete())
}
pub fn set_auto_delete(&mut self, v: bool) {
if let Some(inner) = &mut self.inner {
inner.set_auto_delete(v);
}
}
pub fn get(&self) -> crate::Result<TunnelInfo> {
self.l2tp.get_tunnel(self.tunnel_id)
}
pub fn stats(&self) -> crate::Result<TunnelStats> {
self.l2tp.tunnel_stats(self.tunnel_id)
}
pub fn delete(mut self) -> crate::Result<()> {
let Some(inner) = self.inner.take() else {
return Err(crate::Error::other("the tunnel handle was consumed"));
};
self.l2tp.call(move |_| inner.delete())
}
pub fn reconnect_udp(&self, new_remote: &UdpEndpoint) -> crate::Result<()> {
self.socket()
.ok_or(crate::Error::UnmanagedSocket)?
.reconnect_udp(new_remote)
}
pub fn reconnect_ip(&self, new_remote: &IpEndpoint) -> crate::Result<()> {
self.socket()
.ok_or(crate::Error::UnmanagedSocket)?
.reconnect_ip(new_remote)
}
}
impl Drop for TunnelHandle {
fn drop(&mut self) {
if let Some(inner) = self.inner.take()
&& inner.auto_delete()
{
self.l2tp.detach(move |_| async move {
let _ = inner.delete().await;
});
}
}
}
#[derive(Debug)]
pub struct SessionHandle {
tunnel_id: TunnelId,
session_id: SessionId,
ifname: Option<L2tpIfName>,
ifindex: Option<u32>,
inner: Option<crate::SessionHandle>,
l2tp: L2tpHandle,
}
impl SessionHandle {
fn new(inner: crate::SessionHandle, l2tp: L2tpHandle) -> Self {
Self {
tunnel_id: inner.tunnel_id(),
session_id: inner.session_id(),
ifname: inner.ifname().copied(),
ifindex: inner.ifindex(),
inner: Some(inner),
l2tp,
}
}
pub fn tunnel_id(&self) -> TunnelId {
self.tunnel_id
}
pub fn session_id(&self) -> SessionId {
self.session_id
}
pub fn ifname(&self) -> Option<&L2tpIfName> {
self.ifname.as_ref()
}
pub fn ifindex(&self) -> Option<u32> {
self.ifindex
}
pub fn auto_delete(&self) -> bool {
self.inner.as_ref().is_some_and(|inner| inner.auto_delete())
}
pub fn set_auto_delete(&mut self, v: bool) {
if let Some(inner) = &mut self.inner {
inner.set_auto_delete(v);
}
}
pub fn get(&self) -> crate::Result<SessionInfo> {
self.l2tp.get_session(self.tunnel_id, self.session_id)
}
pub fn stats(&self) -> crate::Result<SessionStats> {
self.l2tp.session_stats(self.tunnel_id, self.session_id)
}
pub fn modify(&self, params: SessionModify) -> crate::Result<()> {
self.l2tp
.modify_session(self.tunnel_id, self.session_id, params)
}
pub fn delete(mut self) -> crate::Result<()> {
let Some(inner) = self.inner.take() else {
return Err(crate::Error::other("the session handle was consumed"));
};
self.l2tp.call(move |_| inner.delete())
}
}
impl Drop for SessionHandle {
fn drop(&mut self) {
if let Some(inner) = self.inner.take()
&& inner.auto_delete()
{
self.l2tp.detach(move |_| async move {
let _ = inner.delete().await;
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn permission_or_missing(err: &crate::Error) {
let errno = err.raw_os_error();
assert!(
errno == Some(libc::EPERM)
|| errno == Some(libc::ENODEV)
|| matches!(err, crate::Error::FamilyResolution(_)),
"{err:?}"
);
}
#[test]
fn requests_complete_from_a_plain_thread() {
let handle = L2tpHandle::new().unwrap();
let err = handle.get_tunnel(TunnelId(u32::MAX)).unwrap_err();
permission_or_missing(&err);
let clone = handle.clone();
let err = std::thread::spawn(move || {
clone.open_tunnel(TunnelId(u32::MAX)).unwrap_err()
})
.join()
.unwrap();
permission_or_missing(&err);
}
#[test]
fn requests_complete_inside_spawn_blocking() {
let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap();
let err = runtime.block_on(async {
tokio::task::spawn_blocking(|| {
let handle = L2tpHandle::new().unwrap();
handle
.open_tunnel(TunnelId(u32::MAX))
.map(|_| ())
.unwrap_err()
})
.await
.unwrap()
});
permission_or_missing(&err);
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let err = runtime.block_on(async {
tokio::task::spawn_blocking(|| {
L2tpHandle::new()
.unwrap()
.get_session(TunnelId(u32::MAX), SessionId(1))
.unwrap_err()
})
.await
.unwrap()
});
permission_or_missing(&err);
}
#[test]
fn worker_stops_when_the_last_handle_drops() {
let handle = L2tpHandle::new().unwrap();
let worker = Arc::downgrade(&handle.worker);
drop(handle);
assert!(worker.upgrade().is_none());
}
#[test]
fn event_iterator_blocks_without_a_tokio_context() {
let handle = L2tpHandle::new().unwrap();
let (tx, rx) = mpsc::channel();
let (stop_tx, stop_rx) = tokio::sync::oneshot::channel();
tx.send(Err(crate::Error::other("probe"))).unwrap();
drop(tx);
let mut iter = EventIter {
receiver: rx,
_stop: stop_tx,
_worker: handle,
};
let runtime = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let (first, second) = runtime
.block_on(async { (iter.next().is_some(), iter.next().is_none()) });
assert!(first);
assert!(second);
drop(iter);
assert!(runtime.block_on(stop_rx).is_err());
}
#[test]
fn event_subscription_can_be_opened_and_dropped() {
let handle = L2tpHandle::new().unwrap();
match handle.events() {
Ok(events) => drop(events),
Err(crate::Error::FamilyResolution(_)) => {}
Err(err) => panic!("unexpected error: {err:?}"),
}
}
}