add support for setting up routing inside the netns

This commit is contained in:
Robin Appelman 2026-02-23 22:59:31 +01:00
commit 7588b5db00
18 changed files with 272 additions and 53 deletions

10
Cargo.lock generated
View file

@ -82,6 +82,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "cidr"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579504560394e388085d0c080ea587dfa5c15f7e251b4d5247d1e1a61d1d6928"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.58" version = "4.5.58"
@ -509,6 +518,7 @@ dependencies = [
name = "netnsd" name = "netnsd"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"cidr",
"clap", "clap",
"either", "either",
"futures", "futures",

View file

@ -23,6 +23,7 @@ either = "1.15.0"
uzers = "0.12.2" uzers = "0.12.2"
sysinfo = "0.38.1" sysinfo = "0.38.1"
landlock = "0.4.4" landlock = "0.4.4"
cidr = { version = "0.3.2", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
serde_test = "1.0.177" serde_test = "1.0.177"

View file

@ -8,6 +8,7 @@ A declarative manager for Linux network namespaces.
- Hot reloading of configuration - Hot reloading of configuration
- Port forwarding into or out of the namespace - Port forwarding into or out of the namespace
- Moving network devices to the namespace - Moving network devices to the namespace
- Setting up routing inside the namespace
## Usage ## Usage
@ -57,6 +58,11 @@ name = "test"
# move existing devices into the namespace # move existing devices into the namespace
devices = ["somelink"] devices = ["somelink"]
# create a route inside the namespace
[[namespace.route]]
destination = "default" # either "default" or an ip range in CIDR notation
device = "somelink"
# You can define any number of port forwards to setup into the namespace # You can define any number of port forwards to setup into the namespace
[[namespace.forward]] [[namespace.forward]]
# port, address or socket outside the namespace to listen on # port, address or socket outside the namespace to listen on

View file

@ -5,6 +5,11 @@ name = "test"
# move existing devices into the namespace # move existing devices into the namespace
devices = ["somelink"] devices = ["somelink"]
# create a route inside the namespace
[[namespace.route]]
destination = "default" # either "default" or an ip range in CIDR notation
device = "somelink"
# You can define any number of port forwards to setup into the namespace # You can define any number of port forwards to setup into the namespace
[[namespace.forward]] [[namespace.forward]]
# port, address or socket outside the namespace to listen on # port, address or socket outside the namespace to listen on

View file

@ -60,6 +60,22 @@ in {
default = []; default = [];
description = "devices to move into the namespace"; description = "devices to move into the namespace";
}; };
route = mkOption {
type = types.listOf (types.submodule ({config, ...}: {
options = {
device = mkOption {
type = types.str;
description = "device to route the traffic trough";
};
destination = mkOption {
type = types.str;
description = "What traffic to route. Either \"default\" or an ip range in CIDR notation";
};
};
}));
description = "routes to setup inside the namespace";
default = [];
};
}; };
})); }));
description = "namespaces to setup"; description = "namespaces to setup";

View file

