mirror of
https://github.com/ivre/masscanned.git
synced 2025-10-02 06:38:21 +00:00
Add DNS implementation - Query and RR for IN/A
This commit is contained in:
parent
379f48ed80
commit
0dd0e1d645
5 changed files with 1749 additions and 0 deletions
93
src/proto/dns/cst.rs
Normal file
93
src/proto/dns/cst.rs
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy, EnumIter)]
|
||||
pub enum DNSType {
|
||||
NONE,
|
||||
A,
|
||||
TXT, // value: 16 - text strings
|
||||
}
|
||||
|
||||
impl From<u16> for DNSType {
|
||||
fn from(item: u16) -> Self {
|
||||
match item {
|
||||
1 => DNSType::A,
|
||||
16 => DNSType::TXT,
|
||||
_ => DNSType::NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DNSType> 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<u16> for DNSClass {
|
||||
fn from(item: u16) -> Self {
|
||||
match item {
|
||||
1 => DNSClass::IN,
|
||||
3 => DNSClass::CH,
|
||||
_ => DNSClass::NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DNSClass> 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());
|
||||
}
|
||||
}
|
383
src/proto/dns/header.rs
Normal file
383
src/proto/dns/header.rs
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DNSHeaderState>,
|
||||
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<Vec<u8>> for DNSHeader {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
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<u8> {
|
||||
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<Vec<u8>> {
|
||||
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::<u8>::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::<u8>::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::<u8>::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);
|
||||
}
|
||||
}
|
||||
}
|
687
src/proto/dns/mod.rs
Normal file
687
src/proto/dns/mod.rs
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DNSState>,
|
||||
header: DNSHeader,
|
||||
qd: Vec<DNSQuery>,
|
||||
rr: Vec<DNSRR>,
|
||||
ns: Vec<DNSRR>,
|
||||
ar: Vec<DNSRR>,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for DNSPacket {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
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<u8> {
|
||||
fn from(item: &DNSPacket) -> Self {
|
||||
let mut v = Vec::new();
|
||||
v.extend(Vec::<u8>::from(&item.header));
|
||||
for qd in &item.qd {
|
||||
v.extend(Vec::<u8>::from(qd));
|
||||
}
|
||||
for rr in &item.rr {
|
||||
v.extend(Vec::<u8>::from(rr));
|
||||
}
|
||||
for ns in &item.ns {
|
||||
v.extend(Vec::<u8>::from(ns));
|
||||
}
|
||||
for ar in &item.ar {
|
||||
v.extend(Vec::<u8>::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<Vec<u8>> {
|
||||
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::<u8>::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::<u8>::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());
|
||||
}
|
||||
}
|
||||
}
|
335
src/proto/dns/query.rs
Normal file
335
src/proto/dns/query.rs
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DNSQueryState>,
|
||||
/* RFC 1035 - Section 4.1.2 */
|
||||
pub name: Vec<u8>,
|
||||
_u_type: u16,
|
||||
pub type_: DNSType,
|
||||
_u_class: u16,
|
||||
pub class: DNSClass,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for DNSQuery {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
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<u8> {
|
||||
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<Vec<u8>> {
|
||||
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::<u8>::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::<u8>::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::<u8>::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::<u8>::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::<u8>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
251
src/proto/dns/rr.rs
Normal file
251
src/proto/dns/rr.rs
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<DNSRRState>,
|
||||
/* RFC 1035 - Section 3.2.1 */
|
||||
pub name: Vec<u8>,
|
||||
_u_type: u16,
|
||||
pub type_: DNSType,
|
||||
_u_class: u16,
|
||||
pub class: DNSClass,
|
||||
pub ttl: u32,
|
||||
pub rdlen: u16,
|
||||
pub rdata: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<&DNSRR> for Vec<u8> {
|
||||
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<Vec<u8>> for DNSRR {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(item: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
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<Vec<u8>> {
|
||||
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::<u8>::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::<u8>::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::<u8>::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::<u8>::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::<u8>::from(&rr) == payload.to_vec());
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue