ensure that request codes are properly set on responses

This commit is contained in:
Benjamin Fry 2021-08-14 11:33:17 -07:00
parent aebc682dfa
commit d4ee9d5d7b
6 changed files with 303 additions and 282 deletions

482
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -282,6 +282,7 @@ fn test_server_continues_on_bad_data_tcp() {
#[test]
#[cfg(feature = "resolver")]
#[ignore] // FIXME: the client needs to be updated to specify that recursion is desired on the request... before commit
fn test_forward() {
use server_harness::query_message;

View File

@ -7,9 +7,8 @@
use std::fmt::{self, Display};
use crate::proto::error::*;
use crate::op::Query;
use crate::proto::error::*;
use crate::rr::{DNSClass, LowerName, RecordType};
use crate::serialize::binary::*;

View File

@ -110,6 +110,43 @@ impl Header {
Default::default()
}
/// Construct a new header based off the request header. This copies over the RD (recursion-desired)
/// and CD (checking-disabled), as well as the op_code and id of the request.
///
/// See https://datatracker.ietf.org/doc/html/rfc6895#section-2
///
/// ```text
/// The AA, TC, RD, RA, and CD bits are each theoretically meaningful
/// only in queries or only in responses, depending on the bit. The AD
/// bit was only meaningful in responses but is expected to have a
/// separate but related meaning in queries (see Section 5.7 of
/// [RFC6840]). Only the RD and CD bits are expected to be copied from
/// the query to the response; however, some DNS implementations copy all
/// the query header as the initial value of the response header. Thus,
/// any attempt to use a "query" bit with a different meaning in a
/// response or to define a query meaning for a "response" bit may be
/// dangerous, given the existing implementation. Meanings for these
/// bits may only be assigned by a Standards Action.
/// ```
pub fn response_from_request(header: &Header) -> Self {
Header {
id: header.id,
message_type: MessageType::Response,
op_code: header.op_code,
authoritative: false,
truncation: false,
recursion_desired: header.recursion_desired,
recursion_available: false,
authentic_data: false,
checking_disabled: header.checking_disabled,
response_code: ResponseCode::default(),
query_count: 0,
answer_count: 0,
name_server_count: 0,
additional_count: 0,
}
}
/// Length of the header, always 12 bytes
#[inline(always)]
pub fn len() -> usize {

View File

@ -88,8 +88,7 @@ impl RequestHandler for Catalog {
// check if it's edns
if let Some(req_edns) = request_message.edns() {
let mut response = MessageResponseBuilder::new(Some(request_message.raw_queries()));
let mut response_header = Header::default();
response_header.set_id(request_message.id());
let mut response_header = Header::response_from_request(request_message.header());
let mut resp_edns: Edns = Edns::new();
@ -343,6 +342,7 @@ impl Catalog {
response_edns: Option<Edns>,
response_handle: R,
) -> impl Future<Output = ()> + 'static {
// find matching authorities for the request
let queries_and_authorities = request
.queries()
.iter()
@ -353,8 +353,10 @@ impl Catalog {
})
.collect::<Vec<_>>();
// if this is empty then the there are no authorities registered that can handle the request
if queries_and_authorities.is_empty() {
let response = MessageResponseBuilder::new(Some(request.raw_queries()));
send_response(
response_edns
.as_ref()
@ -408,8 +410,14 @@ async fn lookup<R: ResponseHandler + Unpin>(
authority.origin()
);
let (response_header, sections) =
build_response(&*authority, request.id(), query, request.edns()).await;
let (response_header, sections) = build_response(
&*authority,
request.id(),
request.header(),
query,
request.edns(),
)
.await;
let response = MessageResponseBuilder::new(Some(request.raw_queries())).build(
response_header,
@ -453,6 +461,7 @@ fn lookup_options_for_edns(edns: Option<&Edns>) -> LookupOptions {
async fn build_response(
authority: &dyn AuthorityObject,
request_id: u16,
request_header: &Header,
query: &LowerQuery,
edns: Option<&Edns>,
) -> (Header, LookupSections) {
@ -466,10 +475,7 @@ async fn build_response(
);
}
let mut response_header = Header::new();
response_header.set_id(request_id);
response_header.set_op_code(OpCode::Query);
response_header.set_message_type(MessageType::Response);
let mut response_header = Header::response_from_request(request_header);
response_header.set_authoritative(authority.zone_type().is_authoritative());
debug!("performing {} on {}", query, authority.origin());
@ -489,7 +495,7 @@ async fn build_response(
.await
}
ZoneType::Forward | ZoneType::Hint => {
send_forwarded_response(future, &mut response_header).await
send_forwarded_response(future, request_header, &mut response_header).await
}
};
@ -598,20 +604,37 @@ async fn send_authoritative_response(
async fn send_forwarded_response(
future: BoxedLookupFuture,
request_header: &Header,
response_header: &mut Header,
) -> LookupSections {
let answers = match future.await {
Ok(rsp) => rsp,
Err(e) => {
if e.is_nx_domain() {
response_header.set_response_code(ResponseCode::NXDomain);
response_header.set_recursion_available(true);
response_header.set_authoritative(false);
// Don't perform the recursive query if this is disabled...
let answers = if request_header.recursion_desired() {
// cancel the future??
// future.cancel();
drop(future);
info!(
"request disabled recursion, returning no records: {}",
request_header.id()
);
Box::new(EmptyLookup)
} else {
match future.await {
Ok(rsp) => rsp,
Err(e) => {
if e.is_nx_domain() {
response_header.set_response_code(ResponseCode::NXDomain);
}
error!("error resolving: {}", e);
Box::new(EmptyLookup)
}
error!("error resolving: {}", e);
Box::new(EmptyLookup)
}
};
response_header.set_authoritative(false);
LookupSections {
answers,
ns: Box::new(AuthLookup::default()) as Box<dyn LookupObject>,

View File

@ -25,6 +25,11 @@ pub struct MessageRequest {
}
impl MessageRequest {
/// Return the request header
pub fn header(&self) -> &Header {
&self.header
}
/// see `Header::id()`
pub fn id(&self) -> u16 {
self.header.id()