From 2f04dc55312224b62e0517ac8e3f09cb6a373096 Mon Sep 17 00:00:00 2001 From: Alexander Sagen Date: Wed, 14 Aug 2024 12:30:02 +0200 Subject: [PATCH 1/2] ServiceClient: Use ureq::Agent with a connection pool of 1 This avoids always creating new connections, leading to excessive lingering TCP connections in TIME_WAIT state if the ZeroTier system service is unavailable. --- src/serviceclient.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/serviceclient.rs b/src/serviceclient.rs index 48a11dd..8ae4f68 100644 --- a/src/serviceclient.rs +++ b/src/serviceclient.rs @@ -9,7 +9,6 @@ use std::cell::Cell; use std::collections::{HashMap, LinkedList}; use std::env::VarError; -use std::ffi::CString; use std::io::Write; use std::path::Path; use std::sync::atomic::{AtomicBool, Ordering}; @@ -17,6 +16,7 @@ use std::sync::Arc; use std::time::{Duration, SystemTime}; use serde_json::{Map, Value}; +use ureq::{Agent, AgentBuilder}; const QUERY_TIMEOUT_MS: u64 = 2000; @@ -36,6 +36,7 @@ pub struct ServiceClient { dirty: Arc, online: bool, try_escalate_privs: isize, + http_agent: Agent, } pub fn ms_since_epoch() -> i64 { @@ -244,6 +245,9 @@ impl ServiceClient { dirty: dirty_flag.clone(), online: false, try_escalate_privs: 2, // try this twice at startup, but not forever + http_agent: AgentBuilder::new() + .timeout(Duration::from_millis(QUERY_TIMEOUT_MS)) + .build(), }, dirty_flag, ) @@ -416,8 +420,7 @@ impl ServiceClient { if self.auth_token.is_empty() || self.base_url.is_empty() { (0, String::new()) } else { - ureq::get(format!("{}{}", self.base_url, path).as_str()) - .timeout(Duration::from_millis(QUERY_TIMEOUT_MS)) + self.http_agent.get(format!("{}{}", self.base_url, path).as_str()) .set("X-ZT1-Auth", self.auth_token.as_str()) .call() .map_or_else( @@ -439,8 +442,7 @@ impl ServiceClient { if self.auth_token.is_empty() || self.base_url.is_empty() { (0, String::new()) } else { - ureq::post(format!("{}{}", self.base_url, path).as_str()) - .timeout(Duration::from_millis(QUERY_TIMEOUT_MS)) + self.http_agent.post(format!("{}{}", self.base_url, path).as_str()) .set("X-ZT1-Auth", self.auth_token.as_str()) .send_string(payload) .map_or_else( @@ -509,8 +511,7 @@ impl ServiceClient { if pq.is_some() { let pq = pq.unwrap(); posted = true; - let _ = ureq::delete(format!("{}{}", self.base_url, pq).as_str()) - .timeout(Duration::from_millis(QUERY_TIMEOUT_MS)) + let _ = self.http_agent.delete(format!("{}{}", self.base_url, pq).as_str()) .set("X-ZT1-Auth", self.auth_token.as_str()) .call() .map_or(0_u16, |res| res.status()); From f17b8f3f1ada1eaa8dee0d3cd0a11efd935a0204 Mon Sep 17 00:00:00 2001 From: Alexander Sagen Date: Wed, 14 Aug 2024 12:48:38 +0200 Subject: [PATCH 2/2] ServiceClient: Attempt IPv6 localhost connection if IPv4 localhost connection failed, and vice-versa --- src/serviceclient.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/serviceclient.rs b/src/serviceclient.rs index 8ae4f68..23b9b06 100644 --- a/src/serviceclient.rs +++ b/src/serviceclient.rs @@ -10,6 +10,7 @@ use std::cell::Cell; use std::collections::{HashMap, LinkedList}; use std::env::VarError; use std::io::Write; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::path::Path; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; @@ -27,6 +28,7 @@ pub struct ServiceClient { refresh_base_paths: Vec<&'static str>, auth_token: String, port: u16, + address: IpAddr, base_url: String, saved_networks: Map, state_hash: HashMap, @@ -229,6 +231,7 @@ impl ServiceClient { refresh_base_paths, auth_token: String::new(), port: 0, + address: IpAddr::V4(Ipv4Addr::LOCALHOST), base_url: String::new(), saved_networks: std::fs::read(unsafe { crate::NETWORK_CACHE_PATH.as_str() }) .map_or_else( @@ -484,7 +487,7 @@ impl ServiceClient { if self.auth_token != token || self.port != port { self.auth_token = token; self.port = port; - self.base_url = format!("http://127.0.0.1:{}/", self.port); + self.base_url = format!("http://{}/", SocketAddr::new(self.address, self.port)); } } } @@ -567,11 +570,11 @@ impl ServiceClient { let mut dirty = false; for endpoint in self.refresh_base_paths.iter() { let endpoint = *endpoint; - let data = self.http_get(endpoint); - if data.0 == 200 { + let (status, res) = self.http_get(endpoint); + if status == 200 { let endpoint = String::from(endpoint); let data = - serde_json::from_str::(data.1.as_str()).unwrap_or(Value::Null); + serde_json::from_str::(res.as_str()).unwrap_or(Value::Null); let mut c64 = crc64::Crc64::new(); hash_result(&data, &mut c64); @@ -583,7 +586,7 @@ impl ServiceClient { self.dirty.store(true, Ordering::Relaxed); dirty = true; } - } else if data.0 == 0 { + } else if status == 0 { self.online = false; } else { self.online = false; @@ -596,6 +599,16 @@ impl ServiceClient { } } } + // HACK: if IPv4 localhost connection failed, try IPv6 localhost connection on next attempt + // this works around an issue where the ZeroTierOne service could fail to bind to an IPv4 socket + // https://github.com/zerotier/ZeroTierOne/issues/2342 + if !self.online { + self.address = match self.address { + IpAddr::V4(Ipv4Addr::LOCALHOST) => IpAddr::V6(Ipv6Addr::LOCALHOST), + IpAddr::V6(Ipv6Addr::LOCALHOST) => IpAddr::V4(Ipv4Addr::LOCALHOST), + other => other, + }; + } if dirty { self.state.insert( "saved_networks".into(),