@ -5,10 +5,15 @@ mod target;
pub use crate::config::name::{DeviceName, NamespaceName}; pub use crate::config::name::{DeviceName, NamespaceName};
pub use crate::config::source::ForwardSource; pub use crate::config::source::ForwardSource;
pub use crate::config::target::ForwardTarget; pub use crate::config::target::ForwardTarget;
use serde::Deserialize; use cidr::AnyIpCidr;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use std::borrow::Cow;
use std::collections::HashSet; use std::collections::HashSet;
use std::fmt::{Display, Formatter};
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
use toml::from_str; use toml::from_str;
@ -84,6 +89,8 @@ pub struct NamespaceConfig {
pub forward: Vec<ForwardConfig>, pub forward: Vec<ForwardConfig>,
#[serde(default)] #[serde(default)]
pub devices: Vec<DeviceName>, pub devices: Vec<DeviceName>,
#[serde(default, rename = "route")]
pub routes: Vec<RouteConfig>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -94,6 +101,27 @@ pub struct ForwardConfig {
pub reverse: bool, pub reverse: bool,
} }
#[derive(Deserialize, Debug, PartialEq, Clone)]
pub struct RouteConfig {
#[serde(deserialize_with = "parse_cidr")]
pub destination: AnyIpCidr,
pub device: DeviceName,
}
impl Display for RouteConfig {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{} dev {}", self.destination, self.device)
}
}
fn parse_cidr<'de, D: Deserializer<'de>>(deserializer: D) -> Result<AnyIpCidr, D::Error> {
let str = Cow::<'de, str>::deserialize(deserializer)?;
match str.as_ref() {
"default" => Ok(AnyIpCidr::Any),
str => AnyIpCidr::from_str(str).map_err(D::Error::custom),
}
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum ConfigError { pub enum ConfigError {
#[error("Error while reading config from {}: {error:#}", path.display())] #[error("Error while reading config from {}: {error:#}", path.display())]

View file

@ -186,7 +186,7 @@ fn validate_name(name: &str) -> bool {
#[test] #[test]
fn test_de() { fn test_de() {
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; use serde_test::{Token, assert_de_tokens, assert_de_tokens_error};
assert_de_tokens(&NamespaceName("foo".into()), &[Token::String("foo")]); assert_de_tokens(&NamespaceName("foo".into()), &[Token::String("foo")]);

View file

@ -114,7 +114,7 @@ pub struct InvalidForwardSource {
#[test] #[test]
fn test_de() { fn test_de() {
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; use serde_test::{Token, assert_de_tokens, assert_de_tokens_error};
let addr_str = "127.0.0.1:80"; let addr_str = "127.0.0.1:80";
let addr = SocketAddr::from_str("127.0.0.1:80").unwrap(); let addr = SocketAddr::from_str("127.0.0.1:80").unwrap();

View file

@ -111,7 +111,7 @@ pub struct InvalidForwardTarget {
#[test] #[test]
fn test_de() { fn test_de() {
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; use serde_test::{Token, assert_de_tokens, assert_de_tokens_error};
let addr_str = "127.0.0.1:80"; let addr_str = "127.0.0.1:80";
let addr = SocketAddr::from_str("127.0.0.1:80").unwrap(); let addr = SocketAddr::from_str("127.0.0.1:80").unwrap();

View file

@ -1,4 +1,6 @@
use crate::config::{Config, DeviceName, ForwardConfig, NamespaceConfig, NamespaceName}; use crate::config::{
Config, DeviceName, ForwardConfig, NamespaceConfig, NamespaceName, RouteConfig,
};
use crate::link::{LinkError, LinkManager}; use crate::link::{LinkError, LinkManager};
use crate::namespace::{ use crate::namespace::{
NamespaceEnterError, NamespaceError, NamespaceHandle, NamespaceHandleError, NetNs, NamespaceEnterError, NamespaceError, NamespaceHandle, NamespaceHandleError, NetNs,
@ -128,6 +130,7 @@ impl State {
let config = config.get_namespace(namespace.name()).unwrap(); let config = config.get_namespace(namespace.name()).unwrap();
namespace.update_proxies(config)?; namespace.update_proxies(config)?;
namespace.update_devices(config)?; namespace.update_devices(config)?;
namespace.update_links(config)?;
} }
Ok(()) Ok(())
@ -144,6 +147,7 @@ struct ActiveNamespace {
ns: NetNs, ns: NetNs,
proxies: Vec<ActiveProxy>, proxies: Vec<ActiveProxy>,
devices: Vec<DeviceName>, devices: Vec<DeviceName>,
routes: Vec<RouteConfig>,
} }
impl ActiveNamespace { impl ActiveNamespace {
@ -154,6 +158,7 @@ impl ActiveNamespace {
ns, ns,
proxies: Vec::default(), proxies: Vec::default(),
devices: Vec::default(), devices: Vec::default(),
routes: Vec::default(),
}) })
} }
@ -212,6 +217,52 @@ impl ActiveNamespace {
Ok(()) Ok(())
} }
pub fn update_links(&mut self, config: &NamespaceConfig) -> Result<(), DaemonError> {
let removed: Vec<_> = self
.routes
.extract_if(.., |existing| {
!config.routes.iter().any(|new| existing == new)
})
.collect();
let mut added = Vec::new();
for new in &config.routes {
if !self.has_route(new) {
added.push(new.clone());
}
}
self.ns.handle().run_in(|| {
let link_manager = LinkManager::new()?;
for link in link_manager.get_links()?.flatten() {
if let Some(route) = removed
.iter()
.find(|route| route.device == link.name.as_str())
{
info!(namespace = %config.name, %route, "deleting route");
link_manager.delete_route(&link, route.destination)?;
}
}
for link in link_manager.get_links()?.flatten() {
if let Some(route) = added
.iter()
.find(|route| route.device == link.name.as_str())
{
info!(namespace = %config.name, %route, "adding route");
link_manager.add_route(&link, route.destination)?;
}
}
Ok::<_, DaemonError>(())
})??;
for new in added {
self.routes.push(new);
}
Ok(())
}
fn has_forward(&self, config: &ForwardConfig) -> bool { fn has_forward(&self, config: &ForwardConfig) -> bool {
self.proxies.iter().any(|existing| existing == config) self.proxies.iter().any(|existing| existing == config)
} }
@ -220,6 +271,10 @@ impl ActiveNamespace {
self.devices.iter().any(|existing| existing == name) self.devices.iter().any(|existing| existing == name)
} }
fn has_route(&self, route: &RouteConfig) -> bool {
self.routes.iter().any(|existing| existing == route)
}
pub fn name(&self) -> &NamespaceName { pub fn name(&self) -> &NamespaceName {
self.ns.name() self.ns.name()
} }

View file

@ -1,26 +1,27 @@
use cidr::{AnyIpCidr, Family};
use neli::consts::nl::NlmF; use neli::consts::nl::NlmF;
use neli::consts::rtnl::Ifla;
use neli::consts::rtnl::RtAddrFamily;
use neli::consts::rtnl::Rtm; use neli::consts::rtnl::Rtm;
use neli::consts::rtnl::{Ifla, RtScope, RtTable, Rta};
use neli::consts::rtnl::{RtAddrFamily, Rtn, Rtprot};
use neli::consts::socket::NlFamily; use neli::consts::socket::NlFamily;
use neli::err::RouterError; use neli::err::RouterError;
use neli::nl::NlPayload; use neli::nl::NlPayload;
use neli::router::synchronous::NlRouter; use neli::router::synchronous::NlRouter;
use neli::rtnl::IfinfomsgBuilder; use neli::rtnl::{Ifinfomsg, RtattrBuilder, Rtmsg};
use neli::rtnl::{Ifinfomsg, RtattrBuilder}; use neli::rtnl::{IfinfomsgBuilder, RtmsgBuilder};
use neli::types::{Buffer, RtBuffer}; use neli::types::{Buffer, RtBuffer};
use neli::utils::Groups; use neli::utils::Groups;
use nix::libc::c_int; use nix::libc::c_int;
use std::fmt::Debug; use std::fmt::{Debug, Display, Formatter};
use std::os::fd::AsRawFd; use std::os::fd::AsRawFd;
use thiserror::Error; use thiserror::Error;
use tracing::info; use tracing::{info, instrument};
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum LinkError { pub enum LinkError {
#[error("Failed to communicate with netlink:")] #[error("Failed to communicate with netlink: {0}")]
Netlink(String), Netlink(String),
#[error("Failed to parse netlink response")] #[error("Failed to parse netlink response: {0}")]
Parse(String), Parse(String),
#[error("Link not found: {0}")] #[error("Link not found: {0}")]
NotFound(String), NotFound(String),
@ -46,6 +47,12 @@ pub struct Link {
pub name: String, pub name: String,
} }
impl Display for Link {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.name)
}
}
impl Link { impl Link {
fn msg_builder(&self) -> IfinfomsgBuilder { fn msg_builder(&self) -> IfinfomsgBuilder {
IfinfomsgBuilder::default() IfinfomsgBuilder::default()
@ -112,7 +119,7 @@ impl LinkManager {
info_attrs.push( info_attrs.push(
RtattrBuilder::default() RtattrBuilder::default()
.rta_type(Ifla::NetNsFd) .rta_type(Ifla::NetNsFd)
.rta_payload(Buffer::from(ns_handle.to_ne_bytes().as_slice())) .rta_payload(ns_handle)
.build() .build()
.expect("invalid rtattr"), .expect("invalid rtattr"),
); );
@ -135,6 +142,47 @@ impl LinkManager {
)?; )?;
Ok(()) Ok(())
} }
#[instrument(skip_all, fields(link = %link, destination = %destination))]
pub fn add_route(&self, link: &Link, destination: AnyIpCidr) -> Result<(), LinkError> {
let rt_msg = route_message_for(link, destination);
let res = self.router.send::<_, _, Rtm, Rtmsg>(
Rtm::Newroute,
NlmF::CREATE | NlmF::EXCL | NlmF::REQUEST | NlmF::ACK,
NlPayload::Payload(rt_msg),
)?;
for msg in res {
match msg {
Err(RouterError::Nlmsgerr(err)) if *err.error() == -17 => {
info!("route already exists");
// already exists
}
Err(err) => {
return Err(err.into());
}
_ => {}
}
}
Ok(())
}
#[instrument(skip_all, fields(link = %link, destination = %destination))]
pub fn delete_route(&self, link: &Link, destination: AnyIpCidr) -> Result<(), LinkError> {
let rt_msg = route_message_for(link, destination);
let res = self.router.send::<_, _, Rtm, Rtmsg>(
Rtm::Delroute,
NlmF::REQUEST | NlmF::ACK,
NlPayload::Payload(rt_msg),
)?;
for msg in res {
msg?;
}
Ok(())
}
} }
/// Set a link to UP /// Set a link to UP
@ -147,6 +195,7 @@ pub fn link_up(link_name: &str) -> Result<(), LinkError> {
/// Move a link into a namespace /// Move a link into a namespace
pub fn move_link_into<Fd: AsRawFd>(link_name: &str, namespace: &Fd) -> Result<(), LinkError> { pub fn move_link_into<Fd: AsRawFd>(link_name: &str, namespace: &Fd) -> Result<(), LinkError> {
let manager = LinkManager::new()?; let manager = LinkManager::new()?;
// todo, might already be in target ns
let link = manager.get_link(link_name)?; let link = manager.get_link(link_name)?;
info!(name = &link.name, "moving link into namespace"); info!(name = &link.name, "moving link into namespace");
manager.move_link(&link, namespace) manager.move_link(&link, namespace)
@ -163,3 +212,54 @@ pub fn move_all_links<Fd: AsRawFd>(namespace: &Fd) -> Result<(), LinkError> {
} }
Ok::<_, LinkError>(()) Ok::<_, LinkError>(())
} }
fn route_message_for(link: &Link, destination: AnyIpCidr) -> Rtmsg {
let mut info_attrs = RtBuffer::<Rta, Buffer>::new();
match &destination {
AnyIpCidr::V4(addr) => {
info_attrs.push(
RtattrBuilder::default()
.rta_type(Rta::Dst)
.rta_payload(addr.first_address().octets())
.build()
.expect("invalid rtattr"),
);
}
AnyIpCidr::V6(addr) => {
info_attrs.push(
RtattrBuilder::default()
.rta_type(Rta::Dst)
.rta_payload(addr.first_address().octets())
.build()
.expect("invalid rtattr"),
);
}
_ => {}
}
info_attrs.push(
RtattrBuilder::default()
.rta_type(Rta::Oif)
.rta_payload(link.index)
.build()
.expect("invalid rtattr"),
);
let family = match &destination.family() {
None => RtAddrFamily::Inet,
Some(Family::Ipv4) => RtAddrFamily::Inet,
Some(Family::Ipv6) => RtAddrFamily::Inet6,
};
RtmsgBuilder::default()
.rtm_table(RtTable::Main)
.rtm_scope(RtScope::Universe)
.rtm_family(family)
.rtattrs(info_attrs)
.rtm_src_len(0)
.rtm_tos(0)
.rtm_protocol(Rtprot::Boot)
.rtm_type(Rtn::Unicast)
.rtm_dst_len(destination.network_length().unwrap_or_default())
.build()
.expect("rt msg")
}

View file

@ -5,10 +5,10 @@ use crate::proxy::proxy;
use crate::up::up; use crate::up::up;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use main_error::MainResult; use main_error::MainResult;
use std::path::PathBuf;
use nix::errno::Errno; use nix::errno::Errno;
use nix::sys::signal::{kill, Signal}; use nix::sys::signal::{Signal, kill};
use nix::unistd::Pid; use nix::unistd::Pid;
use std::path::PathBuf;
use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind}; use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
use tracing::{error, info, warn}; use tracing::{error, info, warn};
@ -94,10 +94,12 @@ fn reload() -> MainResult {
match kill(Pid::from_raw(proc.pid().as_u32() as i32), Signal::SIGHUP) { match kill(Pid::from_raw(proc.pid().as_u32() as i32), Signal::SIGHUP) {
Ok(_) => { Ok(_) => {
info!("Sent reload command to daemon") info!("Sent reload command to daemon")
}, }
Err(Errno::EPERM) => { Err(Errno::EPERM) => {
error!("Sending signal not permitted, try are you running the command as root?"); error!(
}, "Sending signal not permitted, try are you running the command as root?"
);
}
Err(error) => { Err(error) => {
error!(%error, "Unexpected error"); error!(%error, "Unexpected error");
} }

View file

@ -1,6 +1,6 @@
use crate::namespace::NamespaceEnterError; use crate::namespace::NamespaceEnterError;
use nix::errno::Errno; use nix::errno::Errno;
use nix::sched::{setns, CloneFlags}; use nix::sched::{CloneFlags, setns};
use std::fs::File; use std::fs::File;
use std::io::Error as IoError; use std::io::Error as IoError;
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd}; use std::os::fd::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd};

View file

@ -2,13 +2,13 @@ mod handle;
mod raw; mod raw;
use crate::config::{DeviceName, NamespaceName}; use crate::config::{DeviceName, NamespaceName};
use crate::link::{link_up, move_all_links, move_link_into, LinkError}; use crate::link::{LinkError, link_up, move_all_links, move_link_into};
pub use crate::namespace::handle::{NamespaceHandle, NamespaceHandleError}; pub use crate::namespace::handle::{NamespaceHandle, NamespaceHandleError};
use crate::namespace::raw::{create_network_namespace, NamespaceSetupError}; use crate::namespace::raw::{NamespaceSetupError, create_network_namespace};
use either::Either; use either::Either;
use nix::errno::Errno; use nix::errno::Errno;
use nix::mount::{mount, umount2, MntFlags, MsFlags}; use nix::mount::{MntFlags, MsFlags, mount, umount2};
use std::fs::{create_dir, read_dir, remove_file, File}; use std::fs::{File, create_dir, read_dir, remove_file};
use std::io::{Error as IoError, ErrorKind}; use std::io::{Error as IoError, ErrorKind};
use std::iter::empty; use std::iter::empty;
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
@ -162,9 +162,7 @@ impl NetNs {
pub fn delete(self) -> Result<(), NamespaceError> { pub fn delete(self) -> Result<(), NamespaceError> {
let parent_namespace = NamespaceHandle::parent()?; let parent_namespace = NamespaceHandle::parent()?;
self.handle.run_in(|| { self.handle.run_in(|| move_all_links(&parent_namespace))??;
move_all_links(&parent_namespace)
})??;
let name = self.path.file_name().unwrap().to_str().unwrap(); let name = self.path.file_name().unwrap().to_str().unwrap();
info!(name, "deleting network namespace"); info!(name, "deleting network namespace");
match umount2(&self.path, MntFlags::MNT_DETACH) { match umount2(&self.path, MntFlags::MNT_DETACH) {

View file

@ -1,7 +1,7 @@
use nix::errno::Errno; use nix::errno::Errno;
use nix::sched::{clone, CloneFlags}; use nix::sched::{CloneFlags, clone};
use nix::sys::signal::Signal; use nix::sys::signal::Signal;
use nix::sys::wait::{waitpid, WaitStatus}; use nix::sys::wait::{WaitStatus, waitpid};
use std::path::PathBuf; use std::path::PathBuf;
use thiserror::Error; use thiserror::Error;

View file

@ -3,7 +3,12 @@ mod tcp;
use crate::config::{ForwardConfig, ForwardSource, ForwardTarget, NamespaceName}; use crate::config::{ForwardConfig, ForwardSource, ForwardTarget, NamespaceName};
use crate::proxy::tcp::Proxy; use crate::proxy::tcp::Proxy;
use futures::future::AbortHandle; use futures::future::AbortHandle;
use landlock::{
ABI, Access, AccessFs, AccessNet, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr,
RulesetError, RulesetStatus,
};
use main_error::MainResult; use main_error::MainResult;
use nix::errno::Errno;
use nix::sched::{CloneFlags, setns}; use nix::sched::{CloneFlags, setns};
use nix::sys::signal::{SIGINT, kill}; use nix::sys::signal::{SIGINT, kill};
use nix::unistd::{Gid, Pid, Uid, setgid, setuid}; use nix::unistd::{Gid, Pid, Uid, setgid, setuid};
@ -13,8 +18,6 @@ use std::net::SocketAddr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, Command}; use std::process::{Child, Command};
use std::thread::spawn; use std::thread::spawn;
use landlock::{Access, AccessFs, AccessNet, NetPort, Ruleset, RulesetAttr, RulesetCreatedAttr, RulesetError, RulesetStatus, ABI};
use nix::errno::Errno;
use thiserror::Error; use thiserror::Error;
use tokio::runtime::Builder; use tokio::runtime::Builder;
use tokio::signal::ctrl_c; use tokio::signal::ctrl_c;

View file

@ -1,6 +1,6 @@
/// Loosely based on https://github.com/fooker/netns-proxy/blob/main/src/tcp.rs /// Loosely based on https://github.com/fooker/netns-proxy/blob/main/src/tcp.rs
use crate::config::{ForwardTarget, ForwardSource}; use crate::config::{ForwardSource, ForwardTarget};
use crate::proxy::{ProxyError}; use crate::proxy::ProxyError;
use futures::TryStreamExt; use futures::TryStreamExt;
use futures::stream::{AbortRegistration, Abortable}; use futures::stream::{AbortRegistration, Abortable};
use std::fs::{remove_file, set_permissions}; use std::fs::{remove_file, set_permissions};
@ -56,28 +56,18 @@ impl Proxy {
})?; })?;
debug!("Created TCP socket"); debug!("Created TCP socket");
Ok(Self { Ok(Self { socket })
socket,
})
} }
pub async fn run(self, target: ForwardTarget, abort: AbortRegistration) { pub async fn run(self, target: ForwardTarget, abort: AbortRegistration) {
match self.socket { match self.socket {
ProxyListener::Tcp(socket) => { ProxyListener::Tcp(socket) => run_tcp(socket, target.addr, abort).await,
run_tcp(socket, target.addr, abort).await ProxyListener::Unix(socket) => run_unix(socket, target.addr, abort).await,
}
ProxyListener::Unix(socket) => {
run_unix(socket, target.addr, abort).await
}
} }
} }
} }
async fn run_tcp( async fn run_tcp(socket: TcpListener, target: SocketAddr, abort: AbortRegistration) {
socket: TcpListener,
target: SocketAddr,
abort: AbortRegistration,
) {
let accepts = TcpListenerStream::new(socket).map_err(|error| ProxyError::Accept { error }); let accepts = TcpListenerStream::new(socket).map_err(|error| ProxyError::Accept { error });
let mut accepts = pin!(Abortable::new(accepts, abort)); let mut accepts = pin!(Abortable::new(accepts, abort));
while let Some(client) = accepts.next().await { while let Some(client) = accepts.next().await {
@ -94,11 +84,7 @@ async fn run_tcp(
} }
} }
async fn run_unix( async fn run_unix(socket: UnixListener, target: SocketAddr, abort: AbortRegistration) {
socket: UnixListener,
target: SocketAddr,
abort: AbortRegistration,
) {
let accepts = UnixListenerStream::new(socket).map_err(|error| ProxyError::Accept { error }); let accepts = UnixListenerStream::new(socket).map_err(|error| ProxyError::Accept { error });
let mut accepts = pin!(Abortable::new(accepts, abort)); let mut accepts = pin!(Abortable::new(accepts, abort));
while let Some(client) = accepts.next().await { while let Some(client) = accepts.next().await {

View file

@ -1,4 +1,5 @@
use crate::config::{Config, NamespaceName}; use crate::config::{Config, NamespaceName};
use crate::link::{LinkError, LinkManager};
use crate::namespace::NetNs; use crate::namespace::NetNs;
use main_error::MainResult; use main_error::MainResult;
use tracing::error; use tracing::error;
@ -24,6 +25,14 @@ pub fn up(config: Config) -> MainResult {
error!(%error, "failed to move device into namespace"); error!(%error, "failed to move device into namespace");
} }
} }
for route in new.routes {
namespace.handle().run_in(|| {
let manager = LinkManager::new()?;
let link = manager.get_link(&route.device)?;
manager.add_route(&link, route.destination)?;
Ok::<_, LinkError>(())
})??;
}
} }
Ok(()) Ok(())