134 lines
3.8 KiB
Rust
134 lines
3.8 KiB
Rust
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<str>) -> anyhow::Result<DnsResponse> {
|
|
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<str>) -> anyhow::Result<Self> {
|
|
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<Vec<DnsResponse>> {
|
|
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<String>,
|
|
// ANSWER_SECTION:
|
|
// - google.com. 228 IN A 142.250.189.142
|
|
#[serde(rename = "ANSWER_SECTION")]
|
|
answer_section: Vec<String>,
|
|
}
|