router/src/startup/mod.rs

224 lines
6.3 KiB
Rust

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<String>,
}
#[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<InterfaceInfo>,
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<u8>,
}
impl ServerState {
pub(crate) fn init(config: &Config) -> Result<Self, anyhow::Error> {
let secret = base64::decode(&config.server.secret)?;
Ok(ServerState {
debug: config.server.debug,
secret,
})
}
}
impl Config {
pub(crate) fn read() -> Result<Self, anyhow::Error> {
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<Self, anyhow::Error> {
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<impl Iterator<Item = InterfaceInfo> + '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: <LOOPBACK,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <BROADCAST,MULTICAST,UP,LOWER_UP> 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: <POINTOPOINT,NOARP,UP,LOWER_UP> 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);
}
}