use anyhow::anyhow; use regex::Regex; use std::{ io::Write, net::Ipv4Addr, process::{Command, Stdio}, }; mod preload; #[derive(serde::Deserialize)] pub(crate) struct Config { interface: InterfaceConfig, network: NetworkConfig, server: ServerConfig, } #[derive(serde::Deserialize)] struct InterfaceConfig { external: String, internal: Vec, } #[derive(serde::Deserialize)] #[serde(rename_all = "kebab-case")] struct NetworkConfig { shared_internal: bool, } #[derive(serde::Deserialize)] struct ServerConfig { debug: bool, secret: String, } pub(crate) struct Interfaces { pub(crate) external: InterfaceInfo, pub(crate) internal: Vec, shared_internal: bool, } pub(crate) struct InterfaceInfo { pub(crate) interface: String, pub(crate) ip: Ipv4Addr, pub(crate) mask: u8, } pub(crate) struct ServerState { pub(crate) debug: bool, pub(crate) secret: Vec, } impl ServerState { pub(crate) fn init(config: &Config) -> Result { let secret = base64::decode(&config.server.secret)?; Ok(ServerState { debug: config.server.debug, secret, }) } } impl Config { pub(crate) fn read() -> Result { let mut config = config::Config::new(); config.merge(config::File::with_name("config.toml"))?; Ok(config.try_into()?) } } impl Interfaces { pub(crate) fn init_blocking(config: &Config) -> Result { if config.server.debug { // Fake Data from Fake Things for Faking :tm: return Ok(Interfaces { external: InterfaceInfo { interface: String::from("eth0"), ip: "123.123.123.123".parse()?, mask: 20, }, internal: vec![InterfaceInfo { interface: String::from("enp1s0"), ip: "192.168.6.1".parse()?, mask: 24, }], shared_internal: false, }); } let output = Command::new("ip").arg("addr").output()?; let output = String::from_utf8_lossy(&output.stdout); let external = parse_interface_info(&output, &config.interface.external)? .next() .ok_or(anyhow!( "Failed to parse IP for interface {}", config.interface.external, ))?; let mut internal = Vec::new(); for iface in &config.interface.internal { internal.extend(parse_interface_info(&output, &iface)?); } if internal.len() == 0 { return Err(anyhow!( "No internal interfaces found for {:?}", config.interface.internal )); } Ok(Interfaces { external, internal, shared_internal: config.network.shared_internal, }) } pub(crate) fn reset_blocking(&self) -> Result<(), anyhow::Error> { let firewall_rules = self::preload::firewall_rules(self); println!("LOADING RULES"); println!("{}", firewall_rules); let mut child = Command::new("iptables-restore") .stdin(Stdio::piped()) .spawn()?; let stdin = child .stdin .as_mut() .expect("Failed to open STDIN (shouldn't happen)"); stdin.write_all(firewall_rules.as_bytes())?; child.wait()?; Ok(()) } } fn parse_interface_info<'a>( output: &'a str, interface: &str, ) -> Result + 'a, anyhow::Error> { let iface_regx = Regex::new(interface)?; let ip_regex = Regex::new(r"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})/([0-9]+)")?; let iter = output.split('\n').filter_map(move |line| { let iface_capture = iface_regx.captures_iter(line).next()?; let iface_match = iface_capture.get(0)?; let ip_capture = ip_regex.captures_iter(line).next()?; let ip_match = ip_capture.get(1)?; let mask_match = ip_capture.get(2)?; let ip = ip_match.as_str().parse().ok()?; let mask = mask_match.as_str().parse().ok()?; Some(InterfaceInfo { interface: iface_match.as_str().to_owned(), mask, ip, }) }); Ok(iter) } #[cfg(test)] mod tests { use super::parse_interface_info; const OUTPUT: &'static str = r#"1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 2: enp1s0: mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff inet 192.168.6.1/24 brd 192.168.5.255 scope global enp1s0 valid_lft forever preferred_lft forever inet6 fe80::264b:feff:fe37:adb1/64 scope link valid_lft forever preferred_lft forever 3: eth1: mtu 1500 qdisc mq state UP group default qlen 1000 link/ether ca:b8:a7:51:07:3d brd ff:ff:ff:ff:ff:ff inet 136.49.5.58/20 brd 136.49.15.255 scope global dynamic eth1 valid_lft 85973sec preferred_lft 85973sec inet6 fe80::c8b8:a7ff:fe51:73d/64 scope link valid_lft forever preferred_lft forever 4: wg0: mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 link/none inet 192.168.5.0/24 scope global wg0 valid_lft forever preferred_lft forever "#; #[test] fn parses_external_interface() { let info = parse_interface_info(OUTPUT, "eth[0-9]+") .unwrap() .next() .unwrap(); assert_eq!(info.interface, "eth1"); assert_eq!(info.ip.to_string(), "136.49.5.58"); assert_eq!(info.mask, 20); } #[test] fn parses_internal_interface() { let info = parse_interface_info(OUTPUT, "enp1s0") .unwrap() .next() .unwrap(); assert_eq!(info.interface, "enp1s0"); assert_eq!(info.ip.to_string(), "192.168.6.1"); assert_eq!(info.mask, 24); } }