From 72e98e5fcfbf77cefcb538eb41bb03bfa9142345 Mon Sep 17 00:00:00 2001 From: Jun Kurihara Date: Mon, 30 Oct 2023 21:58:36 +0900 Subject: [PATCH] feat: implement health check service --- dap-bin/Cargo.toml | 2 + dap-lib/Cargo.toml | 6 +- dap-lib/src/constants.rs | 11 ++- .../src/doh_client/doh_client_healthcheck.rs | 82 ++++++++++++++++++- dap-lib/src/doh_client/doh_client_main.rs | 8 +- dap-lib/src/doh_client/odoh.rs | 1 + dap-lib/src/doh_client/path_manage.rs | 2 +- 7 files changed, 100 insertions(+), 12 deletions(-) diff --git a/dap-bin/Cargo.toml b/dap-bin/Cargo.toml index 4f20a84..7c070ca 100644 --- a/dap-bin/Cargo.toml +++ b/dap-bin/Cargo.toml @@ -20,7 +20,9 @@ keywords = [ "doh", "oblivious-dns-over-https", "odoh", + "mutualized-oblivious-dns", "mutualized-odoh", + "modoh", "proxy", "authorization", ] diff --git a/dap-lib/Cargo.toml b/dap-lib/Cargo.toml index 55730d6..cb64282 100644 --- a/dap-lib/Cargo.toml +++ b/dap-lib/Cargo.toml @@ -20,7 +20,9 @@ keywords = [ "doh", "oblivious-dns-over-https", "odoh", + "mutualized-oblivious-dns", "mutualized-odoh", + "modoh", "proxy", "authorization", ] @@ -50,11 +52,12 @@ serde = { version = "1.0.189", features = ["derive"] } itertools = "0.11.0" rustc-hash = "1.1.0" -# doh and odoh client +# doh and odoh client with cache and query plugins odoh-rs = { git = "https://github.com/junkurihara/odoh-rs.git" } bytes = "1.5.0" hickory-proto = { version = "0.24.0", default-features = false } data-encoding = "2.4.0" +hashlink = "0.8.4" # network socket2 = "0.5.5" @@ -74,7 +77,6 @@ hickory-resolver = { version = "0.24.0", default-features = false, features = [ # authentication auth-client = { git = "https://github.com/junkurihara/rust-token-server", package = "rust-token-server-client", branch = "develop" } -hashlink = "0.8.4" [dev-dependencies] hickory-client = "0.24.0" diff --git a/dap-lib/src/constants.rs b/dap-lib/src/constants.rs index 3b0a24c..da56e53 100644 --- a/dap-lib/src/constants.rs +++ b/dap-lib/src/constants.rs @@ -2,17 +2,22 @@ // Constant Values for Config // //////////////////////////////// // Cannot override by config.toml -pub const UDP_BUFFER_SIZE: usize = 2048; // TODO: バッファサイズめちゃ適当 + +/// UDP buffer size TODO: めちゃ適当 +pub const UDP_BUFFER_SIZE: usize = 2048; +/// UDP channel Capacity TODO: めちゃ適当 pub const UDP_CHANNEL_CAPACITY: usize = 1024; // TODO: channelキャパシティめちゃ適当 +/// UDP timeout in secs pub const UDP_TIMEOUT_SEC: u64 = 10; +/// TCP listen backlog pub const TCP_LISTEN_BACKLOG: u32 = 1024; /// Max connections via UPD and TCP (total) TODO: めちゃ適当 pub const MAX_CONNECTIONS: usize = 128; /// Time out secs for HTTP requests pub const HTTP_TIMEOUT_SEC: u64 = 10; - -pub const MIN_TTL: u32 = 10; // TTL for overridden records (plugin) +/// TTL for overridden records (plugin) in synthetic response +pub const MIN_TTL: u32 = 10; //////////////////////////////// // Default Values for Config // diff --git a/dap-lib/src/doh_client/doh_client_healthcheck.rs b/dap-lib/src/doh_client/doh_client_healthcheck.rs index a6462b5..519c63a 100644 --- a/dap-lib/src/doh_client/doh_client_healthcheck.rs +++ b/dap-lib/src/doh_client/doh_client_healthcheck.rs @@ -1,5 +1,11 @@ -use super::DoHClient; -use crate::{error::*, log::*}; +use super::{dns_message, path_manage::DoHPath, DoHClient}; +use crate::{ + constants::{HEALTHCHECK_TARGET_ADDR, HEALTHCHECK_TARGET_FQDN}, + error::*, + log::*, +}; +use futures::future::join_all; +use hickory_proto::op::response_code::ResponseCode; use std::sync::Arc; use tokio::sync::Notify; @@ -38,11 +44,79 @@ impl DoHClient { debug!("Purged {} expired entries from cache", purged); }); - // TODO: health check for every path - // make_doh_query_innerを使う + // health check for every path + let futures = self + .path_manager + .paths + .iter() + .flatten() + .flatten() + .map(|path| async move { + if let Err(e) = self.healthcheck(path).await { + warn!("Healthcheck fails for {}: {e}", path.as_url()?) + } + Ok(()) as Result<()> + }); + let _ = join_all(futures).await; + + if !self + .path_manager + .paths + .iter() + .flatten() + .flatten() + .any(|v| v.is_healthy()) + { + error!("All possible paths are unhealthy. Should check the Internet connection"); + } tokio::time::sleep(self.healthcheck_period_sec).await; } + } + + /// Check health for a given path, and update health status for the path. + async fn healthcheck(&self, path: &Arc) -> Result<()> { + let q_msg = dns_message::build_query_a(HEALTHCHECK_TARGET_FQDN)?; + let packet_buf = dns_message::encode(&q_msg)?; + + let Ok((_, res_msg)) = self.make_doh_query_inner(&packet_buf, path).await else { + path.make_unhealthy(); + warn!( + "Failed to query or invalid response. Path {} is unhealthy", + path.as_url()? + ); + return Ok(()); + }; + + if res_msg.header().response_code() != ResponseCode::NoError { + path.make_unhealthy(); + warn!("Response is not Ok. Path {} is unhealthy", path.as_url()?); + return Ok(()); + } + + let answers = res_msg.answers(); + if answers.is_empty() { + path.make_unhealthy(); + warn!("Response has no answer. Path {} is unhealthy", path.as_url()?); + + return Ok(()); + } + + let target_addr_contains = answers + .iter() + .filter_map(|answer| answer.data()) + .any(|v| v.to_string() == HEALTHCHECK_TARGET_ADDR); + + if !target_addr_contains { + path.make_unhealthy(); + warn!( + "Response has no or wrong rdata. Maybe suspicious and polluted target. Path {} is unhealthy.", + path.as_url()? + ); + return Ok(()); + } + path.make_healthy(); + debug!("Path {} is healthy", path.as_url()?); Ok(()) } } diff --git a/dap-lib/src/doh_client/doh_client_main.rs b/dap-lib/src/doh_client/doh_client_main.rs index cf10961..3a57f6a 100644 --- a/dap-lib/src/doh_client/doh_client_main.rs +++ b/dap-lib/src/doh_client/doh_client_main.rs @@ -28,7 +28,7 @@ pub struct DoHClient { /// auth_client to retrieve id token auth_client: Option>, /// path candidates with health flags - path_manager: Arc, + pub(super) path_manager: Arc, /// odoh config store odoh_configs: Option>, /// DNS cache @@ -192,7 +192,11 @@ impl DoHClient { /// Make DoH query with a specifically given path. /// Note cache and plugins are disabled to be used for health check - async fn make_doh_query_inner(&self, packet_buf: &[u8], path: &Arc) -> Result<(Vec, Message)> { + pub(super) async fn make_doh_query_inner( + &self, + packet_buf: &[u8], + path: &Arc, + ) -> Result<(Vec, Message)> { let headers = self.build_headers().await?; let response_buf = match self.doh_type { DoHType::Standard => self.serve_doh_query(packet_buf, path, headers).await, diff --git a/dap-lib/src/doh_client/odoh.rs b/dap-lib/src/doh_client/odoh.rs index 4c52867..c1dc7e8 100644 --- a/dap-lib/src/doh_client/odoh.rs +++ b/dap-lib/src/doh_client/odoh.rs @@ -9,6 +9,7 @@ use rand::{rngs::StdRng, SeedableRng}; #[derive(Debug, Clone)] /// ODoH config pub struct ODoHConfig { + #[allow(dead_code)] authority: String, inner: ObliviousDoHConfigContents, } diff --git a/dap-lib/src/doh_client/path_manage.rs b/dap-lib/src/doh_client/path_manage.rs index 596a581..882b24f 100644 --- a/dap-lib/src/doh_client/path_manage.rs +++ b/dap-lib/src/doh_client/path_manage.rs @@ -170,7 +170,7 @@ pub struct DoHPathManager { /// first dimension: depends on doh target resolver /// second dimension: depends on next-hop relays. for the standard doh, its is one dimensional. /// third dimension: actual paths. for the standard doh, its is one dimensional. - paths: Vec>>>, + pub(super) paths: Vec>>>, /// target randomization target_randomization: bool, /// next-hop randomization