From 05b51d1c85dac05408e0f4f9958036634fc7b057 Mon Sep 17 00:00:00 2001 From: "D. Scott Boggs" Date: Sun, 9 Feb 2025 09:43:55 -0500 Subject: [PATCH] Basic DNS check works --- .gitignore | 1 + Cargo.lock | 415 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 ++ example.txt | 35 ++++ examples/deser.rs | 12 ++ src/dig_response.rs | 133 ++++++++++++++ src/main.rs | 31 ++++ 7 files changed, 640 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 example.txt create mode 100644 examples/deser.rs create mode 100644 src/dig_response.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ae857ab --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,415 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "dns-check" +version = "0.1.0" +dependencies = [ + "anyhow", + "femme", + "log", + "serde", + "serde_yaml_ng", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "femme" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc04871e5ae3aa2952d552dae6b291b3099723bf779a8054281c1366a54613ef" +dependencies = [ + "cfg-if", + "js-sys", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "indexmap" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" +dependencies = [ + "serde", + "value-bag", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" + +[[package]] +name = "serde" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_fmt" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d4ddca14104cd60529e8c7f7ba71a2c8acd8f7f5cfcdc2faf97eeb7c3010a4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.138" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sval" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c2f18f53c889ec3dfe1c08b20fd51406d09b14bf18b366416718763ccff05a" + +[[package]] +name = "sval_buffer" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b8cb1bb48d0bed828b908e6b99e7ab8c7244994dc27948a2e31d42e8c4d77c1" +dependencies = [ + "sval", + "sval_ref", +] + +[[package]] +name = "sval_dynamic" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba574872d4ad653071a9db76c49656082db83a37cd5f559874273d36b4a02b9d" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_fmt" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944450b2dbbf8aae98537776b399b23d72b19243ee42522cfd110305f3c9ba5a" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_json" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411bbd543c413796ccfbaa44f6676e20032b6c69e4996cb6c3e6ef30c79b96d1" +dependencies = [ + "itoa", + "ryu", + "sval", +] + +[[package]] +name = "sval_nested" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30582d2a90869b380f8260559138c1b68ac3e0765520959f22a1a1fdca31769" +dependencies = [ + "sval", + "sval_buffer", + "sval_ref", +] + +[[package]] +name = "sval_ref" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "762d3fbf3c0869064b7c93808c67ad2ed0292dde9b060ac282817941d4707dff" +dependencies = [ + "sval", +] + +[[package]] +name = "sval_serde" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "752d307438c6a6a3d095a2fecf6950cfb946d301a5bd6b57f047db4f6f8d97b9" +dependencies = [ + "serde", + "sval", + "sval_nested", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "value-bag" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" +dependencies = [ + "value-bag-serde1", + "value-bag-sval2", +] + +[[package]] +name = "value-bag-serde1" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb773bd36fd59c7ca6e336c94454d9c66386416734817927ac93d81cb3c5b0b" +dependencies = [ + "erased-serde", + "serde", + "serde_fmt", +] + +[[package]] +name = "value-bag-sval2" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a916a702cac43a88694c97657d449775667bcd14b70419441d05b7fea4a83a" +dependencies = [ + "sval", + "sval_buffer", + "sval_dynamic", + "sval_fmt", + "sval_json", + "sval_ref", + "sval_serde", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dc5be1a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "dns-check" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1.0.95" +femme = "2.2.1" +serde = { version = "1.0.217", features = ["derive"] } +serde_yaml_ng = "0.10.0" +[dependencies.log] +version = "0.4.25" +features = ["kv", "serde"] diff --git a/example.txt b/example.txt new file mode 100644 index 0000000..c235682 --- /dev/null +++ b/example.txt @@ -0,0 +1,35 @@ +type: MESSAGE +message: + type: RECURSIVE_RESPONSE + query_time: !!timestamp 2025-02-09T14:12:32.022Z + response_time: !!timestamp 2025-02-09T14:12:32.061Z + message_size: 135b + socket_family: INET + socket_protocol: UDP + response_address: "100.64.0.4" + response_port: 53 + query_address: "0.0.0.0" + query_port: 0 + response_message_data: + opcode: QUERY + status: NOERROR + id: 615 + flags: qr rd ra + QUESTION: 1 + ANSWER: 6 + AUTHORITY: 0 + ADDITIONAL: 1 + OPT_PSEUDOSECTION: + EDNS: + version: 0 + flags: + udp: 1232 + QUESTION_SECTION: + - google.com. IN A + ANSWER_SECTION: + - google.com. 62 IN A 172.253.122.102 + - google.com. 62 IN A 172.253.122.101 + - google.com. 62 IN A 172.253.122.100 + - google.com. 62 IN A 172.253.122.139 + - google.com. 62 IN A 172.253.122.138 + - google.com. 62 IN A 172.253.122.113 diff --git a/examples/deser.rs b/examples/deser.rs new file mode 100644 index 0000000..63bb0d7 --- /dev/null +++ b/examples/deser.rs @@ -0,0 +1,12 @@ +use serde::Deserialize; + + +#[derive(Debug, Deserialize)] +struct Demo { + query_time: String +} + +fn main() { + let d: Demo = serde_yaml_ng::from_str("query_time: !!timestamp 2025-02-09T14:12:32.022Z").unwrap(); + println!("{}", d.query_time); +} \ No newline at end of file diff --git a/src/dig_response.rs b/src/dig_response.rs new file mode 100644 index 0000000..dbfb44e --- /dev/null +++ b/src/dig_response.rs @@ -0,0 +1,133 @@ +use std::{net::IpAddr, process::Command, vec}; + +use anyhow::anyhow; +use serde::{Deserialize, Serialize}; + +#[derive(Debug)] +pub struct DnsResponse { + pub domain: String, + pub ttl: i32, + pub class: String, + pub r#type: String, + pub address: IpAddr, +} + +impl DnsResponse { + pub fn parse(text: impl AsRef) -> anyhow::Result { + let text = text.as_ref(); + let mut split = text.split(' '); + let domain = split + .next() + .ok_or_else(|| anyhow!("domain not found in answer {text:?}"))? + .to_owned(); + let ttl = split + .next() + .ok_or_else(|| anyhow!("ttl not found in answer {text:?}"))?; + let ttl: i32 = ttl.parse()?; + let class = split + .next() + .ok_or_else(|| anyhow!("class not found in answer {text:?}"))? + .to_owned(); + let r#type = split + .next() + .ok_or_else(|| anyhow!("type not found in answer {text:?}"))? + .to_owned(); + let address = split + .next() + .ok_or_else(|| anyhow!("address not found in answer {text:?}"))?; + let address: IpAddr = address.parse()?; + Ok(Self { + domain, + ttl, + class, + r#type, + address, + }) + } +} + +impl DigResponse { + pub fn query(domain: impl AsRef) -> anyhow::Result { + let cmd = Command::new("dig") + .arg("+yaml") + .arg(domain.as_ref()) + .output()?; + if cmd.status.success() { + Ok(serde_yaml_ng::from_str( + String::from_utf8(cmd.stdout.into_iter().skip(2).collect())?.as_str(), + )?) + } else { + Err(anyhow!( + "dig command failed:\n\tstatus: {:?}\n\tstdout: {}\n\tstderr: {}", + cmd.status, + String::from_utf8_lossy(&cmd.stdout), + String::from_utf8_lossy(&cmd.stderr) + )) + } + } + pub fn answers(&self) -> anyhow::Result> { + let mut answers = vec![]; + // let DigResponseType::Message(ref message) = self.message; + // for answer in &message.response_message_data.answer_section { + for answer in &self.message.response_message_data.answer_section { + answers.push(DnsResponse::parse(answer)?); + } + Ok(answers) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum DigResponseType { + Message(DigResponseMessage), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DigResponseMessage { + pub r#type: String, + pub query_time: String, + pub response_time: String, + pub message_size: String, + pub socket_family: String, + pub socket_protocol: String, + pub response_address: String, + pub response_port: String, + pub response_message_data: DigResponseMessageData, +} +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DigResponse { + pub r#type: String, + pub message: DigResponseMessage, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct DigResponseMessageData { + // opcode: QUERY + opcode: String, + // status: NOERROR + status: String, + // id: 57945 + id: i32, + // flags: qr rd ra + flags: String, + // QUESTION: 1 + #[serde(rename = "QUESTION")] + question: i32, + // ANSWER: 1 + #[serde(rename = "ANSWER")] + answer: i32, + // AUTHORITY: 0 + #[serde(rename = "AUTHORITY")] + authority: i32, + // ADDITIONAL: 1 + #[serde(rename = "ADDITIONAL")] + additional: i32, + // QUESTION_SECTION: + // - google.com. IN A + #[serde(rename = "QUESTION_SECTION")] + question_section: Vec, + // ANSWER_SECTION: + // - google.com. 228 IN A 142.250.189.142 + #[serde(rename = "ANSWER_SECTION")] + answer_section: Vec, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e198e3a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,31 @@ +pub mod dig_response; + +use std::net::IpAddr; + +use dig_response::DigResponse; + +use anyhow::anyhow; + +fn main() -> anyhow::Result<()> { + run_dns_check("techwork.zone", "67.240.19.249".parse()?) + // let txt = File::open("example.txt")?; + // let data: DigResponse = serde_yaml_ng::from_reader(txt)?; + // println!("{data:?}"); + // println!("{:?}", data.answers()?); + // Ok(()) +} + +fn run_dns_check(domain: impl AsRef, expected_ip: IpAddr) -> anyhow::Result<()> { + let domain = domain.as_ref(); + let result = DigResponse::query(domain)?; + let addresses: Vec<_> = result.answers()?.iter().map(|a| a.address).collect(); + for address in &addresses { + if address == &expected_ip { + println!("found expected IP {address:?} for domain {domain}"); + return Ok(()); + } + } + Err(anyhow!( + "expected IP {expected_ip:?} not found in DNS results: {addresses:?}" + )) +}