224 lines
6.3 KiB
Rust
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);
|
|
}
|
|
}
|