vopono_core 0.1.2

Library code for running VPN connections in network namespaces
Documentation
use anyhow::Context;
use log::{debug, warn};
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use std::io::BufRead;
use std::io::Write;
use std::net::IpAddr;
use std::os::unix::fs::PermissionsExt;

#[derive(Serialize, Deserialize, Debug)]
pub struct DnsConfig {
    ns_name: String,
}

impl DnsConfig {
    pub fn new(
        ns_name: String,
        servers: &[IpAddr],
        suffixes: &[&str],
        hosts_entries: Option<&Vec<String>>,
    ) -> anyhow::Result<Self> {
        let dir_path = format!("/etc/netns/{}", ns_name);
        std::fs::create_dir_all(&dir_path)
            .with_context(|| format!("Failed to create directory: {}", &dir_path))?;
        std::fs::set_permissions(&dir_path, PermissionsExt::from_mode(0o644))
            .with_context(|| format!("Failed to set directory permissions for {}", dir_path))?;

        let resolv_conf_path = format!("/etc/netns/{}/resolv.conf", ns_name);
        let mut resolv = std::fs::File::create(&resolv_conf_path)
            .with_context(|| format!("Failed to open resolv.conf: {}", &resolv_conf_path))?;
        std::fs::set_permissions(&resolv_conf_path, PermissionsExt::from_mode(0o644))
            .with_context(|| format!("Failed to set file permissions for {}", resolv_conf_path))?;

        debug!(
            "Setting namespace {} DNS server to {}",
            ns_name,
            &servers
                .iter()
                .map(|x| format!("{}", x))
                .collect::<Vec<String>>()
                .join(", ")
        );

        let suffix = suffixes.join(" ");
        if !suffix.is_empty() {
            writeln!(resolv, "search {}", suffix).with_context(|| {
                format!(
                    "Failed to overwrite resolv.conf: /etc/netns/{}/resolv.conf",
                    ns_name
                )
            })?;
        }

        for dns in servers {
            writeln!(resolv, "nameserver {}", dns).with_context(|| {
                format!(
                    "Failed to overwrite resolv.conf: /etc/netns/{}/resolv.conf",
                    ns_name
                )
            })?;
        }

        if let Some(my_hosts_entries) = hosts_entries {
            let hosts_path = format!("/etc/netns/{}/hosts", ns_name);
            let mut hosts = std::fs::File::create(&hosts_path)
                .with_context(|| format!("Failed to open hosts: {}", &hosts_path))?;
            std::fs::set_permissions(&hosts_path, PermissionsExt::from_mode(0o644))
                .with_context(|| format!("Failed to set file permissions for {}", &hosts_path))?;

            for hosts_enty in my_hosts_entries {
                writeln!(hosts, "{}", hosts_enty).with_context(|| {
                    format!("Failed to overwrite hosts: /etc/netns/{}/hosts", ns_name)
                })?;
            }
        }

        if std::path::Path::new("/etc/nsswitch.conf").exists() {
            let nsswitch_src = std::fs::File::open("/etc/nsswitch.conf")
                .with_context(|| "Failed to open nsswitch.conf: /etc/nsswitch.conf")?;

            let nsswitch_path = format!("/etc/netns/{}/nsswitch.conf", ns_name);
            let mut nsswitch = std::fs::File::create(&nsswitch_path)
                .with_context(|| format!("Failed to open nsswitch.conf: {}", nsswitch_path))?;
            std::fs::set_permissions(&nsswitch_path, PermissionsExt::from_mode(0o644))
                .with_context(|| {
                    format!("Failed to set file permissions for {}", &nsswitch_path)
                })?;

            for line in std::io::BufReader::new(nsswitch_src).lines() {
                writeln!(
                    nsswitch,
                    "{}",
                    Regex::new(r"^hosts:.*$")
                        .unwrap()
                        .replace(&line?, |_caps: &Captures| {
                            "hosts: files mymachines myhostname dns"
                        })
                )
                .with_context(|| {
                    format!(
                        "Failed to overwrite nsswitch.conf: /etc/netns/{}/nsswitch.conf",
                        ns_name
                    )
                })?;
            }
        }

        Ok(Self { ns_name })
    }
}

impl Drop for DnsConfig {
    fn drop(&mut self) {
        let path = format!("/etc/netns/{}", self.ns_name);
        match std::fs::remove_dir_all(&path) {
            Ok(_) => {}
            Err(e) => warn!(
                "Failed to delete network namespace directory: {}: {:?}",
                &path, e
            ),
        }
    }
}