Commit 18491370 authored by Stephen Morris's avatar Stephen Morris
Browse files

Merge branch 'trac487'

Conflicts:
	doc/Doxyfile
parents dd37e953 98d6a12c
......@@ -568,7 +568,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils
INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
......
......@@ -37,6 +37,7 @@ spec_config.h: spec_config.h.pre
BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-resolver
b10_resolver_SOURCES = resolver.cc resolver.h
b10_resolver_SOURCES += response_classifier.cc response_classifier.h
b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/change_user.h
b10_resolver_SOURCES += $(top_builddir)/src/bin/auth/common.h
b10_resolver_SOURCES += main.cc
......
// 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 <cstddef>
#include <vector>
#include <resolver/response_classifier.h>
#include <dns/name.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
#include <dns/rrset.h>
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<QuestionPtr> 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<RRsetPtr> answer(
message->beginSection(Message::SECTION_ANSWER),
message->endSection(Message::SECTION_ANSWER)
);
const vector<RRsetPtr> 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<RRsetPtr> ansrrset(answer);
vector<int> 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<RRsetPtr>& ansrrset,
vector<int>& 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);
}
// 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$
#ifndef __RESPONSE_CLASSIFIER_H
#define __RESPONSE_CLASSIFIER_H
#include <cstddef>
#include <dns/question.h>
#include <dns/message.h>
#include <dns/question.h>
/// \brief Classify Server Response
///
/// This class is used in the recursive server. It is passed an answer received
/// from an upstream server and categorises it.
///
/// TODO: It is unlikely that the code can be used in this form. Some adaption
/// of it will be required to put it in the server.
///
/// TODO: The code here does not take into account any EDNS0 fields.
class ResponseClassifier {
public:
/// \brief Category of Answer
///
/// In the valid answers, not the distinction between REFERRAL and CNAME.
/// A REFERRAL answer means that the answer section of the message is
/// empty, but there is something in the authority section. A CNAME means
/// that the answer section contains one or more CNAMES in a chain that
/// do not end with a non-CNAME RRset.
enum Category {
// Codes indicating that a message is valid.
ANSWER, ///< Response contains the answer
ANSWERCNAME, ///< Response was a CNAME chain ending in an answer
CNAME, ///< Response was a CNAME
NXDOMAIN, ///< Response was an NXDOMAIN
REFERRAL, ///< Response contains a referral
// Codes indicating that a message is invalid. Note that the error()
// method relies on these appearing after the "message valid" codes.
EMPTY, ///< No answer or authority sections
EXTRADATA, ///< Answer section contains more RRsets than needed
INVNAMCLASS, ///< Invalid name or class in answer
INVTYPE, ///< Name/class of answer correct, type is wrong
MISMATQUEST, ///< Response question section != question
MULTICLASS, ///< Multiple classes in multi-RR answer
NOTONEQUEST, ///< Not one question in response question section
NOTRESPONSE, ///< Response has the Query/Response bit clear
NOTSINGLE, ///< CNAME has multiple RDATA elements.
OPCODE, ///< Opcode field does not indicate a query
RCODE, ///< RCODE indicated an error
TRUNCATED ///< Response was truncated
};
/// \brief Check Error
///
/// An inline routine to quickly classify whether the return category is
/// an error or not. This makes use of internal knowledge of the order of
/// codes in the Category enum.
///
/// \param code Return category from classify()
///
/// \return true if the category is an error, false if not.
static bool error(Category code) {
return (code > REFERRAL);
}
/// \brief Classify
///
/// Classify the response in the "message" object.
///
/// \param question Question that was sent to the server
/// \param message Pointer to the associated response from the server.
/// \param tcignore If set, the TC bit in a response packet is
/// ignored. Otherwise the error code TRUNCATED will be returned. The
/// only time this is likely to be used is in development where we are not
/// going to fail over to TCP and will want to use what is returned, even
/// if some of the response was lost.
static Category classify(const isc::dns::Question& question,
const isc::dns::MessagePtr& message, bool tcignore = false);
private:
/// \brief Follow CNAMEs
///
/// Given a QNAME and an answer section that contains CNAMEs, assume that
/// they form a CNAME chain and search through them. Possible outcomes
/// are:
///
/// a) All CNAMES and they form a chain. The result is a referral.
/// b) All but one are CNAMES and they form a chain. The other is pointed
/// to by the last element of the chain and is the correct QTYPE. The
/// result is an answer.
/// c) Having followed the CNAME chain as far as we can, there is one
/// remaining RRset that is of the wrong type, or there are multiple
/// RRsets remaining. return the EXTRADATA code.
///
/// \param qname Question name we are searching for
/// \param qtype Question type we are search for. (This is assumed not
/// to be "ANY".)
/// \param ansrrset Vector of RRsetPtr pointing to the RRsets we are
/// considering.
/// \param present Array of "int" the same size of ansrrset, with each
/// element set to "1" to allow the corresponding element of ansrrset to
/// be checked, and "0" to skip it. This might be premature optimisation,
/// but the algorithm would otherwise involve duplicating the RRset
/// vector then removing elements from random positions one by one. As
/// each removal involves the destruction of an "xxxPtr" element (which
/// presently is implemented by boost::shared_ptr), the overhad of memory
/// management seemed high. This solution imposes some additional loop
/// cycles, but that should be minimal compared with the overhead of the
/// memory management.
/// \param size Number of elements to check. See description of \c present
/// for details.
static Category cnameChase(const isc::dns::Name& qname,
const isc::dns::RRType& qtype,
std::vector<isc::dns::RRsetPtr>& ansrrset, std::vector<int>& present,
size_t size);
};
#endif // __RESPONSE_CLASSIFIER_H
......@@ -20,8 +20,10 @@ TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
run_unittests_SOURCES += ../resolver.h ../resolver.cc
run_unittests_SOURCES += ../response_classifier.h ../response_classifier.cc
run_unittests_SOURCES += resolver_unittest.cc
run_unittests_SOURCES += resolver_config_unittest.cc
run_unittests_SOURCES += response_classifier_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
......
This diff is collapsed.
......@@ -228,10 +228,10 @@ public:
//@}
///
/// \name Comparison Operator
/// \name Comparison Operators
///
//@{
/// A comparison operator is needed for this class so it can
/// A "less than" operator is needed for this class so it can
/// function as an index to std::map.
bool operator <(const Question& rhs) const {
return (rrclass_ < rhs.rrclass_ ||
......@@ -239,6 +239,26 @@ public:
(rrtype_ < rhs.rrtype_ ||
(rrtype_ == rhs.rrtype_ && (name_ < rhs.name_)))));
}
/// Equality operator. Primarily used to compare the question section in
/// a response to that in the query.
///
/// \param rhs Question to compare against
/// \return true if name, class and type are equal, false otherwise
bool operator==(const Question& rhs) const {
return ((rrclass_ == rhs.rrclass_) && (rrtype_ == rhs.rrtype_) &&
(name_ == rhs.name_));
}
/// Inequality operator. Primarily used to compare the question section in
/// a response to that in the query.
///
/// \param rhs Question to compare against
/// \return true if one or more of the name, class and type do not match,
/// false otherwise.
bool operator!=(const Question& rhs) const {
return (!operator==(rhs));
}
//@}
private:
......
......@@ -139,6 +139,39 @@ TEST_F(QuestionTest, comparison) {
EXPECT_FALSE(Question(a, ch, ns) < Question(a, ch, ns));
EXPECT_FALSE(Question(b, in, ns) < Question(b, in, ns));
EXPECT_FALSE(Question(b, in, aaaa) < Question(b, in, aaaa));
// Identical questions are equal
EXPECT_TRUE(Question(a, in, ns) == Question(a, in, ns));
EXPECT_FALSE(Question(a, in, ns) != Question(a, in, ns));
// Components differing by one component are unequal...
EXPECT_FALSE(Question(b, in, ns) == Question(a, in, ns));
EXPECT_TRUE(Question(b, in, ns) != Question(a, in, ns));
EXPECT_FALSE(Question(a, ch, ns) == Question(a, in, ns));
EXPECT_TRUE(Question(a, ch, ns) != Question(a, in, ns));
EXPECT_FALSE(Question(a, in, aaaa) == Question(a, in, ns));
EXPECT_TRUE(Question(a, in, aaaa) != Question(a, in, ns));
// ... as are those differing by two components
EXPECT_FALSE(Question(b, ch, ns) == Question(a, in, ns));
EXPECT_TRUE(Question(b, ch, ns) != Question(a, in, ns));
EXPECT_FALSE(Question(b, in, aaaa) == Question(a, in, ns));
EXPECT_TRUE(Question(b, in, aaaa) != Question(a, in, ns));
EXPECT_FALSE(Question(a, ch, aaaa) == Question(a, in, ns));
EXPECT_TRUE(Question(a, ch, aaaa) != Question(a, in, ns));
// ... and question differing by all three
EXPECT_FALSE(Question(b, ch, aaaa) == Question(a, in, ns));
EXPECT_TRUE(Question(b, ch, aaaa) != Question(a, in, ns));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment