use crate::types::*;
use std::net::{Ipv4Addr, Ipv6Addr};
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct DnsResponseBuilder {
id: u16,
flags: Flags,
queries: Vec<Query>,
answers: Vec<Record>,
authorities: Vec<Record>,
additionals: Vec<Record>,
}
impl Default for DnsResponseBuilder {
fn default() -> Self {
Self::new()
}
}
impl DnsResponseBuilder {
pub fn new() -> Self {
Self {
id: 0,
flags: Flags {
qr: true, opcode: 0, aa: false, tc: false, rd: true, ra: true, z: 0, rcode: 0, },
queries: Vec::new(),
answers: Vec::new(),
authorities: Vec::new(),
additionals: Vec::new(),
}
}
pub fn with_id(mut self, id: u16) -> Self {
self.id = id;
self
}
pub fn with_response_code(mut self, rcode: u8) -> Self {
self.flags.rcode = rcode;
self
}
pub fn with_authoritative(mut self, aa: bool) -> Self {
self.flags.aa = aa;
self
}
pub fn with_truncated(mut self, tc: bool) -> Self {
self.flags.tc = tc;
self
}
pub fn add_query(mut self, name: String, qtype: RecordType, qclass: QClass) -> Self {
self.queries.push(Query {
name,
qtype,
qclass,
});
self
}
pub fn add_a_answer(mut self, name: String, ttl: u32, ip: Ipv4Addr) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::A,
class: QClass::IN,
ttl,
data: RecordData::A(ip),
});
self
}
pub fn add_aaaa_answer(mut self, name: String, ttl: u32, ip: Ipv6Addr) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::AAAA,
class: QClass::IN,
ttl,
data: RecordData::AAAA(ip),
});
self
}
pub fn add_cname_answer(mut self, name: String, ttl: u32, target: String) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::CNAME,
class: QClass::IN,
ttl,
data: RecordData::CNAME(target),
});
self
}
pub fn add_mx_answer(mut self, name: String, ttl: u32, priority: u16, exchange: String) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::MX,
class: QClass::IN,
ttl,
data: RecordData::MX { priority, exchange },
});
self
}
pub fn add_ns_answer(mut self, name: String, ttl: u32, nameserver: String) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::NS,
class: QClass::IN,
ttl,
data: RecordData::NS(nameserver),
});
self
}
pub fn add_ptr_answer(mut self, name: String, ttl: u32, target: String) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::PTR,
class: QClass::IN,
ttl,
data: RecordData::PTR(target),
});
self
}
pub fn add_soa_answer(
mut self,
name: String,
ttl: u32,
mname: String,
rname: String,
serial: u32,
refresh: u32,
retry: u32,
expire: u32,
minimum: u32,
) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::SOA,
class: QClass::IN,
ttl,
data: RecordData::SOA {
mname,
rname,
serial,
refresh,
retry,
expire,
minimum,
},
});
self
}
pub fn add_txt_answer(mut self, name: String, ttl: u32, texts: Vec<String>) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::TXT,
class: QClass::IN,
ttl,
data: RecordData::TXT(texts),
});
self
}
pub fn add_srv_answer(
mut self,
name: String,
ttl: u32,
priority: u16,
weight: u16,
port: u16,
target: String,
) -> Self {
self.answers.push(Record {
name,
rtype: RecordType::SRV,
class: QClass::IN,
ttl,
data: RecordData::SRV {
priority,
weight,
port,
target,
},
});
self
}
pub fn add_authority(mut self, record: Record) -> Self {
self.authorities.push(record);
self
}
pub fn add_additional(mut self, record: Record) -> Self {
self.additionals.push(record);
self
}
pub fn build(self) -> Response {
Response {
id: self.id,
flags: self.flags,
queries: self.queries,
answers: self.answers,
authorities: self.authorities,
additionals: self.additionals,
}
}
}
pub struct DnsResponseWrapper;
impl DnsResponseWrapper {
pub fn create_a_response(query_id: u16, domain: &str, ips: &[Ipv4Addr], ttl: u32) -> Response {
let mut builder = DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::A, QClass::IN);
for ip in ips {
builder = builder.add_a_answer(domain.to_string(), ttl, *ip);
}
builder.build()
}
pub fn create_aaaa_response(query_id: u16, domain: &str, ips: &[Ipv6Addr], ttl: u32) -> Response {
let mut builder = DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::AAAA, QClass::IN);
for ip in ips {
builder = builder.add_aaaa_answer(domain.to_string(), ttl, *ip);
}
builder.build()
}
pub fn create_nxdomain_response(query_id: u16, domain: &str, qtype: RecordType) -> Response {
DnsResponseBuilder::new()
.with_id(query_id)
.with_response_code(3) .add_query(domain.to_string(), qtype, QClass::IN)
.build()
}
pub fn create_server_failure_response(query_id: u16, domain: &str, qtype: RecordType) -> Response {
DnsResponseBuilder::new()
.with_id(query_id)
.with_response_code(2) .add_query(domain.to_string(), qtype, QClass::IN)
.build()
}
pub fn create_cname_response(query_id: u16, domain: &str, target: &str, ttl: u32) -> Response {
DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::CNAME, QClass::IN)
.add_cname_answer(domain.to_string(), ttl, target.to_string())
.build()
}
pub fn create_mx_response(query_id: u16, domain: &str, mx_records: &[(u16, String)], ttl: u32) -> Response {
let mut builder = DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::MX, QClass::IN);
for (priority, exchange) in mx_records {
builder = builder.add_mx_answer(domain.to_string(), ttl, *priority, exchange.clone());
}
builder.build()
}
pub fn create_txt_response(query_id: u16, domain: &str, texts: &[String], ttl: u32) -> Response {
DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::TXT, QClass::IN)
.add_txt_answer(domain.to_string(), ttl, texts.to_vec())
.build()
}
pub fn create_soa_response(
query_id: u16,
domain: &str,
mname: &str,
rname: &str,
ttl: u32,
) -> Response {
let serial = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::SOA, QClass::IN)
.add_soa_answer(
domain.to_string(),
ttl,
mname.to_string(),
rname.to_string(),
serial,
3600, 1800, 604800, 86400, )
.build()
}
pub fn create_srv_response(
query_id: u16,
domain: &str,
srv_records: &[(u16, u16, u16, String)], ttl: u32,
) -> Response {
let mut builder = DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::SRV, QClass::IN);
for (priority, weight, port, target) in srv_records {
builder = builder.add_srv_answer(
domain.to_string(),
ttl,
*priority,
*weight,
*port,
target.clone(),
);
}
builder.build()
}
pub fn create_ptr_response(query_id: u16, ptr_name: &str, target: &str, ttl: u32) -> Response {
DnsResponseBuilder::new()
.with_id(query_id)
.add_query(ptr_name.to_string(), RecordType::PTR, QClass::IN)
.add_ptr_answer(ptr_name.to_string(), ttl, target.to_string())
.build()
}
pub fn create_ns_response(query_id: u16, domain: &str, nameservers: &[String], ttl: u32) -> Response {
let mut builder = DnsResponseBuilder::new()
.with_id(query_id)
.add_query(domain.to_string(), RecordType::NS, QClass::IN);
for ns in nameservers {
builder = builder.add_ns_answer(domain.to_string(), ttl, ns.clone());
}
builder.build()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_dns_response_builder() {
let response = DnsResponseBuilder::new()
.with_id(12345)
.with_authoritative(true)
.add_query("example.com".to_string(), RecordType::A, QClass::IN)
.add_a_answer("example.com".to_string(), 300, Ipv4Addr::new(192, 168, 1, 1))
.build();
assert_eq!(response.id, 12345);
assert!(response.flags.aa);
assert_eq!(response.queries.len(), 1);
assert_eq!(response.answers.len(), 1);
assert_eq!(response.queries[0].name, "example.com");
if let RecordData::A(ip) = &response.answers[0].data {
assert_eq!(*ip, Ipv4Addr::new(192, 168, 1, 1));
} else {
panic!("Expected A record data");
}
}
#[test]
fn test_create_a_response() {
let ips = vec![Ipv4Addr::new(1, 2, 3, 4), Ipv4Addr::new(5, 6, 7, 8)];
let response = DnsResponseWrapper::create_a_response(123, "test.com", &ips, 300);
assert_eq!(response.id, 123);
assert_eq!(response.answers.len(), 2);
assert_eq!(response.queries[0].name, "test.com");
}
#[test]
fn test_create_nxdomain_response() {
let response = DnsResponseWrapper::create_nxdomain_response(456, "notfound.com", RecordType::A);
assert_eq!(response.id, 456);
assert_eq!(response.flags.rcode, 3); assert_eq!(response.answers.len(), 0);
assert_eq!(response.queries[0].name, "notfound.com");
}
}