diff options
-rw-r--r-- | docker-compose.yml | 9 | ||||
-rw-r--r-- | src/config/mod.rs | 2 | ||||
-rw-r--r-- | src/config/options.rs | 78 | ||||
-rw-r--r-- | src/config/options_test.rs | 83 | ||||
-rw-r--r-- | src/config/runtime.rs | 75 | ||||
-rw-r--r-- | src/consul.rs | 3 | ||||
-rw-r--r-- | src/consul_actor.rs | 22 | ||||
-rw-r--r-- | src/diplonat.rs | 55 | ||||
-rw-r--r-- | src/fw.rs | 8 | ||||
-rw-r--r-- | src/fw_actor.rs | 32 | ||||
-rw-r--r-- | src/igd_actor.rs | 35 |
11 files changed, 255 insertions, 147 deletions
diff --git a/docker-compose.yml b/docker-compose.yml index 0780c86..ab0dd92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,10 +5,13 @@ services: image: darkgallium/amd64_diplonat:v2 network_mode: host # required by UPNP/IGD environment: - DIPLONAT_PRIVATE_IP: 192.168.0.18 - DIPLONAT_REFRESH_TIME: 60 - DIPLONAT_EXPIRATION_TIME: 300 DIPLONAT_CONSUL_NODE_NAME: lheureduthe + DIPLONAT_FIREWALL_ENABLE: true + DIPLONAT_FIREWALL_REFRESH_TIME: 60 + DIPLONAT_IGD_ENABLE: true + DIPLONAT_IGD_PRIVATE_IP: 192.168.0.18 + DIPLONAT_IGD_EXPIRATION_TIME: 300 + DIPLONAT_IGD_REFRESH_TIME: 60 RUST_LOG: debug diff --git a/src/config/mod.rs b/src/config/mod.rs index 14926bd..024e114 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,7 +3,7 @@ mod options; mod options_test; mod runtime; -pub use options::{ConfigOpts, ConfigOptsAcme, ConfigOptsBase, ConfigOptsConsul}; +pub use options::{ConfigOpts, ConfigOptsConsul, ConfigOptsAcme, ConfigOptsFirewall, ConfigOptsIgd}; pub use runtime::{RuntimeConfig, RuntimeConfigAcme, RuntimeConfigConsul, RuntimeConfigFirewall, RuntimeConfigIgd}; pub const EXPIRATION_TIME: u16 = 300; diff --git a/src/config/options.rs b/src/config/options.rs index 36da475..b3a63b0 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -8,21 +8,27 @@ use crate::config::RuntimeConfig; // This file parses the options that can be declared in the environment. // runtime.rs applies business logic and builds RuntimeConfig structs. -/// Base configuration options +// - Note for the future - +// 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 +// contain them, and parse the 'DIPLONAT_*' prefix directly. +// Only in runtime.rs would these options find their proper location in each +// module's struct. + + +/// Consul configuration options #[derive(Clone, Default, Deserialize)] -pub struct ConfigOptsBase { - /// This node's private IP address [default: None] - pub private_ip: Option<String>, - /// Expiration time for IGD rules [default: 60] - pub expiration_time: Option<u16>, - /// Refresh time for IGD and Firewall rules [default: 300] - pub refresh_time: Option<u16>, +pub struct ConfigOptsConsul { + /// Consul's node name [default: None] + pub node_name: Option<String>, + /// Consul's REST URL [default: "http://127.0.0.1:8500"] + pub url: Option<String>, } /// ACME configuration options #[derive(Clone, Default, Deserialize)] pub struct ConfigOptsAcme { - /// Whether ACME is enabled [default: false] + /// Whether the ACME module is enabled [default: false] #[serde(default)] pub enable: bool, @@ -30,32 +36,52 @@ pub struct ConfigOptsAcme { pub email: Option<String>, } -/// Consul configuration options +/// Firewall configuration options #[derive(Clone, Default, Deserialize)] -pub struct ConfigOptsConsul { - /// Consul's node name [default: None] - pub node_name: Option<String>, - /// Consul's REST URL [default: "http://127.0.0.1:8500"] - pub url: Option<String>, +pub struct ConfigOptsFirewall { + /// Whether the firewall module is enabled [default: false] + #[serde(default)] + pub enable: bool, + + /// Refresh time for firewall rules [default: 300] + pub refresh_time: Option<u16>, +} + +/// IGD configuration options +#[derive(Clone, Default, Deserialize)] +pub struct ConfigOptsIgd { + /// Whether the IGD module is enabled [default: false] + #[serde(default)] + pub enable: bool, + + /// This node's private IP address [default: None] + pub private_ip: Option<String>, + /// Expiration time for IGD rules [default: 60] + pub expiration_time: Option<u16>, + /// Refresh time for IGD rules [default: 300] + pub refresh_time: Option<u16>, } /// Model of all potential configuration options pub struct ConfigOpts { - pub base: ConfigOptsBase, - pub acme: ConfigOptsAcme, pub consul: ConfigOptsConsul, + pub acme: ConfigOptsAcme, + pub firewall: ConfigOptsFirewall, + pub igd: ConfigOptsIgd, } impl ConfigOpts { pub fn from_env() -> Result<RuntimeConfig> { - let base: ConfigOptsBase = envy::prefixed("DIPLONAT_").from_env()?; let consul: ConfigOptsConsul = envy::prefixed("DIPLONAT_CONSUL_").from_env()?; let acme: ConfigOptsAcme = envy::prefixed("DIPLONAT_ACME_").from_env()?; + let firewall: ConfigOptsFirewall = envy::prefixed("DIPLONAT_FIREWALL_").from_env()?; + let igd: ConfigOptsIgd = envy::prefixed("DIPLONAT_IGD_").from_env()?; RuntimeConfig::new(Self { - base: base, - consul: consul, - acme: acme, + consul, + acme, + firewall, + igd, }) } @@ -63,14 +89,16 @@ impl ConfigOpts { #[allow(dead_code)] pub fn from_iter<Iter: Clone>(iter: Iter) -> Result<RuntimeConfig> where Iter: IntoIterator<Item = (String, String)> { - let base: ConfigOptsBase = envy::prefixed("DIPLONAT_").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 firewall: ConfigOptsFirewall = envy::prefixed("DIPLONAT_FIREWALL_").from_iter(iter.clone())?; + let igd: ConfigOptsIgd = envy::prefixed("DIPLONAT_IGD_").from_iter(iter.clone())?; RuntimeConfig::new(Self { - base: base, - consul: consul, - acme: acme, + consul, + acme, + firewall, + igd, }) } }
\ No newline at end of file diff --git a/src/config/options_test.rs b/src/config/options_test.rs index a6063fd..98fc625 100644 --- a/src/config/options_test.rs +++ b/src/config/options_test.rs @@ -11,35 +11,44 @@ use crate::config::*; fn minimal_valid_options() -> HashMap<String, String> { let mut opts = HashMap::new(); - opts.insert("DIPLONAT_PRIVATE_IP".to_string(), "172.123.43.555".to_string()); opts.insert("DIPLONAT_CONSUL_NODE_NAME".to_string(), "consul_node".to_string()); opts } fn all_valid_options() -> HashMap<String, String> { let mut opts = minimal_valid_options(); - opts.insert("DIPLONAT_EXPIRATION_TIME".to_string(), "30".to_string()); - opts.insert("DIPLONAT_REFRESH_TIME".to_string(), "10".to_string()); opts.insert("DIPLONAT_CONSUL_URL".to_string(), "http://127.0.0.1:9999".to_string()); opts.insert("DIPLONAT_ACME_ENABLE".to_string(), "true".to_string()); opts.insert("DIPLONAT_ACME_EMAIL".to_string(), "bozo@bozo.net".to_string()); + opts.insert("DIPLONAT_FIREWALL_ENABLE".to_string(), "true".to_string()); + opts.insert("DIPLONAT_FIREWALL_REFRESH_TIME".to_string(), "20".to_string()); + opts.insert("DIPLONAT_IGD_ENABLE".to_string(), "true".to_string()); + opts.insert("DIPLONAT_IGD_PRIVATE_IP".to_string(), "172.123.43.555".to_string()); + opts.insert("DIPLONAT_IGD_EXPIRATION_TIME".to_string(), "60".to_string()); + opts.insert("DIPLONAT_IGD_REFRESH_TIME".to_string(), "10".to_string()); opts } +// #[test] +// #[should_panic] +// fn err_empty_env() { +// std::env::remove_var("DIPLONAT_CONSUL_NODE_NAME"); +// ConfigOpts::from_env().unwrap(); +// } + #[test] #[should_panic] fn err_empty_env() { - std::env::remove_var("DIPLONAT_PRIVATE_IP"); std::env::remove_var("DIPLONAT_CONSUL_NODE_NAME"); - ConfigOpts::from_env().unwrap(); + let opts: HashMap<String, String> = HashMap::new(); + ConfigOpts::from_iter(opts).unwrap(); } #[test] -fn ok_from_iter_minimal_valid_options() { +fn ok_minimal_valid_options() { let opts = minimal_valid_options(); let rt_config = ConfigOpts::from_iter(opts.clone()).unwrap(); - assert!(rt_config.acme.is_none()); assert_eq!( &rt_config.consul.node_name, opts.get(&"DIPLONAT_CONSUL_NODE_NAME".to_string()).unwrap() @@ -48,7 +57,10 @@ fn ok_from_iter_minimal_valid_options() { rt_config.consul.url, CONSUL_URL.to_string() ); - assert_eq!( + assert!(rt_config.acme.is_none()); + assert!(rt_config.firewall.is_none()); + assert!(rt_config.igd.is_none()); + /*assert_eq!( rt_config.firewall.refresh_time, Duration::from_secs(REFRESH_TIME.into()) ); @@ -63,36 +75,37 @@ fn ok_from_iter_minimal_valid_options() { assert_eq!( rt_config.igd.refresh_time, Duration::from_secs(REFRESH_TIME.into()) - ); + );*/ } #[test] #[should_panic] -fn err_from_iter_invalid_refresh_time() { +fn err_invalid_igd_options() { let mut opts = minimal_valid_options(); - opts.insert("DIPLONAT_EXPIRATION_TIME".to_string(), "60".to_string()); - opts.insert("DIPLONAT_REFRESH_TIME".to_string(), "60".to_string()); + opts.insert("DIPLONAT_IGD_ENABLE".to_string(), "true".to_string()); + opts.insert("DIPLONAT_IGD_EXPIRATION_TIME".to_string(), "60".to_string()); + opts.insert("DIPLONAT_IGD_REFRESH_TIME".to_string(), "60".to_string()); ConfigOpts::from_iter(opts).unwrap(); } #[test] -fn ok_from_iter_all_valid_options() { +fn ok_all_valid_options() { let opts = all_valid_options(); let rt_config = ConfigOpts::from_iter(opts.clone()).unwrap(); - let expiration_time = Duration::from_secs( - opts.get(&"DIPLONAT_EXPIRATION_TIME".to_string()).unwrap() + let firewall_refresh_time = Duration::from_secs( + opts.get(&"DIPLONAT_FIREWALL_REFRESH_TIME".to_string()).unwrap() .parse::<u64>().unwrap() .into()); - let refresh_time = Duration::from_secs( - opts.get(&"DIPLONAT_REFRESH_TIME".to_string()).unwrap() + let igd_expiration_time = Duration::from_secs( + opts.get(&"DIPLONAT_IGD_EXPIRATION_TIME".to_string()).unwrap() + .parse::<u64>().unwrap() + .into()); + let igd_refresh_time = Duration::from_secs( + opts.get(&"DIPLONAT_IGD_REFRESH_TIME".to_string()).unwrap() .parse::<u64>().unwrap() .into()); - assert!(rt_config.acme.is_some()); - assert_eq!( - &rt_config.acme.unwrap().email, - opts.get(&"DIPLONAT_ACME_EMAIL".to_string()).unwrap()); assert_eq!( &rt_config.consul.node_name, opts.get(&"DIPLONAT_CONSUL_NODE_NAME".to_string()).unwrap() @@ -101,20 +114,32 @@ fn ok_from_iter_all_valid_options() { &rt_config.consul.url, opts.get(&"DIPLONAT_CONSUL_URL".to_string()).unwrap() ); + + assert!(rt_config.acme.is_some()); + let acme = rt_config.acme.unwrap(); assert_eq!( - rt_config.firewall.refresh_time, - refresh_time + &acme.email, + opts.get(&"DIPLONAT_ACME_EMAIL".to_string()).unwrap()); + + assert!(rt_config.firewall.is_some()); + let firewall = rt_config.firewall.unwrap(); + assert_eq!( + firewall.refresh_time, + firewall_refresh_time ); + + assert!(rt_config.igd.is_some()); + let igd = rt_config.igd.unwrap(); assert_eq!( - &rt_config.igd.private_ip, - opts.get(&"DIPLONAT_PRIVATE_IP".to_string()).unwrap() + &igd.private_ip, + opts.get(&"DIPLONAT_IGD_PRIVATE_IP".to_string()).unwrap() ); assert_eq!( - rt_config.igd.expiration_time, - expiration_time + igd.expiration_time, + igd_expiration_time ); assert_eq!( - rt_config.igd.refresh_time, - refresh_time + igd.refresh_time, + igd_refresh_time ); }
\ No newline at end of file diff --git a/src/config/runtime.rs b/src/config/runtime.rs index 58c86b9..f83a6b5 100644 --- a/src/config/runtime.rs +++ b/src/config/runtime.rs @@ -2,17 +2,14 @@ use std::time::Duration; use anyhow::{Result, anyhow}; -use crate::config::{ConfigOpts, ConfigOptsAcme, ConfigOptsBase, ConfigOptsConsul}; +use crate::config::{ConfigOpts, ConfigOptsConsul, ConfigOptsAcme, ConfigOptsFirewall, ConfigOptsIgd}; // 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 RuntimeConfigAcme { - pub email: String, -} +// Consul config is mandatory, all the others are optional. #[derive(Debug)] pub struct RuntimeConfigConsul { @@ -21,6 +18,11 @@ pub struct RuntimeConfigConsul { } #[derive(Debug)] +pub struct RuntimeConfigAcme { + pub email: String, +} + +#[derive(Debug)] pub struct RuntimeConfigFirewall { pub refresh_time: Duration, } @@ -34,18 +36,18 @@ pub struct RuntimeConfigIgd { #[derive(Debug)] pub struct RuntimeConfig { - pub acme: Option<RuntimeConfigAcme>, pub consul: RuntimeConfigConsul, - pub firewall: RuntimeConfigFirewall, - pub igd: RuntimeConfigIgd, + pub acme: Option<RuntimeConfigAcme>, + pub firewall: Option<RuntimeConfigFirewall>, + pub igd: Option<RuntimeConfigIgd>, } 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())?; + let acme = RuntimeConfigAcme::new(opts.acme.clone())?; + let firewall = RuntimeConfigFirewall::new(opts.firewall.clone())?; + let igd = RuntimeConfigIgd::new(opts.igd.clone())?; Ok(Self { acme, @@ -56,6 +58,19 @@ impl RuntimeConfig { } } +impl RuntimeConfigConsul { + pub(super) fn new(opts: ConfigOptsConsul) -> Result<Self> { + let node_name = opts.node_name.expect( + "'DIPLONAT_CONSUL_NODE_NAME' is required"); + let url = opts.url.unwrap_or(super::CONSUL_URL.to_string()); + + Ok(Self { + node_name, + url, + }) + } +} + impl RuntimeConfigAcme { pub fn new(opts: ConfigOptsAcme) -> Result<Option<Self>> { if !opts.enable { @@ -63,8 +78,7 @@ impl RuntimeConfigAcme { } let email = opts.email.expect( - "'DIPLONAT_ACME_EMAIL' environment variable is required \ - if 'DIPLONAT_ACME_ENABLE' == 'true'"); + "'DIPLONAT_ACME_EMAIL' is required if ACME is enabled"); Ok(Some(Self { email, @@ -72,34 +86,29 @@ impl RuntimeConfigAcme { } } -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()); - - Ok(Self { - node_name, - url, - }) - } -} - impl RuntimeConfigFirewall { - pub(super) fn new(opts: ConfigOptsBase) -> Result<Self> { + pub(super) fn new(opts: ConfigOptsFirewall) -> Result<Option<Self>> { + if !opts.enable { + return Ok(None); + } + let refresh_time = Duration::from_secs( opts.refresh_time.unwrap_or(super::REFRESH_TIME).into()); - Ok(Self { + Ok(Some(Self { refresh_time, - }) + })) } } impl RuntimeConfigIgd { - pub(super) fn new(opts: ConfigOptsBase) -> Result<Self> { + pub(super) fn new(opts: ConfigOptsIgd) -> Result<Option<Self>> { + if !opts.enable { + return Ok(None); + } + let private_ip = opts.private_ip.expect( - "'DIPLONAT_PRIVATE_IP' environment variable is required"); + "'DIPLONAT_IGD_PRIVATE_IP' is required if IGD is enabled"); let expiration_time = Duration::from_secs( opts.expiration_time.unwrap_or(super::EXPIRATION_TIME).into()); let refresh_time = Duration::from_secs( @@ -112,10 +121,10 @@ impl RuntimeConfigIgd { refresh_time.as_secs())); } - Ok(Self { + Ok(Some(Self { private_ip, expiration_time, refresh_time, - }) + })) } }
\ No newline at end of file diff --git a/src/consul.rs b/src/consul.rs index 1bb30aa..9a91782 100644 --- a/src/consul.rs +++ b/src/consul.rs @@ -1,6 +1,7 @@ -use serde::{Serialize, Deserialize}; use std::collections::HashMap; + use anyhow::{Result, anyhow}; +use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] pub struct ServiceEntry { diff --git a/src/consul_actor.rs b/src/consul_actor.rs index ba5d704..dcbd79e 100644 --- a/src/consul_actor.rs +++ b/src/consul_actor.rs @@ -1,14 +1,17 @@ use std::cmp; +use std::collections::HashSet; use std::time::Duration; -use log::*; -use tokio::sync::watch; -use tokio::time::delay_for; + use anyhow::Result; +use log::*; use serde::{Serialize, Deserialize}; use serde_lexpr::{from_str,error}; -use crate::messages; +use tokio::sync::watch; +use tokio::time::delay_for; + +use crate::config::RuntimeConfigConsul; use crate::consul; -use std::collections::HashSet; +use crate::messages; #[derive(Serialize, Deserialize, Debug)] pub enum DiplonatParameter { @@ -27,6 +30,7 @@ pub struct ConsulActor { consul: consul::Consul, node: String, retries: u32, + tx_open_ports: watch::Sender<messages::PublicExposedPorts> } @@ -72,18 +76,18 @@ fn to_open_ports(params: &Vec<DiplonatConsul>) -> messages::PublicExposedPorts { } impl ConsulActor { - pub fn new(url: &str, node: &str) -> Self { + pub fn new(config: RuntimeConfigConsul) -> Self { let (tx, rx) = watch::channel(messages::PublicExposedPorts{ tcp_ports: HashSet::new(), udp_ports: HashSet::new() }); return Self { - consul: consul::Consul::new(url), + consul: consul::Consul::new(&config.url), + node: config.node_name, + retries: 0, rx_open_ports: rx, tx_open_ports: tx, - node: node.to_string(), - retries: 0, }; } diff --git a/src/diplonat.rs b/src/diplonat.rs index 7049530..6334e5b 100644 --- a/src/diplonat.rs +++ b/src/diplonat.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use tokio::try_join; use crate::config::ConfigOpts; @@ -8,43 +8,60 @@ use crate::igd_actor::IgdActor; pub struct Diplonat { consul: ConsulActor, - firewall: FirewallActor, - igd: IgdActor, + + firewall: Option<FirewallActor>, + igd: Option<IgdActor>, } impl Diplonat { pub async fn new() -> Result<Self> { - let rt_cfg = ConfigOpts::from_env()?; - println!("{:#?}", rt_cfg); + let config = ConfigOpts::from_env()?; + println!("{:#?}", config); - let ca = ConsulActor::new(&rt_cfg.consul.url, &rt_cfg.consul.node_name); + let consul_actor = ConsulActor::new(config.consul); - let fw = FirewallActor::new( - rt_cfg.firewall.refresh_time, - &ca.rx_open_ports + let firewall_actor = FirewallActor::new( + config.firewall, + &consul_actor.rx_open_ports ).await?; - let ia = IgdActor::new( - &rt_cfg.igd.private_ip, - rt_cfg.igd.refresh_time, - rt_cfg.igd.expiration_time, - &ca.rx_open_ports + let igd_actor = IgdActor::new( + config.igd, + &consul_actor.rx_open_ports ).await?; + if firewall_actor.is_none() && igd_actor.is_none() { + return Err(anyhow!( + "At least enable *one* module, otherwise it's boring!")); + } + let ctx = Self { - consul: ca, - igd: ia, - firewall: fw + consul: consul_actor, + firewall: firewall_actor, + igd: igd_actor, }; return Ok(ctx); } pub async fn listen(&mut self) -> Result<()> { + let firewall = &mut self.firewall; + let igd = &mut self.igd; + try_join!( self.consul.listen(), - self.igd.listen(), - self.firewall.listen() + async { + match firewall { + Some(x) => x.listen().await, + None => Ok(()) + } + }, + async { + match igd { + Some(x) => x.listen().await, + None => Ok(()) + } + }, )?; return Ok(()); @@ -1,9 +1,11 @@ -use iptables; -use regex::Regex; use std::collections::HashSet; -use crate::messages; + use anyhow::{Result,Context}; +use iptables; use log::*; +use regex::Regex; + +use crate::messages; pub fn setup(ipt: &iptables::IPTables) -> Result<()> { diff --git a/src/fw_actor.rs b/src/fw_actor.rs index b5e4c7e..29e6473 100644 --- a/src/fw_actor.rs +++ b/src/fw_actor.rs @@ -1,37 +1,47 @@ +use std::collections::HashSet; + use anyhow::Result; +use iptables; +use log::*; use tokio::{ select, sync::watch, time::{ + Duration, self, - Duration }}; -use log::*; -use iptables; -use crate::messages; +use crate::config::RuntimeConfigFirewall; use crate::fw; -use std::collections::HashSet; +use crate::messages; + pub struct FirewallActor { pub ipt: iptables::IPTables, - rx_ports: watch::Receiver<messages::PublicExposedPorts>, + last_ports: messages::PublicExposedPorts, - refresh: Duration + refresh: Duration, + + rx_ports: watch::Receiver<messages::PublicExposedPorts>, } impl FirewallActor { - pub async fn new(_refresh: Duration, rxp: &watch::Receiver<messages::PublicExposedPorts>) -> Result<Self> { + pub async fn new(config: Option<RuntimeConfigFirewall>, rxp: &watch::Receiver<messages::PublicExposedPorts>) -> Result<Option<Self>> { + if config.is_none() { + return Ok(None); + } + let config = config.unwrap(); + let ctx = Self { ipt: iptables::new(false)?, - rx_ports: rxp.clone(), last_ports: messages::PublicExposedPorts::new(), - refresh: _refresh, + refresh: config.refresh_time, + rx_ports: rxp.clone(), }; fw::setup(&ctx.ipt)?; - return Ok(ctx); + return Ok(Some(ctx)); } pub async fn listen(&mut self) -> Result<()> { diff --git a/src/igd_actor.rs b/src/igd_actor.rs index 55d9c5f..19a7e93 100644 --- a/src/igd_actor.rs +++ b/src/igd_actor.rs @@ -1,43 +1,52 @@ +use std::net::SocketAddrV4; + +use anyhow::{Result, Context}; use igd::aio::*; use igd::PortMappingProtocol; -use std::net::SocketAddrV4; use log::*; -use anyhow::{Result, Context}; use tokio::{ select, sync::watch, time::{ + Duration, self, - Duration }}; + +use crate::config::RuntimeConfigIgd; use crate::messages; pub struct IgdActor { - last_ports: messages::PublicExposedPorts, - rx_ports: watch::Receiver<messages::PublicExposedPorts>, + expire: Duration, gateway: Gateway, + last_ports: messages::PublicExposedPorts, + private_ip: String, refresh: Duration, - expire: Duration, - private_ip: String + + rx_ports: watch::Receiver<messages::PublicExposedPorts>, } impl IgdActor { - pub async fn new(priv_ip: &str, refresh: Duration, expire: Duration, rxp: &watch::Receiver<messages::PublicExposedPorts>) -> Result<Self> { + pub async fn new(config: Option<RuntimeConfigIgd>, rxp: &watch::Receiver<messages::PublicExposedPorts>) -> Result<Option<Self>> { + if config.is_none() { + return Ok(None); + } + let config = config.unwrap(); + let gw = search_gateway(Default::default()) .await .context("Failed to find IGD gateway")?; info!("IGD gateway: {}", gw); let ctx = Self { + expire: config.expiration_time, gateway: gw, + last_ports: messages::PublicExposedPorts::new(), + private_ip: config.private_ip, + refresh: config.refresh_time, rx_ports: rxp.clone(), - private_ip: priv_ip.to_string(), - refresh: refresh, - expire: expire, - last_ports: messages::PublicExposedPorts::new() }; - return Ok(ctx); + return Ok(Some(ctx)); } pub async fn listen(&mut self) -> Result<()> { |