Skip to main content

tuwunel_router/serve/
unix.rs

1#![cfg(unix)]
2
3use std::{
4	fs,
5	net::{IpAddr, Ipv4Addr, SocketAddr},
6	os::unix::{self, fs::PermissionsExt, net::UnixListener},
7	path::Path,
8};
9
10use axum::{Extension, Router, extract::ConnectInfo};
11use axum_server::Handle;
12use futures::{FutureExt, future::BoxFuture};
13use tuwunel_core::{Result, err, warn};
14
15#[tracing::instrument(skip_all, level = "debug")]
16pub(super) async fn serve<'a>(
17	router: &Router,
18	handle: &Handle<unix::net::SocketAddr>,
19	listeners: impl Iterator<Item = UnixListener>,
20	path: Option<&Path>,
21	socket_perms: u32,
22) -> Result<Vec<BoxFuture<'a, Result<(), std::io::Error>>>> {
23	// Loopback so a unix-socket peer bypasses a configured `ip_source`.
24	let router = router
25		.clone()
26		.layer(Extension(ConnectInfo(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0))))
27		.into_make_service();
28
29	let mut acceptors = listeners
30		.map(|listener| {
31			Ok(axum_server::from_unix(listener)?
32				.handle(handle.clone())
33				.serve(router.clone())
34				.boxed())
35		})
36		.collect::<Result<Vec<_>>>()?;
37
38	if let Some(path) = path {
39		if path.exists() {
40			warn!("Removing existing UNIX socket {path:?} (unclean shutdown?)...");
41			fs::remove_file(path).map_err(|e| {
42				err!(Config(
43					"unix_socket_path",
44					"Failed to remove stale UNIX socket at {path:?}: {e}",
45				))
46			})?;
47		}
48
49		let unix_listener = UnixListener::bind(path).map_err(|e| {
50			err!(Config("unix_socket_path", "Failed to bind UNIX socket at {path:?}: {e}",))
51		})?;
52
53		unix_listener.set_nonblocking(true)?;
54
55		let perms = fs::Permissions::from_mode(socket_perms);
56		fs::set_permissions(path, perms).map_err(|e| {
57			err!(Config(
58				"unix_socket_path",
59				"Failed to set permissions {socket_perms:o} on UNIX socket at {path:?}: {e}",
60			))
61		})?;
62
63		let bound_acceptor = axum_server::from_unix(unix_listener)?
64			.handle(handle.clone())
65			.serve(router)
66			.inspect({
67				let path = path.to_owned();
68				|_| {
69					if let Err(err) = fs::remove_file(path) {
70						warn!("Failed to remove UNIX socket: {err}");
71					}
72				}
73			})
74			.boxed();
75
76		acceptors.push(bound_acceptor);
77	}
78
79	Ok(acceptors)
80}