mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
add a DNS parser. that was fun. :-|
yes, i know there is some fancy way to code this with iterators. i don't like that way. this ways is easier for me to understand and verify. It works now. Sometimes, Rust sucks a bit. It's ok, you can cry.
This commit is contained in:
parent
1a77f0afae
commit
cee4e83746
2 changed files with 290 additions and 23 deletions
|
|
@ -12,7 +12,10 @@ pub(crate) enum Opcode {
|
|||
Lookup,
|
||||
Flush,
|
||||
|
||||
/// issuing this opcode causes all future attempts to change the DNS server configs to be ignored
|
||||
/// used internally to update the TTL field, and eventually expire the cache (unless cache is frozen)
|
||||
UpdateTtl,
|
||||
|
||||
/// issuing this opcode causes all future attempts to change the DNS server configs to be ignored. This also freezes the cache.
|
||||
FreezeConfig,
|
||||
/// this allows automatic updates to the DNS server configs based on DHCP. This is the default state.
|
||||
ThawConfig,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@ use api::*;
|
|||
|
||||
use net::{NetIpAddr, Duration};
|
||||
use num_traits::*;
|
||||
use xous::msg_scalar_unpack;
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::collections::HashMap;
|
||||
use std::io::ErrorKind;
|
||||
use xous_ipc::{String, Buffer};
|
||||
use std::convert::TryInto;
|
||||
use std::thread;
|
||||
|
||||
// KISS DNS
|
||||
|
||||
|
|
@ -97,6 +99,180 @@ impl Message {
|
|||
}
|
||||
}
|
||||
|
||||
fn fast_foward_name(&self, start: usize) -> Result<usize, DnsResponseCode> {
|
||||
use DnsResponseCode::FormatError;
|
||||
let mut index = start;
|
||||
loop {
|
||||
log::info!("cname index: {}", index);
|
||||
if *(self.datagram.get(index).ok_or(FormatError)?) == 0 {
|
||||
index += 1;
|
||||
break;
|
||||
} else {
|
||||
index += *(self.datagram.get(index).ok_or(FormatError)?) as usize;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn parse_response(&self) -> Result<HashMap<IpAddr, u32>, DnsResponseCode> {
|
||||
use DnsResponseCode::FormatError;
|
||||
log::info!("parsing packet: {:?}", self.datagram);
|
||||
|
||||
let mut map = HashMap::<IpAddr, u32>::new();
|
||||
// ASSUME: the query ID and response bit fields have already been checked
|
||||
// and that the rcode is valid
|
||||
let qdcount = u16::from_be_bytes(self.datagram[4..6].try_into().unwrap());
|
||||
let ancount = u16::from_be_bytes(self.datagram[6..8].try_into().unwrap());
|
||||
|
||||
let mut index = 12;
|
||||
// fast forward past the qname
|
||||
for queries in 0..qdcount {
|
||||
log::info!("parsing query{}, index {}", queries, index);
|
||||
index = self.fast_foward_name(index)?;
|
||||
log::info!("fast forward through qname to {}", index);
|
||||
// index is now at qtype
|
||||
let qtype = u16::from_be_bytes(self.datagram[index..index+2].try_into().unwrap());
|
||||
if qtype != 1 && qtype != 28 { // A = 1, AAAA = 28
|
||||
log::error!("Problem parsing qname, qtype is not 1 or 28: {}", qtype);
|
||||
return Err(FormatError)
|
||||
}
|
||||
index += 2;
|
||||
let qclass = u16::from_be_bytes(self.datagram[index..index+2].try_into().unwrap());
|
||||
if qclass != 1 {
|
||||
log::error!("Problem parsing qname, qclass is not 1: {}", qclass);
|
||||
return Err(FormatError)
|
||||
}
|
||||
index += 2;
|
||||
}
|
||||
// index is now at the aname section
|
||||
for aname in 0..ancount {
|
||||
log::info!("parsing aname{}, index {}", aname, index);
|
||||
// first check to see if we're dealing with a pointer or a name
|
||||
if self.datagram[index] >= 0xc0 {
|
||||
// pointer
|
||||
index += 1;
|
||||
if self.datagram[index] != 0xc {
|
||||
log::error!("Found aname pointer, but value does not conform to length of aname header");
|
||||
return Err(FormatError)
|
||||
}
|
||||
index += 1;
|
||||
} else {
|
||||
// name, fast forward past the name
|
||||
index = self.fast_foward_name(index)?;
|
||||
log::info!("fast forward aname to {}", index);
|
||||
}
|
||||
// index is now at type
|
||||
let atype = u16::from_be_bytes(self.datagram[index..index+2].try_into().unwrap());
|
||||
if atype != 1 && atype != 28 { // A = 1, AAAA = 28
|
||||
log::error!("Problem parsing aname, type is not 1 or 28: {}", atype);
|
||||
return Err(FormatError)
|
||||
}
|
||||
index += 2;
|
||||
let aclass = u16::from_be_bytes(self.datagram[index..index+2].try_into().unwrap());
|
||||
if aclass != 1 {
|
||||
log::error!("Problem parsing aname, aclass is not 1: {}", aclass);
|
||||
return Err(FormatError)
|
||||
}
|
||||
index += 2;
|
||||
// this is our TTL
|
||||
let ttl = u32::from_be_bytes(self.datagram[index..index+4].try_into().unwrap());
|
||||
index += 4;
|
||||
// this is the payload length
|
||||
let addr_len = u16::from_be_bytes(self.datagram[index..index+2].try_into().unwrap());
|
||||
index += 2;
|
||||
match addr_len {
|
||||
4 => { // ipv4
|
||||
if atype != 1 {
|
||||
log::error!("Got a 4-byte address, but ATYPE != A (1)");
|
||||
return Err(FormatError)
|
||||
}
|
||||
// this copy happens because I can't figure out how to get Ipv4Addr::from() to realize it's casting from a [u8;4]
|
||||
let mut rdata: [u8; 4] = [0; 4];
|
||||
for (&src, dst) in self.datagram[index..index+4].iter().zip(rdata.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
let addr = IpAddr::V4(Ipv4Addr::from(rdata));
|
||||
index += 4;
|
||||
map.insert(addr, ttl);
|
||||
}
|
||||
16 => { // ipv6
|
||||
if atype != 28 {
|
||||
log::error!("Got a 16-byte address, but ATYPE != AAAA (28)");
|
||||
return Err(FormatError)
|
||||
}
|
||||
// this copy happens because I can't figure out how to get Ipv6Addr::from() to realize it's casting from a [u8;4]
|
||||
let mut rdata: [u8; 16] = [0; 16];
|
||||
for (&src, dst) in self.datagram[index..index+16].iter().zip(rdata.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
let addr = IpAddr::V6(Ipv6Addr::from(rdata));
|
||||
index += 16;
|
||||
map.insert(addr, ttl);
|
||||
}
|
||||
_ => {
|
||||
log::error!("Length field does not match a known record type");
|
||||
return Err(FormatError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
|
||||
/*
|
||||
example response for: betrusted.io->185.199.111.153
|
||||
Header:
|
||||
61, ca, id
|
||||
81, 80, header
|
||||
0, 1, qdcount
|
||||
0, 4, ancount
|
||||
0, 0, nscount
|
||||
0, 0, arcount
|
||||
qname:
|
||||
9, length 9
|
||||
62, 65, 74, 72, 75, 73, 74, 65, 64, "betrusted"
|
||||
2, length 2
|
||||
69, 6f, "io"
|
||||
0, end of name
|
||||
qtype:
|
||||
0, 1, type A
|
||||
qclass:
|
||||
0, 1, type IN
|
||||
aname0:
|
||||
c0, name is a pointer (any value > 192 is a pointer)
|
||||
c, offset of 12 from start of aname0
|
||||
0, 1, type A
|
||||
0, 1, class IN
|
||||
0, 0, e, 10, 0xe10 = 3600 seconds TTL
|
||||
0, 4, 4 bytes address
|
||||
b9, c7, 6c, 99, address
|
||||
aname1:
|
||||
c0, name is a pointer
|
||||
c,
|
||||
0, 1, type A
|
||||
0, 1, class IN
|
||||
0, 0, e, 10, TTL
|
||||
0, 4, 4 byte address
|
||||
b9, c7, 6d, 99, address
|
||||
aname2:
|
||||
c0,
|
||||
c,
|
||||
0, 1,
|
||||
0, 1,
|
||||
0, 0, e, 10,
|
||||
0, 4,
|
||||
b9, c7, 6e, 99,
|
||||
aname3:
|
||||
c0,
|
||||
c,
|
||||
0, 1,
|
||||
0, 1,
|
||||
0, 0, e, 10,
|
||||
0, 4,
|
||||
b9, c7, 6f, 99
|
||||
*/
|
||||
|
||||
/*
|
||||
pub fn is_query(&self) -> bool {
|
||||
!self.is_response()
|
||||
|
|
@ -122,6 +298,7 @@ pub struct Resolver {
|
|||
socket: net::UdpSocket,
|
||||
buf: [u8; DNS_PKT_MAX_LEN],
|
||||
trng: trng::Trng,
|
||||
freeze: bool,
|
||||
}
|
||||
impl Resolver {
|
||||
pub fn new(xns: &xous_names::XousNames) -> Resolver {
|
||||
|
|
@ -142,6 +319,7 @@ impl Resolver {
|
|||
socket,
|
||||
buf: [0; DNS_PKT_MAX_LEN],
|
||||
trng,
|
||||
freeze: false,
|
||||
}
|
||||
}
|
||||
pub fn add_server(&mut self, addr: IpAddr) {
|
||||
|
|
@ -154,9 +332,15 @@ impl Resolver {
|
|||
self.mgr.clear();
|
||||
}
|
||||
pub fn set_freeze_config(&mut self, freeze: bool) {
|
||||
self.freeze = freeze;
|
||||
self.mgr.set_freeze(freeze);
|
||||
}
|
||||
pub fn resolve(&mut self, name: &str) -> Result<IpAddr, DnsResponseCode> {
|
||||
pub fn get_freeze(&self) -> bool { self.freeze }
|
||||
/// this allows us to re-use the TRNG object
|
||||
pub fn trng_u32(&self) -> u32 {
|
||||
self.trng.get_u32().unwrap()
|
||||
}
|
||||
pub fn resolve(&mut self, name: &str) -> Result<HashMap::<IpAddr, u32>, DnsResponseCode> {
|
||||
if let Some(dns_address) = self.mgr.get_random() {
|
||||
let dns_port = 53;
|
||||
let server = SocketAddr::new(dns_address, dns_port);
|
||||
|
|
@ -171,22 +355,23 @@ impl Resolver {
|
|||
|
||||
match self.socket.recv(&mut self.buf) {
|
||||
Ok(len) => {
|
||||
log::info!("buf {}: {:x?}", len, &self.buf[..len]);
|
||||
let message = Message::from(&self.buf[..len]);
|
||||
if message.id() == query.id() && message.is_response() {
|
||||
return match message.rcode() {
|
||||
DnsResponseCode::NoError => {
|
||||
/*
|
||||
let mut map = HashMap::<IpAddr, u32>::new();
|
||||
// TODO: Parse the datagram instead of
|
||||
// extracting the last 4 bytes.
|
||||
//let rdata = message.answer().rdata();
|
||||
let n = message.datagram.len();
|
||||
let mut rdata: [u8; 4] = [0; 4];
|
||||
log::info!("datagram{}: {:x?}", n, message.datagram);
|
||||
log::trace!("datagram{}: {:x?}", n, message.datagram);
|
||||
for (&src, dst) in message.datagram[(n - 4)..].iter().zip(rdata.iter_mut()) {
|
||||
*dst = src;
|
||||
}
|
||||
log::info!("rdata: {:?}", rdata);
|
||||
Ok(IpAddr::V4(Ipv4Addr::from(rdata)))
|
||||
map.insert(IpAddr::V4(Ipv4Addr::from(rdata)), 3600);*/
|
||||
message.parse_response()
|
||||
}
|
||||
rcode => {
|
||||
Err(rcode)
|
||||
|
|
@ -229,9 +414,23 @@ fn xmain() -> ! {
|
|||
// if you wanted to force a server into the initial config, you can do it here, for example:
|
||||
// resolver.add_server(IpAddr::V4(Ipv4Addr::new(1,1,1,1)));
|
||||
|
||||
// perhaps eventually this should be expanded to include a TTL and periodic sweep is done to clear the cache.
|
||||
// but for now, let's go for something simple.
|
||||
let mut dns_cache = HashMap::<std::string::String, IpAddr>::new();
|
||||
let mut dns_cache = HashMap::<std::string::String, HashMap::<IpAddr, u32>>::new();
|
||||
|
||||
// build a thread that pings the UpdateTtl function once every few minutes to expire the DNS cache
|
||||
thread::spawn({
|
||||
let local_cid = xous::connect(dns_sid).unwrap();
|
||||
move || {
|
||||
const TTL_INTERVAL_SECS: usize = 300; // every 5 minutes update the map
|
||||
let tt = ticktimer_server::Ticktimer::new().unwrap();
|
||||
loop {
|
||||
tt.sleep_ms(TTL_INTERVAL_SECS * 1000).unwrap();
|
||||
xous::send_message(local_cid,
|
||||
xous::Message::new_scalar(Opcode::UpdateTtl.to_usize().unwrap(),
|
||||
TTL_INTERVAL_SECS, 0, 0, 0)
|
||||
).expect("couldn't increment DNS cache");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
log::trace!("ready to accept requests");
|
||||
loop {
|
||||
|
|
@ -241,23 +440,50 @@ fn xmain() -> ! {
|
|||
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
||||
let name = buf.to_original::<String::<DNS_NAME_LENGTH_LIMIT>, _>().unwrap();
|
||||
let name_std = std::string::String::from(name.as_str().unwrap());
|
||||
if let Some(ip_addr) = dns_cache.get(&name_std) {
|
||||
log::info!("DNS cached: {}->{:?}", name, ip_addr);
|
||||
let response = DnsResponse {
|
||||
addr: Some(NetIpAddr::from(*ip_addr)),
|
||||
code: DnsResponseCode::NoError,
|
||||
};
|
||||
buf.replace(response).unwrap();
|
||||
} else {
|
||||
match resolver.resolve(name.as_str().unwrap()) {
|
||||
Ok(ip_addr) => {
|
||||
log::info!("DNS queried: {}->{:?}", name, ip_addr);
|
||||
dns_cache.insert(name_std, ip_addr);
|
||||
if let Some(cache_entry) = dns_cache.get(&name_std) {
|
||||
// pick a random entry
|
||||
let rand = resolver.trng_u32() as usize % cache_entry.len();
|
||||
for (index, (ip_addr, _)) in cache_entry.iter().enumerate() {
|
||||
if rand == index {
|
||||
log::info!("DNS cached: {}->{:?}", name, ip_addr);
|
||||
let response = DnsResponse {
|
||||
addr: Some(NetIpAddr::from(ip_addr)),
|
||||
addr: Some(NetIpAddr::from(*ip_addr)),
|
||||
code: DnsResponseCode::NoError,
|
||||
};
|
||||
buf.replace(response).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match resolver.resolve(name.as_str().unwrap()) {
|
||||
Ok(cache_entry) => {
|
||||
if cache_entry.len() > 0 {
|
||||
dns_cache.insert(name_std, cache_entry);
|
||||
|
||||
// now pick the entry back out again, as it was consumed...
|
||||
let name_std = std::string::String::from(name.as_str().unwrap());
|
||||
let cache_entry = dns_cache.get(&name_std).unwrap();
|
||||
|
||||
// pick a random entry from the query response
|
||||
let rand = resolver.trng_u32() as usize % cache_entry.len();
|
||||
for (index, (ip_addr, _)) in cache_entry.iter().enumerate() {
|
||||
if rand == index {
|
||||
let response = DnsResponse {
|
||||
addr: Some(NetIpAddr::from(*ip_addr)),
|
||||
code: DnsResponseCode::NoError,
|
||||
};
|
||||
buf.replace(response).unwrap();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no names found
|
||||
let response = DnsResponse {
|
||||
addr: None,
|
||||
code: DnsResponseCode::NameError,
|
||||
};
|
||||
buf.replace(response).unwrap();
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
log::info!("DNS query failed: {}->{:?}", name, e);
|
||||
|
|
@ -270,6 +496,44 @@ fn xmain() -> ! {
|
|||
}
|
||||
}
|
||||
},
|
||||
Some(Opcode::UpdateTtl) => msg_scalar_unpack!(msg, incr_secs, _, _, _, {
|
||||
let increment = if incr_secs < u32::MAX as usize {
|
||||
incr_secs as u32
|
||||
} else {
|
||||
u32::MAX
|
||||
};
|
||||
if !resolver.get_freeze() {
|
||||
let mut expired_names = Vec::<std::string::String>::new();
|
||||
for (name, cache_map) in dns_cache.iter_mut() {
|
||||
// each entry can have multiple names with a different TTL
|
||||
// decrement the TTL, and note which go to zero
|
||||
let mut expired_entries = Vec::<IpAddr>::new();
|
||||
for (entry, ttl) in cache_map.iter_mut() {
|
||||
if *ttl < increment {
|
||||
*ttl = 0;
|
||||
expired_entries.push(*entry);
|
||||
} else {
|
||||
*ttl = *ttl - increment as u32;
|
||||
}
|
||||
}
|
||||
// remove the entries that are 0
|
||||
for entry in expired_entries {
|
||||
log::info!("DNS cache expiring {:?}", entry);
|
||||
cache_map.remove(&entry);
|
||||
}
|
||||
// if all the entries are removed, mark for removal from the cache entirely
|
||||
if cache_map.len() == 0 {
|
||||
// have to copy the name to a new object to track it
|
||||
let name = std::string::String::from(name.as_str());
|
||||
expired_names.push(name);
|
||||
}
|
||||
}
|
||||
for name in expired_names {
|
||||
log::info!("DNS cache removing {}", &name);
|
||||
dns_cache.remove(&name);
|
||||
}
|
||||
}
|
||||
}),
|
||||
Some(Opcode::Flush) => {
|
||||
dns_cache.clear();
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue