initial parsing for resource record, and stubbed rdata parsers

This commit is contained in:
Benjamin Fry 2015-08-12 00:54:54 -07:00
parent 55dac96d79
commit 8b7facb69e
17 changed files with 859 additions and 77 deletions

102
src/rr/domain.rs Normal file
View File

@ -0,0 +1,102 @@
use std::string::FromUtf8Error;
pub struct Name {
labels: Vec<String>
}
impl Name {
/// parses the chain of labels
/// this has a max of 255 octets, with each label being less than 63.
/// all names will be stored lowercase internally.
/// This will consume the portions of the Vec which it is reading...
pub fn parse(slice: &mut Vec<u8>) -> Result<Name, FromUtf8Error> {
let mut state: LabelParseState = LabelParseState::LabelLengthOrPointer;
let mut labels: Vec<String> = Vec::with_capacity(3); // most labels will be around three, e.g. www.example.com
// assume all chars are utf-8. We're doing byte-by-byte operations, no endianess issues...
// reserved: (1000 0000 aka 0800) && (0100 0000 aka 0400)
// pointer: (slice == 1100 0000 aka C0) & C0 == true, then 03FF & slice = offset
// label: 03FF & slice = length; slice.next(length) = label
// root: 0000
loop {
state = match state {
LabelParseState::LabelLengthOrPointer => {
// determine what the next label is
match slice.remove(0) {
0 => LabelParseState::Root,
byte if byte & 0xC0 == 0xC0 => LabelParseState::Pointer(byte & 0x3F),
byte if byte <= 0x3F => LabelParseState::Label(byte),
_ => unimplemented!(),
}
},
LabelParseState::Label(count) => {
//let label_slice: &mut [u8] = &mut Vec::with_capacity(count as usize)[..];
//let mut label_slice = Vec::with_capacity(count as usize);
//let mut label_vec: Vec<u8> = iter::FromIterator::from_iter(iter::repeat(0).take(count as usize));
//let mut label_slice: &mut [u8] = &mut label_vec[..];
// TODO once Drain stabalizes on Vec, this should be replaced...
let mut label_slice: Vec<u8> = Vec::with_capacity(count as usize);
for i in 0..count as usize {
label_slice.push(slice.remove(i-i)); // get rid of the unused i warning...
}
//println!("count: {} slice: {} label_slice: {}", count, slice.len(), label_slice.len());
//let label_slice: Vec<u8> = iter::FromIterator::from_iter(iter.take(count as usize).map(|&i| i).collect::<Vec<u8>>());
//assert_eq!((&*slice).read(label_slice).ok().unwrap(), count as usize);
println!("count: {} slice: {} label_slice: {}", count, slice.len(), label_slice.len());
// translate bytes to string, then lowercase...
//let label_slice = &*label_slice;
//let label = try!(String::from_utf8(label_slice.into())).to_lowercase();
let label = try!(String::from_utf8(label_slice)).to_lowercase();
labels.push(label);
// reset to collect more data
LabelParseState::LabelLengthOrPointer
},
LabelParseState::Pointer(offset) => {
// lookup in the hashmap the label to use
unimplemented!()
},
LabelParseState::Root => {
// technically could return here...
break;
}
}
}
Ok(Name { labels: labels })
}
}
/// This is the list of states for the label parsing state machine
enum LabelParseState {
LabelLengthOrPointer, // basically the start of the FSM
Label(u8), // storing length of the label, must be < 63
Pointer(u8), // location of pointer in slice,
Root, // root is the end of the labels list, aka null
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse() {
let data: Vec<(Vec<u8>, Vec<String>)> = vec![
(vec![0], vec![]), // base case, only the root
(vec![1,b'a',0], vec!["a".to_string()]), // a single 'a' label
(vec![1,b'a',2,b'b',b'c',0], vec!["a".to_string(), "bc".to_string()]), // two labels, 'a.bc'
(vec![1,b'a',3,0xE2,0x99,0xA5,0], vec!["a".to_string(), "".to_string()]), // two labels utf8, 'a.♥'
(vec![1,b'A',0], vec!["a".to_string()]), // a single 'a' label, lowercased
];
let mut test_num = 0;
for (mut binary, expect) in data {
test_num += 1;
println!("test: {}", test_num);
assert_eq!(Name::parse(&mut binary).ok().unwrap().labels, expect);
}
}
}

View File

@ -1,3 +1,7 @@
pub mod record_type;
pub mod dns_class;
pub mod resource;
pub mod record_data;
pub mod domain;
mod rdata;

26
src/rr/rdata/a.rs Normal file
View File

@ -0,0 +1,26 @@
use super::super::record_data::RData;
// 3.4. Internet specific RRs
//
// 3.4.1. A RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ADDRESS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// ADDRESS A 32 bit Internet address.
//
// Hosts that have multiple Internet addresses will have multiple A
// records.
//
// A records cause no additional section processing. The RDATA section of
// an A line in a master file is an Internet address expressed as four
// decimal numbers separated by dots without any imbedded spaces (e.g.,
// "10.2.0.52" or "192.0.5.6").
//
// A { address: u32 }
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

12
src/rr/rdata/aaaa.rs Normal file
View File

@ -0,0 +1,12 @@
use super::super::record_data::RData;
//-- RFC 1886 -- IPv6 DNS Extensions December 1995
// 2.2 AAAA data format
//
// A 128 bit IPv6 address is encoded in the data portion of an AAAA
// resource record in network byte order (high-order byte first).
//
// AAAA { high: u64, low: u64 }
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

22
src/rr/rdata/cname.rs Normal file
View File

@ -0,0 +1,22 @@
use super::super::record_data::RData;
// 3.3.1. CNAME RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / CNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// CNAME A <domain-name> which specifies the canonical or primary
// name for the owner. The owner name is an alias.
//
// CNAME RRs cause no additional section processing, but name servers may
// choose to restart the query at the canonical name in certain cases. See
// the description of name server logic in [RFC-1034] for details.
//
// CNAME { cname: Name },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

27
src/rr/rdata/hinfo.rs Normal file
View File

@ -0,0 +1,27 @@
use super::super::record_data::RData;
// 3.3.2. HINFO RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / CPU /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / OS /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// CPU A <character-string> which specifies the CPU type.
//
// OS A <character-string> which specifies the operating
// system type.
//
// Standard values for CPU and OS can be found in [RFC-1010].
//
// HINFO records are used to acquire general information about a host. The
// main use is for protocols such as FTP that can use special procedures
// when talking between machines or operating systems of the same type.
//
// HINFO { cpu: String, os: String},
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

12
src/rr/rdata/mod.rs Normal file
View File

@ -0,0 +1,12 @@
// each of these module's has the parser for that rdata embedded, to keep the file sizes down...
pub mod cname;
pub mod hinfo;
pub mod mx;
pub mod null;
pub mod ns;
pub mod ptr;
pub mod soa;
pub mod txt;
pub mod a;
pub mod wks;
pub mod aaaa;

28
src/rr/rdata/mx.rs Normal file
View File

@ -0,0 +1,28 @@
use super::super::record_data::RData;
// 3.3.9. MX RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | PREFERENCE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / EXCHANGE /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// PREFERENCE A 16 bit integer which specifies the preference given to
// this RR among others at the same owner. Lower values
// are preferred.
//
// EXCHANGE A <domain-name> which specifies a host willing to act as
// a mail exchange for the owner name.
//
// MX records cause type A additional section processing for the host
// specified by EXCHANGE. The use of MX RRs is explained in detail in
// [RFC-974].
//
// MX { preference: u16, exchange: Name },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

29
src/rr/rdata/ns.rs Normal file
View File

@ -0,0 +1,29 @@
use super::super::record_data::RData;
// 3.3.11. NS RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / NSDNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// NSDNAME A <domain-name> which specifies a host which should be
// authoritative for the specified class and domain.
//
// NS records cause both the usual additional section processing to locate
// a type A record, and, when used in a referral, a special search of the
// zone in which they reside for glue information.
//
// The NS RR states that the named host should be expected to have a zone
// starting at owner name of the specified class. Note that the class may
// not indicate the protocol family which should be used to communicate
// with the host, although it is typically a strong hint. For example,
// hosts which are name servers for either Internet (IN) or Hesiod (HS)
// class information are normally queried using IN class protocols.
//
// NS { nsdname: Name },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

20
src/rr/rdata/null.rs Normal file
View File

@ -0,0 +1,20 @@
use super::super::record_data::RData;
// 3.3.10. NULL RDATA format (EXPERIMENTAL)
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / <anything> /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// Anything at all may be in the RDATA field so long as it is 65535 octets
// or less.
//
// NULL records cause no additional section processing. NULL RRs are not
// allowed in master files. NULLs are used as placeholders in some
// experimental extensions of the DNS.
//
// NULL { anything: Vec<u8> },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

23
src/rr/rdata/ptr.rs Normal file
View File

@ -0,0 +1,23 @@
use super::super::record_data::RData;
// 3.3.12. PTR RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / PTRDNAME /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// PTRDNAME A <domain-name> which points to some location in the
// domain name space.
//
// PTR records cause no additional section processing. These RRs are used
// in special domains to point to some other location in the domain space.
// These records are simple data, and don't imply any special processing
// similar to that performed by CNAME, which identifies aliases. See the
// description of the IN-ADDR.ARPA domain for an example.
//
// PTR { ptrdnam: Name },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

71
src/rr/rdata/soa.rs Normal file
View File

@ -0,0 +1,71 @@
use super::super::record_data::RData;
// 3.3.13. SOA RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / MNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / RNAME /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | SERIAL |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | REFRESH |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | RETRY |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | EXPIRE |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | MINIMUM |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// MNAME The <domain-name> of the name server that was the
// original or primary source of data for this zone.
//
// RNAME A <domain-name> which specifies the mailbox of the
// person responsible for this zone.
//
// SERIAL The unsigned 32 bit version number of the original copy
// of the zone. Zone transfers preserve this value. This
// value wraps and should be compared using sequence space
// arithmetic.
//
// REFRESH A 32 bit time interval before the zone should be
// refreshed.
//
// RETRY A 32 bit time interval that should elapse before a
// failed refresh should be retried.
//
// EXPIRE A 32 bit time value that specifies the upper limit on
// the time interval that can elapse before the zone is no
// longer authoritative.
//
// MINIMUM The unsigned 32 bit minimum TTL field that should be
// exported with any RR from this zone.
//
// SOA records cause no additional section processing.
//
// All times are in units of seconds.
//
// Most of these fields are pertinent only for name server maintenance
// operations. However, MINIMUM is used in all query operations that
// retrieve RRs from a zone. Whenever a RR is sent in a response to a
// query, the TTL field is set to the maximum of the TTL field from the RR
// and the MINIMUM field in the appropriate SOA. Thus MINIMUM is a lower
// bound on the TTL field for all RRs in a zone. Note that this use of
// MINIMUM should occur when the RRs are copied into the response and not
// when the zone is loaded from a master file or via a zone transfer. The
// reason for this provison is to allow future dynamic update facilities to
// change the SOA RR with known semantics.
//
// SOA { mname: Name, rname: Name, serial: u32, refresh: i32, retry: i32, expire: i32, minimum: u32, },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

19
src/rr/rdata/txt.rs Normal file
View File

@ -0,0 +1,19 @@
use super::super::record_data::RData;
// 3.3.14. TXT RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / TXT-DATA /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// TXT-DATA One or more <character-string>s.
//
// TXT RRs are used to hold descriptive text. The semantics of the text
// depends on the domain where it is found.
//
// TXT { txt_data: Vec<String> }
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

49
src/rr/rdata/wks.rs Normal file
View File

@ -0,0 +1,49 @@
use super::super::record_data::RData;
// 3.4.2. WKS RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ADDRESS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | PROTOCOL | |
// +--+--+--+--+--+--+--+--+ |
// | |
// / <BIT MAP> /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// ADDRESS An 32 bit Internet address
//
// PROTOCOL An 8 bit IP protocol number
//
// <BIT MAP> A variable length bit map. The bit map must be a
// multiple of 8 bits long.
//
// The WKS record is used to describe the well known services supported by
// a particular protocol on a particular internet address. The PROTOCOL
// field specifies an IP protocol number, and the bit map has one bit per
// port of the specified protocol. The first bit corresponds to port 0,
// the second to port 1, etc. If the bit map does not include a bit for a
// protocol of interest, that bit is assumed zero. The appropriate values
// and mnemonics for ports and protocols are specified in [RFC-1010].
//
// For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port
// 25 (SMTP). If this bit is set, a SMTP server should be listening on TCP
// port 25; if zero, SMTP service is not supported on the specified
// address.
//
// The purpose of WKS RRs is to provide availability information for
// servers for TCP and UDP. If a server supports both TCP and UDP, or has
// multiple Internet addresses, then multiple WKS RRs are used.
//
// WKS RRs cause no additional section processing.
//
// In master files, both ports and protocols are expressed using mnemonics
// or decimal numbers.
//
// WKS { address: u32, protocol: u8, bitmap: Vec<u8> },
pub fn parse(data: &mut Vec<u8>) -> RData {
unimplemented!()
}

