Modular Diplonat (with the option to disable useless modules) #8

Closed
adrien wants to merge 12 commits from adrien/diplonat:feature/modular-config into main
10 changed files with 131 additions and 70 deletions
Showing only changes of commit 900ba20fbe - Show all commits

View file

@ -1,2 +1,75 @@
unstable_features = true
array_width = 60
attr_fn_like_width = 70
binop_separator = "Front"
blank_lines_lower_bound = 0
blank_lines_upper_bound = 1
brace_style = "SameLineWhere"
chain_width = 60
color = "Auto"
combine_control_expr = true
comment_width = 80
condense_wildcard_suffixes = true
control_brace_style = "AlwaysSameLine"
disable_all_formatting = false
empty_item_single_line = true
enum_discrim_align_threshold = 0
error_on_line_overflow = true
error_on_unformatted = true
fn_args_layout = "Tall"
fn_call_width = 60
fn_single_line = true
force_explicit_abi = true
force_multiline_blocks = false
format_code_in_doc_comments = true
# format_generated_files = true
format_macro_matchers = true
format_macro_bodies = true
format_strings = true
hard_tabs = false hard_tabs = false
#hex_literal_case = "Lower"
hide_parse_errors = false
ignore = []
imports_indent = "Block"
imports_layout = "Mixed"
indent_style = "Block"
inline_attribute_width = 0
license_template_path = ""
match_arm_blocks = true
match_arm_leading_pipes = "Never"
match_block_trailing_comma = false
max_width = 100
merge_derives = true
imports_granularity = "Crate"
newline_style = "Unix"
normalize_comments = true
normalize_doc_attributes = true
overflow_delimited_expr = false
remove_nested_parens = true
reorder_impl_items = true
reorder_imports = true
group_imports = "StdExternalCrate"
reorder_modules = true
report_fixme = "Unnumbered"
report_todo = "Unnumbered"
required_version = "1.4.37"
skip_children = false
single_line_if_else_max_width = 50
space_after_colon = true
space_before_colon = false
#space_around_ranges = false
struct_field_align_threshold = 0
struct_lit_single_line = true
struct_lit_width = 18
struct_variant_width = 35
tab_spaces = 2 tab_spaces = 2
trailing_comma = "Vertical"
trailing_semicolon = false
type_punctuation_density = "Wide"
use_field_init_shorthand = false
use_small_heuristics = "Off"
use_try_shorthand = true
version = "Two"
where_single_line = true
wrap_comments = true

View file

