iter_resptype.c revision 291767
1/*
2 * iterator/iter_resptype.c - response type information and classification.
3 *
4 * Copyright (c) 2007, NLnet Labs. All rights reserved.
5 *
6 * This software is open source.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 *
15 * Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 *
19 * Neither the name of the NLNET LABS nor the names of its contributors may
20 * be used to endorse or promote products derived from this software without
21 * specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
27 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
29 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36/**
37 * \file
38 *
39 * This file defines the response type. DNS Responses can be classified as
40 * one of the response types.
41 */
42#include "config.h"
43#include "iterator/iter_resptype.h"
44#include "iterator/iter_delegpt.h"
45#include "services/cache/dns.h"
46#include "util/net_help.h"
47#include "util/data/dname.h"
48#include "sldns/rrdef.h"
49#include "sldns/pkthdr.h"
50
51enum response_type
52response_type_from_cache(struct dns_msg* msg,
53	struct query_info* request)
54{
55	/* If the message is NXDOMAIN, then it is an ANSWER. */
56	if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN)
57		return RESPONSE_TYPE_ANSWER;
58	if(request->qtype == LDNS_RR_TYPE_ANY)
59		return RESPONSE_TYPE_ANSWER;
60
61	/* First we look at the answer section. This can tell us if this is
62	 * CNAME or positive ANSWER. */
63	if(msg->rep->an_numrrsets > 0) {
64		/* Now look at the answer section first. 3 states:
65		 *	o our answer is there directly,
66		 *	o our answer is there after a cname,
67		 *	o or there is just a cname. */
68		uint8_t* mname = request->qname;
69		size_t mname_len = request->qname_len;
70		size_t i;
71		for(i=0; i<msg->rep->an_numrrsets; i++) {
72			struct ub_packed_rrset_key* s = msg->rep->rrsets[i];
73
74			/* If we have encountered an answer (before or
75			 * after a CNAME), then we are done! Note that
76			 * if qtype == CNAME then this will be noted as
77			 * an ANSWER before it gets treated as a CNAME,
78			 * as it should */
79			if(ntohs(s->rk.type) == request->qtype &&
80				ntohs(s->rk.rrset_class) == request->qclass &&
81				query_dname_compare(mname, s->rk.dname) == 0) {
82				return RESPONSE_TYPE_ANSWER;
83			}
84
85			/* If we have encountered a CNAME, make sure that
86			 * it is relevant. */
87			if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME &&
88				query_dname_compare(mname, s->rk.dname) == 0) {
89				get_cname_target(s, &mname, &mname_len);
90			}
91		}
92
93		/* if we encountered a CNAME (or a bunch of CNAMEs), and
94		 * still got to here, then it is a CNAME response. (i.e.,
95		 * the CNAME chain didn't terminate in an answer rrset.) */
96		if(mname != request->qname) {
97			return RESPONSE_TYPE_CNAME;
98		}
99	}
100
101	/* At this point, since we don't need to detect REFERRAL or LAME
102	 * messages, it can only be an ANSWER. */
103	return RESPONSE_TYPE_ANSWER;
104}
105
106enum response_type
107response_type_from_server(int rdset,
108	struct dns_msg* msg, struct query_info* request, struct delegpt* dp)
109{
110	uint8_t* origzone = (uint8_t*)"\000"; /* the default */
111	struct ub_packed_rrset_key* s;
112	size_t i;
113
114	if(!msg || !request)
115		return RESPONSE_TYPE_THROWAWAY;
116
117	/* If the message is NXDOMAIN, then it answers the question. */
118	if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) {
119		/* make sure its not recursive when we don't want it to */
120		if( (msg->rep->flags&BIT_RA) &&
121			!(msg->rep->flags&BIT_AA) && !rdset)
122				return RESPONSE_TYPE_REC_LAME;
123		/* it could be a CNAME with NXDOMAIN rcode */
124		for(i=0; i<msg->rep->an_numrrsets; i++) {
125			s = msg->rep->rrsets[i];
126			if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME &&
127				query_dname_compare(request->qname,
128				s->rk.dname) == 0) {
129				return RESPONSE_TYPE_CNAME;
130			}
131		}
132		return RESPONSE_TYPE_ANSWER;
133	}
134
135	/* Other response codes mean (so far) to throw the response away as
136	 * meaningless and move on to the next nameserver. */
137	if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR)
138		return RESPONSE_TYPE_THROWAWAY;
139
140	/* Note: TC bit has already been handled */
141
142	if(dp) {
143		origzone = dp->name;
144	}
145
146	/* First we look at the answer section. This can tell us if this is a
147	 * CNAME or ANSWER or (provisional) ANSWER. */
148	if(msg->rep->an_numrrsets > 0) {
149		uint8_t* mname = request->qname;
150		size_t mname_len = request->qname_len;
151
152		/* Now look at the answer section first. 3 states: our
153		 * answer is there directly, our answer is there after
154		 * a cname, or there is just a cname. */
155		for(i=0; i<msg->rep->an_numrrsets; i++) {
156			s = msg->rep->rrsets[i];
157
158			/* if the answer section has NS rrset, and qtype ANY
159		 	 * and the delegation is lower, and no CNAMEs followed,
160		 	 * this is a referral where the NS went to AN section */
161			if((request->qtype == LDNS_RR_TYPE_ANY ||
162				request->qtype == LDNS_RR_TYPE_NS) &&
163				ntohs(s->rk.type) == LDNS_RR_TYPE_NS &&
164				ntohs(s->rk.rrset_class) == request->qclass &&
165				dname_strict_subdomain_c(s->rk.dname,
166				origzone)) {
167				if((msg->rep->flags&BIT_AA))
168					return RESPONSE_TYPE_ANSWER;
169				return RESPONSE_TYPE_REFERRAL;
170			}
171
172			/* If we have encountered an answer (before or
173			 * after a CNAME), then we are done! Note that
174			 * if qtype == CNAME then this will be noted as an
175			 * ANSWER before it gets treated as a CNAME, as
176			 * it should. */
177			if(ntohs(s->rk.type) == request->qtype &&
178				ntohs(s->rk.rrset_class) == request->qclass &&
179				query_dname_compare(mname, s->rk.dname) == 0) {
180				if((msg->rep->flags&BIT_AA))
181					return RESPONSE_TYPE_ANSWER;
182				/* If the AA bit isn't on, and we've seen
183				 * the answer, we only provisionally say
184				 * 'ANSWER' -- it very well could be a
185				 * REFERRAL. */
186				break;
187			}
188
189			/* If we have encountered a CNAME, make sure that
190			 * it is relevant. */
191			if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME &&
192				query_dname_compare(mname, s->rk.dname) == 0) {
193				get_cname_target(s, &mname, &mname_len);
194			}
195		}
196		/* not a referral, and qtype any, thus an answer */
197		if(request->qtype == LDNS_RR_TYPE_ANY)
198			return RESPONSE_TYPE_ANSWER;
199		/* if we encountered a CNAME (or a bunch of CNAMEs), and
200		 * still got to here, then it is a CNAME response.
201		 * (This is regardless of the AA bit at this point) */
202		if(mname != request->qname) {
203			return RESPONSE_TYPE_CNAME;
204		}
205	}
206
207	/* Looking at the authority section, we just look and see if
208	 * there is a SOA record, that means a NOERROR/NODATA */
209	for(i = msg->rep->an_numrrsets; i < (msg->rep->an_numrrsets +
210		msg->rep->ns_numrrsets); i++) {
211		s = msg->rep->rrsets[i];
212
213		/* The normal way of detecting NOERROR/NODATA. */
214		if(ntohs(s->rk.type) == LDNS_RR_TYPE_SOA &&
215			dname_subdomain_c(request->qname, s->rk.dname)) {
216			/* we do our own recursion, thank you */
217			if( (msg->rep->flags&BIT_RA) &&
218				!(msg->rep->flags&BIT_AA) && !rdset)
219				return RESPONSE_TYPE_REC_LAME;
220			return RESPONSE_TYPE_ANSWER;
221		}
222	}
223	/* Looking at the authority section, we just look and see if
224	 * there is a delegation NS set, turning it into a delegation.
225	 * Otherwise, we will have to conclude ANSWER (either it is
226	 * NOERROR/NODATA, or an non-authoritative answer). */
227	for(i = msg->rep->an_numrrsets; i < (msg->rep->an_numrrsets +
228		msg->rep->ns_numrrsets); i++) {
229		s = msg->rep->rrsets[i];
230
231		/* Detect REFERRAL/LAME/ANSWER based on the relationship
232		 * of the NS set to the originating zone name. */
233		if(ntohs(s->rk.type) == LDNS_RR_TYPE_NS) {
234			/* If we are getting an NS set for the zone we
235			 * thought we were contacting, then it is an answer.*/
236			if(query_dname_compare(s->rk.dname, origzone) == 0) {
237				/* see if mistakenly a recursive server was
238				 * deployed and is responding nonAA */
239				if( (msg->rep->flags&BIT_RA) &&
240					!(msg->rep->flags&BIT_AA) && !rdset)
241					return RESPONSE_TYPE_REC_LAME;
242				/* Or if a lame server is deployed,
243				 * which gives ns==zone delegation from cache
244				 * without AA bit as well, with nodata nosoa*/
245				/* real answer must be +AA and SOA RFC(2308),
246				 * so this is wrong, and we SERVFAIL it if
247				 * this is the only possible reply, if it
248				 * is misdeployed the THROWAWAY makes us pick
249				 * the next server from the selection */
250				if(msg->rep->an_numrrsets==0 &&
251					!(msg->rep->flags&BIT_AA) && !rdset)
252					return RESPONSE_TYPE_THROWAWAY;
253				return RESPONSE_TYPE_ANSWER;
254			}
255			/* If we are getting a referral upwards (or to
256			 * the same zone), then the server is 'lame'. */
257			if(dname_subdomain_c(origzone, s->rk.dname)) {
258				if(rdset) /* forward or reclame not LAME */
259					return RESPONSE_TYPE_THROWAWAY;
260				return RESPONSE_TYPE_LAME;
261			}
262			/* If the NS set is below the delegation point we
263			 * are on, and it is non-authoritative, then it is
264			 * a referral, otherwise it is an answer. */
265			if(dname_subdomain_c(s->rk.dname, origzone)) {
266				/* NOTE: I no longer remember in what case
267				 * we would like this to be an answer.
268				 * NODATA should have a SOA or nothing,
269				 * not an NS rrset.
270				 * True, referrals should not have the AA
271				 * bit set, but... */
272
273				/* if((msg->rep->flags&BIT_AA))
274					return RESPONSE_TYPE_ANSWER; */
275				return RESPONSE_TYPE_REFERRAL;
276			}
277			/* Otherwise, the NS set is irrelevant. */
278		}
279	}
280
281	/* If we've gotten this far, this is NOERROR/NODATA (which could
282	 * be an entirely empty message) */
283	/* check if recursive answer; saying it has empty cache */
284	if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset)
285		return RESPONSE_TYPE_REC_LAME;
286	return RESPONSE_TYPE_ANSWER;
287}
288