324
src/rr/record_data.rs Normal file
View File

@ -0,0 +1,324 @@
use super::domain::Name;
use super::record_type::RecordType;
// 3.3. Standard RRs
//
// The following RR definitions are expected to occur, at least
// potentially, in all classes. In particular, NS, SOA, CNAME, and PTR
// will be used in all classes, and have the same format in all classes.
// Because their RDATA format is known, all domain names in the RDATA
// section of these RRs may be compressed.
//
// <domain-name> is a domain name represented as a series of labels, and
// terminated by a label with zero length. <character-string> is a single
// length octet followed by that number of characters. <character-string>
// is treated as binary information, and can be up to 256 characters in
// length (including the length octet).
//
pub enum RData {
//-- RFC 1035 -- Domain Implementation and Specification November 1987
// 3.3. Standard RRs
//
// The following RR definitions are expected to occur, at least
// potentially, in all classes. In particular, NS, SOA, CNAME, and PTR
// will be used in all classes, and have the same format in all classes.
// Because their RDATA format is known, all domain names in the RDATA
// section of these RRs may be compressed.
//
// <domain-name> is a domain name represented as a series of labels, and
// terminated by a label with zero length. <character-string> is a single
// length octet followed by that number of characters. <character-string>
// is treated as binary information, and can be up to 256 characters in
// length (including the length octet).
//
// 3.3.1. CNAME RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / CNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// CNAME A <domain-name> which specifies the canonical or primary
// name for the owner. The owner name is an alias.
//
// CNAME RRs cause no additional section processing, but name servers may
// choose to restart the query at the canonical name in certain cases. See
// the description of name server logic in [RFC-1034] for details.
CNAME { cname: Name },
// 3.3.2. HINFO RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / CPU /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / OS /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// CPU A <character-string> which specifies the CPU type.
//
// OS A <character-string> which specifies the operating
// system type.
//
// Standard values for CPU and OS can be found in [RFC-1010].
//
// HINFO records are used to acquire general information about a host. The
// main use is for protocols such as FTP that can use special procedures
// when talking between machines or operating systems of the same type.
HINFO { cpu: String, os: String},
// 3.3.9. MX RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | PREFERENCE |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / EXCHANGE /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// PREFERENCE A 16 bit integer which specifies the preference given to
// this RR among others at the same owner. Lower values
// are preferred.
//
// EXCHANGE A <domain-name> which specifies a host willing to act as
// a mail exchange for the owner name.
//
// MX records cause type A additional section processing for the host
// specified by EXCHANGE. The use of MX RRs is explained in detail in
// [RFC-974].
MX { preference: u16, exchange: Name },
// 3.3.10. NULL RDATA format (EXPERIMENTAL)
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / <anything> /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// Anything at all may be in the RDATA field so long as it is 65535 octets
// or less.
//
// NULL records cause no additional section processing. NULL RRs are not
// allowed in master files. NULLs are used as placeholders in some
// experimental extensions of the DNS.
NULL { anything: Vec<u8> },
// 3.3.11. NS RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / NSDNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// NSDNAME A <domain-name> which specifies a host which should be
// authoritative for the specified class and domain.
//
// NS records cause both the usual additional section processing to locate
// a type A record, and, when used in a referral, a special search of the
// zone in which they reside for glue information.
//
// The NS RR states that the named host should be expected to have a zone
// starting at owner name of the specified class. Note that the class may
// not indicate the protocol family which should be used to communicate
// with the host, although it is typically a strong hint. For example,
// hosts which are name servers for either Internet (IN) or Hesiod (HS)
// class information are normally queried using IN class protocols.
NS { nsdname: Name },
// 3.3.12. PTR RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / PTRDNAME /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// PTRDNAME A <domain-name> which points to some location in the
// domain name space.
//
// PTR records cause no additional section processing. These RRs are used
// in special domains to point to some other location in the domain space.
// These records are simple data, and don't imply any special processing
// similar to that performed by CNAME, which identifies aliases. See the
// description of the IN-ADDR.ARPA domain for an example.
PTR { ptrdnam: Name },
// 3.3.13. SOA RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / MNAME /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / RNAME /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | SERIAL |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | REFRESH |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | RETRY |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | EXPIRE |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | MINIMUM |
// | |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// MNAME The <domain-name> of the name server that was the
// original or primary source of data for this zone.
//
// RNAME A <domain-name> which specifies the mailbox of the
// person responsible for this zone.
//
// SERIAL The unsigned 32 bit version number of the original copy
// of the zone. Zone transfers preserve this value. This
// value wraps and should be compared using sequence space
// arithmetic.
//
// REFRESH A 32 bit time interval before the zone should be
// refreshed.
//
// RETRY A 32 bit time interval that should elapse before a
// failed refresh should be retried.
//
// EXPIRE A 32 bit time value that specifies the upper limit on
// the time interval that can elapse before the zone is no
// longer authoritative.
//
// MINIMUM The unsigned 32 bit minimum TTL field that should be
// exported with any RR from this zone.
//
// SOA records cause no additional section processing.
//
// All times are in units of seconds.
//
// Most of these fields are pertinent only for name server maintenance
// operations. However, MINIMUM is used in all query operations that
// retrieve RRs from a zone. Whenever a RR is sent in a response to a
// query, the TTL field is set to the maximum of the TTL field from the RR
// and the MINIMUM field in the appropriate SOA. Thus MINIMUM is a lower
// bound on the TTL field for all RRs in a zone. Note that this use of
// MINIMUM should occur when the RRs are copied into the response and not
// when the zone is loaded from a master file or via a zone transfer. The
// reason for this provison is to allow future dynamic update facilities to
// change the SOA RR with known semantics.
SOA { mname: Name, rname: Name, serial: u32, refresh: i32, retry: i32, expire: i32, minimum: u32, },
// 3.3.14. TXT RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// / TXT-DATA /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// TXT-DATA One or more <character-string>s.
//
// TXT RRs are used to hold descriptive text. The semantics of the text
// depends on the domain where it is found.
TXT { txt_data: Vec<String> },
// 3.4. Internet specific RRs
//
// 3.4.1. A RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ADDRESS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// ADDRESS A 32 bit Internet address.
//
// Hosts that have multiple Internet addresses will have multiple A
// records.
//
// A records cause no additional section processing. The RDATA section of
// an A line in a master file is an Internet address expressed as four
// decimal numbers separated by dots without any imbedded spaces (e.g.,
// "10.2.0.52" or "192.0.5.6").
A { address: u32 },
// 3.4.2. WKS RDATA format
//
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | ADDRESS |
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
// | PROTOCOL | |
// +--+--+--+--+--+--+--+--+ |
// | |
// / <BIT MAP> /
// / /
// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
//
// where:
//
// ADDRESS An 32 bit Internet address
//
// PROTOCOL An 8 bit IP protocol number
//
// <BIT MAP> A variable length bit map. The bit map must be a
// multiple of 8 bits long.
//
// The WKS record is used to describe the well known services supported by
// a particular protocol on a particular internet address. The PROTOCOL
// field specifies an IP protocol number, and the bit map has one bit per
// port of the specified protocol. The first bit corresponds to port 0,
// the second to port 1, etc. If the bit map does not include a bit for a
// protocol of interest, that bit is assumed zero. The appropriate values
// and mnemonics for ports and protocols are specified in [RFC-1010].
//
// For example, if PROTOCOL=TCP (6), the 26th bit corresponds to TCP port
// 25 (SMTP). If this bit is set, a SMTP server should be listening on TCP
// port 25; if zero, SMTP service is not supported on the specified
// address.
//
// The purpose of WKS RRs is to provide availability information for
// servers for TCP and UDP. If a server supports both TCP and UDP, or has
// multiple Internet addresses, then multiple WKS RRs are used.
//
// WKS RRs cause no additional section processing.
//
// In master files, both ports and protocols are expressed using mnemonics
// or decimal numbers.
WKS { address: u32, protocol: u8, bitmap: Vec<u8> },
//-- RFC 1886 -- IPv6 DNS Extensions December 1995
// 2.2 AAAA data format
//
// A 128 bit IPv6 address is encoded in the data portion of an AAAA
// resource record in network byte order (high-order byte first).
AAAA { high: u64, low: u64 }
}
impl RData {
pub fn parse(data: &mut Vec<u8>, rtype: &RecordType, rd_length: u16) -> Self {
use super::rdata;
match rtype {
&RecordType::A => rdata::a::parse(data),
&RecordType::AAAA => rdata::aaaa::parse(data),
&RecordType::CNAME => rdata::cname::parse(data),
&RecordType::MX => rdata::mx::parse(data),
&RecordType::NS => rdata::ns::parse(data),
&RecordType::PTR => rdata::ptr::parse(data),
&RecordType::SOA => rdata::soa::parse(data),
&RecordType::TXT => rdata::txt::parse(data),
_ => unimplemented!()
}
}
}

