mirror of
https://codeberg.org/icewind/netnsd.git
synced 2026-06-03 09:04:07 +02:00
reload destination -> target and module fixes
This commit is contained in:
parent
645a6e9978
commit
5e5ee227fc
10 changed files with 69 additions and 42 deletions
9
README.md
Normal file
9
README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# netnsd
|
||||
|
||||
A declarative manager for Linux network namespaces.
|
||||
|
||||
## Features
|
||||
|
||||
- Fully declarative configuration
|
||||
- Hot reloading of configuration
|
||||
- Port forwarding into the namespace
|
||||
|
|
@ -21,7 +21,8 @@
|
|||
...
|
||||
}: {
|
||||
imports = [./nix/module.nix];
|
||||
config = lib.mkIf config.networking.netnsd.enable {
|
||||
config = lib.mkIf (config.networking.netnsd.namespaces != {}) {
|
||||
nixpkgs.overlays = [ outputs.overlays.default ];
|
||||
networking.netnsd.package = lib.mkDefault pkgs.netnsd;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ with lib; let
|
|||
hasNamespaces = cfg.namespaces != {};
|
||||
format = pkgs.formats.toml {};
|
||||
configFile = format.generate "netnsd.toml" {
|
||||
inherit (cfg) namespaces;
|
||||
namespace = mapAttrsToList (_: value: value) cfg.namespaces;
|
||||
};
|
||||
in {
|
||||
options.networking.netnsd = {
|
||||
|
|
@ -18,6 +18,12 @@ in {
|
|||
description = "package to use";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.str;
|
||||
default = "info";
|
||||
description = "Log level";
|
||||
};
|
||||
|
||||
namespaces = mkOption {
|
||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||
options = {
|
||||
|
|
@ -31,11 +37,11 @@ in {
|
|||
options = {
|
||||
source = mkOption {
|
||||
type = types.oneOf [types.port types.str];
|
||||
default = config.destination;
|
||||
defaultText = "<destination>";
|
||||
default = config.target;
|
||||
defaultText = "<target>";
|
||||
description = "source port, address or socket outside the namespace";
|
||||
};
|
||||
destination = mkOption {
|
||||
target = mkOption {
|
||||
type = types.oneOf [types.port types.str];
|
||||
description = "target port or address inside the namespace";
|
||||
};
|
||||
|
|
@ -46,6 +52,7 @@ in {
|
|||
};
|
||||
}));
|
||||
description = "namespaces to setup";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -53,17 +60,20 @@ in {
|
|||
# symlink instead of passing `configFile` directly to netnsd to allow changing the config without changing the path
|
||||
environment.etc."netnsd/netnsd.toml".source = configFile;
|
||||
|
||||
systemd.services.netcsctl = {
|
||||
systemd.services.netnsd = {
|
||||
reloadTriggers = [configFile];
|
||||
|
||||
wantedBy = ["multi-user.target"];
|
||||
before = ["network.target"];
|
||||
|
||||
environment = {
|
||||
RUST_LOG = cfg.logLevel;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "on-failure";
|
||||
Type = "notify-reload";
|
||||
ExecStart = "${getExec cfg.pkg} daemon -c /etc/netnsd/netnsd.toml";
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = true;
|
||||
ExecStart = "${getExe cfg.package} daemon -c /etc/netnsd/netnsd.toml";
|
||||
NoNewPrivileges = true;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,4 +16,6 @@ in
|
|||
cargoLock = {
|
||||
lockFile = ../Cargo.lock;
|
||||
};
|
||||
|
||||
meta.mainProgram = "netnsd";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
mod destination;
|
||||
mod target;
|
||||
mod name;
|
||||
mod source;
|
||||
|
||||
pub use crate::config::destination::ForwardDestination;
|
||||
pub use crate::config::target::ForwardTarget;
|
||||
pub use crate::config::name::NamespaceName;
|
||||
pub use crate::config::source::ForwardSource;
|
||||
use serde::Deserialize;
|
||||
|
|
@ -80,7 +80,7 @@ pub struct NamespaceConfig {
|
|||
#[derive(Deserialize, Debug)]
|
||||
pub struct ForwardConfig {
|
||||
pub source: ForwardSource,
|
||||
pub destination: ForwardDestination,
|
||||
pub target: ForwardTarget,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -5,31 +5,31 @@ use std::net::{IpAddr, SocketAddr};
|
|||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash, Eq)]
|
||||
pub struct ForwardDestination {
|
||||
pub struct ForwardTarget {
|
||||
pub addr: SocketAddr,
|
||||
}
|
||||
|
||||
impl From<ForwardDestination> for SocketAddr {
|
||||
fn from(value: ForwardDestination) -> Self {
|
||||
impl From<ForwardTarget> for SocketAddr {
|
||||
fn from(value: ForwardTarget) -> Self {
|
||||
value.addr
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ForwardDestination {
|
||||
impl Display for ForwardTarget {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.addr)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ForwardDestination {
|
||||
impl<'de> Deserialize<'de> for ForwardTarget {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ForwardDestinationVisitor;
|
||||
struct ForwardTargetVisitor;
|
||||
|
||||
impl Visitor<'_> for ForwardDestinationVisitor {
|
||||
type Value = ForwardDestination;
|
||||
impl Visitor<'_> for ForwardTargetVisitor {
|
||||
type Value = ForwardTarget;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
formatter
|
||||
|
|
@ -51,7 +51,7 @@ impl<'de> Deserialize<'de> for ForwardDestination {
|
|||
E: Error,
|
||||
{
|
||||
let ip = IpAddr::from([127, 0, 0, 1]);
|
||||
Ok(ForwardDestination {
|
||||
Ok(ForwardTarget {
|
||||
addr: SocketAddr::from((ip, v)),
|
||||
})
|
||||
}
|
||||
|
|
@ -76,11 +76,11 @@ impl<'de> Deserialize<'de> for ForwardDestination {
|
|||
let addr = v
|
||||
.parse()
|
||||
.map_err(|_| E::invalid_value(Unexpected::Str(v), &self))?;
|
||||
Ok(ForwardDestination { addr })
|
||||
Ok(ForwardTarget { addr })
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_any(ForwardDestinationVisitor)
|
||||
deserializer.deserialize_any(ForwardTargetVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,14 +90,14 @@ fn test_de() {
|
|||
|
||||
let addr_str = "127.0.0.1:80";
|
||||
let addr = SocketAddr::from_str("127.0.0.1:80").unwrap();
|
||||
fn port_addr(port: u16) -> ForwardDestination {
|
||||
ForwardDestination {
|
||||
fn port_addr(port: u16) -> ForwardTarget {
|
||||
ForwardTarget {
|
||||
addr: SocketAddr::new(IpAddr::from([127, 0, 0, 1]), port),
|
||||
}
|
||||
}
|
||||
|
||||
assert_de_tokens(&ForwardDestination { addr }, &[Token::String(addr_str)]);
|
||||
assert_de_tokens(&ForwardDestination { addr }, &[Token::Str(addr_str)]);
|
||||
assert_de_tokens(&ForwardTarget { addr }, &[Token::String(addr_str)]);
|
||||
assert_de_tokens(&ForwardTarget { addr }, &[Token::Str(addr_str)]);
|
||||
assert_de_tokens(&port_addr(80), &[Token::Str("80")]);
|
||||
|
||||
assert_de_tokens(&port_addr(80), &[Token::U8(80)]);
|
||||
|
|
@ -107,19 +107,19 @@ fn test_de() {
|
|||
assert_de_tokens(&port_addr(80), &[Token::I16(80)]);
|
||||
assert_de_tokens(&port_addr(80), &[Token::I64(80)]);
|
||||
|
||||
assert_de_tokens_error::<ForwardDestination>(
|
||||
assert_de_tokens_error::<ForwardTarget>(
|
||||
&[Token::I64(-80)],
|
||||
"invalid value: integer `-80`, expected Either a port as integer, or a string containing a socket address",
|
||||
);
|
||||
assert_de_tokens_error::<ForwardDestination>(
|
||||
assert_de_tokens_error::<ForwardTarget>(
|
||||
&[Token::U64(12345678)],
|
||||
"invalid value: integer `12345678`, expected Either a port as integer, or a string containing a socket address",
|
||||
);
|
||||
assert_de_tokens_error::<ForwardDestination>(
|
||||
assert_de_tokens_error::<ForwardTarget>(
|
||||
&[Token::Str("hello world")],
|
||||
"invalid value: string \"hello world\", expected Either a port as integer, or a string containing a socket address",
|
||||
);
|
||||
assert_de_tokens_error::<ForwardDestination>(
|
||||
assert_de_tokens_error::<ForwardTarget>(
|
||||
&[Token::Str("localhost:80")],
|
||||
"invalid value: string \"localhost:80\", expected Either a port as integer, or a string containing a socket address",
|
||||
);
|
||||
|
|
@ -38,9 +38,12 @@ async fn daemon_async(mut config: Config) -> Result<(), DaemonError> {
|
|||
let info_signal = signal(SignalKind::user_defined1()).map_err(DaemonError::Signal)?;
|
||||
let info_signal = SignalStream::new(info_signal).map(|_| Event::Info);
|
||||
|
||||
let stop_signal = signal(SignalKind::terminate()).map_err(DaemonError::Signal)?;
|
||||
let stop_signal = SignalStream::new(stop_signal).map(|_| Event::Quit);
|
||||
|
||||
let quit_signal = ctrl_c().into_stream().map(|_| Event::Quit);
|
||||
|
||||
let events = (reload_signal, info_signal, quit_signal).merge();
|
||||
let events = (reload_signal, info_signal, stop_signal, quit_signal).merge();
|
||||
|
||||
let mut events = pin!(events);
|
||||
while let Some(event) = events.next().await {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use std::fs::{File, create_dir_all, remove_file};
|
|||
use std::io::{Error as IoError, ErrorKind};
|
||||
use std::path::{Path, PathBuf};
|
||||
use thiserror::Error;
|
||||
use tracing::{error, info};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub struct NetNs {
|
||||
name: NamespaceName,
|
||||
|
|
@ -21,7 +21,6 @@ pub struct NetNs {
|
|||
impl NetNs {
|
||||
/// Create a new named network namespace that will be removed when dropped
|
||||
pub fn new(name: NamespaceName) -> Result<Self, NamespaceError> {
|
||||
info!(%name, "creating network namespace");
|
||||
let parent = Path::new("/var/run/netns");
|
||||
create_dir_all(parent).map_err(NamespaceError::Parent)?;
|
||||
let path = parent.join(&name);
|
||||
|
|
@ -29,6 +28,7 @@ impl NetNs {
|
|||
match File::create_new(&path) {
|
||||
Ok(_) => {}
|
||||
Err(e) if e.kind() == ErrorKind::AlreadyExists => {
|
||||
info!(%name, "using existing network namespace");
|
||||
return Ok(NetNs {
|
||||
name: name.clone(),
|
||||
path,
|
||||
|
|
@ -36,6 +36,7 @@ impl NetNs {
|
|||
}
|
||||
Err(e) => return Err(NamespaceError::from_create(path.clone(), e)),
|
||||
}
|
||||
info!(%name, "creating network namespace");
|
||||
|
||||
let ns = create_network_namespace(move |ns| {
|
||||
bind_namespace(&ns, &path)?;
|
||||
|
|
@ -52,6 +53,7 @@ impl NetNs {
|
|||
}
|
||||
|
||||
fn bind_namespace(namespace: &Path, path: &Path) -> Result<(), NamespaceError> {
|
||||
debug!(namespace = %namespace.display(), path = %path.display(), "mounting namespace");
|
||||
mount(
|
||||
Some(namespace.as_os_str()),
|
||||
path.as_os_str(),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
mod tcp;
|
||||
|
||||
use crate::config::{ForwardConfig, ForwardDestination, ForwardSource, NamespaceName};
|
||||
use crate::config::{ForwardConfig, ForwardTarget, ForwardSource, NamespaceName};
|
||||
use crate::daemon::proxy::tcp::Proxy;
|
||||
use futures::future::AbortHandle;
|
||||
use nix::sched::{CloneFlags, setns};
|
||||
|
|
@ -35,7 +35,7 @@ pub enum ProxyError {
|
|||
|
||||
pub struct ActiveProxy {
|
||||
pub source: ForwardSource,
|
||||
pub destination: ForwardDestination,
|
||||
pub destination: ForwardTarget,
|
||||
abort: AbortHandle,
|
||||
pub stats: ProxyStats,
|
||||
}
|
||||
|
|
@ -50,7 +50,7 @@ impl ActiveProxy {
|
|||
|
||||
let (abort, abort_reg) = AbortHandle::new_pair();
|
||||
|
||||
let destination = config.destination.clone();
|
||||
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 {
|
||||
|
|
@ -75,7 +75,7 @@ impl ActiveProxy {
|
|||
|
||||
Ok(ActiveProxy {
|
||||
source: config.source.clone(),
|
||||
destination: config.destination.clone(),
|
||||
destination: config.target.clone(),
|
||||
abort,
|
||||
stats,
|
||||
})
|
||||
|
|
@ -95,7 +95,7 @@ impl Drop for ActiveProxy {
|
|||
|
||||
impl PartialEq<ForwardConfig> for ActiveProxy {
|
||||
fn eq(&self, other: &ForwardConfig) -> bool {
|
||||
self.source == other.source && self.destination == other.destination
|
||||
self.source == other.source && self.destination == other.target
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/// Loosely based on https://github.com/fooker/netns-proxy/blob/main/src/tcp.rs
|
||||
use crate::config::{ForwardDestination, ForwardSource};
|
||||
use crate::config::{ForwardTarget, ForwardSource};
|
||||
use crate::daemon::proxy::{ProxyError, ProxyStats};
|
||||
use futures::TryStreamExt;
|
||||
use futures::stream::{AbortRegistration, Abortable};
|
||||
|
|
@ -61,7 +61,7 @@ impl Proxy {
|
|||
})
|
||||
}
|
||||
|
||||
pub async fn run(self, target: ForwardDestination, abort: AbortRegistration, stats: ProxyStats) {
|
||||
pub async fn run(self, target: ForwardTarget, abort: AbortRegistration, stats: ProxyStats) {
|
||||
let proxy_stats = stats.clone();
|
||||
match self.socket {
|
||||
ProxyListener::Tcp(socket) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue