mirror of
https://codeberg.org/icewind/netnsd.git
synced 2026-06-03 09:04:07 +02:00
basic reloading
This commit is contained in:
parent
a4c7b3c1c9
commit
78e716f949
9 changed files with 562 additions and 32 deletions
|
|
@ -28,7 +28,7 @@ impl<'de> Deserialize<'de> for ForwardDestination {
|
|||
{
|
||||
struct ForwardDestinationVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ForwardDestinationVisitor {
|
||||
impl Visitor<'_> for ForwardDestinationVisitor {
|
||||
type Value = ForwardDestination;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
|
|
@ -75,7 +75,7 @@ impl<'de> Deserialize<'de> for ForwardDestination {
|
|||
}
|
||||
let addr = v
|
||||
.parse()
|
||||
.map_err(|_| E::invalid_value(Unexpected::Str(&v), &self))?;
|
||||
.map_err(|_| E::invalid_value(Unexpected::Str(v), &self))?;
|
||||
Ok(ForwardDestination { addr })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@ impl Config {
|
|||
error,
|
||||
path: path.to_owned(),
|
||||
})?;
|
||||
Ok(config
|
||||
config
|
||||
.validate(path)
|
||||
.map_err(|error| ConfigError::Validation {
|
||||
error,
|
||||
path: path.to_owned(),
|
||||
})?)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn reload(&self) -> Result<Config, ConfigError> {
|
||||
Self::load(&self.path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::path::Path;
|
|||
use serde::{Deserialize, Deserializer};
|
||||
use serde::de::{Error, Unexpected, Visitor};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct NamespaceName(String);
|
||||
|
||||
impl Display for NamespaceName {
|
||||
|
|
@ -37,7 +37,7 @@ impl<'de> Deserialize<'de> for NamespaceName {
|
|||
{
|
||||
struct NamespaceNameVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for NamespaceNameVisitor {
|
||||
impl Visitor<'_> for NamespaceNameVisitor {
|
||||
type Value = NamespaceName;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
|
|
@ -82,7 +82,7 @@ fn validate_name(name: &str) -> bool {
|
|||
fn test_de() {
|
||||
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")]);
|
||||
|
||||
assert_de_tokens_error::<NamespaceName>(
|
||||
&[Token::String("foo/bar")],
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ impl<'de> Deserialize<'de> for ForwardSource {
|
|||
{
|
||||
struct ForwardSourceVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for ForwardSourceVisitor {
|
||||
impl Visitor<'_> for ForwardSourceVisitor {
|
||||
type Value = ForwardSource;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
|
|
@ -61,7 +61,7 @@ impl<'de> Deserialize<'de> for ForwardSource {
|
|||
.map_err(|_| E::invalid_value(Unexpected::Unsigned(v), &self))?;
|
||||
self.visit_u16(v)
|
||||
}
|
||||
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
|
|
@ -85,7 +85,7 @@ impl<'de> Deserialize<'de> for ForwardSource {
|
|||
}
|
||||
let addr = v
|
||||
.parse()
|
||||
.map_err(|_| E::invalid_value(Unexpected::Str(&v), &self))?;
|
||||
.map_err(|_| E::invalid_value(Unexpected::Str(v), &self))?;
|
||||
Ok(ForwardSource::Ip(addr))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,27 @@
|
|||
mod namespace;
|
||||
|
||||
use crate::config::{Config, NamespaceName};
|
||||
use crate::config::{Config, NamespaceConfig, NamespaceName};
|
||||
use crate::daemon::namespace::{NamespaceError, NetNs};
|
||||
use main_error::MainResult;
|
||||
use sd_notify::{notify, NotifyState};
|
||||
use sd_notify::{NotifyState, notify};
|
||||
use std::io::Error as IoError;
|
||||
use std::pin::pin;
|
||||
use futures::FutureExt;
|
||||
use thiserror::Error;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::signal::ctrl_c;
|
||||
use tokio::signal::unix::{SignalKind, signal};
|
||||
use futures::StreamExt;
|
||||
use futures_concurrency::stream::Merge;
|
||||
use tokio_stream::wrappers::SignalStream;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub fn daemon(config: Config) -> MainResult {
|
||||
let rt = Runtime::new()?;
|
||||
Ok(rt.block_on(daemon_async(config))?)
|
||||
}
|
||||
|
||||
async fn daemon_async(config: Config) -> Result<(), DaemonError> {
|
||||
async fn daemon_async(mut config: Config) -> Result<(), DaemonError> {
|
||||
for namespace in &config.namespaces {
|
||||
println!("{}:", namespace.name);
|
||||
for forward in &namespace.forward {
|
||||
|
|
@ -22,28 +29,104 @@ async fn daemon_async(config: Config) -> Result<(), DaemonError> {
|
|||
}
|
||||
}
|
||||
|
||||
let namespaces = config
|
||||
.namespaces
|
||||
.iter()
|
||||
.map(|ns| ActiveNamespace::new(&ns.name))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let mut state = State::default();
|
||||
state.update(&config)?;
|
||||
|
||||
// now the namespaces are setup, we can tell systemd to start any service depending on them
|
||||
notify(true, &[NotifyState::Ready]).map_err(DaemonError::Notify)?;
|
||||
|
||||
let _ = ctrl_c().await;
|
||||
let reload_signal = signal(SignalKind::hangup()).map_err(DaemonError::ReloadSignal)?;
|
||||
let reload_signal = SignalStream::new(reload_signal).map(|_| Event::Reload);
|
||||
let quit_signal = ctrl_c().into_stream().map(|_| Event::Quit);
|
||||
|
||||
let events = (reload_signal, quit_signal).merge();
|
||||
|
||||
let mut events = pin!(events);
|
||||
while let Some(event) = events.next().await {
|
||||
debug!(?event, "handling event");
|
||||
match event {
|
||||
Event::Quit => {
|
||||
break;
|
||||
}
|
||||
Event::Reload => {
|
||||
info!("reloading config");
|
||||
|
||||
match NotifyState::monotonic_usec_now() {
|
||||
Ok(notify_time) => {
|
||||
notify(true, &[NotifyState::Reloading, notify_time]).map_err(DaemonError::Notify)?;
|
||||
}
|
||||
Err(error) => {
|
||||
error!(%error, "failed to get current time, not sending reload signal");
|
||||
}
|
||||
}
|
||||
|
||||
match config.reload() {
|
||||
Ok(new_config) => {
|
||||
state.update(&new_config)?;
|
||||
config = new_config;
|
||||
},
|
||||
Err(error) => {
|
||||
error!(%error, "Failed to load new config");
|
||||
}
|
||||
}
|
||||
|
||||
notify(true, &[NotifyState::Ready]).map_err(DaemonError::Notify)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
Reload,
|
||||
Quit,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
namespaces: Vec<ActiveNamespace>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn update(&mut self, config: &Config) -> Result<(), DaemonError> {
|
||||
self.namespaces.retain(|existing| {
|
||||
config
|
||||
.namespaces
|
||||
.iter()
|
||||
.any(|new| &new.name == existing.name())
|
||||
});
|
||||
|
||||
for new in &config.namespaces {
|
||||
if !self.has_namespace(&new.name) {
|
||||
self.namespaces.push(ActiveNamespace::new(new)?);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_namespace(&self, name: &NamespaceName) -> bool {
|
||||
self
|
||||
.namespaces
|
||||
.iter()
|
||||
.any(|existing| existing.name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
struct ActiveNamespace {
|
||||
ns: NetNs,
|
||||
}
|
||||
|
||||
impl ActiveNamespace {
|
||||
pub fn new(name: &NamespaceName) -> Result<Self, DaemonError> {
|
||||
let ns = NetNs::new(name)?;
|
||||
pub fn new(config: &NamespaceConfig) -> Result<Self, DaemonError> {
|
||||
let ns = NetNs::new(&config.name)?;
|
||||
Ok(ActiveNamespace { ns })
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &NamespaceName {
|
||||
self.ns.name()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -51,5 +134,7 @@ pub enum DaemonError {
|
|||
#[error(transparent)]
|
||||
Namespace(#[from] NamespaceError),
|
||||
#[error("Error sending notification to systemd: {0:#}")]
|
||||
Notify(IoError)
|
||||
Notify(IoError),
|
||||
#[error("Error setting up reload signal listener: {0:#}")]
|
||||
ReloadSignal(IoError),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,17 @@ use std::io::{Error as IoError, ErrorKind};
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::thread::{JoinHandle, spawn};
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error};
|
||||
use tracing::{error, info};
|
||||
|
||||
pub struct NetNs {
|
||||
name: NamespaceName,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl NetNs {
|
||||
/// Create a new named network namespace that will be removed when dropped
|
||||
pub fn new(name: &NamespaceName) -> Result<Self, NamespaceError> {
|
||||
debug!(%name, "creating network namespace");
|
||||
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);
|
||||
|
|
@ -38,14 +39,23 @@ impl NetNs {
|
|||
Ok(())
|
||||
});
|
||||
handle.join().unwrap()?;
|
||||
Ok(NetNs { path })
|
||||
Ok(NetNs {
|
||||
name: name.clone(),
|
||||
path
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl NetNs {
|
||||
pub fn name(&self) -> &NamespaceName {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NetNs {
|
||||
fn drop(&mut self) {
|
||||
let name = self.path.file_name().unwrap().to_str().unwrap();
|
||||
debug!(name, "deleting network namespace");
|
||||
info!(name, "deleting network namespace");
|
||||
if let Err(error) = umount(&self.path) {
|
||||
error!(%error, path = %self.path.display(), "Failed to unmount network namespace");
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue