diff --git a/src/proto/dns/cst.rs b/src/proto/dns/cst.rs new file mode 100644 index 0000000..ff306c4 --- /dev/null +++ b/src/proto/dns/cst.rs @@ -0,0 +1,93 @@ +// This file is part of masscanned. +// Copyright 2022 - The IVRE project +// +// Masscanned is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Masscanned is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Masscanned. If not, see . + +use strum_macros::EnumIter; + +#[derive(PartialEq, Debug, Clone, Copy, EnumIter)] +pub enum DNSType { + NONE, + A, + TXT, // value: 16 - text strings +} + +impl From for DNSType { + fn from(item: u16) -> Self { + match item { + 1 => DNSType::A, + 16 => DNSType::TXT, + _ => DNSType::NONE, + } + } +} + +impl From for u16 { + fn from(item: DNSType) -> Self { + match item { + DNSType::A => 1, + DNSType::TXT => 16, + _ => 0, + } + } +} + +#[derive(PartialEq, Debug, Clone, Copy, EnumIter)] +pub enum DNSClass { + NONE, + IN, // value: 1 - the Internet + CH, // value: 3 - the CHAOS class +} + +impl From for DNSClass { + fn from(item: u16) -> Self { + match item { + 1 => DNSClass::IN, + 3 => DNSClass::CH, + _ => DNSClass::NONE, + } + } +} + +impl From for u16 { + fn from(item: DNSClass) -> Self { + match item { + DNSClass::IN => 1, + DNSClass::CH => 3, + _ => 0, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn type_parse() { + /* type TXT */ + assert!(DNSType::from(1) == DNSType::A); + assert!(1 as u16 == DNSType::A.into()); + assert!(DNSType::from(16) == DNSType::TXT); + assert!(16 as u16 == DNSType::TXT.into()); + } + + #[test] + fn class_parse() { + assert!(DNSClass::from(1) == DNSClass::IN); + assert!(1 as u16 == DNSClass::IN.into()); + assert!(DNSClass::from(3) == DNSClass::CH); + assert!(3 as u16 == DNSClass::CH.into()); + } +} diff --git a/src/proto/dns/header.rs b/src/proto/dns/header.rs new file mode 100644 index 0000000..5a53318 --- /dev/null +++ b/src/proto/dns/header.rs @@ -0,0 +1,383 @@ +// This file is part of masscanned. +// Copyright 2022 - The IVRE project +// +// Masscanned is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Masscanned is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Masscanned. If not, see . + +use std::convert::TryFrom; + +use crate::proto::dissector::{MPacket, PacketDissector}; +use crate::proto::ClientInfo; +use crate::proto::TCPControlBlock; +use crate::Masscanned; + +#[derive(PartialEq)] +pub enum DNSHeaderState { + Id, + Flags, + QDCount, + ANCount, + NSCount, + ARCount, + End, +} + +pub struct DNSHeader { + pub d: PacketDissector, + pub id: u16, + pub flags: u16, + pub _qr: bool, + pub _opcode: u8, + pub _aa: bool, + pub _tc: bool, + pub _rd: bool, + pub _ra: bool, + pub _z: u8, + pub _rcode: u8, + pub qdcount: u16, + pub ancount: u16, + pub nscount: u16, + pub arcount: u16, +} + +impl TryFrom> for DNSHeader { + type Error = &'static str; + + fn try_from(item: Vec) -> Result { + let mut hdr = DNSHeader::new(); + for b in item { + hdr.parse(&b); + } + if hdr.d.state == DNSHeaderState::End { + Ok(hdr) + } else { + Err("packet is incomplete") + } + } +} + +impl From<&DNSHeader> for Vec { + fn from(item: &DNSHeader) -> Self { + let mut v = Vec::new(); + /* id */ + v.push((item.id >> 8) as u8); + v.push((item.id & 0xFF) as u8); + + /* flags */ + /* QR | OPCODE | AA | TC | RD */ + v.push( + ((item._qr as u8) << 7) + | (item._opcode << 3) + | ((item._aa as u8) << 2) + | ((item._tc as u8) << 1) + | (item._rd as u8), + ); + /* AA | ZZZ | RCODE */ + v.push(0); + + /* qdcount */ + v.push((item.qdcount >> 8) as u8); + v.push((item.qdcount & 0xFF) as u8); + + /* ancount */ + v.push((item.ancount >> 8) as u8); + v.push((item.ancount & 0xFF) as u8); + + /* nscount */ + v.push((item.nscount >> 8) as u8); + v.push((item.nscount & 0xFF) as u8); + + /* arcount */ + v.push((item.arcount >> 8) as u8); + v.push((item.arcount & 0xFF) as u8); + + v + } +} + +impl MPacket for DNSHeader { + fn new() -> Self { + DNSHeader { + d: PacketDissector::new(DNSHeaderState::Id), + id: 0, + flags: 0, + _qr: false, + _opcode: 0, + _aa: false, + _tc: false, + _rd: false, + _ra: false, + _z: 0, + _rcode: 0, + qdcount: 0, + ancount: 0, + nscount: 0, + arcount: 0, + } + } + + fn parse(&mut self, byte: &u8) { + match self.d.state { + DNSHeaderState::Id => { + self.id = self.d.read_u16(byte, self.id, DNSHeaderState::Flags); + } + DNSHeaderState::Flags => { + self.flags = self.d.read_u16(byte, self.flags, DNSHeaderState::QDCount); + } + DNSHeaderState::QDCount => { + self.qdcount = self.d.read_u16(byte, self.qdcount, DNSHeaderState::ANCount); + } + DNSHeaderState::ANCount => { + self.ancount = self.d.read_u16(byte, self.ancount, DNSHeaderState::NSCount); + } + DNSHeaderState::NSCount => { + self.nscount = self.d.read_u16(byte, self.nscount, DNSHeaderState::ARCount); + } + DNSHeaderState::ARCount => { + self.arcount = self.d.read_u16(byte, self.arcount, DNSHeaderState::End); + } + DNSHeaderState::End => {} + } + /* we need this to be executed at the same call + * the state changes to End, hence it is not in the + * match structure + **/ + if self.d.state == DNSHeaderState::End { + self._qr = (self.flags >> 15) == 1; + self._opcode = ((self.flags >> 11) & 0x0F) as u8; + self._aa = (self.flags >> 10) & 0x01 == 1; + self._tc = (self.flags >> 9) & 0x01 == 1; + self._rd = (self.flags >> 8) & 0x01 == 1; + self._ra = (self.flags >> 7) & 0x01 == 1; + self._z = ((self.flags >> 4) & 0x07) as u8; + self._rcode = (self.flags & 0x0F) as u8; + } + } + + fn repl( + &self, + _masscanned: &Masscanned, + _client_info: &ClientInfo, + _tcb: Option<&mut TCPControlBlock>, + ) -> Option> { + let mut r = DNSHeader::new(); + r.id = self.id; + r._qr = true; + r._opcode = self._opcode; + r._aa = true; + r._tc = false; + /* RFC1035 + * Recursion Desired - this bit may be set in a query and + * is copied into the response. */ + r._rd = self._rd; + r._ra = false; + r.qdcount = self.qdcount; + r.ancount = self.qdcount; + Some(Vec::::from(&r)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use pnet::util::MacAddr; + use std::str::FromStr; + + use crate::logger::MetaLogger; + + #[test] + fn parse_all() { + let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00"; + let hdr = match DNSHeader::try_from(payload.to_vec()) { + Ok(_hdr) => _hdr, + Err(e) => panic!("error while parsing DNS header: {}", e), + }; + assert!(hdr.d.state == DNSHeaderState::End); + assert!(hdr.id == 0xb307); + assert!(hdr.flags == 0x0100); + assert!(hdr._qr == false); + assert!(hdr._opcode == 0); + assert!(hdr._aa == false); + assert!(hdr._tc == false); + assert!(hdr._rd == true); + assert!(hdr._ra == false); + assert!(hdr._z == 0); + assert!(hdr._rcode == 0); + assert!(hdr.qdcount == 1); + assert!(hdr.ancount == 0); + assert!(hdr.nscount == 0); + assert!(hdr.arcount == 0); + assert!(Vec::::from(&hdr) == payload.to_vec()); + /* KO */ + let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00"; + match DNSHeader::try_from(payload.to_vec()) { + Ok(_) => panic!("parsing should have failed"), + Err(_) => {} + }; + } + + #[test] + fn parse_byte_by_byte() { + /* OK */ + let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00"; + let mut hdr = DNSHeader::new(); + for b in payload { + assert!(hdr.d.state != DNSHeaderState::End); + hdr.parse(b); + } + assert!(hdr.d.state == DNSHeaderState::End); + assert!(hdr.id == 0xb307); + assert!(hdr.flags == 0x0100); + assert!(hdr._qr == false); + assert!(hdr._opcode == 0); + assert!(hdr._aa == false); + assert!(hdr._tc == false); + assert!(hdr._rd == true); + assert!(hdr._ra == false); + assert!(hdr._z == 0); + assert!(hdr._rcode == 0); + assert!(hdr.qdcount == 1); + assert!(hdr.ancount == 0); + assert!(hdr.nscount == 0); + assert!(hdr.arcount == 0); + assert!(Vec::::from(&hdr) == payload.to_vec()); + /* KO */ + let payload = b"\xb3\x07\x01\x00\x00\x01\x00\x00\x00\x00\x00"; + let mut hdr = DNSHeader::new(); + for b in payload { + hdr.parse(b); + } + assert!(hdr.d.state != DNSHeaderState::End); + } + + fn consistency_qd_rr(qd: &DNSHeader, rr: &DNSHeader) { + assert!(rr.id == qd.id); + assert!(rr._qr == true); + assert!(rr._opcode == qd._opcode); + assert!(rr._aa == true); + assert!(rr._tc == false); + assert!(rr._rd == qd._rd); + assert!(rr._ra == false); + assert!(rr._z == 0); + assert!(rr._rcode == 0); + /* check flags */ + assert!(rr.flags >> 15 == rr._qr as u16); + assert!((rr.flags >> 11) & 0xF == rr._opcode as u16); + assert!((rr.flags >> 10) & 0x1 == rr._aa as u16); + assert!((rr.flags >> 9) & 0x1 == rr._tc as u16); + assert!((rr.flags >> 8) & 0x1 == rr._rd as u16); + assert!((rr.flags >> 7) & 0x1 == rr._ra as u16); + assert!((rr.flags >> 4) & 0x7 == rr._z as u16); + assert!(rr.flags & 0xF == rr._rcode as u16); + assert!(rr.qdcount == qd.qdcount); + assert!(rr.ancount == qd.qdcount); + assert!(rr.nscount == 0); + assert!(rr.arcount == 0); + } + + #[test] + fn repl_id() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let client_info = ClientInfo::new(); + let mut hdr = DNSHeader::new(); + hdr._qr = false; + for id in [0x1234, 0x4321, 0xffff, 0x0, 0x1337] { + hdr.id = id; + let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) { + DNSHeader::try_from(r).unwrap() + } else { + panic!("expected DNS header answer, got None"); + }; + consistency_qd_rr(&hdr, &hdr_repl); + } + } + + #[test] + fn repl_opcode() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let client_info = ClientInfo::new(); + let mut hdr = DNSHeader::new(); + hdr._qr = false; + /* opcode */ + for opcode in 0..3 { + hdr._opcode = opcode; + let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) { + DNSHeader::try_from(r).unwrap() + } else { + panic!("expected DNS header answer, got None"); + }; + consistency_qd_rr(&hdr, &hdr_repl); + } + } + + #[test] + fn repl_rd() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let client_info = ClientInfo::new(); + let mut hdr = DNSHeader::new(); + hdr._qr = false; + /* rd */ + for rd in [false, true] { + hdr._rd = rd; + let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) { + DNSHeader::try_from(r).unwrap() + } else { + panic!("expected DNS header answer, got None"); + }; + consistency_qd_rr(&hdr, &hdr_repl); + } + } + + #[test] + fn repl_ancount() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let client_info = ClientInfo::new(); + let mut hdr = DNSHeader::new(); + hdr._qr = false; + /* rd */ + for qdcount in 0..16 { + hdr.qdcount = qdcount; + let hdr_repl = if let Some(r) = hdr.repl(&masscanned, &client_info, None) { + DNSHeader::try_from(r).unwrap() + } else { + panic!("expected DNS header answer, got None"); + }; + consistency_qd_rr(&hdr, &hdr_repl); + } + } +} diff --git a/src/proto/dns/mod.rs b/src/proto/dns/mod.rs new file mode 100644 index 0000000..0c97cd5 --- /dev/null +++ b/src/proto/dns/mod.rs @@ -0,0 +1,687 @@ +// This file is part of masscanned. +// Copyright 2022 - The IVRE project +// +// Masscanned is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Masscanned is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Masscanned. If not, see . + +use std::convert::TryFrom; + +mod cst; + +mod header; +use header::{DNSHeader, DNSHeaderState}; + +mod query; +use query::{DNSQuery, DNSQueryState}; + +mod rr; +use rr::{DNSRRState, DNSRR}; + +use crate::proto::dissector::{MPacket, PacketDissector}; +use crate::proto::ClientInfo; +use crate::proto::TCPControlBlock; +use crate::Masscanned; + +#[derive(PartialEq, Debug)] +enum DNSState { + Header, + Query, + Answer, + Authority, + Additional, + End, +} + +pub struct DNSPacket { + d: PacketDissector, + header: DNSHeader, + qd: Vec, + rr: Vec, + ns: Vec, + ar: Vec, +} + +impl TryFrom> for DNSPacket { + type Error = &'static str; + + fn try_from(item: Vec) -> Result { + let mut dns = DNSPacket::new(); + for b in item { + dns.parse(&b); + } + if dns.d.state == DNSState::End { + Ok(dns) + } else { + Err("packet is incomplete") + } + } +} + +impl From<&DNSPacket> for Vec { + fn from(item: &DNSPacket) -> Self { + let mut v = Vec::new(); + v.extend(Vec::::from(&item.header)); + for qd in &item.qd { + v.extend(Vec::::from(qd)); + } + for rr in &item.rr { + v.extend(Vec::::from(rr)); + } + for ns in &item.ns { + v.extend(Vec::::from(ns)); + } + for ar in &item.ar { + v.extend(Vec::::from(ar)); + } + v + } +} + +impl MPacket for DNSPacket { + fn new() -> Self { + DNSPacket { + d: PacketDissector::new(DNSState::Header), + header: DNSHeader::new(), + qd: Vec::new(), + rr: Vec::new(), + ns: Vec::new(), + ar: Vec::new(), + } + } + + fn parse(&mut self, byte: &u8) { + match self.d.state { + DNSState::Header => { + self.header.parse(byte); + if self.header.d.state == DNSHeaderState::End { + if self.header.qdcount > 0 { + self.qd.push(DNSQuery::new()); + self.d.next_state(DNSState::Query); + } else if self.header.ancount > 0 { + self.rr.push(DNSRR::new()); + self.d.next_state(DNSState::Answer); + } else if self.header.nscount > 0 { + self.d.next_state(DNSState::Authority); + } else if self.header.arcount > 0 { + self.d.next_state(DNSState::Additional); + } else { + self.d.next_state(DNSState::End); + } + } + } + DNSState::Query => { + let qdcount = self.qd.len(); + self.qd[qdcount - 1].parse(byte); + if self.qd[qdcount - 1].d.state == DNSQueryState::End { + if self.header.qdcount as usize > self.qd.len() { + self.qd.push(DNSQuery::new()); + } else if self.header.ancount > 0 { + self.rr.push(DNSRR::new()); + self.d.next_state(DNSState::Answer); + } else if self.header.nscount > 0 { + self.d.next_state(DNSState::Authority); + } else if self.header.arcount > 0 { + self.d.next_state(DNSState::Additional); + } else { + self.d.next_state(DNSState::End); + } + } + } + DNSState::Answer => { + let ancount = self.rr.len(); + self.rr[ancount - 1].parse(byte); + if self.rr[ancount - 1].d.state == DNSRRState::End { + if self.header.ancount as usize > self.rr.len() { + self.rr.push(DNSRR::new()); + } else if self.header.nscount > 0 { + self.d.next_state(DNSState::Authority); + } else if self.header.arcount > 0 { + self.d.next_state(DNSState::Additional); + } else { + self.d.next_state(DNSState::End); + } + } + } + _ => {} + } + } + + fn repl( + &self, + masscanned: &Masscanned, + client_info: &ClientInfo, + _tcb: Option<&mut TCPControlBlock>, + ) -> Option> { + let mut ans = DNSPacket::new(); + ans.header = if let Some(hdr) = self.header.repl(&masscanned, &client_info, None) { + if let Ok(h) = DNSHeader::try_from(hdr) { + h + } else { + return None; + } + } else { + return None; + }; + /* reply to qd */ + for qd in &self.qd { + if let Ok(q) = DNSQuery::try_from(Vec::::from(qd)) { + ans.qd.push(q); + } else { + return None; + } + if let Some(raw_rr) = qd.repl(&masscanned, &client_info, None) { + if let Ok(rr) = DNSRR::try_from(raw_rr) { + ans.rr.push(rr); + } else { + return None; + } + } else { + return None; + } + } + Some(Vec::::from(&ans)) + } +} + +#[cfg(test)] +mod tests { + use super::cst::{DNSClass, DNSType}; + use super::*; + + use pnet::util::MacAddr; + use std::net::{IpAddr, Ipv4Addr}; + use std::str::FromStr; + + use crate::logger::MetaLogger; + + #[test] + fn parse_qd_all() { + /* OK */ + /* scapy: DNS(id=0x1337, + * qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com")) + **/ + let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01"; + let dns = match DNSPacket::try_from(payload.to_vec()) { + Ok(_dns) => _dns, + Err(e) => panic!("error while parsing DNS packet: {}", e), + }; + assert!(dns.header.id == 0x1337); + assert!(dns.header._qr == false); + assert!(dns.header._opcode == 0); + assert!(dns.header._aa == false); + assert!(dns.header._tc == false); + assert!(dns.header._rd == true); + assert!(dns.header._ra == false); + assert!(dns.header._z == 0); + assert!(dns.header._rcode == 0); + assert!(dns.header.qdcount == 3); + assert!(dns.header.ancount == 0); + assert!(dns.header.nscount == 0); + assert!(dns.header.arcount == 0); + assert!(dns.qd.len() == 3); + assert!( + dns.qd[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[0].type_ == DNSType::A); + assert!(dns.qd[0].class == DNSClass::IN); + assert!( + dns.qd[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[1].type_ == DNSType::A); + assert!(dns.qd[1].class == DNSClass::IN); + assert!( + dns.qd[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[2].type_ == DNSType::A); + assert!(dns.qd[2].class == DNSClass::IN); + /* KO */ + let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00"; + match DNSPacket::try_from(payload.to_vec()) { + Ok(_) => panic!("parsing should have failed"), + Err(_) => {} + } + let payload = b"xxx"; + match DNSPacket::try_from(payload.to_vec()) { + Ok(_) => panic!("parsing should have failed"), + Err(_) => {} + } + } + + #[test] + fn parse_qd_byte_by_byte() { + /* scapy: DNS(id=0x1337, + * qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com")) + **/ + let payload = b"\x137\x01\x00\x00\x03\x00\x00\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01"; + let mut dns = DNSPacket::new(); + for b in payload { + assert!(dns.d.state != DNSState::End); + dns.parse(&b); + } + assert!(dns.d.state == DNSState::End); + assert!(dns.header.id == 0x1337); + assert!(dns.header._qr == false); + assert!(dns.header._opcode == 0); + assert!(dns.header._aa == false); + assert!(dns.header._tc == false); + assert!(dns.header._rd == true); + assert!(dns.header._ra == false); + assert!(dns.header._z == 0); + assert!(dns.header._rcode == 0); + assert!(dns.header.qdcount == 3); + assert!(dns.header.ancount == 0); + assert!(dns.header.nscount == 0); + assert!(dns.header.arcount == 0); + assert!(dns.qd.len() == 3); + assert!( + dns.qd[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[0].type_ == DNSType::A); + assert!(dns.qd[0].class == DNSClass::IN); + assert!( + dns.qd[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[1].type_ == DNSType::A); + assert!(dns.qd[1].class == DNSClass::IN); + assert!( + dns.qd[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[2].type_ == DNSType::A); + assert!(dns.qd[2].class == DNSClass::IN); + } + + #[test] + fn parse_rr_all() { + /* OK */ + /* scapy: DNS(id=1234, qr=True, aa=True, qd=None, + * an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3")) + **/ + let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03"; + let dns = match DNSPacket::try_from(payload.to_vec()) { + Ok(_dns) => _dns, + Err(e) => panic!("error while parsing DNS packet: {}", e), + }; + assert!(dns.header.id == 1234); + assert!(dns.header._qr == true); + assert!(dns.header._opcode == 0); + assert!(dns.header._aa == true); + assert!(dns.header._tc == false); + assert!(dns.header._rd == true); + assert!(dns.header._ra == false); + assert!(dns.header._z == 0); + assert!(dns.header._rcode == 0); + assert!(dns.header.qdcount == 0); + assert!(dns.header.ancount == 3); + assert!(dns.header.nscount == 0); + assert!(dns.header.arcount == 0); + assert!(dns.rr.len() == 3); + assert!( + dns.rr[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[0].type_ == DNSType::A); + assert!(dns.rr[0].class == DNSClass::IN); + assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]); + assert!( + dns.rr[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[1].type_ == DNSType::A); + assert!(dns.rr[1].class == DNSClass::IN); + assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]); + assert!( + dns.rr[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[2].type_ == DNSType::A); + assert!(dns.rr[2].class == DNSClass::IN); + assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]); + /* KO */ + let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x04\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03"; + match DNSPacket::try_from(payload.to_vec()) { + Ok(_) => panic!("parsing should have failed"), + Err(_) => {} + } + let payload = b"xxx"; + match DNSPacket::try_from(payload.to_vec()) { + Ok(_) => panic!("parsing should have failed"), + Err(_) => {} + } + } + + #[test] + fn parse_rr_byte_by_byte() { + /* scapy: DNS(id=1234, qr=True, aa=True, qd=None, + * an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3")) + **/ + let payload = b"\x04\xd2\x85\x00\x00\x00\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03"; + let mut dns = DNSPacket::new(); + for b in payload { + assert!(dns.d.state != DNSState::End); + dns.parse(&b); + } + assert!(dns.d.state == DNSState::End); + assert!(dns.header.id == 1234); + assert!(dns.header._qr == true); + assert!(dns.header._opcode == 0); + assert!(dns.header._aa == true); + assert!(dns.header._tc == false); + assert!(dns.header._rd == true); + assert!(dns.header._ra == false); + assert!(dns.header._z == 0); + assert!(dns.header._rcode == 0); + assert!(dns.header.qdcount == 0); + assert!(dns.header.ancount == 3); + assert!(dns.header.nscount == 0); + assert!(dns.header.arcount == 0); + assert!(dns.rr.len() == 3); + assert!( + dns.rr[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[0].type_ == DNSType::A); + assert!(dns.rr[0].class == DNSClass::IN); + assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]); + assert!( + dns.rr[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[1].type_ == DNSType::A); + assert!(dns.rr[1].class == DNSClass::IN); + assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]); + assert!( + dns.rr[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[2].type_ == DNSType::A); + assert!(dns.rr[2].class == DNSClass::IN); + assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]); + } + + #[test] + fn parse_qd_rr_all() { + /* scapy: DNS(id=1234, qr=True, aa=True, + * qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"), + * an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3")) + */ + let payload = b"\x04\xd2\x85\x00\x00\x03\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03"; + let dns = match DNSPacket::try_from(payload.to_vec()) { + Ok(_dns) => _dns, + Err(e) => panic!("error while parsing DNS packet: {}", e), + }; + assert!(dns.header.id == 1234); + assert!(dns.header._qr == true); + assert!(dns.header._opcode == 0); + assert!(dns.header._aa == true); + assert!(dns.header._tc == false); + assert!(dns.header._rd == true); + assert!(dns.header._ra == false); + assert!(dns.header._z == 0); + assert!(dns.header._rcode == 0); + assert!(dns.header.qdcount == 3); + assert!(dns.header.ancount == 3); + assert!(dns.header.nscount == 0); + assert!(dns.header.arcount == 0); + assert!(dns.qd.len() == 3); + assert!( + dns.qd[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[0].type_ == DNSType::A); + assert!(dns.qd[0].class == DNSClass::IN); + assert!( + dns.qd[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[1].type_ == DNSType::A); + assert!(dns.qd[1].class == DNSClass::IN); + assert!( + dns.qd[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[2].type_ == DNSType::A); + assert!(dns.qd[2].class == DNSClass::IN); + assert!(dns.rr.len() == 3); + assert!( + dns.rr[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[0].type_ == DNSType::A); + assert!(dns.rr[0].class == DNSClass::IN); + assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]); + assert!( + dns.rr[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[1].type_ == DNSType::A); + assert!(dns.rr[1].class == DNSClass::IN); + assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]); + assert!( + dns.rr[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[2].type_ == DNSType::A); + assert!(dns.rr[2].class == DNSClass::IN); + assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]); + } + + #[test] + fn parse_qr_rr_byte_by_byte() { + /* scapy: DNS(id=1234, qr=True, aa=True, + * qd=DNSQR(qname="www.example1.com")/DNSQR(qname="www.example2.com")/DNSQR(qname="www.example3.com"), + * an=DNSRR(rrname="www.example1.com", rdata="127.0.0.1")/DNSRR(rrname="www.example2.com", rdata="127.0.0.2")/DNSRR(rrname="www.example3.com", rdata="127.0.0.3")) + */ + let payload = b"\x04\xd2\x85\x00\x00\x03\x00\x03\x00\x00\x00\x00\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x03www\x08example1\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01\x03www\x08example2\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x02\x03www\x08example3\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x03"; + let mut dns = DNSPacket::new(); + for b in payload { + assert!(dns.d.state != DNSState::End); + dns.parse(&b); + } + assert!(dns.d.state == DNSState::End); + assert!(dns.header.id == 1234); + assert!(dns.header._qr == true); + assert!(dns.header._opcode == 0); + assert!(dns.header._aa == true); + assert!(dns.header._tc == false); + assert!(dns.header._rd == true); + assert!(dns.header._ra == false); + assert!(dns.header._z == 0); + assert!(dns.header._rcode == 0); + assert!(dns.header.qdcount == 3); + assert!(dns.header.ancount == 3); + assert!(dns.header.nscount == 0); + assert!(dns.header.arcount == 0); + assert!(dns.qd.len() == 3); + assert!( + dns.qd[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[0].type_ == DNSType::A); + assert!(dns.qd[0].class == DNSClass::IN); + assert!( + dns.qd[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[1].type_ == DNSType::A); + assert!(dns.qd[1].class == DNSClass::IN); + assert!( + dns.qd[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.qd[2].type_ == DNSType::A); + assert!(dns.qd[2].class == DNSClass::IN); + assert!(dns.rr.len() == 3); + assert!( + dns.rr[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x31, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[0].type_ == DNSType::A); + assert!(dns.rr[0].class == DNSClass::IN); + assert!(dns.rr[0].rdata == [0x7f, 0x00, 0x00, 0x01]); + assert!( + dns.rr[1].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x32, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[1].type_ == DNSType::A); + assert!(dns.rr[1].class == DNSClass::IN); + assert!(dns.rr[1].rdata == [0x7f, 0x00, 0x00, 0x02]); + assert!( + dns.rr[2].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x08, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x33, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(dns.rr[2].type_ == DNSType::A); + assert!(dns.rr[2].class == DNSClass::IN); + assert!(dns.rr[2].rdata == [0x7f, 0x00, 0x00, 0x03]); + } + + #[test] + fn reply_in_a() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let mut client_info = ClientInfo::new(); + /* scapy: DNS(id=0x1337, + * qd=DNSQR(qname="www.example.com")) + **/ + let payload = b"\x137\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x03www\x07example\x03com\x00\x00\x01\x00\x01"; + let dns = DNSPacket::try_from(payload.to_vec()).unwrap(); + for ip in [ + Ipv4Addr::new(127, 0, 0, 1), + Ipv4Addr::new(0, 0, 0, 0), + Ipv4Addr::new(4, 3, 2, 1), + ] { + client_info.ip.dst = Some(IpAddr::V4(ip)); + let ans = if let Some(a) = dns.repl(&masscanned, &client_info, None) { + DNSPacket::try_from(a).unwrap() + } else { + panic!("expected a reply, got None"); + }; + assert!(ans.header.id == 0x1337); + assert!(ans.header._qr == true); + assert!(ans.header._opcode == 0); + assert!(ans.header._aa == true); + assert!(ans.header._tc == false); + assert!(ans.header._rd == dns.header._rd); + assert!(ans.header._ra == false); + assert!(ans.header._z == 0); + assert!(ans.header._rcode == 0); + assert!(ans.header.qdcount == 1); + assert!(ans.header.ancount == 1); + assert!(ans.header.nscount == 0); + assert!(ans.header.arcount == 0); + assert!(ans.qd.len() == 1); + assert!( + ans.qd[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(ans.qd[0].type_ == DNSType::A); + assert!(ans.qd[0].class == DNSClass::IN); + assert!(ans.rr.len() == 1); + assert!( + ans.rr[0].name + == [ + 0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(ans.rr[0].type_ == DNSType::A); + assert!(ans.rr[0].class == DNSClass::IN); + assert!(ans.rr[0].rdata == ip.octets()); + } + } +} diff --git a/src/proto/dns/query.rs b/src/proto/dns/query.rs new file mode 100644 index 0000000..b5d92cf --- /dev/null +++ b/src/proto/dns/query.rs @@ -0,0 +1,335 @@ +// This file is part of masscanned. +// Copyright 2022 - The IVRE project +// +// Masscanned is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Masscanned is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Masscanned. If not, see . + +use super::cst::{DNSClass, DNSType}; +use super::rr::DNSRR; + +use std::convert::TryFrom; +use std::net::IpAddr; + +use crate::proto::dissector::{MPacket, PacketDissector}; +use crate::proto::ClientInfo; +use crate::proto::TCPControlBlock; +use crate::Masscanned; + +#[derive(PartialEq)] +pub enum DNSQueryState { + Name, + Type, + Class, + End, +} + +pub struct DNSQuery { + pub d: PacketDissector, + /* RFC 1035 - Section 4.1.2 */ + pub name: Vec, + _u_type: u16, + pub type_: DNSType, + _u_class: u16, + pub class: DNSClass, +} + +impl TryFrom> for DNSQuery { + type Error = &'static str; + + fn try_from(item: Vec) -> Result { + let mut query = DNSQuery::new(); + for b in item { + query.parse(&b); + } + if query.d.state == DNSQueryState::End { + Ok(query) + } else { + Err("packet is incomplete") + } + } +} + +impl From<&DNSQuery> for Vec { + fn from(item: &DNSQuery) -> Self { + let mut v = Vec::new(); + /* name */ + v.extend(&item.name); + /* type */ + v.push(((u16::from(item.type_)) >> 8) as u8); + v.push(((u16::from(item.type_)) & 0xFF) as u8); + /* class */ + v.push(((u16::from(item.class)) >> 8) as u8); + v.push(((u16::from(item.class)) & 0xFF) as u8); + /* return */ + v + } +} + +impl MPacket for DNSQuery { + fn new() -> Self { + DNSQuery { + d: PacketDissector::new(DNSQueryState::Name), + name: Vec::new(), + _u_type: 0, + type_: DNSType::NONE, + _u_class: 0, + class: DNSClass::NONE, + } + } + + fn parse(&mut self, byte: &u8) { + match self.d.state { + DNSQueryState::Name => { + self.name.push(*byte); + if *byte == 0 { + self.d.next_state(DNSQueryState::Type); + } + } + DNSQueryState::Type => { + self._u_type = self.d.read_u16(byte, self._u_type, DNSQueryState::Class); + } + DNSQueryState::Class => { + self._u_class = self.d.read_u16(byte, self._u_class, DNSQueryState::End); + } + DNSQueryState::End => {} + } + /* we need this to be executed at the same call + * the state changes to End, hence it is not in the + * match structure + **/ + if self.d.state == DNSQueryState::End { + self.type_ = DNSType::from(self._u_type); + self.class = DNSClass::from(self._u_class); + } + } + + fn repl( + &self, + _masscanned: &Masscanned, + client_info: &ClientInfo, + _tcb: Option<&mut TCPControlBlock>, + ) -> Option> { + match self.class { + DNSClass::IN => { + match self.type_ { + DNSType::A => { + let mut rr = DNSRR::new(); + /* copy request */ + for b in &self.name { + rr.name.push(*b); + } + rr.type_ = DNSType::A; + rr.class = DNSClass::IN; + rr.ttl = 43200; + rr.rdata = match client_info.ip.dst { + Some(IpAddr::V4(ip)) => ip.octets().to_vec(), + Some(IpAddr::V6(_)) => Vec::new(), + None => Vec::new(), + }; + rr.rdlen = rr.rdata.len() as u16; + Some(Vec::::from(&rr)) + } + _ => None, + } + } + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use pnet::util::MacAddr; + use std::net::{IpAddr, Ipv4Addr}; + use std::str::FromStr; + use strum::IntoEnumIterator; + + use crate::client::ClientInfoSrcDst; + use crate::logger::MetaLogger; + + #[test] + fn parse_in_a_all() { + /* A */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01"; + let qr = match DNSQuery::try_from(payload.to_vec()) { + Ok(_qr) => _qr, + Err(e) => panic!("error while parsing DNS query: {}", e), + }; + assert!( + qr.name + == [ + 0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(qr.type_ == DNSType::A); + assert!(qr.class == DNSClass::IN); + assert!(Vec::::from(&qr) == payload.to_vec()); + /* TXT */ + let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03"; + let qr = match DNSQuery::try_from(payload.to_vec()) { + Ok(_qr) => _qr, + Err(e) => panic!("error while parsing DNS query: {}", e), + }; + assert!(qr.type_ == DNSType::TXT); + assert!(qr.class == DNSClass::CH); + assert!(Vec::::from(&qr) == payload.to_vec()); + /* KO */ + let payload = b"xxx"; + match DNSQuery::try_from(payload.to_vec()) { + Ok(_) => panic!("parsing should have failed"), + Err(_) => {} + } + } + + #[test] + fn parse_in_a_byte_by_byte() { + /* A */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01"; + let mut qr = DNSQuery::new(); + for b in payload { + qr.parse(b); + } + assert!(qr.d.state == DNSQueryState::End); + assert!( + qr.name + == [ + 0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(qr.type_ == DNSType::A); + assert!(qr.class == DNSClass::IN); + assert!(Vec::::from(&qr) == payload.to_vec()); + /* TXT */ + let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03"; + let mut qr = DNSQuery::new(); + for b in payload { + qr.parse(b); + } + assert!(qr.d.state == DNSQueryState::End); + assert!(qr.type_ == DNSType::TXT); + assert!(qr.class == DNSClass::CH); + assert!(Vec::::from(&qr) == payload.to_vec()); + /* KO */ + let payload = b"xxx"; + let mut qr = DNSQuery::new(); + for b in payload { + qr.parse(b); + } + assert!(qr.d.state != DNSQueryState::End); + } + + #[test] + fn reply_in_a() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let ip_src = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let ip_dst = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 2)); + let client_info = ClientInfo { + mac: ClientInfoSrcDst { + src: None, + dst: None, + }, + ip: ClientInfoSrcDst { + src: Some(ip_src), + dst: Some(ip_dst), + }, + transport: None, + port: ClientInfoSrcDst { + src: None, + dst: None, + }, + cookie: None, + }; + /* TXT */ + let payload = b"\x07version\x04bind\x00\x00\x10\x00\x03"; + let mut qr = DNSQuery::new(); + for b in payload { + qr.parse(b); + } + assert!(qr.type_ == DNSType::TXT); + assert!(qr.class == DNSClass::CH); + /* A */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01"; + let mut qr = DNSQuery::new(); + for b in payload { + qr.parse(b); + } + assert!( + qr.name + == [ + 0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00 + ] + ); + assert!(qr.type_ == DNSType::A); + assert!(qr.class == DNSClass::IN); + let rr_raw = match qr.repl(&masscanned, &client_info, None) { + None => { + panic!() + } + Some(r) => r, + }; + let mut rr = DNSRR::new(); + for b in rr_raw { + rr.parse(&b); + } + assert!(rr.name == qr.name); + assert!(rr.type_ == DNSType::A); + assert!(rr.class == DNSClass::IN); + assert!(rr.ttl == 43200); + assert!(rr.rdata == [127, 0, 0, 2]); + } + + #[test] + fn repl() { + let masscanned = Masscanned { + synack_key: [0, 0], + mac: MacAddr::from_str("00:00:00:00:00:00").expect("error parsing default MAC address"), + iface: None, + ip_addresses: None, + log: MetaLogger::new(), + }; + let client_info = ClientInfo::new(); + /* exhaustive tests */ + let supported: Vec<(DNSClass, DNSType)> = vec![(DNSClass::IN, DNSType::A)]; + let mut qd = DNSQuery::new(); + qd.name = vec![ + 0x03, 0x77, 0x77, 0x77, 0x07, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x03, 0x63, + 0x6f, 0x6d, 0x00, + ]; + for c in DNSClass::iter() { + qd.class = c; + for t in DNSType::iter() { + qd.type_ = t; + if supported.contains(&(c, t)) { + if qd.repl(&masscanned, &client_info, None) == None { + panic!("expected reply, got None"); + } + } else { + if qd.repl(&masscanned, &client_info, None) != None { + panic!("expected no reply, got one for {:?}, {:?}", c, t); + } + } + } + } + } +} diff --git a/src/proto/dns/rr.rs b/src/proto/dns/rr.rs new file mode 100644 index 0000000..db188bb --- /dev/null +++ b/src/proto/dns/rr.rs @@ -0,0 +1,251 @@ +// This file is part of masscanned. +// Copyright 2022 - The IVRE project +// +// Masscanned is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Masscanned is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public +// License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Masscanned. If not, see . + +use super::cst::{DNSClass, DNSType}; + +use std::convert::TryFrom; + +use crate::proto::dissector::{MPacket, PacketDissector}; +use crate::proto::ClientInfo; +use crate::proto::TCPControlBlock; +use crate::Masscanned; + +#[derive(PartialEq, Debug)] +pub enum DNSRRState { + Name, + Type, + Class, + TTL, + RDLength, + RData, + End, +} + +pub struct DNSRR { + pub d: PacketDissector, + /* RFC 1035 - Section 3.2.1 */ + pub name: Vec, + _u_type: u16, + pub type_: DNSType, + _u_class: u16, + pub class: DNSClass, + pub ttl: u32, + pub rdlen: u16, + pub rdata: Vec, +} + +impl From<&DNSRR> for Vec { + fn from(item: &DNSRR) -> Self { + /* CAUTION: for the rdlen field: + * - if item.rdlen is not 0, its value is packed + * - if item.rdlen = 0, then the length of item.rdata is used instead + */ + let mut v = Vec::new(); + /* name */ + for b in &item.name { + v.push(b.clone()); + } + /* type */ + let type_: u16 = item.type_.into(); + v.push((type_ >> 8) as u8); + v.push((type_ & 0xFF) as u8); + /* class */ + let class: u16 = item.class.into(); + v.push((class >> 8) as u8); + v.push((class & 0xFF) as u8); + /* ttl */ + v.push((item.ttl >> 24) as u8); + v.push((item.ttl >> 16) as u8); + v.push((item.ttl >> 8) as u8); + v.push((item.ttl & 0xFF) as u8); + /* rdlen */ + let rdlen = if item.rdlen == 0 { + item.rdata.len() as u16 + } else { + item.rdlen + }; + v.push((rdlen >> 8) as u8); + v.push((rdlen & 0xFF) as u8); + /* rdata */ + for b in &item.rdata { + v.push(b.clone()); + } + v + } +} + +impl TryFrom> for DNSRR { + type Error = &'static str; + + fn try_from(item: Vec) -> Result { + let mut rr = DNSRR::new(); + for b in item { + rr.parse(&b); + } + if rr.d.state == DNSRRState::End { + Ok(rr) + } else { + Err("packet is incomplete") + } + } +} + +impl MPacket for DNSRR { + fn new() -> Self { + DNSRR { + d: PacketDissector::new(DNSRRState::Name), + name: Vec::new(), + _u_type: 0, + type_: DNSType::NONE, + _u_class: 0, + class: DNSClass::NONE, + rdlen: 0, + ttl: 0, + rdata: Vec::new(), + } + } + + fn parse(&mut self, byte: &u8) { + match self.d.state { + DNSRRState::Name => { + self.name.push(*byte); + if *byte == 0 { + self.d.next_state(DNSRRState::Type); + } + } + DNSRRState::Type => { + self._u_type = self.d.read_u16(byte, self._u_type, DNSRRState::Class); + } + DNSRRState::Class => { + self._u_class = self.d.read_u16(byte, self._u_class, DNSRRState::TTL); + } + DNSRRState::TTL => { + self.ttl = self.d.read_u32(byte, self.ttl, DNSRRState::RDLength); + } + DNSRRState::RDLength => { + self.rdlen = self.d.read_u16(byte, self.rdlen, DNSRRState::RData); + /* when read the rdlen, check if len is 0 */ + if self.d.state == DNSRRState::RData && self.rdlen == 0 { + self.d.state = DNSRRState::End; + } + } + DNSRRState::RData => { + self.rdata.push(*byte); + if self.rdata.len() == self.rdlen as usize { + self.d.next_state(DNSRRState::End); + } + } + DNSRRState::End => {} + } + /* we need this to be executed at the same call + * the state changes to End, hence it is not in the + * match structure + **/ + if self.d.state == DNSRRState::End { + self.type_ = DNSType::from(self._u_type); + self.class = DNSClass::from(self._u_class); + } + } + + fn repl( + &self, + _masscanned: &Masscanned, + _client_info: &ClientInfo, + _tcb: Option<&mut TCPControlBlock>, + ) -> Option> { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build() { + let mut rr = DNSRR::new(); + rr.name = b"\x03www\x07example\x03com\x00".to_vec(); + rr.class = DNSClass::IN; + rr.type_ = DNSType::A; + rr.ttl = 1234; + rr.rdlen = 4; + rr.rdata = b"\x7f\x00\x00\x01".to_vec(); + assert!(Vec::::from(&rr) == b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x04\xd2\x00\x04\x7f\x00\x00\x01"); + } + + #[test] + fn parse_all() { + /* + * raw(DNSRR(rrname="www.example.com", rdata="127.0.0.1")) + */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01"; + let rr = match DNSRR::try_from(payload.to_vec()) { + Ok(r) => r, + Err(e) => panic!("error while parsing DNS RR: {}", e), + }; + assert!(rr.name == b"\x03www\x07example\x03com\x00"); + assert!(rr.class == DNSClass::IN); + assert!(rr.type_ == DNSType::A); + assert!(rr.rdata == b"\x7f\x00\x00\x01"); + assert!(Vec::::from(&rr) == payload.to_vec()); + /* + * empty data + */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00"; + let rr = match DNSRR::try_from(payload.to_vec()) { + Ok(r) => r, + Err(e) => panic!("error while parsing DNS RR: {}", e), + }; + assert!(rr.name == b"\x03www\x07example\x03com\x00"); + assert!(rr.class == DNSClass::IN); + assert!(rr.type_ == DNSType::A); + assert!(rr.rdata == b""); + assert!(Vec::::from(&rr) == payload.to_vec()); + } + + #[test] + fn parse_byte_by_byte() { + /* + * raw(DNSRR(rrname="www.example.com", rdata="127.0.0.1")) + */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x04\x7f\x00\x00\x01"; + let mut rr = DNSRR::new(); + for b in payload { + assert!(rr.d.state != DNSRRState::End); + rr.parse(b); + } + assert!(rr.d.state == DNSRRState::End); + assert!(rr.name == b"\x03www\x07example\x03com\x00"); + assert!(rr.class == DNSClass::IN); + assert!(rr.type_ == DNSType::A); + assert!(rr.rdata == b"\x7f\x00\x00\x01"); + assert!(Vec::::from(&rr) == payload.to_vec()); + /* + * empty data + */ + let payload = b"\x03www\x07example\x03com\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00"; + let mut rr = DNSRR::new(); + for b in payload { + assert!(rr.d.state != DNSRRState::End); + rr.parse(b); + } + assert!(rr.name == b"\x03www\x07example\x03com\x00"); + assert!(rr.class == DNSClass::IN); + assert!(rr.type_ == DNSType::A); + assert!(rr.rdata == b""); + assert!(Vec::::from(&rr) == payload.to_vec()); + } +}