View File

@ -97,6 +97,7 @@ impl<'a> From<&'a str> for RecordType {
"NS" => RecordType::NS,
"SOA" => RecordType::SOA,
"ANY" => RecordType::ANY,
"*" => RecordType::ANY,
_ => unimplemented!(),
}
}

View File

@ -1,95 +1,85 @@
//use std::collections::HashMap;
//use std::error::Error;
use std::string::FromUtf8Error;
use std::io::Read;
use std::iter;
use std::slice::{IterMut,Iter};
use super::record_data::RData;
use super::record_type::RecordType;
use super::dns_class::DNSClass;
use super::domain;
// labels 63 octets or less
// names 255 octets or less
// TTL positive values of a signed 32 bit number.
// UDP messages 512 octets or less
pub struct Record {
name_labels: domain::Name,
rr_type: RecordType,
dns_class: DNSClass,
name_labels: Vec<String>,
ttl: i32,
rdata: RData,
}
impl Record {
/// parse a resource record line example:
///
///
//fn parse(line: &str) -> Record {
pub fn parse(record_bytes: Vec<u8>) -> Result<Record, FromUtf8Error> {
// TODO: it would be better to pass iter to all of these methods, but String::from_utf8 makes that complicated
let mut data: Vec<u8> = record_bytes;
// NAME an owner name, i.e., the name of the node to which this
// resource record pertains.
let name_labels: domain::Name = try!(domain::Name::parse(&mut data));
// tokenize the string
//let mut tokens = line.words();
//}
// TYPE two octets containing one of the RR TYPE codes.
let record_type: RecordType = RecordType::from(Self::parse_u16(&mut data));
/// parses the chain of labels
/// this has a max of 255 octets, with each label being less than 63.
/// all names will be stored lowercase internally.
fn parse_labels(slice: &[u8]) -> Result<Vec<String>, FromUtf8Error> {
let mut state: LabelParseState = LabelParseState::LabelLengthOrPointer;
let mut labels: Vec<String> = Vec::with_capacity(3); // most labels will be around three, e.g. www.example.com
// CLASS two octets containing one of the RR CLASS codes.
let class: DNSClass = DNSClass::from(Self::parse_u16(&mut data));
let mut cur_slice = slice;
// TTL a 32 bit signed integer that specifies the time interval
// that the resource record may be cached before the source
// of the information should again be consulted. Zero
// values are interpreted to mean that the RR can only be
// used for the transaction in progress, and should not be
// cached. For example, SOA records are always distributed
// with a zero TTL to prohibit caching. Zero values can
// also be used for extremely volatile data.
let ttl: i32 = Self::parse_i32(&mut data);
// assume all chars are utf-8. We're doing byte-by-byte operations, no endianess issues...
// reserved: (1000 0000 aka 0800) && (0100 0000 aka 0400)
// pointer: (slice == 1100 0000 aka C0) & C0 == true, then 03FF & slice = offset
// label: 03FF & slice = length; slice.next(length) = label
// root: 0000
loop {
state = match state {
LabelParseState::LabelLengthOrPointer => {
//let byte: u8 = *iter.take(1).next().unwrap(); // could default to zero, but it's an error if this doesn't exist, perhaps try! instead
let (first, tmp_slice) = cur_slice.split_at(1);
cur_slice = tmp_slice;
// RDLENGTH an unsigned 16 bit integer that specifies the length in
// octets of the RDATA field.
let rd_length: u16 = Self::parse_u16(&mut data);
match first.first() {
Some(&0) | None => LabelParseState::Root,
Some(&byte) if byte & 0xC0 == 0xC0 => LabelParseState::Pointer(byte & 0x3F),
Some(&byte) if byte <= 0x3F => LabelParseState::Label(byte),
_ => unimplemented!(),
}
},
LabelParseState::Label(count) => {
//let label_iter: Take<Iter<u8>> = iter.take(count as usize);
//let arr: Vec<&u8> = label_iter.collect();
//let arr2: Vec<u8> = arr.to_vec();
let (label_slice, tmp_slice) = cur_slice.split_at(count as usize);
cur_slice = tmp_slice;
// RDATA a variable length string of octets that describes the
// resource. The format of this information varies
// according to the TYPE and CLASS of the resource record.
let rdata = RData::parse(&mut data, &record_type, rd_length);
// translate bytes to string, then lowercase...
let label = try!(String::from_utf8(label_slice.into())).to_lowercase();
labels.push(label);
// reset to collect more data
LabelParseState::LabelLengthOrPointer
},
LabelParseState::Pointer(offset) => {
// lookup in the hashmap the label to use
unimplemented!()
},
LabelParseState::Root => {
// technically could return here...
break;
}
}
}
return Ok(labels);
Ok(Record{ name_labels: name_labels, rr_type: record_type, dns_class: class, ttl: ttl, rdata: rdata })
}
}
/// This is the list of states for the state machine
enum LabelParseState {
LabelLengthOrPointer, // basically the start of the FSM
Label(u8), // storing length of the label, must be < 63
Pointer(u8), // location of pointer in slice,
Root, // root is the end of the labels list, aka null
/// parses the next 2 bytes into u16. This performs a byte-by-byte manipulation, there
/// which means endianness is implicitly handled (i.e. no network to little endian (intel), issues)
fn parse_u16(data: &mut Vec<u8>) -> u16 {
// TODO use Drain once it stabalizes...
let b1: u8 = data.remove(0);
let b2: u8 = data.remove(0);
// translate from network byte order, i.e. big endian
((b1 as u16) << 8) + (b2 as u16)
}
/// parses the next four bytes into i32. This performs a byte-by-byte manipulation, there
/// which means endianness is implicitly handled (i.e. no network to little endian (intel), issues)
fn parse_i32(data: &mut Vec<u8>) -> i32 {
// TODO use Drain once it stabalizes...
let b1: u8 = data.remove(0);
let b2: u8 = data.remove(0);
let b3: u8 = data.remove(0);
let b4: u8 = data.remove(0);
// translate from network byte order, i.e. big endian
((b1 as i32) << 24) + ((b2 as i32) << 16) + ((b3 as i32) << 8) + (b4 as i32)
}
}
#[cfg(test)]
@ -97,17 +87,40 @@ mod tests {
use super::*;
#[test]
fn parse_labels() {
let data: Vec<(Vec<u8>, Vec<String>)> = vec![
(vec![0], vec![]), // base case, only the root
(vec![1,b'a',0], vec!["a".to_string()]), // a single 'a' label
(vec![1,b'a',2,b'b',b'c',0], vec!["a".to_string(), "bc".to_string()]), // two labels, 'a.bc'
(vec![1,b'a',3,0xE2,0x99,0xA5,0], vec!["a".to_string(), "".to_string()]), // two labels utf8, 'a.♥'
(vec![1,b'A',0], vec!["a".to_string()]), // a single 'a' label, lowercased
fn parse_u16() {
let data: Vec<(Vec<u8>, u16)> = vec![
(vec![0x00,0x00], 0),
(vec![0x00,0x01], 1),
(vec![0x01,0x00], 256),
(vec![0xFF,0xFF], u16::max_value()),
];
for (binary, result) in data {
assert_eq!(Record::parse_labels(&binary[..]).ok().unwrap(), result);
let mut test_num = 0;
for (mut binary, expect) in data {
test_num += 1;
println!("test: {}", test_num);
assert_eq!(Record::parse_u16(&mut binary), expect);
}
}
#[test]
fn parse_i32() {
let data: Vec<(Vec<u8>, i32)> = vec![
(vec![0x00,0x00,0x00,0x00], 0),
(vec![0x00,0x00,0x00,0x01], 1),
(vec![0x00,0x00,0x01,0x00], 256),
(vec![0x00,0x01,0x00,0x00], 256*256),
(vec![0x01,0x00,0x00,0x00], 256*256*256),
(vec![0xFF,0xFF,0xFF,0xFF], -1),
(vec![0x80,0x00,0x00,0x00], i32::min_value()),
(vec![0x7F,0xFF,0xFF,0xFF], i32::max_value()),
];
let mut test_num = 0;
for (mut binary, expect) in data {
test_num += 1;
println!("test: {}", test_num);
assert_eq!(Record::parse_i32(&mut binary), expect);
}
}
}