aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/config/options.rs3
-rw-r--r--src/config/options_test.rs17
-rw-r--r--src/config/runtime.rs25
-rw-r--r--src/diplonat.rs34
-rw-r--r--src/fw_actor.rs58
-rw-r--r--src/stun_actor.rs44
6 files changed, 127 insertions, 54 deletions
diff --git a/src/config/options.rs b/src/config/options.rs
index 100c23c..fc9df16 100644
--- a/src/config/options.rs
+++ b/src/config/options.rs
@@ -19,6 +19,9 @@ pub struct ConfigOptsBase {
pub refresh_time: Option<u16>,
/// STUN server [default: stun.nextcloud.com:443]
pub stun_server: Option<String>,
+ /// IPv6-only mode (disables IGD, IPv4 firewall and IPv4 address autodiscovery) [default: false]
+ #[serde(default)]
+ pub ipv6_only: bool,
}
/// ACME configuration options
diff --git a/src/config/options_test.rs b/src/config/options_test.rs
index 8e05c90..073b9ac 100644
--- a/src/config/options_test.rs
+++ b/src/config/options_test.rs
@@ -63,15 +63,13 @@ fn ok_from_iter_minimal_valid_options() {
rt_config.firewall.refresh_time,
Duration::from_secs(REFRESH_TIME.into())
);
- assert!(rt_config.igd.private_ip.is_none());
+ let igd = rt_config.igd.unwrap();
+ assert!(igd.private_ip.is_none());
assert_eq!(
- rt_config.igd.expiration_time,
+ igd.expiration_time,
Duration::from_secs(EXPIRATION_TIME.into())
);
- assert_eq!(
- rt_config.igd.refresh_time,
- Duration::from_secs(REFRESH_TIME.into())
- );
+ assert_eq!(igd.refresh_time, Duration::from_secs(REFRESH_TIME.into()));
}
#[test]
@@ -117,10 +115,11 @@ fn ok_from_iter_all_valid_options() {
opts.get(&"DIPLONAT_CONSUL_URL".to_string()).unwrap()
);
assert_eq!(rt_config.firewall.refresh_time, refresh_time);
+ let igd = rt_config.igd.unwrap();
assert_eq!(
- &rt_config.igd.private_ip.unwrap().to_string(),
+ &igd.private_ip.unwrap().to_string(),
opts.get(&"DIPLONAT_PRIVATE_IP".to_string()).unwrap()
);
- assert_eq!(rt_config.igd.expiration_time, expiration_time);
- assert_eq!(rt_config.igd.refresh_time, refresh_time);
+ assert_eq!(igd.expiration_time, expiration_time);
+ assert_eq!(igd.refresh_time, refresh_time);
}
diff --git a/src/config/runtime.rs b/src/config/runtime.rs
index 45a29c3..d1a3f89 100644
--- a/src/config/runtime.rs
+++ b/src/config/runtime.rs
@@ -26,6 +26,7 @@ pub struct RuntimeConfigConsul {
#[derive(Debug)]
pub struct RuntimeConfigFirewall {
+ pub ipv6_only: bool,
pub refresh_time: Duration,
}
@@ -38,7 +39,7 @@ pub struct RuntimeConfigIgd {
#[derive(Debug)]
pub struct RuntimeConfigStun {
- pub stun_server_v4: SocketAddr,
+ pub stun_server_v4: Option<SocketAddr>,
pub stun_server_v6: SocketAddr,
pub refresh_time: Duration,
}
@@ -48,7 +49,7 @@ pub struct RuntimeConfig {
pub acme: Option<RuntimeConfigAcme>,
pub consul: RuntimeConfigConsul,
pub firewall: RuntimeConfigFirewall,
- pub igd: RuntimeConfigIgd,
+ pub igd: Option<RuntimeConfigIgd>,
pub stun: RuntimeConfigStun,
}
@@ -57,7 +58,10 @@ impl RuntimeConfig {
let acme = RuntimeConfigAcme::new(opts.acme)?;
let consul = RuntimeConfigConsul::new(opts.consul)?;
let firewall = RuntimeConfigFirewall::new(&opts.base)?;
- let igd = RuntimeConfigIgd::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 {
@@ -131,7 +135,10 @@ impl RuntimeConfigFirewall {
let refresh_time =
Duration::from_secs(opts.refresh_time.unwrap_or(super::REFRESH_TIME).into());
- Ok(Self { refresh_time })
+ Ok(Self {
+ refresh_time,
+ ipv6_only: opts.ipv6_only,
+ })
}
}
@@ -189,9 +196,15 @@ impl RuntimeConfigStun {
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_v4
- .ok_or(anyhow!("Unable to resolve STUN server's IPv4 address"))?,
+ stun_server_v4,
stun_server_v6: stun_server_v6
.ok_or(anyhow!("Unable to resolve STUN server's IPv6 address"))?,
refresh_time,
diff --git a/src/diplonat.rs b/src/diplonat.rs
index a66836a..a94a6f8 100644
--- a/src/diplonat.rs
+++ b/src/diplonat.rs
@@ -9,7 +9,7 @@ use crate::{
pub struct Diplonat {
consul: ConsulActor,
firewall: FirewallActor,
- igd: IgdActor,
+ igd: Option<IgdActor>,
stun: StunActor,
}
@@ -20,16 +20,26 @@ impl Diplonat {
let ca = ConsulActor::new(&rt_cfg.consul, &rt_cfg.consul.node_name);
- let fw = FirewallActor::new(rt_cfg.firewall.refresh_time, &ca.rx_open_ports).await?;
-
- let ia = IgdActor::new(
- rt_cfg.igd.private_ip,
- rt_cfg.igd.refresh_time,
- rt_cfg.igd.expiration_time,
+ let fw = FirewallActor::new(
+ rt_cfg.firewall.ipv6_only,
+ rt_cfg.firewall.refresh_time,
&ca.rx_open_ports,
)
.await?;
+ let ia = match rt_cfg.igd {
+ Some(igdc) => Some(
+ IgdActor::new(
+ igdc.private_ip,
+ igdc.refresh_time,
+ igdc.expiration_time,
+ &ca.rx_open_ports,
+ )
+ .await?,
+ ),
+ None => None,
+ };
+
let sa = StunActor::new(&rt_cfg.consul, &rt_cfg.stun, &rt_cfg.consul.node_name);
let ctx = Self {
@@ -43,9 +53,17 @@ impl Diplonat {
}
pub async fn listen(&mut self) -> Result<()> {
+ let igd_opt = &mut self.igd;
+
try_join!(
self.consul.listen(),
- self.igd.listen(),
+ async {
+ if let Some(igd) = igd_opt {
+ igd.listen().await
+ } else {
+ Ok(())
+ }
+ },
self.firewall.listen(),
self.stun.listen(),
)?;
diff --git a/src/fw_actor.rs b/src/fw_actor.rs
index fe68381..02d8bcb 100644
--- a/src/fw_actor.rs
+++ b/src/fw_actor.rs
@@ -12,7 +12,7 @@ use tokio::{
use crate::{fw, messages};
pub struct FirewallActor {
- pub ipt_v4: iptables::IPTables,
+ pub ipt_v4: Option<iptables::IPTables>,
pub ipt_v6: iptables::IPTables,
rx_ports: watch::Receiver<messages::PublicExposedPorts>,
last_ports: messages::PublicExposedPorts,
@@ -21,18 +21,24 @@ pub struct FirewallActor {
impl FirewallActor {
pub async fn new(
+ ipv6_only: bool,
refresh: Duration,
rxp: &watch::Receiver<messages::PublicExposedPorts>,
) -> Result<Self> {
let ctx = Self {
- ipt_v4: iptables::new(false)?,
+ ipt_v4: match ipv6_only {
+ false => Some(iptables::new(false)?),
+ true => None,
+ },
ipt_v6: iptables::new(true)?,
rx_ports: rxp.clone(),
last_ports: messages::PublicExposedPorts::new(),
refresh,
};
- fw::setup(&ctx.ipt_v4)?;
+ if let Some(ipt_v4) = &ctx.ipt_v4 {
+ fw::setup(ipt_v4)?;
+ }
fw::setup(&ctx.ipt_v6)?;
return Ok(ctx);
@@ -62,29 +68,35 @@ impl FirewallActor {
}
pub async fn do_fw_update(&self) -> Result<()> {
- for ipt in [&self.ipt_v4, &self.ipt_v6] {
- let curr_opened_ports = fw::get_opened_ports(ipt)?;
+ if let Some(ipt_v4) = &self.ipt_v4 {
+ self.do_fw_update_on(ipt_v4).await?;
+ }
+ self.do_fw_update_on(&self.ipt_v6).await?;
+ Ok(())
+ }
- let diff_tcp = self
- .last_ports
- .tcp_ports
- .difference(&curr_opened_ports.tcp_ports)
- .copied()
- .collect::<HashSet<u16>>();
- let diff_udp = self
- .last_ports
- .udp_ports
- .difference(&curr_opened_ports.udp_ports)
- .copied()
- .collect::<HashSet<u16>>();
+ pub async fn do_fw_update_on(&self, ipt: &iptables::IPTables) -> Result<()> {
+ let curr_opened_ports = fw::get_opened_ports(ipt)?;
- let ports_to_open = messages::PublicExposedPorts {
- tcp_ports: diff_tcp,
- udp_ports: diff_udp,
- };
+ let diff_tcp = self
+ .last_ports
+ .tcp_ports
+ .difference(&curr_opened_ports.tcp_ports)
+ .copied()
+ .collect::<HashSet<u16>>();
+ let diff_udp = self
+ .last_ports
+ .udp_ports
+ .difference(&curr_opened_ports.udp_ports)
+ .copied()
+ .collect::<HashSet<u16>>();
- fw::open_ports(ipt, ports_to_open)?;
- }
+ let ports_to_open = messages::PublicExposedPorts {
+ tcp_ports: diff_tcp,
+ udp_ports: diff_udp,
+ };
+
+ fw::open_ports(ipt, ports_to_open)?;
return Ok(());
}
diff --git a/src/stun_actor.rs b/src/stun_actor.rs
index 684d3d8..b112bb0 100644
--- a/src/stun_actor.rs
+++ b/src/stun_actor.rs
@@ -11,7 +11,7 @@ use crate::consul;
pub struct StunActor {
node: String,
consul: consul::Consul,
- stun_server_v4: SocketAddr,
+ stun_server_v4: Option<SocketAddr>,
stun_server_v6: SocketAddr,
refresh_time: Duration,
}
@@ -19,7 +19,7 @@ pub struct StunActor {
#[derive(Serialize, Deserialize, Debug)]
pub struct AutodiscoverResult {
pub timestamp: u64,
- pub address: IpAddr,
+ pub address: Option<IpAddr>,
}
impl StunActor {
@@ -28,7 +28,10 @@ impl StunActor {
stun_config: &RuntimeConfigStun,
node: &str,
) -> Self {
- assert!(stun_config.stun_server_v4.is_ipv4());
+ assert!(stun_config
+ .stun_server_v4
+ .map(|x| x.is_ipv4())
+ .unwrap_or(true));
assert!(stun_config.stun_server_v6.is_ipv6());
Self {
@@ -42,7 +45,11 @@ impl StunActor {
pub async fn listen(&mut self) -> Result<()> {
loop {
- if let Err(e) = self.autodiscover_ip(self.stun_server_v4).await {
+ let ipv4_result = match self.stun_server_v4 {
+ Some(stun_server_v4) => self.autodiscover_ip(stun_server_v4).await,
+ None => self.autodiscover_none_ipv4().await,
+ };
+ if let Err(e) = ipv4_result {
error!("Unable to autodiscover IPv4 address: {}", e);
}
if let Err(e) = self.autodiscover_ip(self.stun_server_v6).await {
@@ -75,10 +82,24 @@ impl StunActor {
.kv_put(
&consul_key,
serde_json::to_vec(&AutodiscoverResult {
- timestamp: SystemTime::now()
- .duration_since(SystemTime::UNIX_EPOCH)?
- .as_secs(),
- address: discovered_addr,
+ timestamp: timestamp(),
+ address: Some(discovered_addr),
+ })?,
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ async fn autodiscover_none_ipv4(&self) -> Result<()> {
+ let consul_key = format!("diplonat/autodiscovery/ipv4/{}", self.node);
+
+ self.consul
+ .kv_put(
+ &consul_key,
+ serde_json::to_vec(&AutodiscoverResult {
+ timestamp: timestamp(),
+ address: None,
})?,
)
.await?;
@@ -101,3 +122,10 @@ async fn get_mapped_addr(stun_server: SocketAddr, binding_addr: SocketAddr) -> R
.ok_or(anyhow!("no XorMappedAddress found in STUN response"))?;
Ok(xor_mapped_addr)
}
+
+fn timestamp() -> u64 {
+ SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .expect("clock error")
+ .as_secs()
+}