pub struct MessageReader<'a> { /* private fields */ }Expand description
A fast and flexible message reader.
MessageReader provides a flexible API for traversing a DNS message.
It doesn’t implement the Iterator trait, and, consequently, it doesn’t support the Rust for
loop. However, because it is not bound to a single type of item, it provides more flexibility
than an iterator-based reader. For instance, it allows reading the record data into dedicated
Rust types, without enclosing them in an artificial enum (which is usually done for returning
multiple types of elements from an Iterator).
The API of MessageReader is roughly divided into three parts:
- A method to read the message header
- Methods to read the Questions section
- Methods to read the resource records (Answers, Authority and Additional sections)
DNS message sections do not have a constant size. Thus, in order to position a MessageReader
at a specific element of a message, all previous elements must be read first.
§Message Header
The message header is read using the header method.
The header contains information about the layout of the sections that follow.
It must be read immediately after creation of a MessageReader. Without this
information MessageReader is unaware of the amount of elements in each of the message
sections, and behaves as if there are zero elements in all sections.
§Questions
The methods to read the Questions section are:
has_questionsquestions_countquestionandquestion_refthe_questionandthe_question_refskip_questions
The Questions section is the first section immediately following the header. The DNS protocol allows more than one question to be encoded in a message. However, this is not used in practice, and usually every message contains a single question only.
DNS question is represented in rsdns by the types Question and QuestionRef.
The functions question and question_ref read and return the next question, and
are intended to be used as follows:
let mut mr = MessageReader::new(msg)?;
mr.header()?;
while mr.has_questions() {
let q = mr.question()?; // or mr.question_ref()
// use q ...
}The functions the_question and the_question_ref are useful when exactly one
question is expected in the message. They return Error::BadQuestionsCount if the number
of questions is anything other than 1.
let mut mr = MessageReader::new(msg)?;
mr.header()?;
let q = mr.the_question()?; // or mr.the_question_ref()
// use q ...Finally, if questions are not of any interest, the whole section may be skipped using the
skip_questions method:
let mut mr = MessageReader::new(msg)?;
mr.header()?;
mr.skip_questions()?;
// the reader is positioned to decode the resource records...§Resource Records
The methods to read the resource record sections (Answers, Authority and Additional) are:
has_recordsandhas_records_inrecords_countandrecords_count_inrecord_marker(G1)record_headerandrecord_header_ref(G1)record_dataandrecord_data_bytes(G2)skip_record_data(G2)opt_record
Reading a resource record is a two-step process. Firstly, the record header must be read using
any method in group G1. Secondly, (immediately after) the record data must be read using any
method in group G2. Every call to a method in G1 must be followed by a call to a method in
G2. Every call to a method in G2 must be preceded by a call to a method from G1.
§Marker, Header and HeaderRef
The types RecordMarker, RecordHeader and RecordHeaderRef are used to parse a record
header. The marker holds all record header fields except the domain name. This information
is required to correctly parse both the domain name and the record data that follows the header.
The RecordHeader and RecordHeaderRef types add the domain name to RecordMarker. The
difference between them is similar to the difference between Question and QuestionRef.
RecordHeader owns the domain name bytes by using a type implementing the DName trait.
RecordHeaderRef doesn’t own the domain name bytes, and points back to the encoded domain name
in the message buffer. This allows efficient comparison of the domain name to a domain name of
another record or the question.
Ideally these three types would be implemented in a single type RecordHeader with a generic
type parameter for the domain name. However, as of now, Rust doesn’t allow having both
NameRef and DName hidden behind the same trait.
§EDNS OPT pseudo-record
The EDNS OPT pseudo-record is handled slightly differently than other record types.
It has a dedicated method opt_record which completes reading the record data, and returns
the Opt struct which holds OPT values from both record header and record data parts.
§Reader Exhaustion and Error State
When all records have been read and the reader is exhausted, an attempt to read another record
fails with Error::ReaderDone.
If an error occurs during parsing of any element, the reader enters an error state and behaves as if it is exhausted.
§Random Access
Random access methods are:
These methods allow random access to record data, assuming the record markers are first traversed and stored for later processing.
Note that these methods are immutable, they do not change the internal buffer pointer of the reader.
§Seeking
Seeking is possible using the seek method.
When MessageReader traverses a message and reaches the first record of any of the records
sections, it internally stores the offset of the record. This allows seeking to the beginning
of an already known section in constant time.
Additionally, a seek to an unknown section position is possible immediately after the header is read. In this case the reader will decode all the elements until it is positioned at the first record of the requested section.
§Examples
use rsdns::{
message::{reader::MessageReader, RCode, RecordsSection},
names::Name,
records::{data::{A, Aaaa}, Type},
Error, Result,
};
fn print_answer_addresses(msg: &[u8]) -> Result<()> {
let mut mr = MessageReader::new(msg)?;
let header = mr.header()?;
let rcode = header.flags.response_code();
if rcode != RCode::NOERROR {
return Err(Error::BadResponseCode(rcode));
}
if header.flags.truncated() {
return Err(Error::MessageTruncated);
}
mr.seek(RecordsSection::Answer)?;
while mr.has_records_in(RecordsSection::Answer) {
let rh = mr.record_header::<Name>()?;
if rh.rtype() == Type::A {
let rdata = mr.record_data::<A>(rh.marker())?;
println!(
"Name: {}; Class: {}; TTL: {}; ipv4: {}",
rh.name(), rh.rclass(), rh.ttl(), rdata.address
);
} else if rh.rtype() == Type::AAAA {
let rdata = mr.record_data::<Aaaa>(rh.marker())?;
println!(
"Name: {}; Class: {}; TTL: {}; ipv6: {}",
rh.name(), rh.rclass(), rh.ttl(), rdata.address
);
} else {
// every record must be read fully: header + data
mr.skip_record_data(rh.marker())?;
}
}
Ok(())
}Implementations§
Source§impl<'s, 'a: 's> MessageReader<'a>
impl<'s, 'a: 's> MessageReader<'a>
Sourcepub fn new(msg: &'a [u8]) -> Result<MessageReader<'a>>
pub fn new(msg: &'a [u8]) -> Result<MessageReader<'a>>
Creates a MessageReader for a given message.
This method only minimally initializes the MessageReader's state. The message header must
be read immediately after creation of the reader in order to finalize its initialization
and properly read the rest of the message.
§Returns
Error::MessageTooLong- if message size exceeds 65535 bytes.
Sourcepub fn header(&mut self) -> Result<Header>
pub fn header(&mut self) -> Result<Header>
Reads the message header.
This is the first method that must be called after creation of a MessageReader.
It reads the message header and initializes internal counters to properly
read the rest of the message.
Sourcepub fn seek(&mut self, section: RecordsSection) -> Result<()>
pub fn seek(&mut self, section: RecordsSection) -> Result<()>
Positions the message reader to the first record of a specific section.
Seeking is possible only in one of the two scenarios:
- The message reader was just created and the header was read. In this case the reader will skip all message data until it positions itself at the first record of the requested section.
- The message was read up to (and including) the last record of the first non-empty section preceding the requested one, or any record beyond that.
As a message is traversed, the reader remembers offsets of its sections. So, when a message was entirely traversed, it is possible to seek to any section.
Note that if the requested section is empty, the first record to be read after seek may belong to a consecutive section, or no records may be left at all.
Sourcepub fn has_questions(&self) -> bool
pub fn has_questions(&self) -> bool
Checks if there are more questions to read.
This is a convenience method. It is equivalent to questions_count() > 0.
Sourcepub fn questions_count(&self) -> usize
pub fn questions_count(&self) -> usize
Returns the number of unread questions.
Returns 0 if the reader is in error state.
Sourcepub fn question_ref(&'s mut self) -> Result<QuestionRef<'a>>
pub fn question_ref(&'s mut self) -> Result<QuestionRef<'a>>
Reads the next question as QuestionRef.
Sourcepub fn the_question(&mut self) -> Result<Question>
pub fn the_question(&mut self) -> Result<Question>
Reads the first and only question.
This method is equivalent to question, except that it returns
Error::BadQuestionsCount if the number of questions is not 1.
Sourcepub fn the_question_ref(&'s mut self) -> Result<QuestionRef<'a>>
pub fn the_question_ref(&'s mut self) -> Result<QuestionRef<'a>>
Reads the first and only question as QuestionRef.
This method is equivalent to question_ref, except that it returns
Error::BadQuestionsCount if the number of questions is not 1.
Sourcepub fn skip_questions(&mut self) -> Result<()>
pub fn skip_questions(&mut self) -> Result<()>
Skips the questions section.
This is a convenience method to advance the reader to the end of the questions section.
Note that this method may be called only immediately after the header is read.
Sourcepub fn has_records(&self) -> bool
pub fn has_records(&self) -> bool
Checks if there are more records to read.
This is a convenience method. It is equivalent to records_count() > 0.
Sourcepub fn has_records_in(&self, section: RecordsSection) -> bool
pub fn has_records_in(&self, section: RecordsSection) -> bool
Checks if there are more records to read in a specific section.
This is a convenience method. It is equivalent to records_count_in() > 0.
Sourcepub fn records_count(&self) -> usize
pub fn records_count(&self) -> usize
Returns the number of unread records.
Returns 0 if the reader is in error state.
Sourcepub fn records_count_in(&self, section: RecordsSection) -> usize
pub fn records_count_in(&self, section: RecordsSection) -> usize
Returns the number of unread records in a specific section.
Returns 0 if the reader is in error state.
Sourcepub fn record_marker(&mut self) -> Result<RecordMarker>
pub fn record_marker(&mut self) -> Result<RecordMarker>
Returns the marker of the current resource record.
Sourcepub fn record_header_ref(&'s mut self) -> Result<RecordHeaderRef<'a>>
pub fn record_header_ref(&'s mut self) -> Result<RecordHeaderRef<'a>>
Reads the header of the current resource record as RecordHeaderRef.
Sourcepub fn record_header<N: DName>(&mut self) -> Result<RecordHeader<N>>
pub fn record_header<N: DName>(&mut self) -> Result<RecordHeader<N>>
Reads the header of the current resource record.
This method is generic over the domain name type N used for the name of the record.
This allows parsing the header without memory allocations, if appropriate type is used.
Sourcepub fn skip_record_data(&mut self, marker: &RecordMarker) -> Result<()>
pub fn skip_record_data(&mut self, marker: &RecordMarker) -> Result<()>
Skips the current record data and advances the reader to the next record.
§Panics
This method uses debug assertions to verify that marker matches the reader’s buffer
pointer.
Sourcepub fn record_data_bytes(
&'s mut self,
marker: &RecordMarker,
) -> Result<&'a [u8]>
pub fn record_data_bytes( &'s mut self, marker: &RecordMarker, ) -> Result<&'a [u8]>
Returns the current record data as a byte slice and advances the reader to the next record.
This method allows reading data of unknown record types, as defined in RFC 3597 section 5.
§Panics
This method uses debug assertions to verify that marker matches the reader’s buffer
pointer.
Sourcepub fn record_data<D: RData>(&mut self, marker: &RecordMarker) -> Result<D>
pub fn record_data<D: RData>(&mut self, marker: &RecordMarker) -> Result<D>
Deserializes the current record data and advances the reader to the next record.
This method is generic over the record data type, and allows deserialization of all
data types supported by rsdns. See RData for the full list of record data types
implementing the trait.
§Panics
This method uses debug assertions to verify that marker matches the reader’s buffer
pointer.
Sourcepub fn opt_record(&mut self, marker: &RecordMarker) -> Result<Opt>
pub fn opt_record(&mut self, marker: &RecordMarker) -> Result<Opt>
Sourcepub fn record_data_bytes_at(&'s self, marker: &RecordMarker) -> Result<&'a [u8]>
pub fn record_data_bytes_at(&'s self, marker: &RecordMarker) -> Result<&'a [u8]>
Reads the data of a record at specified marker and returns it as a byte slice.
This method allows random access to the encoded records of a DNS message.
It is intended to be used in cases when record headers are read in one loop,
while record data is later (possibly selectively) read in another loop. If data is read
together with the header, use MessageReader::record_data_bytes instead, which is more
efficient.
Note that this method is immutable and doesn’t change the reader’s buffer pointer. Nor it is affected by an error state of the reader.
Sourcepub fn record_data_at<D: RData>(&self, marker: &RecordMarker) -> Result<D>
pub fn record_data_at<D: RData>(&self, marker: &RecordMarker) -> Result<D>
Reads and deserializes the data of a record at specified marker.
This method allows random access to the encoded records of a DNS message.
It is intended to be used in cases when record headers are read in one loop,
while record data is later (possibly selectively) read in another loop. If data is read
together with the header, use MessageReader::record_data instead, which is more
efficient.
Note that this method is immutable and doesn’t change the reader’s buffer pointer. Nor it is affected by an error state of the reader.
Sourcepub fn name_ref_at(&'s self, marker: &RecordMarker) -> NameRef<'a>
pub fn name_ref_at(&'s self, marker: &RecordMarker) -> NameRef<'a>
Returns the data of a record at specified marker as NameRef.
This method is handy with records that have a single domain name in the
data section, e.g. CNAME, NS, PTR etc.