diff options
author | Alex <alex@adnab.me> | 2023-04-21 09:56:21 +0000 |
---|---|---|
committer | Alex <alex@adnab.me> | 2023-04-21 09:56:21 +0000 |
commit | 05872634a42bf0aef3ab0a2760e2be4590bc8b73 (patch) | |
tree | f206a05b684a6132f9a46afdfc2f8b9df2aae63b /src/config/runtime.rs | |
parent | e64be9e8816b9bd5d3d787d1d5d57d460ae37569 (diff) | |
parent | f5fc635b75dfa17b83a8db4893a7be206b4f9892 (diff) | |
download | diplonat-05872634a42bf0aef3ab0a2760e2be4590bc8b73.tar.gz diplonat-05872634a42bf0aef3ab0a2760e2be4590bc8b73.zip |
Merge pull request 'public IP address autodiscovery' (#20) from stun into main
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/diplonat/pulls/20
Diffstat (limited to 'src/config/runtime.rs')
-rw-r--r-- | src/config/runtime.rs | 246 |
1 files changed, 143 insertions, 103 deletions
diff --git a/src/config/runtime.rs b/src/config/runtime.rs index 2e7b573..8084439 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -1,10 +1,11 @@ use std::fs::File; use std::io::Read; +use std::net::{Ipv4Addr, SocketAddr, ToSocketAddrs}; use std::time::Duration; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; -use crate::config::{ConfigOpts, ConfigOptsAcme, ConfigOptsBase, ConfigOptsConsul}; +use crate::config::{ConfigOpts, ConfigOptsBase, ConfigOptsConsul}; // This code is inspired by the Trunk crate (https://github.com/thedodd/trunk) @@ -12,140 +13,179 @@ use crate::config::{ConfigOpts, ConfigOptsAcme, ConfigOptsBase, ConfigOptsConsul // RuntimeConfig. We apply default values and business logic. #[derive(Debug)] -pub struct RuntimeConfigAcme { - pub email: String, -} - -#[derive(Debug)] pub struct RuntimeConfigConsul { - pub node_name: String, - pub url: String, - pub tls: Option<(Option<reqwest::Certificate>, bool, reqwest::Identity)>, + pub node_name: String, + pub url: String, + pub tls: Option<(Option<reqwest::Certificate>, bool, reqwest::Identity)>, } #[derive(Debug)] pub struct RuntimeConfigFirewall { - pub refresh_time: Duration, + pub ipv6_only: bool, + pub refresh_time: Duration, } #[derive(Debug)] pub struct RuntimeConfigIgd { - pub private_ip: Option<String>, - pub expiration_time: Duration, - pub refresh_time: Duration, + pub private_ip: Option<Ipv4Addr>, + pub expiration_time: Duration, + pub refresh_time: Duration, } #[derive(Debug)] -pub struct RuntimeConfig { - pub acme: Option<RuntimeConfigAcme>, - pub consul: RuntimeConfigConsul, - pub firewall: RuntimeConfigFirewall, - pub igd: RuntimeConfigIgd, +pub struct RuntimeConfigStun { + pub stun_server_v4: Option<SocketAddr>, + pub stun_server_v6: SocketAddr, + pub refresh_time: Duration, } -impl RuntimeConfig { - pub fn new(opts: ConfigOpts) -> Result<Self> { - let acme = RuntimeConfigAcme::new(opts.acme.clone())?; - let consul = RuntimeConfigConsul::new(opts.consul.clone())?; - let firewall = RuntimeConfigFirewall::new(opts.base.clone())?; - let igd = RuntimeConfigIgd::new(opts.base.clone())?; - - Ok(Self { - acme, - consul, - firewall, - igd, - }) - } +#[derive(Debug)] +pub struct RuntimeConfig { + pub consul: RuntimeConfigConsul, + pub firewall: RuntimeConfigFirewall, + pub igd: Option<RuntimeConfigIgd>, + pub stun: RuntimeConfigStun, } -impl RuntimeConfigAcme { - pub fn new(opts: ConfigOptsAcme) -> Result<Option<Self>> { - if !opts.enable { - return Ok(None); +impl RuntimeConfig { + pub fn new(opts: ConfigOpts) -> Result<Self> { + 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, + }) } - - let email = opts.email.expect( - "'DIPLONAT_ACME_EMAIL' environment variable is required if 'DIPLONAT_ACME_ENABLE' == 'true'", - ); - - Ok(Some(Self { email })) - } } impl RuntimeConfigConsul { - pub(super) fn new(opts: ConfigOptsConsul) -> Result<Self> { - 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, + pub(super) fn new(opts: ConfigOptsConsul) -> Result<Self> { + 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"), }; - 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, - }) - } + Ok(Self { + node_name, + url, + tls, + }) + } } impl RuntimeConfigFirewall { - pub(super) fn new(opts: ConfigOptsBase) -> Result<Self> { - let refresh_time = Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); - - Ok(Self { refresh_time }) - } + pub(super) fn new(opts: &ConfigOptsBase) -> Result<Self> { + 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<Self> { - let private_ip = opts.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!( + pub(super) fn new(opts: &ConfigOptsBase) -> Result<Self> { + 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, + }) } +} - Ok(Self { - private_ip, - expiration_time, - refresh_time, - }) - } +impl RuntimeConfigStun { + pub(super) fn new(opts: &ConfigOptsBase) -> Result<Self> { + 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, + }) + } } |