use derive_builder::Builder;
use either::Either;
use ipnet::IpNet;
use itertools::Itertools as _;
use std::fmt;
use std::net::Ipv4Addr;
use std::{convert::Infallible, net::IpAddr};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::prelude::*;
#[derive(PartialEq, Eq, Clone, Debug, Default)]
pub enum Table {
RoutingTable(usize),
Off,
#[default]
Auto,
}
impl fmt::Display for Table {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Table::RoutingTable(n) => write!(f, "{n}"),
Table::Off => write!(f, "off"),
Table::Auto => write!(f, "auto"),
}
}
}
#[cfg(feature = "serde")]
impl Serialize for Table {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Table::RoutingTable(n) => serializer.serialize_u64(*n as u64),
Table::Off => serializer.serialize_str("off"),
Table::Auto => serializer.serialize_str("auto"),
}
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Table {
fn deserialize<D>(deserializer: D) -> Result<Table, D::Error>
where
D: Deserializer<'de>,
{
struct TableVisitor;
impl de::Visitor<'_> for TableVisitor {
type Value = Table;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an routing table value (number, off or auto)")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
match value {
"off" => Ok(Table::Off),
"auto" => Ok(Table::Auto),
_ => Err(E::invalid_value(de::Unexpected::Str(value), &self)),
}
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Table::RoutingTable(
usize::try_from(value).map_err(E::custom)?,
))
}
}
deserializer.deserialize_any(TableVisitor)
}
}
#[must_use]
#[derive(Clone, Debug, PartialEq, Builder)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[builder(build_fn(private, name = "fallible_build", error = "Infallible"))]
pub struct Interface {
#[builder(
setter(into),
default = "vec![IpNet::new_assert(Ipv4Addr::UNSPECIFIED.into(), 0)]"
)]
pub address: Vec<IpNet>,
#[builder(setter(strip_option), default)]
pub listen_port: Option<u16>,
#[builder(default = "PrivateKey::random()")]
pub private_key: PrivateKey,
#[builder(setter(into, strip_option), default)]
pub dns: Vec<String>,
#[builder(setter(into, strip_option), default)]
pub endpoint: Option<String>,
#[builder(setter(strip_option), default)]
pub table: Option<Table>,
#[builder(setter(strip_option), default)]
pub mtu: Option<usize>,
#[cfg(feature = "amneziawg")]
#[cfg_attr(docsrs, doc(cfg(feature = "amneziawg")))]
#[builder(setter(strip_option), default)]
pub amnezia_settings: Option<AmneziaSettings>,
#[builder(setter(into), default)]
pub pre_up: Vec<String>,
#[builder(setter(into), default)]
pub pre_down: Vec<String>,
#[builder(setter(into), default)]
pub post_up: Vec<String>,
#[builder(setter(into), default)]
pub post_down: Vec<String>,
#[builder(setter(into), default)]
pub peers: Vec<Peer>,
}
impl Interface {
pub fn to_peer(&self) -> Peer {
Peer {
endpoint: self.endpoint.as_ref().map(|server_endpoint| {
format!(
"{server_endpoint}:{server_port}",
server_port = self.listen_port.unwrap_or(51820)
)
}),
allowed_ips: self.address.clone(),
key: Either::Left(self.private_key.clone()),
preshared_key: None,
persistent_keepalive: 0,
}
}
}
impl Interface {
#[must_use]
pub fn builder() -> InterfaceBuilder {
InterfaceBuilder::default()
}
}
impl InterfaceBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add_network<T: Into<IpNet>>(&mut self, value: T) -> &mut Self {
if self.address.is_none() {
self.address = Some(Vec::with_capacity(1));
}
self.address
.as_mut()
.unwrap_or_else(|| unreachable!())
.push(value.into());
self
}
pub fn add_address<T: Into<IpAddr>>(&mut self, value: T) -> &mut Self {
if self.address.is_none() {
self.address = Some(Vec::with_capacity(1));
}
let ip_addr = value.into();
let ip_net = if ip_addr.is_ipv4() {
IpNet::new_assert(ip_addr, 32) } else {
IpNet::new_assert(ip_addr, 128) };
self.address
.as_mut()
.unwrap_or_else(|| unreachable!())
.push(ip_net);
self
}
pub fn build(&self) -> Interface {
self.fallible_build().unwrap_or_else(|_| unreachable!())
}
}
impl fmt::Display for Interface {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "[Interface]")?;
if let Some(endpoint) = &self.endpoint {
writeln!(f, "# Name = {endpoint}")?;
}
writeln!(
f,
"Address = {}",
self.address
.iter()
.map(ToString::to_string)
.map(|addr| {
if addr.ends_with("/32") {
addr.trim_end_matches("/32").to_owned()
} else if addr.ends_with("/128") {
addr.trim_end_matches("/128").to_owned()
} else {
addr
}
})
.join(",")
)?;
if let Some(listen_port) = self.listen_port {
writeln!(f, "ListenPort = {listen_port}")?;
}
writeln!(f, "PrivateKey = {}", self.private_key)?;
if !self.dns.is_empty() {
writeln!(f, "DNS = {}", self.dns.join(","))?;
}
if let Some(table) = &self.table {
writeln!(f, "Table = {table}")?;
}
if let Some(mtu) = &self.mtu {
writeln!(f, "MTU = {mtu}")?;
}
if !self.pre_up.is_empty() {
writeln!(f)?;
for snippet in &self.pre_up {
writeln!(f, "PreUp = {snippet}")?;
}
}
if !self.pre_down.is_empty() {
writeln!(f)?;
for snippet in &self.pre_down {
writeln!(f, "PreDown = {snippet}")?;
}
}
if !self.post_up.is_empty() {
writeln!(f)?;
for snippet in &self.post_up {
writeln!(f, "PostUp = {snippet}")?;
}
}
if !self.post_down.is_empty() {
writeln!(f)?;
for snippet in &self.post_down {
writeln!(f, "PostDown = {snippet}")?;
}
}
#[cfg(feature = "amneziawg")]
if let Some(amnezia_settings) = &self.amnezia_settings {
writeln!(f)?;
writeln!(f, "{amnezia_settings}")?;
}
for peer in &self.peers {
writeln!(f)?;
writeln!(f, "{peer}")?;
}
fmt::Result::Ok(())
}
}