// In testing use a rather small buffer to ensure we hit the allocation paths sometimes. In
// production, we should generally never actually need to go to heap as DNS messages are rarely
// larger than a KiB or two.
-#[cfg(test)]
+#[cfg(any(test, fuzzing))]
const STACK_BUF_LIMIT: u16 = 32;
-#[cfg(not(test))]
+#[cfg(not(any(test, fuzzing)))]
const STACK_BUF_LIMIT: u16 = 2048;
/// A buffer for storing queries and responses.
len: u16,
}
impl QueryBuf {
- fn new_zeroed(len: u16) -> Self {
+ /// Generates a new buffer of the given length, consisting of all zeros.
+ pub fn new_zeroed(len: u16) -> Self {
let heap_buf = if len > STACK_BUF_LIMIT { vec![0; len as usize] } else { Vec::new() };
Self {
buf: [0; STACK_BUF_LIMIT as usize],
len
}
}
- pub(crate) fn extend_from_slice(&mut self, sl: &[u8]) {
+ /// Extends the size of this buffer by appending the given slice.
+ ///
+ /// If the total length of this buffer exceeds [`u16::MAX`] after appending, the buffer's state
+ /// is undefined, however pushing data beyond [`u16::MAX`] will not panic.
+ pub fn extend_from_slice(&mut self, sl: &[u8]) {
let new_len = self.len.saturating_add(sl.len() as u16);
let was_heap = self.len > STACK_BUF_LIMIT;
let is_heap = new_len > STACK_BUF_LIMIT;
target.copy_from_slice(sl);
self.len = new_len;
}
+ /// Converts this query into its bytes on the heap
+ pub fn into_vec(self) -> Vec<u8> {
+ if self.len > STACK_BUF_LIMIT {
+ self.heap_buf
+ } else {
+ self.buf[..self.len as usize].to_vec()
+ }
+ }
}
impl ops::Deref for QueryBuf {
type Target = [u8];
}
}
-// We don't care about transaction IDs as we're only going to accept signed data. Thus, we use
-// this constant instead of a random value.
-const TXID: u16 = 0x4242;
+// We don't care about transaction IDs as we're only going to accept signed data.
+// Further, if we're querying over DoH, the RFC says we SHOULD use a transaction ID of 0 here.
+const TXID: u16 = 0;
fn build_query(domain: &Name, ty: u16) -> QueryBuf {
let mut query = QueryBuf::new_zeroed(0);
- let query_msg_len: u16 = 2 + 2 + 8 + 2 + 2 + name_len(domain) + 11;
- query.extend_from_slice(&query_msg_len.to_be_bytes());
query.extend_from_slice(&TXID.to_be_bytes());
query.extend_from_slice(&[0x01, 0x20]); // Flags: Recursive, Authenticated Data
query.extend_from_slice(&[0, 1, 0, 0, 0, 0, 0, 1]); // One question, One additional
Ok(min_ttl)
}
+#[cfg(fuzzing)]
+/// Read a stream of responses and handle them it as if they came from a server, for fuzzing.
+pub fn fuzz_proof_builder(mut response_stream: &[u8]) {
+ let (mut builder, _) = ProofBuilder::new(&"example.com.".try_into().unwrap(), Txt::TYPE);
+ while builder.awaiting_responses() {
+ let len = if let Ok(len) = read_u16(&mut response_stream) { len } else { return };
+ let mut buf = QueryBuf::new_zeroed(len);
+ if response_stream.len() < len as usize { return; }
+ buf.copy_from_slice(&response_stream[..len as usize]);
+ response_stream = &response_stream[len as usize..];
+ let _ = builder.process_response(&buf);
+ }
+ let _ = builder.finish_proof();
+}
+
const MAX_REQUESTS: usize = 10;
/// A simple state machine which will generate a series of queries and process the responses until
/// it has built a DNSSEC proof.
#[cfg(feature = "std")]
fn send_query(stream: &mut TcpStream, query: &[u8]) -> Result<(), Error> {
+ stream.write_all(&(query.len() as u16).to_be_bytes())?;
stream.write_all(&query)?;
Ok(())
}
#[cfg(feature = "tokio")]
async fn send_query_async(stream: &mut TokioTcpStream, query: &[u8]) -> Result<(), Error> {
+ stream.write_all(&(query.len() as u16).to_be_bytes()).await?;
stream.write_all(&query).await?;
Ok(())
}