@ -8,7 +8,7 @@ use crate::config::RuntimeConfig;
// This file parses the options that can be declared in the environment. // This file parses the options that can be declared in the environment.
// runtime.rs applies business logic and builds RuntimeConfig structs. // runtime.rs applies business logic and builds RuntimeConfig structs.
// - Note for the future - // Note for the future
// There is no *need* to have a 'DIPLONAT_XXX_*' prefix for all config options. // There is no *need* to have a 'DIPLONAT_XXX_*' prefix for all config options.
// If some config options are shared by several modules, a ConfigOptsBase could // If some config options are shared by several modules, a ConfigOptsBase could
// contain them, and parse the 'DIPLONAT_*' prefix directly. // contain them, and parse the 'DIPLONAT_*' prefix directly.
@ -87,9 +87,7 @@ impl ConfigOpts {
// Currently only used in tests // Currently only used in tests
#[allow(dead_code)] #[allow(dead_code)]
pub fn from_iter<Iter: Clone>(iter: Iter) -> Result<RuntimeConfig> pub fn from_iter<Iter: Clone>(iter: Iter) -> Result<RuntimeConfig>
where where Iter: IntoIterator<Item = (String, String)> {
Iter: IntoIterator<Item = (String, String)>,
{
let consul: ConfigOptsConsul = envy::prefixed("DIPLONAT_CONSUL_").from_iter(iter.clone())?; let consul: ConfigOptsConsul = envy::prefixed("DIPLONAT_CONSUL_").from_iter(iter.clone())?;
let acme: ConfigOptsAcme = envy::prefixed("DIPLONAT_ACME_").from_iter(iter.clone())?; let acme: ConfigOptsAcme = envy::prefixed("DIPLONAT_ACME_").from_iter(iter.clone())?;
let firewall: ConfigOptsFirewall = let firewall: ConfigOptsFirewall =

View file

@ -1,5 +1,4 @@
use std::collections::HashMap; use std::{collections::HashMap, time::Duration};
use std::time::Duration;
use crate::config::*; use crate::config::*;
@ -65,22 +64,22 @@ fn ok_minimal_valid_options() {
assert!(rt_config.acme.is_none()); assert!(rt_config.acme.is_none());
assert!(rt_config.firewall.is_none()); assert!(rt_config.firewall.is_none());
assert!(rt_config.igd.is_none()); assert!(rt_config.igd.is_none());
/*assert_eq!( // assert_eq!(
rt_config.firewall.refresh_time, // rt_config.firewall.refresh_time,
Duration::from_secs(REFRESH_TIME.into()) // Duration::from_secs(REFRESH_TIME.into())
); // );
assert_eq!( // assert_eq!(
&rt_config.igd.private_ip, // &rt_config.igd.private_ip,
opts.get(&"DIPLONAT_PRIVATE_IP".to_string()).unwrap() // opts.get(&"DIPLONAT_PRIVATE_IP".to_string()).unwrap()
); // );
assert_eq!( // assert_eq!(
rt_config.igd.expiration_time, // rt_config.igd.expiration_time,
Duration::from_secs(EXPIRATION_TIME.into()) // Duration::from_secs(EXPIRATION_TIME.into())
); // );
assert_eq!( // assert_eq!(
rt_config.igd.refresh_time, // rt_config.igd.refresh_time,
Duration::from_secs(REFRESH_TIME.into()) // Duration::from_secs(REFRESH_TIME.into())
);*/ // );
} }
#[test] #[test]

View file

@ -8,8 +8,8 @@ use crate::config::{
// This code is inspired by the Trunk crate (https://github.com/thedodd/trunk) // 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. // In this file, we take ConfigOpts and transform them into ready-to-use
// We apply default values and business logic. // RuntimeConfig. We apply default values and business logic.
// Consul config is mandatory, all the others are optional. // Consul config is mandatory, all the others are optional.
@ -74,7 +74,7 @@ impl RuntimeConfigConsul {
impl RuntimeConfigAcme { impl RuntimeConfigAcme {
pub fn new(opts: ConfigOptsAcme) -> Result<Option<Self>> { pub fn new(opts: ConfigOptsAcme) -> Result<Option<Self>> {
if !opts.enable { if !opts.enable {
return Ok(None); return Ok(None)
} }
let email = opts let email = opts
@ -88,7 +88,7 @@ impl RuntimeConfigAcme {
impl RuntimeConfigFirewall { impl RuntimeConfigFirewall {
pub(super) fn new(opts: ConfigOptsFirewall) -> Result<Option<Self>> { pub(super) fn new(opts: ConfigOptsFirewall) -> Result<Option<Self>> {
if !opts.enable { if !opts.enable {
return Ok(None); return Ok(None)
} }
let refresh_time = Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); let refresh_time = Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into());
@ -100,7 +100,7 @@ impl RuntimeConfigFirewall {
impl RuntimeConfigIgd { impl RuntimeConfigIgd {
pub(super) fn new(opts: ConfigOptsIgd) -> Result<Option<Self>> { pub(super) fn new(opts: ConfigOptsIgd) -> Result<Option<Self>> {
if !opts.enable { if !opts.enable {
return Ok(None); return Ok(None)
} }
let private_ip = opts let private_ip = opts
@ -116,9 +116,11 @@ impl RuntimeConfigIgd {
if refresh_time.as_secs() * 2 > expiration_time.as_secs() { if refresh_time.as_secs() * 2 > expiration_time.as_secs() {
return Err(anyhow!( return Err(anyhow!(
"IGD expiration time (currently: {}s) must be at least twice bigger than refresh time (currently: {}s)", "IGD expiration time (currently: {}s) must be at least twice bigger than refresh time \
(currently: {}s)",
expiration_time.as_secs(), expiration_time.as_secs(),
refresh_time.as_secs())); refresh_time.as_secs()
))
} }
Ok(Some(Self { Ok(Some(Self {

View file

@ -25,12 +25,10 @@ impl Consul {
client: reqwest::Client::new(), client: reqwest::Client::new(),
url: url.to_string(), url: url.to_string(),
idx: None, idx: None,
}; }
} }
pub fn watch_node_reset(&mut self) -> () { pub fn watch_node_reset(&mut self) -> () { self.idx = None; }
self.idx = None;
}
pub async fn watch_node(&mut self, host: &str) -> Result<CatalogNode> { pub async fn watch_node(&mut self, host: &str) -> Result<CatalogNode> {
let url = match self.idx { let url = match self.idx {
@ -45,6 +43,6 @@ impl Consul {
}; };
let resp: CatalogNode = http.json().await?; let resp: CatalogNode = http.json().await?;
return Ok(resp); return Ok(resp)
} }
} }

View file

@ -1,17 +1,12 @@
use std::cmp; use std::{cmp, collections::HashSet, time::Duration};
use std::collections::HashSet;
use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use log::*; use log::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_lexpr::{error, from_str}; use serde_lexpr::{error, from_str};
use tokio::sync::watch; use tokio::{sync::watch, time::delay_for};
use tokio::time::delay_for;
use crate::config::RuntimeConfigConsul; use crate::{config::RuntimeConfigConsul, consul, messages};
use crate::consul;
use crate::messages;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub enum DiplonatParameter { pub enum DiplonatParameter {
@ -36,11 +31,12 @@ pub struct ConsulActor {
fn retry_to_time(retries: u32, max_time: Duration) -> Duration { fn retry_to_time(retries: u32, max_time: Duration) -> Duration {
// 1.2^x seems to be a good value to exponentially increase time at a good pace // 1.2^x seems to be a good value to exponentially increase time at a good pace
// eg. 1.2^32 = 341 seconds ~= 5 minutes - ie. after 32 retries we wait 5 minutes // eg. 1.2^32 = 341 seconds ~= 5 minutes - ie. after 32 retries we wait 5
// minutes
return Duration::from_secs(cmp::min( return Duration::from_secs(cmp::min(
max_time.as_secs(), max_time.as_secs(),
1.2f64.powf(retries as f64) as u64, 1.2f64.powf(retries as f64) as u64,
)); ))
} }
fn to_parameters(catalog: &consul::CatalogNode) -> Vec<DiplonatConsul> { fn to_parameters(catalog: &consul::CatalogNode) -> Vec<DiplonatConsul> {
@ -56,7 +52,7 @@ fn to_parameters(catalog: &consul::CatalogNode) -> Vec<DiplonatConsul> {
} }
} }
return r; return r
} }
fn to_open_ports(params: &Vec<DiplonatConsul>) -> messages::PublicExposedPorts { fn to_open_ports(params: &Vec<DiplonatConsul>) -> messages::PublicExposedPorts {
@ -75,7 +71,7 @@ fn to_open_ports(params: &Vec<DiplonatConsul>) -> messages::PublicExposedPorts {
} }
} }
return op; return op
} }
impl ConsulActor { impl ConsulActor {
@ -91,7 +87,7 @@ impl ConsulActor {
retries: 0, retries: 0,
rx_open_ports: rx, rx_open_ports: rx,
tx_open_ports: tx, tx_open_ports: tx,
}; }
} }
pub async fn listen(&mut self) -> Result<()> { pub async fn listen(&mut self) -> Result<()> {
@ -108,7 +104,7 @@ impl ConsulActor {
e e
); );
delay_for(will_retry_in).await; delay_for(will_retry_in).await;
continue; continue
} }
}; };
self.retries = 0; self.retries = 0;

View file

@ -1,10 +1,9 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use tokio::try_join; use tokio::try_join;
use crate::config::ConfigOpts; use crate::{
use crate::consul_actor::ConsulActor; config::ConfigOpts, consul_actor::ConsulActor, fw_actor::FirewallActor, igd_actor::IgdActor,
use crate::fw_actor::FirewallActor; };
use crate::igd_actor::IgdActor;
pub struct Diplonat { pub struct Diplonat {
consul: ConsulActor, consul: ConsulActor,
@ -27,7 +26,7 @@ impl Diplonat {
if firewall_actor.is_none() && igd_actor.is_none() { if firewall_actor.is_none() && igd_actor.is_none() {
return Err(anyhow!( return Err(anyhow!(
"At least enable *one* module, otherwise it's boring!" "At least enable *one* module, otherwise it's boring!"
)); ))
} }
let ctx = Self { let ctx = Self {
@ -36,7 +35,7 @@ impl Diplonat {
igd: igd_actor, igd: igd_actor,
}; };
return Ok(ctx); return Ok(ctx)
} }
pub async fn listen(&mut self) -> Result<()> { pub async fn listen(&mut self) -> Result<()> {
@ -59,6 +58,6 @@ impl Diplonat {
}, },
)?; )?;
return Ok(()); return Ok(())
} }
} }

View file

@ -9,9 +9,7 @@ use tokio::{
time::{self, Duration}, time::{self, Duration},
}; };
use crate::config::RuntimeConfigFirewall; use crate::{config::RuntimeConfigFirewall, fw, messages};
use crate::fw;
use crate::messages;
pub struct FirewallActor { pub struct FirewallActor {
pub ipt: iptables::IPTables, pub ipt: iptables::IPTables,
@ -28,7 +26,7 @@ impl FirewallActor {
rxp: &watch::Receiver<messages::PublicExposedPorts>, rxp: &watch::Receiver<messages::PublicExposedPorts>,
) -> Result<Option<Self>> { ) -> Result<Option<Self>> {
if config.is_none() { if config.is_none() {
return Ok(None); return Ok(None)
} }
let config = config.unwrap(); let config = config.unwrap();

No. Never use unwrap.

Your function should be written with a match:

impl FirewallActor {
  pub async fn new(
    _refresh: Duration,
    config: Option<RuntimeConfigFirewall>,
    rxp: &watch::Receiver<messages::PublicExposedPorts>,
  ) -> Result<Option<Self>> {
  match config {
    None => Ok(None)
    Some(c) => {
      let ctx = Self { ... }
      fw::setup(&ctx.ipt)?;
      return Ok(Some(ctx))
    }
  }
}

As a general rule, in many places in your code, you could better communicate your intent with a match

No. Never use unwrap. Your function should be written with a match: ```rust impl FirewallActor { pub async fn new( _refresh: Duration, config: Option<RuntimeConfigFirewall>, rxp: &watch::Receiver<messages::PublicExposedPorts>, ) -> Result<Option<Self>> { match config { None => Ok(None) Some(c) => { let ctx = Self { ... } fw::setup(&ctx.ipt)?; return Ok(Some(ctx)) } } } ``` As a general rule, in many places in your code, you could better communicate your intent with a `match`
@ -41,7 +39,7 @@ impl FirewallActor {
fw::setup(&ctx.ipt)?; fw::setup(&ctx.ipt)?;
return Ok(Some(ctx)); return Ok(Some(ctx))
} }
pub async fn listen(&mut self) -> Result<()> { pub async fn listen(&mut self) -> Result<()> {
@ -90,6 +88,6 @@ impl FirewallActor {
fw::open_ports(&self.ipt, ports_to_open)?; fw::open_ports(&self.ipt, ports_to_open)?;
return Ok(()); return Ok(())
} }
} }

View file

@ -1,8 +1,7 @@
use std::net::SocketAddrV4; use std::net::SocketAddrV4;
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use igd::aio::*; use igd::{aio::*, PortMappingProtocol};
use igd::PortMappingProtocol;
use log::*; use log::*;
use tokio::{ use tokio::{
select, select,
@ -10,8 +9,7 @@ use tokio::{
time::{self, Duration}, time::{self, Duration},
}; };
use crate::config::RuntimeConfigIgd; use crate::{config::RuntimeConfigIgd, messages};
use crate::messages;
pub struct IgdActor { pub struct IgdActor {
expire: Duration, expire: Duration,
@ -29,7 +27,7 @@ impl IgdActor {
rxp: &watch::Receiver<messages::PublicExposedPorts>, rxp: &watch::Receiver<messages::PublicExposedPorts>,
) -> Result<Option<Self>> { ) -> Result<Option<Self>> {
if config.is_none() { if config.is_none() {

Same as my previous comment

Same as my previous comment
return Ok(None); return Ok(None)
} }
let config = config.unwrap(); let config = config.unwrap();
@ -47,7 +45,7 @@ impl IgdActor {
rx_ports: rxp.clone(), rx_ports: rxp.clone(),
}; };
return Ok(Some(ctx)); return Ok(Some(ctx))
} }
pub async fn listen(&mut self) -> Result<()> { pub async fn listen(&mut self) -> Result<()> {
@ -99,6 +97,6 @@ impl IgdActor {
} }
} }
return Ok(()); return Ok(())
} }
} }

View file

@ -11,6 +11,6 @@ impl PublicExposedPorts {
return Self { return Self {
tcp_ports: HashSet::new(), tcp_ports: HashSet::new(),
udp_ports: HashSet::new(), udp_ports: HashSet::new(),
}; }
} }
} }