aboutsummaryrefslogtreecommitdiff
path: root/src/stun_actor.rs
diff options
context:
space:
mode:
authorAlex <alex@adnab.me>2023-04-21 09:56:21 +0000
committerAlex <alex@adnab.me>2023-04-21 09:56:21 +0000
commit05872634a42bf0aef3ab0a2760e2be4590bc8b73 (patch)
treef206a05b684a6132f9a46afdfc2f8b9df2aae63b /src/stun_actor.rs
parente64be9e8816b9bd5d3d787d1d5d57d460ae37569 (diff)
parentf5fc635b75dfa17b83a8db4893a7be206b4f9892 (diff)
downloaddiplonat-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/stun_actor.rs')
-rw-r--r--src/stun_actor.rs147
1 files changed, 147 insertions, 0 deletions
diff --git a/src/stun_actor.rs b/src/stun_actor.rs
new file mode 100644
index 0000000..6740c83
--- /dev/null
+++ b/src/stun_actor.rs
@@ -0,0 +1,147 @@
+use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
+use std::time::{Duration, SystemTime};
+
+use anyhow::{anyhow, bail, Result};
+use log::*;
+use serde::{Deserialize, Serialize};
+
+use crate::config::{RuntimeConfigConsul, RuntimeConfigStun};
+use crate::consul;
+
+pub struct StunActor {
+ node: String,
+ consul: consul::Consul,
+ stun_server_v4: Option<SocketAddr>,
+ stun_server_v6: SocketAddr,
+ refresh_time: Duration,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AutodiscoverResult {
+ pub timestamp: u64,
+ pub address: Option<IpAddr>,
+}
+
+impl StunActor {
+ pub fn new(
+ consul_config: &RuntimeConfigConsul,
+ stun_config: &RuntimeConfigStun,
+ node: &str,
+ ) -> Self {
+ assert!(stun_config
+ .stun_server_v4
+ .map(|x| x.is_ipv4())
+ .unwrap_or(true));
+ assert!(stun_config.stun_server_v6.is_ipv6());
+
+ Self {
+ consul: consul::Consul::new(consul_config),
+ node: node.to_string(),
+ stun_server_v4: stun_config.stun_server_v4,
+ stun_server_v6: stun_config.stun_server_v6,
+ refresh_time: stun_config.refresh_time,
+ }
+ }
+
+ pub async fn listen(&mut self) -> Result<()> {
+ loop {
+ 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 {
+ error!("Unable to autodiscover IPv6 address: {}", e);
+ }
+ tokio::time::sleep(self.refresh_time).await;
+ }
+ }
+
+ async fn autodiscover_ip(&self, stun_server: SocketAddr) -> Result<()> {
+ let binding_ip = match stun_server.is_ipv4() {
+ true => IpAddr::V4(Ipv4Addr::UNSPECIFIED), // 0.0.0.0
+ false => IpAddr::V6(Ipv6Addr::UNSPECIFIED), // [::]
+ };
+ let binding_addr = SocketAddr::new(binding_ip, 0);
+
+ let discovered_addr = get_mapped_addr(stun_server, binding_addr)
+ .await?
+ .map(|x| x.ip());
+
+ let consul_key = match stun_server.is_ipv4() {
+ true => {
+ debug!("Autodiscovered IPv4: {:?}", discovered_addr);
+ format!("diplonat/autodiscovery/ipv4/{}", self.node)
+ }
+ false => {
+ debug!("Autodiscovered IPv6: {:?}", discovered_addr);
+ format!("diplonat/autodiscovery/ipv6/{}", self.node)
+ }
+ };
+
+ self.consul
+ .kv_put(
+ &consul_key,
+ serde_json::to_vec(&AutodiscoverResult {
+ timestamp: timestamp(),
+ address: 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?;
+
+ Ok(())
+ }
+}
+
+async fn get_mapped_addr(
+ stun_server: SocketAddr,
+ binding_addr: SocketAddr,
+) -> Result<Option<SocketAddr>> {
+ use stun_client::*;
+
+ let mut client = Client::new(binding_addr, None).await?;
+ let res = match client.binding_request(stun_server, None).await {
+ Err(e) => {
+ info!(
+ "STUN binding request to {} failed, assuming no address (error: {})",
+ binding_addr, e
+ );
+ return Ok(None);
+ }
+ Ok(r) => r,
+ };
+
+ if res.get_class() != Class::SuccessResponse {
+ bail!("STUN server did not responde with a success response");
+ }
+
+ let xor_mapped_addr = Attribute::get_xor_mapped_address(&res)
+ .ok_or(anyhow!("no XorMappedAddress found in STUN response"))?;
+
+ Ok(Some(xor_mapped_addr))
+}
+
+fn timestamp() -> u64 {
+ SystemTime::now()
+ .duration_since(SystemTime::UNIX_EPOCH)
+ .expect("clock error")
+ .as_secs()
+}