288 lines
10 KiB
C
288 lines
10 KiB
C
|
/*
|
||
|
* iterator/iter_resptype.c - response type information and classification.
|
||
|
*
|
||
|
* Copyright (c) 2007, NLnet Labs. All rights reserved.
|
||
|
*
|
||
|
* This software is open source.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions
|
||
|
* are met:
|
||
|
*
|
||
|
* Redistributions of source code must retain the above copyright notice,
|
||
|
* this list of conditions and the following disclaimer.
|
||
|
*
|
||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||
|
* this list of conditions and the following disclaimer in the documentation
|
||
|
* and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* Neither the name of the NLNET LABS nor the names of its contributors may
|
||
|
* be used to endorse or promote products derived from this software without
|
||
|
* specific prior written permission.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
|
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||
|
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* \file
|
||
|
*
|
||
|
* This file defines the response type. DNS Responses can be classified as
|
||
|
* one of the response types.
|
||
|
*/
|
||
|
#include "config.h"
|
||
|
#include "iterator/iter_resptype.h"
|
||
|
#include "iterator/iter_delegpt.h"
|
||
|
#include "services/cache/dns.h"
|
||
|
#include "util/net_help.h"
|
||
|
#include "util/data/dname.h"
|
||
|
#include "ldns/rrdef.h"
|
||
|
#include "ldns/pkthdr.h"
|
||
|
|
||
|
enum response_type
|
||
|
response_type_from_cache(struct dns_msg* msg,
|
||
|
struct query_info* request)
|
||
|
{
|
||
|
/* If the message is NXDOMAIN, then it is an ANSWER. */
|
||
|
if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN)
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
if(request->qtype == LDNS_RR_TYPE_ANY)
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
|
||
|
/* First we look at the answer section. This can tell us if this is
|
||
|
* CNAME or positive ANSWER. */
|
||
|
if(msg->rep->an_numrrsets > 0) {
|
||
|
/* Now look at the answer section first. 3 states:
|
||
|
* o our answer is there directly,
|
||
|
* o our answer is there after a cname,
|
||
|
* o or there is just a cname. */
|
||
|
uint8_t* mname = request->qname;
|
||
|
size_t mname_len = request->qname_len;
|
||
|
size_t i;
|
||
|
for(i=0; i<msg->rep->an_numrrsets; i++) {
|
||
|
struct ub_packed_rrset_key* s = msg->rep->rrsets[i];
|
||
|
|
||
|
/* If we have encountered an answer (before or
|
||
|
* after a CNAME), then we are done! Note that
|
||
|
* if qtype == CNAME then this will be noted as
|
||
|
* an ANSWER before it gets treated as a CNAME,
|
||
|
* as it should */
|
||
|
if(ntohs(s->rk.type) == request->qtype &&
|
||
|
ntohs(s->rk.rrset_class) == request->qclass &&
|
||
|
query_dname_compare(mname, s->rk.dname) == 0) {
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
}
|
||
|
|
||
|
/* If we have encountered a CNAME, make sure that
|
||
|
* it is relevant. */
|
||
|
if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME &&
|
||
|
query_dname_compare(mname, s->rk.dname) == 0) {
|
||
|
get_cname_target(s, &mname, &mname_len);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* if we encountered a CNAME (or a bunch of CNAMEs), and
|
||
|
* still got to here, then it is a CNAME response. (i.e.,
|
||
|
* the CNAME chain didn't terminate in an answer rrset.) */
|
||
|
if(mname != request->qname) {
|
||
|
return RESPONSE_TYPE_CNAME;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* At this point, since we don't need to detect REFERRAL or LAME
|
||
|
* messages, it can only be an ANSWER. */
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
}
|
||
|
|
||
|
enum response_type
|
||
|
response_type_from_server(int rdset,
|
||
|
struct dns_msg* msg, struct query_info* request, struct delegpt* dp)
|
||
|
{
|
||
|
uint8_t* origzone = (uint8_t*)"\000"; /* the default */
|
||
|
struct ub_packed_rrset_key* s;
|
||
|
size_t i;
|
||
|
|
||
|
if(!msg || !request)
|
||
|
return RESPONSE_TYPE_THROWAWAY;
|
||
|
|
||
|
/* If the message is NXDOMAIN, then it answers the question. */
|
||
|
if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) {
|
||
|
/* make sure its not recursive when we don't want it to */
|
||
|
if( (msg->rep->flags&BIT_RA) &&
|
||
|
!(msg->rep->flags&BIT_AA) && !rdset)
|
||
|
return RESPONSE_TYPE_REC_LAME;
|
||
|
/* it could be a CNAME with NXDOMAIN rcode */
|
||
|
for(i=0; i<msg->rep->an_numrrsets; i++) {
|
||
|
s = msg->rep->rrsets[i];
|
||
|
if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME &&
|
||
|
query_dname_compare(request->qname,
|
||
|
s->rk.dname) == 0) {
|
||
|
return RESPONSE_TYPE_CNAME;
|
||
|
}
|
||
|
}
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
}
|
||
|
|
||
|
/* Other response codes mean (so far) to throw the response away as
|
||
|
* meaningless and move on to the next nameserver. */
|
||
|
if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR)
|
||
|
return RESPONSE_TYPE_THROWAWAY;
|
||
|
|
||
|
/* Note: TC bit has already been handled */
|
||
|
|
||
|
if(dp) {
|
||
|
origzone = dp->name;
|
||
|
}
|
||
|
|
||
|
/* First we look at the answer section. This can tell us if this is a
|
||
|
* CNAME or ANSWER or (provisional) ANSWER. */
|
||
|
if(msg->rep->an_numrrsets > 0) {
|
||
|
uint8_t* mname = request->qname;
|
||
|
size_t mname_len = request->qname_len;
|
||
|
|
||
|
/* Now look at the answer section first. 3 states: our
|
||
|
* answer is there directly, our answer is there after
|
||
|
* a cname, or there is just a cname. */
|
||
|
for(i=0; i<msg->rep->an_numrrsets; i++) {
|
||
|
s = msg->rep->rrsets[i];
|
||
|
|
||
|
/* if the answer section has NS rrset, and qtype ANY
|
||
|
* and the delegation is lower, and no CNAMEs followed,
|
||
|
* this is a referral where the NS went to AN section */
|
||
|
if((request->qtype == LDNS_RR_TYPE_ANY ||
|
||
|
request->qtype == LDNS_RR_TYPE_NS) &&
|
||
|
ntohs(s->rk.type) == LDNS_RR_TYPE_NS &&
|
||
|
ntohs(s->rk.rrset_class) == request->qclass &&
|
||
|
dname_strict_subdomain_c(s->rk.dname,
|
||
|
origzone)) {
|
||
|
if((msg->rep->flags&BIT_AA))
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
return RESPONSE_TYPE_REFERRAL;
|
||
|
}
|
||
|
|
||
|
/* If we have encountered an answer (before or
|
||
|
* after a CNAME), then we are done! Note that
|
||
|
* if qtype == CNAME then this will be noted as an
|
||
|
* ANSWER before it gets treated as a CNAME, as
|
||
|
* it should. */
|
||
|
if(ntohs(s->rk.type) == request->qtype &&
|
||
|
ntohs(s->rk.rrset_class) == request->qclass &&
|
||
|
query_dname_compare(mname, s->rk.dname) == 0) {
|
||
|
if((msg->rep->flags&BIT_AA))
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
/* If the AA bit isn't on, and we've seen
|
||
|
* the answer, we only provisionally say
|
||
|
* 'ANSWER' -- it very well could be a
|
||
|
* REFERRAL. */
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If we have encountered a CNAME, make sure that
|
||
|
* it is relevant. */
|
||
|
if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME &&
|
||
|
query_dname_compare(mname, s->rk.dname) == 0) {
|
||
|
get_cname_target(s, &mname, &mname_len);
|
||
|
}
|
||
|
}
|
||
|
/* not a referral, and qtype any, thus an answer */
|
||
|
if(request->qtype == LDNS_RR_TYPE_ANY)
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
/* if we encountered a CNAME (or a bunch of CNAMEs), and
|
||
|
* still got to here, then it is a CNAME response.
|
||
|
* (This is regardless of the AA bit at this point) */
|
||
|
if(mname != request->qname) {
|
||
|
return RESPONSE_TYPE_CNAME;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Looking at the authority section, we just look and see if
|
||
|
* there is a SOA record, that means a NOERROR/NODATA */
|
||
|
for(i = msg->rep->an_numrrsets; i < (msg->rep->an_numrrsets +
|
||
|
msg->rep->ns_numrrsets); i++) {
|
||
|
s = msg->rep->rrsets[i];
|
||
|
|
||
|
/* The normal way of detecting NOERROR/NODATA. */
|
||
|
if(ntohs(s->rk.type) == LDNS_RR_TYPE_SOA &&
|
||
|
dname_subdomain_c(request->qname, s->rk.dname)) {
|
||
|
/* we do our own recursion, thank you */
|
||
|
if( (msg->rep->flags&BIT_RA) &&
|
||
|
!(msg->rep->flags&BIT_AA) && !rdset)
|
||
|
return RESPONSE_TYPE_REC_LAME;
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
}
|
||
|
}
|
||
|
/* Looking at the authority section, we just look and see if
|
||
|
* there is a delegation NS set, turning it into a delegation.
|
||
|
* Otherwise, we will have to conclude ANSWER (either it is
|
||
|
* NOERROR/NODATA, or an non-authoritative answer). */
|
||
|
for(i = msg->rep->an_numrrsets; i < (msg->rep->an_numrrsets +
|
||
|
msg->rep->ns_numrrsets); i++) {
|
||
|
s = msg->rep->rrsets[i];
|
||
|
|
||
|
/* Detect REFERRAL/LAME/ANSWER based on the relationship
|
||
|
* of the NS set to the originating zone name. */
|
||
|
if(ntohs(s->rk.type) == LDNS_RR_TYPE_NS) {
|
||
|
/* If we are getting an NS set for the zone we
|
||
|
* thought we were contacting, then it is an answer.*/
|
||
|
if(query_dname_compare(s->rk.dname, origzone) == 0) {
|
||
|
/* see if mistakenly a recursive server was
|
||
|
* deployed and is responding nonAA */
|
||
|
if( (msg->rep->flags&BIT_RA) &&
|
||
|
!(msg->rep->flags&BIT_AA) && !rdset)
|
||
|
return RESPONSE_TYPE_REC_LAME;
|
||
|
/* Or if a lame server is deployed,
|
||
|
* which gives ns==zone delegation from cache
|
||
|
* without AA bit as well, with nodata nosoa*/
|
||
|
/* real answer must be +AA and SOA RFC(2308),
|
||
|
* so this is wrong, and we SERVFAIL it if
|
||
|
* this is the only possible reply, if it
|
||
|
* is misdeployed the THROWAWAY makes us pick
|
||
|
* the next server from the selection */
|
||
|
if(msg->rep->an_numrrsets==0 &&
|
||
|
!(msg->rep->flags&BIT_AA) && !rdset)
|
||
|
return RESPONSE_TYPE_THROWAWAY;
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
}
|
||
|
/* If we are getting a referral upwards (or to
|
||
|
* the same zone), then the server is 'lame'. */
|
||
|
if(dname_subdomain_c(origzone, s->rk.dname)) {
|
||
|
if(rdset) /* forward or reclame not LAME */
|
||
|
return RESPONSE_TYPE_THROWAWAY;
|
||
|
return RESPONSE_TYPE_LAME;
|
||
|
}
|
||
|
/* If the NS set is below the delegation point we
|
||
|
* are on, and it is non-authoritative, then it is
|
||
|
* a referral, otherwise it is an answer. */
|
||
|
if(dname_subdomain_c(s->rk.dname, origzone)) {
|
||
|
/* NOTE: I no longer remember in what case
|
||
|
* we would like this to be an answer.
|
||
|
* NODATA should have a SOA or nothing,
|
||
|
* not an NS rrset.
|
||
|
* True, referrals should not have the AA
|
||
|
* bit set, but... */
|
||
|
|
||
|
/* if((msg->rep->flags&BIT_AA))
|
||
|
return RESPONSE_TYPE_ANSWER; */
|
||
|
return RESPONSE_TYPE_REFERRAL;
|
||
|
}
|
||
|
/* Otherwise, the NS set is irrelevant. */
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If we've gotten this far, this is NOERROR/NODATA (which could
|
||
|
* be an entirely empty message) */
|
||
|
/* check if recursive answer; saying it has empty cache */
|
||
|
if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset)
|
||
|
return RESPONSE_TYPE_REC_LAME;
|
||
|
return RESPONSE_TYPE_ANSWER;
|
||
|
}
|