router/src/startup/mod.rs
2022-01-31 20:28:57 -06:00

328 lines
10 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>,
tunnel: Vec<String>,
vlan: Vec<String>,
}
#[derive(serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
struct NetworkConfig {
#[serde(default)]
nats: Vec<String>,
bridges: Vec<[String; 2]>,
}
#[derive(serde::Deserialize)]
struct ServerConfig {
debug: bool,
secret: String,
}
pub struct Interfaces {
pub(crate) external: InterfaceInfo,
pub(crate) internal: Vec<InterfaceInfo>,
pub(crate) vlan: Vec<InterfaceInfo>,
pub(crate) tunnel: Vec<InterfaceInfo>,
pub(crate) nats: Vec<String>,
pub(crate) bridges: Vec<[String; 2]>,
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
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,
}],
vlan: vec![InterfaceInfo {
interface: String::from("vlan20"),
ip: "192.168.6.20".parse()?,
mask: 24,
}],
tunnel: vec![
InterfaceInfo {
interface: String::from("wg0"),
ip: "192.168.5.0".parse()?,
mask: 24,
},
InterfaceInfo {
interface: String::from("wg1"),
ip: "10.42.6.0".parse()?,
mask: 24,
},
InterfaceInfo {
interface: String::from("wg2"),
ip: "10.42.6.0".parse()?,
mask: 24,
},
],
nats: Vec::new(),
bridges: Vec::new(),
});
}
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_else(|| {
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.is_empty() {
return Err(anyhow!(
"No internal interfaces found for {:?}",
config.interface.internal
));
}
let mut vlan = Vec::new();
for iface in &config.interface.vlan {
vlan.extend(parse_interface_info(&output, iface)?);
}
let mut tunnel = Vec::new();
for iface in &config.interface.tunnel {
tunnel.extend(parse_interface_info(&output, iface)?);
}
Ok(Interfaces {
external,
internal,
vlan,
tunnel,
nats: config.network.nats.clone(),
bridges: config.network.bridges.clone(),
})
}
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 70.112.254.176/19 brd 70.112.255.255 scope global dynamic eth1
valid_lft 45780sec preferred_lft 45780sec
inet6 2605:6000:ffc0:77:f985:3af7:8be2:ba44/128 scope global dynamic noprefixroute
valid_lft 584496sec preferred_lft 584496sec
inet6 fe80::c8b8:a7ff:fe51:73d/64 scope link
valid_lft forever preferred_lft forever
4: vlan40@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff
inet 192.168.40.1/24 brd 192.168.40.255 scope global vlan40
valid_lft forever preferred_lft forever
inet6 fe80::264b:feff:fe37:adb1/64 scope link
valid_lft forever preferred_lft forever
5: vlan30@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff
inet 192.168.30.1/24 brd 192.168.30.255 scope global vlan30
valid_lft forever preferred_lft forever
inet6 fe80::264b:feff:fe37:adb1/64 scope link
valid_lft forever preferred_lft forever
6: vlan20@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 24:4b:fe:37:ad:b1 brd ff:ff:ff:ff:ff:ff
inet 192.168.20.1/24 brd 192.168.20.255 scope global vlan20
valid_lft forever preferred_lft forever
inet6 fe80::264b:feff:fe37:adb1/64 scope link
valid_lft forever preferred_lft forever
7: wg1: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.42.6.1/24 scope global wg1
valid_lft forever preferred_lft forever
8: wg2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.42.6.1/24 scope global wg2
valid_lft forever preferred_lft forever
9: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 192.168.5.1/24 scope global wg0
valid_lft forever preferred_lft forever
10: wg3: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 192.168.4.1/24 scope global wg3
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(), "70.112.254.176");
assert_eq!(info.mask, 19);
}
#[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);
}
#[test]
fn parses_tunnel() {
let info = parse_interface_info(OUTPUT, "wg[0-9]+")
.unwrap()
.next()
.unwrap();
assert_eq!(info.interface, "wg1");
assert_eq!(info.ip.to_string(), "10.42.6.1");
assert_eq!(info.mask, 24);
}
#[test]
fn parses_vlan() {
let info = parse_interface_info(OUTPUT, "vlan[0-9]+")
.unwrap()
.next()
.unwrap();
assert_eq!(info.interface, "vlan40");
assert_eq!(info.ip.to_string(), "192.168.40.1");
assert_eq!(info.mask, 24);
}
}