support reverse forwarding

This commit is contained in:
Robin Appelman 2025-11-10 22:19:13 +01:00
commit d8d1bc17df
6 changed files with 65 additions and 21 deletions

View file

@ -87,6 +87,8 @@ pub struct NamespaceConfig {
pub struct ForwardConfig {
pub source: ForwardSource,
pub target: ForwardTarget,
#[serde(default)]
pub reverse: bool,
}
#[derive(Debug, Error)]

View file

@ -7,7 +7,7 @@ use nix::sched::{CloneFlags, setns};
use std::fs::{File, remove_file};
use std::io::Error as IoError;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread::spawn;
@ -45,32 +45,53 @@ impl ActiveProxy {
config: &ForwardConfig,
namespace: &NamespaceName,
) -> Result<ActiveProxy, ProxyError> {
let proxy = Proxy::listen(config.source.clone())?;
let stats = ProxyStats::default();
let (abort, abort_reg) = AbortHandle::new_pair();
let destination = config.target.clone();
let run_stats = stats.clone();
let ns_path = PathBuf::from(format!("/var/run/netns/{namespace}"));
let ns_handle = File::open(&ns_path).map_err(|error| ProxyError::OpenNamespace {
error,
path: ns_path,
})?;
spawn(move || match setns(ns_handle, CloneFlags::CLONE_NEWNET) {
Ok(_) => {
let rt = match Builder::new_current_thread().enable_io().build() {
Ok(rt) => rt,
let ns_handle = open_namespace(format!("/var/run/netns/{namespace}"))?;
let self_ns_handle = open_namespace("/proc/self/ns/net")?;
let (listen_namespace, target_namespace) = if config.reverse {
(Some(ns_handle), self_ns_handle)
} else {
(None, ns_handle)
};
let source = config.source.clone();
spawn(move || {
let rt = match Builder::new_current_thread().enable_io().build() {
Ok(rt) => rt,
Err(error) => {
error!(%error, "Error setting up tokio runtime");
return;
}
};
rt.block_on(async {
if let Some(listen_namespace) = listen_namespace {
if let Err(error) = setns(listen_namespace, CloneFlags::CLONE_NEWNET) {
error!(%error, "Failed to join listen network namespace for proxy");
return;
}
}
let proxy = match Proxy::listen(&source) {
Ok(proxy) => proxy,
Err(error) => {
error!(%error, "Error setting up tokio runtime");
error!(%error, "Failed to listen to {source}");
return;
}
};
rt.block_on(proxy.run(destination, abort_reg, run_stats));
}
Err(error) => {
error!(%error, "Failed to join network namespace for proxy");
}
if let Err(error) = setns(target_namespace, CloneFlags::CLONE_NEWNET) {
error!(%error, "Failed to join target network namespace for proxy");
return;
}
proxy.run(destination, abort_reg, run_stats).await;
});
});
Ok(ActiveProxy {
@ -145,3 +166,11 @@ impl ProxyStats {
self.bytes_written.load(Ordering::Relaxed)
}
}
fn open_namespace(path: impl AsRef<Path>) -> Result<File, ProxyError> {
let path = path.as_ref();
File::open(path).map_err(|error| ProxyError::OpenNamespace {
error,
path: path.into(),
})
}

View file

@ -37,8 +37,8 @@ fn bind_tcp(addr: SocketAddr) -> Result<TcpListener, IoError> {
}
impl Proxy {
pub fn listen(bind: ForwardSource) -> Result<Self, ProxyError> {
let socket = match &bind {
pub fn listen(bind: &ForwardSource) -> Result<Self, ProxyError> {
let socket = match bind {
ForwardSource::Unix(path) => {
let _ = remove_file(path);
UnixListener::bind(path).map(|listener| {
@ -51,7 +51,7 @@ impl Proxy {
ForwardSource::Ip(addr) => bind_tcp(*addr).map(ProxyListener::Tcp),
}
.map_err(|error| ProxyError::Bind {
address: bind,
address: bind.clone(),
error,
})?;
debug!("Created TCP socket");