use std::fs::File; use std::io::Read; use std::net::{Ipv4Addr, SocketAddr, ToSocketAddrs}; use std::time::Duration; use anyhow::{anyhow, bail, Context, Result}; use crate::config::{ConfigOpts, ConfigOptsBase, ConfigOptsConsul}; // This code is inspired by the Trunk crate (https://github.com/thedodd/trunk) // In this file, we take ConfigOpts and transform them into ready-to-use // RuntimeConfig. We apply default values and business logic. #[derive(Debug)] pub struct RuntimeConfigConsul { pub node_name: String, pub url: String, pub tls: Option<(Option, bool, reqwest::Identity)>, } #[derive(Debug)] pub struct RuntimeConfigFirewall { pub ipv6_only: bool, pub refresh_time: Duration, } #[derive(Debug)] pub struct RuntimeConfigIgd { pub private_ip: Option, pub expiration_time: Duration, pub refresh_time: Duration, } #[derive(Debug)] pub struct RuntimeConfigStun { pub stun_server_v4: Option, pub stun_server_v6: SocketAddr, pub refresh_time: Duration, } #[derive(Debug)] pub struct RuntimeConfig { pub consul: RuntimeConfigConsul, pub firewall: RuntimeConfigFirewall, pub igd: Option, pub stun: RuntimeConfigStun, } impl RuntimeConfig { pub fn new(opts: ConfigOpts) -> Result { let consul = RuntimeConfigConsul::new(opts.consul)?; let firewall = RuntimeConfigFirewall::new(&opts.base)?; let igd = match opts.base.ipv6_only { false => Some(RuntimeConfigIgd::new(&opts.base)?), true => None, }; let stun = RuntimeConfigStun::new(&opts.base)?; Ok(Self { consul, firewall, igd, stun, }) } } impl RuntimeConfigConsul { pub(super) fn new(opts: ConfigOptsConsul) -> Result { let node_name = opts .node_name .expect("'DIPLONAT_CONSUL_NODE_NAME' environment variable is required"); let url = opts.url.unwrap_or(super::CONSUL_URL.to_string()); let tls = match (&opts.client_cert, &opts.client_key) { (Some(client_cert), Some(client_key)) => { let cert = match &opts.ca_cert { Some(ca_cert) => { let mut ca_cert_buf = vec![]; File::open(ca_cert)?.read_to_end(&mut ca_cert_buf)?; Some(reqwest::Certificate::from_pem(&ca_cert_buf[..])?) } None => None, }; let mut client_cert_buf = vec![]; File::open(client_cert)?.read_to_end(&mut client_cert_buf)?; let mut client_key_buf = vec![]; File::open(client_key)?.read_to_end(&mut client_key_buf)?; let ident = reqwest::Identity::from_pem( &[&client_cert_buf[..], &client_key_buf[..]].concat()[..], )?; Some((cert, opts.tls_skip_verify, ident)) } (None, None) => None, _ => bail!("Incomplete TLS configuration parameters"), }; Ok(Self { node_name, url, tls, }) } } impl RuntimeConfigFirewall { pub(super) fn new(opts: &ConfigOptsBase) -> Result { let refresh_time = Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); Ok(Self { refresh_time, ipv6_only: opts.ipv6_only, }) } } impl RuntimeConfigIgd { pub(super) fn new(opts: &ConfigOptsBase) -> Result { let private_ip = opts .private_ip .as_ref() .map(|x| x.parse()) .transpose() .context("parse private_ip")?; let expiration_time = Duration::from_secs( opts.expiration_time .unwrap_or(super::EXPIRATION_TIME) .into(), ); let refresh_time = Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); if refresh_time.as_secs() * 2 > expiration_time.as_secs() { return Err(anyhow!( "IGD expiration time (currently: {}s) must be at least twice bigger than refresh time \ (currently: {}s)", expiration_time.as_secs(), refresh_time.as_secs() )); } Ok(Self { private_ip, expiration_time, refresh_time, }) } } impl RuntimeConfigStun { pub(super) fn new(opts: &ConfigOptsBase) -> Result { let mut stun_server_v4 = None; let mut stun_server_v6 = None; for addr in opts .stun_server .as_deref() .unwrap_or(super::STUN_SERVER) .to_socket_addrs()? { if addr.is_ipv4() { stun_server_v4 = Some(addr); } if addr.is_ipv6() { stun_server_v6 = Some(addr); } } let refresh_time = Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); let stun_server_v4 = match opts.ipv6_only { false => Some( stun_server_v4.ok_or(anyhow!("Unable to resolve STUN server's IPv4 address"))?, ), true => None, }; Ok(Self { stun_server_v4, stun_server_v6: stun_server_v6 .ok_or(anyhow!("Unable to resolve STUN server's IPv6 address"))?, refresh_time, }) } }