Recursor: handle NS responses with a different type and no SOA
e.g. `dig m.wikipedia.org. NS` replies with `m.wikipedia.org. CNAME dyna.wikimedia.org` and NO SOA record. the CNAME is worthless, but it's evidence that the nameserver we queried is in fact authoritative for `m.wikipedia.org.`.
This commit is contained in:
@@ -357,6 +357,7 @@ impl<P: ConnectionProvider + Default> Recursor<P> {
|
|||||||
Ok(r) => {
|
Ok(r) => {
|
||||||
let mut r = r.into_message();
|
let mut r = r.into_message();
|
||||||
info!("response: {}", r.header());
|
info!("response: {}", r.header());
|
||||||
|
let mut got_answer_of_any_type = false;
|
||||||
let mut soa = None;
|
let mut soa = None;
|
||||||
let records = r
|
let records = r
|
||||||
.take_answers()
|
.take_answers()
|
||||||
@@ -374,6 +375,9 @@ impl<P: ConnectionProvider + Default> Recursor<P> {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}).map(|x| {
|
}).map(|x| {
|
||||||
|
if x.name() == query.name() {
|
||||||
|
got_answer_of_any_type = true;
|
||||||
|
}
|
||||||
if x.record_type().is_soa() {
|
if x.record_type().is_soa() {
|
||||||
soa = Some(x.name().clone());
|
soa = Some(x.name().clone());
|
||||||
}
|
}
|
||||||
@@ -381,27 +385,31 @@ impl<P: ConnectionProvider + Default> Recursor<P> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let lookup = self.record_cache.insert_records(query.clone(), records, now);
|
let lookup = self.record_cache.insert_records(query.clone(), records, now);
|
||||||
let lookup = lookup.or_else(|| {
|
let lookup = match lookup {
|
||||||
if query.query_type().is_ns() || query.query_type().is_cname() || query.query_type().is_any() {
|
Some(q) => Some(Ok(q)),
|
||||||
None
|
None => if query.query_type().is_ns() {
|
||||||
} else {
|
debug!("no records for {:?}: try SOA {:?}", &query, &soa);
|
||||||
|
if let Some(soa_name) = soa {
|
||||||
|
Some(Err(Error::from(ErrorKind::Forward(soa_name))))
|
||||||
|
} else if got_answer_of_any_type {
|
||||||
|
// if we query `@ns1 foo.example.org. NS` and get a response for
|
||||||
|
// `foo.example.org.` of a different type (e.g. CNAME), we learn that
|
||||||
|
// `ns1` is authoritative for the zone to which `foo.example.org.`
|
||||||
|
// records (of other types) belong.
|
||||||
|
Some(Err(Error::from(ErrorKind::Forward(ns.zone().clone()))))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else if !(query.query_type().is_ns() || query.query_type().is_cname() || query.query_type().is_any()) {
|
||||||
debug!("no records for {:?}: checking for cached CNAME", &query);
|
debug!("no records for {:?}: checking for cached CNAME", &query);
|
||||||
let mut cname_query = query.clone();
|
let mut cname_query = query.clone();
|
||||||
cname_query.set_query_type(RecordType::CNAME);
|
cname_query.set_query_type(RecordType::CNAME);
|
||||||
self.record_cache.get(&cname_query, now).and_then(Result::ok)
|
self.record_cache.get(&cname_query, now).and_then(|r| Some(r)).map(|m| m.map_err(Error::from))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
lookup.unwrap_or_else(|| Err(Error::from("no records found")))
|
||||||
lookup.ok_or_else(|| {
|
|
||||||
if query.query_type().is_ns() {
|
|
||||||
debug!("no records for {:?}: returning SOA {:?}", &query, &soa);
|
|
||||||
if let Some(soa_name) = soa {
|
|
||||||
return Error::from(ErrorKind::Forward(soa_name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Error::from("no records found")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("lookup error: {e}");
|
warn!("lookup error: {e}");
|
||||||
|
Reference in New Issue
Block a user