use std::{collections::HashMap, fmt::Debug, net::IpAddr, str::FromStr};
use ipnetwork::IpNetwork;
use crate::{WgConfError, WgKey};
pub const INTERFACE_TAG: &'static str = "[Interface]";
const PRIVATE_KEY: &'static str = "PrivateKey";
const ADDRESS: &'static str = "Address";
const LISTEN_PORT: &'static str = "ListenPort";
const DNS: &'static str = "DNS";
const POST_UP: &'static str = "PostUp";
const POST_DOWN: &'static str = "PostDown";
#[derive(Clone, PartialEq, Eq)]
pub struct WgInterface {
pub(crate) private_key: WgKey,
pub(crate) address: IpNetwork,
pub(crate) listen_port: Option<u16>,
pub(crate) dns: Option<IpAddr>,
pub(crate) post_up: Option<String>,
pub(crate) post_down: Option<String>,
}
impl Debug for WgInterface {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WgInterface")
.field("private_key", &"***")
.field("address", &self.address)
.field("listen_port", &self.listen_port)
.field("dns", &self.dns)
.field("post_up", &self.post_up)
.field("post_down", &self.post_down)
.finish()
}
}
impl ToString for WgInterface {
fn to_string(&self) -> String {
let listen_port = match &self.listen_port {
Some(val) => format!("\n{} = {}", LISTEN_PORT, val.to_string()),
None => "".to_string(),
};
let dns = match &self.dns {
Some(val) => format!("\n{} = {}", DNS, val.to_string()),
None => "".to_string(),
};
let post_up = match self.post_up.as_deref() {
Some(val) => format!("\n{} = {}", POST_UP, val),
None => "".to_string(),
};
let post_down = match self.post_down.as_deref() {
Some(val) => format!("\n{} = {}", POST_DOWN, &val),
None => "".to_string(),
};
format!(
"{}
{} = {}
{} = {}{}{}{}{}
",
INTERFACE_TAG,
PRIVATE_KEY,
self.private_key.to_string(),
ADDRESS,
self.address.to_string(),
listen_port,
dns,
post_up,
post_down
)
}
}
impl WgInterface {
pub fn new(
private_key: WgKey,
address: IpNetwork,
listen_port: Option<u16>,
dns: Option<IpAddr>,
post_up: Option<String>,
post_down: Option<String>,
) -> Result<WgInterface, WgConfError> {
if let Some(listen_port) = listen_port {
if listen_port == 0 {
return Err(WgConfError::ValidationFailed("port can't be 0".to_string()));
}
}
Ok(WgInterface {
private_key,
address,
listen_port,
dns,
post_up,
post_down,
})
}
pub fn from_raw_values(
private_key: String,
address: String,
listen_port: Option<String>,
dns: Option<String>,
post_up: Option<String>,
post_down: Option<String>,
) -> Result<WgInterface, WgConfError> {
let private_key: WgKey = private_key.parse()?;
let address: IpNetwork = address.parse().map_err(|_| {
WgConfError::ValidationFailed(
"address must be address with mask (e.g. 10.0.0.1/8)".to_string(),
)
})?;
let listen_port: Option<u16> = listen_port
.map(|port| {
port.parse().map_err(|_| {
WgConfError::ValidationFailed("invalid port raw value".to_string())
})
})
.transpose()?;
if let Some(listen_port) = listen_port {
if listen_port == 0 {
return Err(WgConfError::ValidationFailed("port can't be 0".to_string()));
}
}
let dns = dns
.map(|dns| {
IpAddr::from_str(&dns).map_err(|_| {
WgConfError::ValidationFailed("dns must be an ip address".to_string())
})
})
.transpose()?;
Ok(WgInterface {
private_key,
address,
listen_port,
dns,
post_up,
post_down,
})
}
pub fn private_key(&self) -> &WgKey {
&self.private_key
}
pub fn address(&self) -> &IpNetwork {
&self.address
}
pub fn listen_port(&self) -> Option<u16> {
self.listen_port
}
pub fn dns(&self) -> Option<&IpAddr> {
self.dns.as_ref()
}
pub fn post_up(&self) -> Option<&str> {
self.post_up.as_deref()
}
pub fn post_down(&self) -> Option<&str> {
self.post_down.as_deref()
}
pub(crate) fn from_raw_key_values(
raw_key_values: HashMap<String, String>,
) -> Result<WgInterface, WgConfError> {
let mut private_key = String::new();
let mut address = String::new();
let mut listen_port: Option<String> = None;
let mut dns: Option<String> = None;
let mut post_up: Option<String> = None;
let mut post_down: Option<String> = None;
for (k, v) in raw_key_values {
match k {
_ if k == PRIVATE_KEY => private_key = v,
_ if k == ADDRESS => address = v,
_ if k == LISTEN_PORT => listen_port = Some(v),
_ if k == DNS => dns = Some(v),
_ if k == POST_UP => post_up = Some(v),
_ if k == POST_DOWN => post_down = Some(v),
_ => continue,
}
}
WgInterface::from_raw_values(private_key, address, listen_port, dns, post_up, post_down)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wg_interface_0_to_string_0_all_fields() {
let interface = WgInterface::new(
"6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8="
.parse()
.unwrap(),
"192.168.130.131/25".parse().unwrap(),
Some(8082),
Some(IpAddr::from_str("8.8.8.8").unwrap()),
Some("some-script".to_string()),
Some("some-other-script".to_string()),
)
.unwrap();
let interf_raw = interface.to_string();
assert_eq!(
"[Interface]
PrivateKey = 6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8=
Address = 192.168.130.131/25
ListenPort = 8082
DNS = 8.8.8.8
PostUp = some-script
PostDown = some-other-script
",
&interf_raw
)
}
#[test]
fn wg_interface_0_to_string_0_empty_optionals() {
let interface = WgInterface::new(
"6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8="
.parse()
.unwrap(),
"192.168.130.131/25".parse().unwrap(),
None,
None,
None,
None,
)
.unwrap();
let interf_raw = interface.to_string();
assert_eq!(
"[Interface]
PrivateKey = 6FyM4Sq5zanp+9UPXIygLJQBYvlLsfF5lYcrSoa3CX8=
Address = 192.168.130.131/25
",
&interf_raw
)
}
}