diff --git a/src/serviceclient.rs b/src/serviceclient.rs index 48a11dd..23b9b06 100644 --- a/src/serviceclient.rs +++ b/src/serviceclient.rs @@ -9,14 +9,15 @@ use std::cell::Cell; use std::collections::{HashMap, LinkedList}; use std::env::VarError; -use std::ffi::CString; 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; use std::time::{Duration, SystemTime}; use serde_json::{Map, Value}; +use ureq::{Agent, AgentBuilder}; const QUERY_TIMEOUT_MS: u64 = 2000; @@ -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, @@ -36,6 +38,7 @@ pub struct ServiceClient { dirty: Arc, online: bool, try_escalate_privs: isize, + http_agent: Agent, } pub fn ms_since_epoch() -> i64 { @@ -228,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( @@ -244,6 +248,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 +423,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 +445,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( @@ -482,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)); } } } @@ -509,8 +514,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()); @@ -566,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); @@ -582,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; @@ -595,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(),