// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH // REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY // AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM // LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. // $Id$ #include #include #include #include #include #include #include using namespace isc::dns; using namespace std; // Classify the response in the "message" object. ResponseClassifier::Category ResponseClassifier::classify( const Question& question, const MessagePtr& message, bool tcignore) { // Check header bits if (!message->getHeaderFlag(Message::HEADERFLAG_QR)) { return (NOTRESPONSE); // Query-response bit not set in the response } // We only recognise responses to queries here if (message->getOpcode() != Opcode::QUERY()) { return (OPCODE); } // Apparently have a response. There must be a single question in it... const vector msgquestion(message->beginQuestion(), message->endQuestion()); if (msgquestion.size() != 1) { return (NOTONEQUEST); // Not one question in response question section } // ... and the question should be equal to the question given. // XXX: This means that "question" may not be the question sent by the // client. In the case of a CNAME response, the qname of subsequent // questions needs to be altered. if (question != *(msgquestion[0])) { return (MISMATQUEST); } // Check for Rcode-related errors. const Rcode& rcode = message->getRcode(); if (rcode != Rcode::NOERROR()) { if (rcode == Rcode::NXDOMAIN()) { // No such domain. According to RFC2308, the domain referred to by // the QNAME does not exist, although there may be a CNAME in the // answer section and there may be an SOA and/or NS RRs in the // authority section (ignoring any DNSSEC RRs for now). // // Note the "may". There may not be anything. Also, note that if // there is a CNAME in the answer section, the authoritative server // has verified that the name given in the CNAME's RDATA field does // not exist. And that if a CNAME is returned in the answer, then // the QNAME of the RRs in the authority section will refer to the // authority for the CNAME's RDATA and not to the original question. // // Without doing further classification, it is sufficient to say // that if an NXDOMAIN is received, there was no translation of the // QNAME available. return (NXDOMAIN); // Received NXDOMAIN from parent. } else { // Not NXDOMAIN but not NOERROR either. Must be an RCODE-related // error. return (RCODE); } } // All seems OK and we can start looking at the content. However, one // more header check remains - was the response truncated? If so, we'll // probably want to re-query over TCP. However, in some circumstances we // might want to go with what we have. So give the caller the option of // ignoring the TC bit. if (message->getHeaderFlag(Message::HEADERFLAG_TC) && (!tcignore)) { return (TRUNCATED); } // By the time we get here, we're assured that the packet format is correct. // We now need to decide as to whether it is an answer, a CNAME, or a // referral. For this, we need to inspect the contents of the answer // and authority sections. const vector answer( message->beginSection(Message::SECTION_ANSWER), message->endSection(Message::SECTION_ANSWER) ); const vector authority( message->beginSection(Message::SECTION_AUTHORITY), message->endSection(Message::SECTION_AUTHORITY) ); // If there is nothing in the answer section, it is a referral - unless // there is nothing in the authority section if (answer.empty()) { if (authority.empty()) { return (EMPTY); } else { return (REFERRAL); } } // Look at two cases - one RRset in the answer and multiple RRsets in // the answer. if (answer.size() == 1) { // Does the name and class of the answer match that of the question? if ((answer[0]->getName() == question.getName()) && (answer[0]->getClass() == question.getClass())) { // It does. How about the type of the response? The response // is an answer if the type matches that of the question, or if the // question was for type ANY. It is a CNAME reply if the answer // type is CNAME. And it is an error for anything else. if ((answer[0]->getType() == question.getType()) || (question.getType() == RRType::ANY())) { return (ANSWER); } else if (answer[0]->getType() == RRType::CNAME()) { return (CNAME); } else { return (INVTYPE); } } else { // Either the name and/or class of the reply don't match that of // the question. return (INVNAMCLASS); } } // There are multiple RRsets in the answer. They should all have the same // QCLASS, else there is some error in the response. for (int i = 1; i < answer.size(); ++i) { if (answer[0]->getClass() != answer[i]->getClass()) { return (MULTICLASS); } } // If the request type was ANY and they all have the same QNAME, we have // an answer. But if they don't have the same QNAME, we must have an error; // the only way we could get different QNAMES in an answer is if one were a // CNAME - in which case there should no other record types at that QNAME. if (question.getType() == RRType::ANY()) { bool all_same = true; for (int i = 1; (i < answer.size()) && all_same; ++i) { all_same = (answer[0]->getName() == answer[i]->getName()); } if (all_same) { return (ANSWER); } else { return (EXTRADATA); } } // Multiple RRs in the answer, and not all the same QNAME. This // is either an answer, a CNAME (in either case, there could be multiple // CNAMEs in the chain) or an error. // // So we need to follow the CNAME chain to resolve this. For this to work: // // a) There must be one RR that matches the name, class and type of // the question, and this is a CNAME. // b) The CNAME chain is followed until the end of the chain does not // exist (answer is a CNAME) or it is not of type CNAME (ANSWER). // // In the latter case, if there are additional RRs, it must be an error. vector ansrrset(answer); vector present(ansrrset.size(), 1); return cnameChase(question.getName(), question.getType(), ansrrset, present, ansrrset.size()); } // Search the CNAME chain. ResponseClassifier::Category ResponseClassifier::cnameChase( const Name& qname, const RRType& qtype, vector& ansrrset, vector& present, size_t size) { // Search through the vector of RRset pointers until we find one with the // right QNAME. for (int i = 0; i < ansrrset.size(); ++i) { if (present[i]) { // This entry has not been logically removed, so look at it. if (ansrrset[i]->getName() == qname) { // QNAME match. If this RRset is a CNAME, remove it from // further consideration. If nothing is left, the end of the // chain is a CNAME so this is a CNAME. Otherwise replace // the name with the RDATA of the CNAME and call ourself // recursively. if (ansrrset[i]->getType() == RRType::CNAME()) { // Don't consider it in the next iteration (although we // can still access it for now). present[i] = 0; --size; if (size == 0) { return (CNAME); } else { if (ansrrset[i]->getRdataCount() != 1) { // Multiple RDATA for a CNAME? This is invalid. return (NOTSINGLE); } RdataIteratorPtr it = ansrrset[i]->getRdataIterator(); Name newname(it->getCurrent().toText()); return cnameChase(newname, qtype, ansrrset, present, size); } } else { // We've got here because the element is not a CNAME. If // this is the last element and the type is the one we are // after, we've found the answer, or it is an error. If // there is more than one RRset left in the list we are // searching, we have extra data in the answer. if (ansrrset[i]->getType() == qtype) { if (size == 1) { return (ANSWERCNAME); } else { return (EXTRADATA); } } return (INVTYPE); } } } } // We get here if we've dropped off the end of the list without finding the // QNAME we are looking for. This means that the CNAME chain has ended // but there are additional RRsets in the data. return (EXTRADATA); }