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());
+ }
+}