From cbf60f5a056b1155e5286680af2d70587e397cf2 Mon Sep 17 00:00:00 2001 From: Celeste Date: Tue, 12 Apr 2022 21:17:32 +0100 Subject: [PATCH] 0.4.0 --- Cargo.toml | 3 +- src/dns.rs | 528 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + 3 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 src/dns.rs diff --git a/Cargo.toml b/Cargo.toml index 611ddd4..8f7414d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "packeteer" description = "An attempt at a Rust library that can be used to assist in programmatically analysing, serving and handling received protocol packets." -version = "0.3.2" +version = "0.4.0" edition = "2021" authors = ["Celeste "] license = "MPL-2.0" @@ -18,5 +18,6 @@ all-features = true http1 = [] gemini = [] ftp = [] +dns = [] [dependencies] diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 0000000..098a130 --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,528 @@ +//! A module for handling DNS operations. +//! It contains structures for DNS flags, packets, queries and answers as well as functions for unpacking and constructing them and enumerators for DNS record classes and types. + +/// An enumerator for defining types of DNS classes. Unwrapped by dns::fromclass. +#[derive(Debug, Copy, Clone)] +pub enum RecordClass { + /// Internet class. You probably want this one. + IN, + /// CSNET class. Obsolete class that was used by the Computer Science NETwork which extended ARPAnet to computer science departments at institutions who couldn't afford or weren't allowed to directly connect to ARPAnet. + /// + /// Probably shouldn't be used for anything but probably better to use if you wanna do stuff like expose your versioning over DNS (a la BIND except using a defunct class instead of the active CH) + /// + /// [Read more about CSNET here.](https://en.wikipedia.org/wiki/CSNET) + CS, + /// Chaosnet class. Variously misused by many implementations of DNS clients and servers like BIND. + /// + /// Chaosnet's intended purpose was to be a "cheap, efficient and fast" LAN "protocol stack" "without central control" that was 10 times faster than ARPAnet was at the time. It was developed around the same time that Ethernet+TCP was developed (aka the Internet stack.) + /// + /// [Read more about Chaosnet here.](https://www.chaosnet.net) + CH, + /// Hesoid class. Used by an MIT directory service to provide mostly-static database information through DNS records. + /// + /// Uses for this class seems limited outside of MIT's campus systems. If anyone from MIT uses this, please email me about it! + /// + /// [Read more about Hesoid here.](https://en.wikipedia.org/wiki/Hesiod_(name_service)) + HS, +} + +/// A structure for DNS queries to be added to a DNS packet. +/// +/// A function is not included to generate these since the only custom components are the RecordType and RecordClass which are easily made and human-readable. This simplicity to create a DnsQuery makes a function superfluous. +#[derive(Debug)] +pub struct DnsQuery { + /// Domain name. + pub name: String, + /// Record wanted by the client. + pub rtype: RecordType, + /// Class of the DNS record. + pub class: RecordClass, +} + +/// A structure for DNS answers to be added to a DNS packet. +/// +/// Again, a function has not been provided to generate these as it is not hard to make them yourself. Bring your own code. +#[derive(Debug)] +pub struct DnsAnswer { + /// ??? + /// + /// Set to 0xc00c + pub name: u16, + /// Record sent by the server. + /// + /// Failed or invalid records should have an SOA sent back to them. + pub rtype: RecordType, + /// Class of the DNS record. + pub class: RecordClass, + /// Time to Live in seconds. + pub ttl: u32, + /// Data to be sent. Requires a data length to be added. + /// + /// Data length is automatically handled if you use the built-in functions. + /// + /// TXT records have their own TXT length added after the data length which is one byte long (u8) instead of the data length's two bytes (u16), keep this in mind when writing custom wrappers around this structure. + pub data: Vec, +} + +/// A structure for DNS packets. Most interaction will take place within one of these. +/// +/// The ID field doesn't have to be unique per se but has to be unique enough that you don't accidentally send two packets within about 2 seconds of each other that both have the id `0xbeef` +#[derive(Debug)] +pub struct DnsPacket { + /// The semi-unique transaction ID. + /// + /// Responses have the same TIDs as the requests. + pub id: u16, + /// Flags to set stuff like the reply codes, opcodes and whether the response was from an authoritative name server. + pub flags: DnsFlags, + /// Stores the queries of the packet. + /// + /// Responses have the same queries as the request. + pub queries: Vec, + /// Stores the answers of the packet. + /// + /// Requests should have no answers. + pub answers: Vec, +} + +/// A structure for defining DNS packet flags. +#[derive(Debug)] +pub struct DnsFlags { + /// Indicates whether a packet is a request or a response. + pub response: bool, + /// Indicates the opcode, you probably want 0 for query. + pub opcode: u8, + /// Indicates whether this packet came from an authoritative name server. + pub authoritative: bool, + /// Indicates whether this packet has been cut off or made shorter. + pub truncated: bool, + /// Indicates whether a client wants recursive queries. + pub wantrecursion: bool, + /// Indicates whether a server can do recursive queries. + pub haverecursion: bool, + /// Indicates if the packet is cryptographically verified. + pub authentic: bool, + /// Indicates a packet has already been verified and does not need checking again. + pub checkingdisabled: bool, + /// A reply code from the server. The common ones you want are listed. + /// + /// * 0 - NoError, analogous to HTTP 200 + /// * 1 - FormErr, usually used for malformed packets. + /// * 2 - ServFail, indicates a failure in the server. + /// * 3 - NXDomain, Non-Existent Domain. + /// * 4 - NotImp, returned when the server can't handle the client's request (opcode). + /// * 5 - Refused. + /// + /// Client packets should set this to 0. + pub rcode: u8, +} + +/// Converts a Vec of a packet to a DnsPacket structure. +/// +/// This function is not very good. YMMV. +pub fn construct(packet: Vec) -> DnsPacket { + let tid = ((packet[0] as u16) << 8) + packet[1] as u16; + let rawflags = ((packet[2] as u16) << 8) + packet[3] as u16; + let flags = construct_flags(rawflags); + let questions = ((packet[4] as u16) << 8) + packet[5] as u16; + let answers = ((packet[6] as u16) << 8) + packet[7] as u16; + let autht = ((packet[8] as u16) << 8) + packet[9] as u16; + let mut i = 12 as usize; + let mut strings: Vec = vec![]; + let mut a = packet[i]; + 'outer: loop { + while a > 0 { + i = i + 1; + a = a - 1; + strings.push(packet[i]); + } + if packet[i+1] != 0x00 { + strings.push(0x2e); + i = i + 1; + a = packet[i]; + } else { + break 'outer; + } + } + i = i + 1; + let mut class = toclass((packet[i] as u16) << 8 + packet[i+1] as u16); + i = i + 1; + let mut rtype = totype(((packet[i] as u16) << 8) + packet[i+1] as u16); + let mut answerz: Vec = vec![]; + if answers != 0 { + i = i + 6; + let mut zrtype = totype(((packet[i] as u16) << 8) + packet[i+1] as u16); + i = i + 4; + let ttl = ((packet[i] as u32) << 24) + ((packet[i+1] as u32) << 16) + ((packet[i+2] as u32) << 8) + packet[i+3] as u32; + i = i + 4; + let mut v = ((packet[i] as u16) << 8) + packet[i+1] as u16; + i = i + 2; + let mut data: Vec = vec![]; + while v > 0 { + data.push(packet[i]); + i = i + 1; + v = v - 1; + } + let ans = DnsAnswer { name: 0xc00c, rtype: zrtype, class: class, ttl: ttl, data: data }; + answerz.push(ans); + } + let query = DnsQuery { name: String::from_utf8_lossy(&strings).to_string(), rtype: rtype, class: class }; + let packet = DnsPacket { id: tid, flags: flags, queries: vec![query], answers: answerz }; + return packet +} + +/// Converts a u16 of flags to a DnsFlags structure +pub fn construct_flags(flags: u16) -> DnsFlags { + let mut bin = format!("{:b}",flags); + while bin.len() < 16 { + bin = format!("0{}", bin); + } + let mut bitarray: Vec = vec![]; + let char_vec: Vec = bin.chars().collect(); + for x in char_vec { + if x == '0' { + bitarray.push(false); + } else { + bitarray.push(true); + } + } + let response = bitarray[0]; + let opcode = ((bitarray[1] as u8) << 3) + ((bitarray[2] as u8) << 2) + ((bitarray[3] as u8) << 1) + (bitarray[4] as u8); + let authoritative = bitarray[5]; + let truncated = bitarray[6]; + let wantrecursion = bitarray[7]; + let haverecursion = bitarray[8]; + let authentic = bitarray[10]; + let checkingdisabled = bitarray[11]; + let rcode = ((bitarray[12] as u8) << 3) + ((bitarray[13] as u8) << 2) + ((bitarray[14] as u8) << 1) + (bitarray[15] as u8); + let flags = DnsFlags { response: response, opcode: opcode, authoritative: authoritative, truncated: truncated, wantrecursion: wantrecursion, haverecursion: haverecursion, authentic: authentic, checkingdisabled: checkingdisabled, rcode: rcode }; + return flags +} + +/// Turns a DnsAnswer structure to Vec raw data. +pub fn unpack_answer(answer: DnsAnswer) -> Vec { + let mut raw: Vec = vec![]; + let class = fromclass(answer.class); + let rtype = fromtype(answer.rtype); + raw.push((answer.name >> 8) as u8); + raw.push((answer.name & 0xff) as u8); + raw.push((rtype >> 8) as u8); + raw.push((rtype & 0xff) as u8); + raw.push((class >> 8) as u8); + raw.push((class & 0xff) as u8); + let high_ttl = ((answer.ttl & 0xffff0000) >> 16) as u16; + let low_ttl = ((answer.ttl & 0xffff)) as u16; + raw.push((high_ttl >> 8) as u8); + raw.push((high_ttl & 0xff) as u8); + raw.push((low_ttl >> 8) as u8); + raw.push((low_ttl & 0xff) as u8); + let datums = answer.data; + let len = datums.len() as u16; + raw.push((len >> 8) as u8); + raw.push((len & 0xff) as u8); + for byte in datums { + raw.push(byte); + } + return raw +} + +/// Turns a DnsQuery structure to Vec raw data. +pub fn unpack_query(query: DnsQuery) -> Vec { + let mut raw: Vec = vec![]; + let v = query.name.split("."); + for a in v { + raw.push(a.len() as u8); + let b = a.to_string().into_bytes(); + for n in b { + raw.push(n); + } + } + raw.push(0x00); + let class = fromclass(query.class); + let rtype = fromtype(query.rtype); + raw.push((rtype >> 8) as u8); + raw.push((rtype & 0xff) as u8); + raw.push((class >> 8) as u8); + raw.push((class & 0xff) as u8); + return raw +} + +/// Turns a DnsPacket structure to Vec raw data. +pub fn unpack(pack: DnsPacket) -> Vec { + let mut raw: Vec = vec![]; + raw.push((pack.id >> 8) as u8); + raw.push((pack.id & 0xff) as u8); + let flags = fromflag(pack.flags); + raw.push((flags >> 8) as u8); + raw.push((flags & 0xff) as u8); + let length_of_queries = pack.queries.len() as u16; + raw.push((length_of_queries >> 8) as u8); + raw.push((length_of_queries & 0xff) as u8); + let mut length_of_answers = pack.answers.len() as u16; + let mut length_of_soas: u16 = 0; + for answer in &pack.answers { + if fromtype(answer.rtype) == fromtype(RecordType::SOA) { + length_of_answers = length_of_answers - 1; + length_of_soas = length_of_soas + 1; + } + } + raw.push((length_of_answers >> 8) as u8); + raw.push((length_of_answers & 0xff) as u8); + raw.push((length_of_soas >> 8) as u8); + raw.push((length_of_soas & 0xff) as u8); + raw.push(0x00); + raw.push(0x00); + for query in pack.queries { + let resulter = unpack_query(query); + for item in resulter { + raw.push(item); + } + } + for answer in pack.answers { + let resulter = unpack_answer(answer); + for item in resulter { + raw.push(item); + } + } + return raw +} + +/// Converts DnsFlags to its raw u16 equivalent. +pub fn fromflag(flags: DnsFlags) -> u16 { + let mut bit: u16 = 0; + if flags.response { bit = bit + 0b1000000000000000; } + bit = bit + ((flags.opcode as u16) << 11); + if flags.authoritative { bit = bit + 0b10000000000; } + if flags.truncated { bit = bit + 0b1000000000; } + if flags.wantrecursion { bit = bit + 0b100000000; } + if flags.haverecursion { bit = bit + 0b10000000; } + if flags.authentic { bit = bit + 0b100000; } + if flags.checkingdisabled { bit = bit + 0b10000; } + bit = bit + flags.rcode as u16; + return bit +} + +/// Converts a RecordType to its u16/raw equivalent. +pub fn fromtype(rtype: RecordType) -> u16 { + match rtype { + RecordType::A => 1, + RecordType::NS => 2, + RecordType::CNAME => 5, + RecordType::SOA => 6, + RecordType::PTR => 12, + RecordType::HINFO => 13, + RecordType::MX => 15, + RecordType::TXT => 16, + RecordType::RP => 17, + RecordType::AFSDB => 18, + RecordType::SIG => 24, + RecordType::KEY => 25, + RecordType::AAAA => 28, + RecordType::LOC => 29, + RecordType::SRV => 33, + RecordType::NAPTR => 35, + RecordType::KX => 36, + RecordType::CERT => 37, + RecordType::DNAME => 39, + RecordType::APL => 42, + RecordType::DS => 43, + RecordType::SSHFP => 44, + RecordType::IPSECKEY => 45, + RecordType::RRSIG => 46, + RecordType::NSEC => 47, + RecordType::DNSKEY => 48, + RecordType::DHCID => 49, + RecordType::NSEC3 => 50, + RecordType::NSEC3PARAM => 51, + RecordType::TLSA => 52, + RecordType::SMIMEA => 53, + RecordType::HIP => 55, + RecordType::CDS => 59, + RecordType::CDNSKEY => 60, + RecordType::OPENPGPKEY => 61, + RecordType::CSYNC => 62, + RecordType::ZONEMD => 63, + // RecordType::SVCB => 64, // Currently disabled as only availabe as IETF Draft + // RecordType::HTTPS => 65, // Same as above + RecordType::EUI48 => 108, + RecordType::EUI64 => 109, + RecordType::TKEY => 249, + RecordType::TSIG => 250, + RecordType::URI => 256, + RecordType::CAA => 257, + RecordType::TA => 32768, + RecordType::DLV => 32769, + } +} + +/// Converts a u16/raw equivalent of a type to a RecordType. +pub fn totype(rtype: u16) -> RecordType { + match rtype { + 1 => RecordType::A, + 2 => RecordType::NS, + 5 => RecordType::CNAME, + 6 => RecordType::SOA, + 12 => RecordType::PTR, + 13 => RecordType::HINFO, + 15 => RecordType::MX, + 16 => RecordType::TXT, + 17 => RecordType::RP, + 18 => RecordType::AFSDB, + 24 => RecordType::SIG, + 25 => RecordType::KEY, + 28 => RecordType::AAAA, + 29 => RecordType::LOC, + 33 => RecordType::SRV, + 35 => RecordType::NAPTR, + 36 => RecordType::KX, + 37 => RecordType::CERT, + 39 => RecordType::DNAME, + 42 => RecordType::APL, + 43 => RecordType::DS, + 44 => RecordType::SSHFP, + 45 => RecordType::IPSECKEY, + 46 => RecordType::RRSIG, + 47 => RecordType::NSEC, + 48 => RecordType::DNSKEY, + 49 => RecordType::DHCID, + 50 => RecordType::NSEC3, + 51 => RecordType::NSEC3PARAM, + 52 => RecordType::TLSA, + 53 => RecordType::SMIMEA, + 55 => RecordType::HIP, + 59 => RecordType::CDS, + 60 => RecordType::CDNSKEY, + 61 => RecordType::OPENPGPKEY, + 62 => RecordType::CSYNC, + 63 => RecordType::ZONEMD, + 108 => RecordType::EUI48, + 109 => RecordType::EUI64, + 249 => RecordType::TKEY, + 250 => RecordType::TSIG, + 256 => RecordType::URI, + 257 => RecordType::CAA, + 32768 => RecordType::TA, + 32769 => RecordType::DLV, + _ => RecordType::SOA, + } +} + +/// Converts a RecordClass to its u16/raw equivalent. +pub fn fromclass(rclass: RecordClass) -> u16 { + match rclass { + RecordClass::IN => 1, + RecordClass::CS => 2, + RecordClass::CH => 3, + RecordClass::HS => 4, + } +} + +/// Converts a u16/raw equivalent of a class to a RecordClass. +pub fn toclass(rclass: u16) -> RecordClass { + match rclass { + 1 => RecordClass::IN, + 2 => RecordClass::CS, + 3 => RecordClass::CH, + 4 => RecordClass::HS, + _ => RecordClass::IN, + } +} + +/// An enumerator for defining types of DNS records. Unwrapped by dns::fromtype. +#[derive(Debug, Copy, Clone)] +pub enum RecordType { + /// Address record + A, + /// IPv6 address record + AAAA, + /// Mail exchange record + MX, + /// Text record + TXT, + /// Domain name pointer record + PTR, + /// Start of a zone of authority record + SOA, + /// Name server record + NS, + /// Canonical name record + CNAME, + /// Host information + HINFO, + /// Responsible person + RP, + /// AFS database record + AFSDB, + /// Signature + SIG, + /// Key record + KEY, + /// Location record + LOC, + /// Service locator + SRV, + /// Naming authority pointer + NAPTR, + /// Key Exchanger record + KX, + /// Certificate record (stores PKIX, SPKI, PGP, etc) + CERT, + /// Delegation name record + DNAME, + /// Address Prefix List + APL, + /// Delegation signer + DS, + /// SSH Public Key Fingerprint + SSHFP, + /// IPsec Key + IPSECKEY, + /// DNSSEC signature + RRSIG, + /// Next Secure record + NSEC, + /// DNS Key record + DNSKEY, + /// DHCP identifier + DHCID, + /// Next Secure record version 3 + NSEC3, + /// NSEC3 parameters + NSEC3PARAM, + /// TLSA certificate association + TLSA, + /// S/Mime cert association + SMIMEA, + /// Host Identity Protocol + HIP, + /// Child DS + CDS, + /// Child DNSKEY + CDNSKEY, + /// Open PGP public key record + OPENPGPKEY, + /// Child to Parent synchronization + CSYNC, + /// Message digests for DNS zones + ZONEMD, + // Service Binding +// SVCB, + // HTTPS binding +// HTTPS, + /// MAC address (EUI-48) + EUI48, + /// MAC address (EUI-64) + EUI64, + /// Transaction Key record + TKEY, + /// Transaction signature + TSIG, + /// Uniform resource identifier + URI, + /// Certification authority authorization + CAA, + /// DNSSEC Trust Authorities + TA, + /// DNSSEC Lookaside Validation record + DLV, +} diff --git a/src/lib.rs b/src/lib.rs index 8363ea5..63120f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -85,3 +85,6 @@ pub mod gemini; #[cfg(feature = "ftp")] pub mod ftp; + +#[cfg(feature = "dns")] +pub mod dns;