refactor to async tokio with modular architecture
- Replace synchronous std::net::UdpSocket with tokio async runtime - Spawn concurrent task per incoming DNS query via tokio::spawn - Extract monolithic main.rs into modules: buffer, header, question, record, packet, config, cache, forward, stats - Share state across tasks via Arc<ServerCtx> with scoped Mutex locks - Add TOML config loading, TTL-aware cache, structured logging, stats - Add CLAUDE.md, README, dns_fun.toml config, and design docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
249
src/record.rs
Normal file
249
src/record.rs
Normal file
@@ -0,0 +1,249 @@
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use crate::buffer::BytePacketBuffer;
|
||||
use crate::question::QueryType;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[allow(dead_code)]
|
||||
pub enum DnsRecord {
|
||||
UNKNOWN {
|
||||
domain: String,
|
||||
qtype: u16,
|
||||
data_len: u16,
|
||||
ttl: u32,
|
||||
},
|
||||
A {
|
||||
domain: String,
|
||||
addr: Ipv4Addr,
|
||||
ttl: u32,
|
||||
},
|
||||
NS {
|
||||
domain: String,
|
||||
host: String,
|
||||
ttl: u32,
|
||||
},
|
||||
CNAME {
|
||||
domain: String,
|
||||
host: String,
|
||||
ttl: u32,
|
||||
},
|
||||
MX {
|
||||
domain: String,
|
||||
priority: u16,
|
||||
host: String,
|
||||
ttl: u32,
|
||||
},
|
||||
AAAA {
|
||||
domain: String,
|
||||
addr: Ipv6Addr,
|
||||
ttl: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl DnsRecord {
|
||||
pub fn ttl(&self) -> u32 {
|
||||
match self {
|
||||
DnsRecord::A { ttl, .. }
|
||||
| DnsRecord::NS { ttl, .. }
|
||||
| DnsRecord::CNAME { ttl, .. }
|
||||
| DnsRecord::MX { ttl, .. }
|
||||
| DnsRecord::AAAA { ttl, .. }
|
||||
| DnsRecord::UNKNOWN { ttl, .. } => *ttl,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_ttl(&mut self, new_ttl: u32) {
|
||||
match self {
|
||||
DnsRecord::A { ttl, .. }
|
||||
| DnsRecord::NS { ttl, .. }
|
||||
| DnsRecord::CNAME { ttl, .. }
|
||||
| DnsRecord::MX { ttl, .. }
|
||||
| DnsRecord::AAAA { ttl, .. }
|
||||
| DnsRecord::UNKNOWN { ttl, .. } => *ttl = new_ttl,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(buffer: &mut BytePacketBuffer) -> Result<DnsRecord> {
|
||||
let mut domain = String::new();
|
||||
buffer.read_qname(&mut domain)?;
|
||||
|
||||
let qtype_num = buffer.read_u16()?;
|
||||
let qtype = QueryType::from_num(qtype_num);
|
||||
let _ = buffer.read_u16()?;
|
||||
let ttl = buffer.read_u32()?;
|
||||
let data_len = buffer.read_u16()?;
|
||||
|
||||
match qtype {
|
||||
QueryType::A => {
|
||||
let raw_addr = buffer.read_u32()?;
|
||||
let addr = Ipv4Addr::new(
|
||||
((raw_addr >> 24) & 0xFF) as u8,
|
||||
((raw_addr >> 16) & 0xFF) as u8,
|
||||
((raw_addr >> 8) & 0xFF) as u8,
|
||||
((raw_addr >> 0) & 0xFF) as u8,
|
||||
);
|
||||
|
||||
Ok(DnsRecord::A { domain, addr, ttl })
|
||||
}
|
||||
QueryType::AAAA => {
|
||||
let raw_addr1 = buffer.read_u32()?;
|
||||
let raw_addr2 = buffer.read_u32()?;
|
||||
let raw_addr3 = buffer.read_u32()?;
|
||||
let raw_addr4 = buffer.read_u32()?;
|
||||
let addr = Ipv6Addr::new(
|
||||
((raw_addr1 >> 16) & 0xFFFF) as u16,
|
||||
((raw_addr1 >> 0) & 0xFFFF) as u16,
|
||||
((raw_addr2 >> 16) & 0xFFFF) as u16,
|
||||
((raw_addr2 >> 0) & 0xFFFF) as u16,
|
||||
((raw_addr3 >> 16) & 0xFFFF) as u16,
|
||||
((raw_addr3 >> 0) & 0xFFFF) as u16,
|
||||
((raw_addr4 >> 16) & 0xFFFF) as u16,
|
||||
((raw_addr4 >> 0) & 0xFFFF) as u16,
|
||||
);
|
||||
|
||||
Ok(DnsRecord::AAAA { domain, addr, ttl })
|
||||
}
|
||||
QueryType::NS => {
|
||||
let mut ns = String::new();
|
||||
buffer.read_qname(&mut ns)?;
|
||||
|
||||
Ok(DnsRecord::NS {
|
||||
domain,
|
||||
host: ns,
|
||||
ttl,
|
||||
})
|
||||
}
|
||||
QueryType::CNAME => {
|
||||
let mut cname = String::new();
|
||||
buffer.read_qname(&mut cname)?;
|
||||
|
||||
Ok(DnsRecord::CNAME {
|
||||
domain,
|
||||
host: cname,
|
||||
ttl,
|
||||
})
|
||||
}
|
||||
QueryType::MX => {
|
||||
let priority = buffer.read_u16()?;
|
||||
let mut mx = String::new();
|
||||
buffer.read_qname(&mut mx)?;
|
||||
|
||||
Ok(DnsRecord::MX {
|
||||
domain,
|
||||
priority,
|
||||
host: mx,
|
||||
ttl,
|
||||
})
|
||||
}
|
||||
QueryType::UNKNOWN(_) => {
|
||||
buffer.step(data_len as usize)?;
|
||||
|
||||
Ok(DnsRecord::UNKNOWN {
|
||||
domain,
|
||||
qtype: qtype_num,
|
||||
data_len,
|
||||
ttl,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, buffer: &mut BytePacketBuffer) -> Result<usize> {
|
||||
let start_pos = buffer.pos();
|
||||
|
||||
match *self {
|
||||
DnsRecord::A {
|
||||
ref domain,
|
||||
ref addr,
|
||||
ttl,
|
||||
} => {
|
||||
buffer.write_qname(domain)?;
|
||||
buffer.write_u16(QueryType::A.to_num())?;
|
||||
buffer.write_u16(1)?;
|
||||
buffer.write_u32(ttl)?;
|
||||
buffer.write_u16(4)?;
|
||||
|
||||
let octets = addr.octets();
|
||||
buffer.write_u8(octets[0])?;
|
||||
buffer.write_u8(octets[1])?;
|
||||
buffer.write_u8(octets[2])?;
|
||||
buffer.write_u8(octets[3])?;
|
||||
}
|
||||
DnsRecord::NS {
|
||||
ref domain,
|
||||
ref host,
|
||||
ttl,
|
||||
} => {
|
||||
buffer.write_qname(domain)?;
|
||||
buffer.write_u16(QueryType::NS.to_num())?;
|
||||
buffer.write_u16(1)?;
|
||||
buffer.write_u32(ttl)?;
|
||||
|
||||
let pos = buffer.pos();
|
||||
buffer.write_u16(0)?;
|
||||
buffer.write_qname(host)?;
|
||||
|
||||
let size = buffer.pos() - (pos + 2);
|
||||
buffer.set_u16(pos, size as u16)?;
|
||||
}
|
||||
DnsRecord::CNAME {
|
||||
ref domain,
|
||||
ref host,
|
||||
ttl,
|
||||
} => {
|
||||
buffer.write_qname(domain)?;
|
||||
buffer.write_u16(QueryType::CNAME.to_num())?;
|
||||
buffer.write_u16(1)?;
|
||||
buffer.write_u32(ttl)?;
|
||||
|
||||
let pos = buffer.pos();
|
||||
buffer.write_u16(0)?;
|
||||
buffer.write_qname(host)?;
|
||||
|
||||
let size = buffer.pos() - (pos + 2);
|
||||
buffer.set_u16(pos, size as u16)?;
|
||||
}
|
||||
DnsRecord::MX {
|
||||
ref domain,
|
||||
priority,
|
||||
ref host,
|
||||
ttl,
|
||||
} => {
|
||||
buffer.write_qname(domain)?;
|
||||
buffer.write_u16(QueryType::MX.to_num())?;
|
||||
buffer.write_u16(1)?;
|
||||
buffer.write_u32(ttl)?;
|
||||
|
||||
let pos = buffer.pos();
|
||||
buffer.write_u16(0)?;
|
||||
buffer.write_u16(priority)?;
|
||||
buffer.write_qname(host)?;
|
||||
|
||||
let size = buffer.pos() - (pos + 2);
|
||||
buffer.set_u16(pos, size as u16)?;
|
||||
}
|
||||
DnsRecord::AAAA {
|
||||
ref domain,
|
||||
ref addr,
|
||||
ttl,
|
||||
} => {
|
||||
buffer.write_qname(domain)?;
|
||||
buffer.write_u16(QueryType::AAAA.to_num())?;
|
||||
buffer.write_u16(1)?;
|
||||
buffer.write_u32(ttl)?;
|
||||
buffer.write_u16(16)?;
|
||||
|
||||
for octet in &addr.segments() {
|
||||
buffer.write_u16(*octet)?;
|
||||
}
|
||||
}
|
||||
DnsRecord::UNKNOWN { .. } => {
|
||||
println!("Skipping record: {:?}", self);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(buffer.pos() - start_pos)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user