This commit is contained in:
abbie 2022-04-12 21:17:32 +01:00
parent 53f7a08a01
commit cbf60f5a05
No known key found for this signature in database
GPG key ID: 04DDE463F9200F87
3 changed files with 533 additions and 1 deletions

View file

@ -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 <colean@colean.cc>"]
license = "MPL-2.0"
@ -18,5 +18,6 @@ all-features = true
http1 = []
gemini = []
ftp = []
dns = []
[dependencies]

528
src/dns.rs Normal file
View file

@ -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<u8>,
}
/// 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<DnsQuery>,
/// Stores the answers of the packet.
///
/// Requests should have no answers.
pub answers: Vec<DnsAnswer>,
}
/// 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<u8> of a packet to a DnsPacket structure.
///
/// This function is not very good. YMMV.
pub fn construct(packet: Vec<u8>) -> 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<u8> = 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<DnsAnswer> = 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<u8> = 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<bool> = vec![];
let char_vec: Vec<char> = 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<u8> raw data.
pub fn unpack_answer(answer: DnsAnswer) -> Vec<u8> {
let mut raw: Vec<u8> = 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<u8> raw data.
pub fn unpack_query(query: DnsQuery) -> Vec<u8> {
let mut raw: Vec<u8> = 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<u8> raw data.
pub fn unpack(pack: DnsPacket) -> Vec<u8> {
let mut raw: Vec<u8> = 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,
}

View file

@ -85,3 +85,6 @@ pub mod gemini;
#[cfg(feature = "ftp")]
pub mod ftp;
#[cfg(feature = "dns")]
pub mod dns;