Browse Source

Merge branch 'master' of ssh://jreed@bind10.isc.org//var/bind10/git/bind10

Jeremy C. Reed 14 years ago
parent
commit
00dab940cb

+ 33 - 0
ChangeLog

@@ -1,3 +1,36 @@
+  160.  [func]		jelte
+  	Updated the resolver to take 3 different timeout values;
+	timeout_query for outstanding queries we sent while resolving
+	timeout_client for sending an answer back to the client
+	timeout_lookup for stopping the resolving
+	(currently 2 and 3 have the same final effect)
+	(Trac 489, git 578ea7f4ba94dc0d8a3d39231dad2be118e125a2)
+
+  159.	[func]		smann
+	The resolver now has a configurable set of root servers to start
+	resolving at (called root_addresses). By default these are not
+	(yet) filled in. If empty, a hardcoded address for f-root will be
+	used right now.
+	(Trac #483, git a07e078b4feeb01949133fc88c9939254c38aa7c)
+
+  158.	[func]		jelte
+	The Resolver module will now do (very limited) resolving, if not
+	set to forwarding mode (i.e. if the configuration option
+	forward_addresses is left empty). It only supports referrals that
+	contain glue addresses at this point, and does no other processing
+	of authoritative answers.
+	(Trac #484, git 7b84de4c0e11f4a070e038ca4f093486e55622af)
+
+  157.  [bug]       vorner
+	One frozen process no longer freezes the whole b10-msgq. It caused the
+	whole system to stop working.
+	(Trac #420, git 93697f58e4d912fa87bc7f9a591c1febc9e0d139)
+
+  156.	[func]		stephen
+	Added ResponseClassifier class to examine response from
+	a server and classify it into one of several categories.
+	(Trac #487, git 18491370576e7438c7893f8551bbb8647001be9c)
+
 bind10-devel-20110120 released on January 20, 2011
 
   155.	[doc]		jreed

+ 1 - 1
doc/Doxyfile

@@ -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

+ 7 - 3
src/bin/auth/auth_srv.cc

@@ -159,9 +159,13 @@ AuthSrvImpl::~AuthSrvImpl() {
 class MessageLookup : public DNSLookup {
 public:
     MessageLookup(AuthSrv* srv) : server_(srv) {}
-    virtual void operator()(const IOMessage& io_message, MessagePtr message,
-                            OutputBufferPtr buffer, DNSServer* server) const
+    virtual void operator()(const IOMessage& io_message,
+                            MessagePtr message,
+                            MessagePtr answer_message,
+                            OutputBufferPtr buffer,
+                            DNSServer* server) const
     {
+        (void) answer_message;
         server_->processMessage(io_message, message, buffer, server);
     }
 private:
@@ -180,7 +184,7 @@ class MessageAnswer : public DNSAnswer {
 public:
     MessageAnswer(AuthSrv*) {}
     virtual void operator()(const IOMessage&, MessagePtr,
-                            OutputBufferPtr) const
+                            MessagePtr, OutputBufferPtr) const
     {}
 };
 

+ 32 - 9
src/bin/auth/query.cc

@@ -66,7 +66,8 @@ Query::findAddrs(const Zone& zone, const Name& qname,
 
     // Find A rrset
     if (qname_ != qname || qtype_ != RRType::A()) {
-        Zone::FindResult a_result = zone.find(qname, RRType::A(), options);
+        Zone::FindResult a_result = zone.find(qname, RRType::A(), NULL,
+                                              options);
         if (a_result.code == Zone::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
                     boost::const_pointer_cast<RRset>(a_result.rrset));
@@ -76,7 +77,7 @@ Query::findAddrs(const Zone& zone, const Name& qname,
     // Find AAAA rrset
     if (qname_ != qname || qtype_ != RRType::AAAA()) {
         Zone::FindResult aaaa_result =
-            zone.find(qname, RRType::AAAA(), options);
+            zone.find(qname, RRType::AAAA(), NULL, options);
         if (aaaa_result.code == Zone::SUCCESS) {
             response_.addRRset(Message::SECTION_ADDITIONAL,
                     boost::const_pointer_cast<RRset>(aaaa_result.rrset));
@@ -121,6 +122,8 @@ Query::getAuthAdditional(const Zone& zone) const {
 void
 Query::process() const {
     bool keep_doing = true;
+    const bool qtype_is_any = (qtype_ == RRType::ANY());
+
     response_.setHeaderFlag(Message::HEADERFLAG_AA, false);
     const MemoryDataSrc::FindResult result =
         memory_datasrc_.findZone(qname_);
@@ -140,20 +143,41 @@ Query::process() const {
     response_.setHeaderFlag(Message::HEADERFLAG_AA);
     while (keep_doing) {
         keep_doing = false;
-        Zone::FindResult db_result = result.zone->find(qname_, qtype_);
+        std::auto_ptr<RRsetList> target(qtype_is_any ? new RRsetList : NULL);
+        Zone::FindResult db_result =
+            result.zone->find(qname_, qtype_, target.get());
+
         switch (db_result.code) {
+            case Zone::CNAME:
+                /*
+                 * We don't do chaining yet. Therefore handling a CNAME is
+                 * mostly the same as handling SUCCESS, but we didn't get
+                 * what we expected. It means no exceptions in ANY or NS
+                 * on the origin (though CNAME in origin is probably
+                 * forbidden anyway).
+                 */
+                // No break; here, fall trough.
             case Zone::SUCCESS:
                 response_.setRcode(Rcode::NOERROR());
-                response_.addRRset(Message::SECTION_ANSWER,
-                    boost::const_pointer_cast<RRset>(db_result.rrset));
-                // Handle additional for answer section
-                getAdditional(*result.zone, *db_result.rrset);
+                if (qtype_is_any) {
+                    // If quety type is ANY, insert all RRs under the domain
+                    // into answer section.
+                    BOOST_FOREACH(RRsetPtr rrset, *target) {
+                        response_.addRRset(Message::SECTION_ANSWER, rrset);
+                    }
+                } else {
+                    response_.addRRset(Message::SECTION_ANSWER,
+                        boost::const_pointer_cast<RRset>(db_result.rrset));
+                    // Handle additional for answer section
+                    getAdditional(*result.zone, *db_result.rrset);
+                }
                 // If apex NS records haven't been provided in the answer
                 // section, insert apex NS records into the authority section
                 // and AAAA/A RRS of each of the NS RDATA into the additional
                 // section.
                 if (qname_ != result.zone->getOrigin() ||
-                    (qtype_ != RRType::NS() && qtype_ != RRType::ANY()))
+                    db_result.code != Zone::SUCCESS ||
+                    (qtype_ != RRType::NS() && !qtype_is_any))
                 {
                     getAuthAdditional(*result.zone);
                 }
@@ -175,7 +199,6 @@ Query::process() const {
                 response_.setRcode(Rcode::NOERROR());
                 putSOA(*result.zone);
                 break;
-            case Zone::CNAME:
             case Zone::DNAME:
                 // TODO : replace qname, continue lookup
                 break;

+ 4 - 2
src/bin/auth/tests/auth_srv_unittest.cc

@@ -138,9 +138,10 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
     createRequestPacket(request_message, IPPROTO_UDP);
 
     (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_message,
                                      response_obuffer, &dnsserv);
     (*server.getDNSAnswerProvider())(*io_message, parse_message,
-                                     response_obuffer);
+                                     response_message, response_obuffer);
 
     createBuiltinVersionResponse(default_qid, response_data);
     EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -153,9 +154,10 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
 TEST_F(AuthSrvTest, iqueryViaDNSServer) {
     createDataFromFile("iquery_fromWire.wire");
     (*server.getDNSLookupProvider())(*io_message, parse_message,
+                                     response_message,
                                      response_obuffer, &dnsserv);
     (*server.getDNSAnswerProvider())(*io_message, parse_message,
-                                     response_obuffer);
+                                     response_message, response_obuffer);
 
     UnitTestUtil::readWireData("iquery_response_fromWire.wire",
                                response_data);

+ 151 - 10
src/bin/auth/tests/query_unittest.cc

@@ -59,18 +59,24 @@ const char* const ns_addrs_txt =
     "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
     "noglue.example.com. 3600 IN A 192.0.2.53\n";
 const char* const delegation_txt =
-        "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
-        "delegation.example.com. 3600 IN NS noglue.example.com.\n"
-        "delegation.example.com. 3600 IN NS cname.example.com.\n"
-        "delegation.example.com. 3600 IN NS example.org.\n";
+    "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
+    "delegation.example.com. 3600 IN NS noglue.example.com.\n"
+    "delegation.example.com. 3600 IN NS cname.example.com.\n"
+    "delegation.example.com. 3600 IN NS example.org.\n";
 const char* const mx_txt =
     "mx.example.com. 3600 IN MX 10 www.example.com.\n"
     "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
     "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
 const char* const www_a_txt = "www.example.com. 3600 IN A 192.0.2.80\n";
+const char* const cname_txt =
+    "cname.example.com. 3600 IN CNAME www.example.com.\n";
+const char* const cname_nxdom_txt =
+    "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
+// CNAME Leading out of zone
+const char* const cname_out_txt =
+    "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
 // The rest of data won't be referenced from the test cases.
 const char* const other_zone_rrs =
-    "cname.example.com. 3600 IN CNAME www.example.com.\n"
     "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
     "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
     "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
@@ -95,7 +101,8 @@ public:
     {
         stringstream zone_stream;
         zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
-            delegation_txt << mx_txt << www_a_txt << other_zone_rrs;
+            delegation_txt << mx_txt << www_a_txt << cname_txt <<
+            cname_nxdom_txt << cname_out_txt << other_zone_rrs;
 
         masterLoad(zone_stream, origin_, rrclass_,
                    boost::bind(&MockZone::loadRRset, this, _1));
@@ -104,6 +111,7 @@ public:
     virtual const isc::dns::RRClass& getClass() const { return (rrclass_); }
     virtual FindResult find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
+                            RRsetList* target = NULL,
                             const FindOptions options = FIND_DEFAULT) const;
 
     // If false is passed, it makes the zone broken as if it didn't have the
@@ -136,7 +144,7 @@ private:
 
 Zone::FindResult
 MockZone::find(const Name& name, const RRType& type,
-               const FindOptions options) const
+               RRsetList* target, const FindOptions options) const
 {
     // Emulating a broken zone: mandatory apex RRs are missing if specifically
     // configured so (which are rare cases).
@@ -165,8 +173,15 @@ MockZone::find(const Name& name, const RRType& type,
             return (FindResult(SUCCESS, found_rrset->second));
         }
 
-        // If not found but the qtype is ANY, return the first RRset
-        if (type == RRType::ANY()) {
+        // If not found but we have a target, fill it with all RRsets here
+        if (!found_domain->second.empty() && target != NULL) {
+            for (found_rrset = found_domain->second.begin();
+                 found_rrset != found_domain->second.end(); found_rrset++)
+            {
+                // Insert RRs under the domain name into target
+                target->addRRset(
+                    boost::const_pointer_cast<RRset>(found_rrset->second));
+            }
             return (FindResult(SUCCESS, found_domain->second.begin()->second));
         }
 
@@ -228,7 +243,8 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
     if (expected_answer != NULL) {
         rrsetsCheck(expected_answer,
                     response.beginSection(Message::SECTION_ANSWER),
-                    response.endSection(Message::SECTION_ANSWER));
+                    response.endSection(Message::SECTION_ANSWER),
+                    check_origin);
     }
     if (expected_authority != NULL) {
         rrsetsCheck(expected_authority,
@@ -282,6 +298,7 @@ TEST_F(QueryTest, apexNSMatch) {
                   zone_ns_txt, NULL, ns_addrs_txt);
 }
 
+// test type any query logic
 TEST_F(QueryTest, exactAnyMatch) {
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
@@ -295,6 +312,33 @@ TEST_F(QueryTest, exactAnyMatch) {
                   "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
 }
 
+TEST_F(QueryTest, apexAnyMatch) {
+    // find match rrset, omit additional data which has already been provided
+    // in the answer section from the additional.
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"),
+                          RRType::ANY(), response).process());
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 0, 0,
+                  "example.com. 3600 IN SOA . . 0 0 0 0 0\n"
+                  "example.com. 3600 IN NS glue.delegation.example.com.\n"
+                  "example.com. 3600 IN NS noglue.example.com.\n"
+                  "example.com. 3600 IN NS example.net.\n",
+                  NULL, NULL, mock_zone->getOrigin());
+}
+
+TEST_F(QueryTest, glueANYMatch) {
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("delegation.example.com"),
+                          RRType::ANY(), response).process());
+    responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
+                  NULL, delegation_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, nodomainANY) {
+    EXPECT_NO_THROW(Query(memory_datasrc, Name("nxdomain.example.com"),
+                          RRType::ANY(), response).process());
+    responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
+                  NULL, soa_txt, NULL, mock_zone->getOrigin());
+}
+
 // This tests that when we need to look up Zone's apex NS records for
 // authoritative answer, and there is no apex NS records. It should
 // throw in that case.
@@ -385,4 +429,101 @@ TEST_F(QueryTest, MXAlias) {
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
                   NULL, NULL, ns_addrs_txt);
 }
+
+/*
+ * Tests encountering a cname.
+ *
+ * There are tests leading to successful answers, NXRRSET, NXDOMAIN and
+ * out of the zone.
+ *
+ * TODO: We currently don't do chaining, so only the CNAME itself should be
+ * returned.
+ */
+TEST_F(QueryTest, CNAME) {
+    Query(memory_datasrc, Name("cname.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, explicitCNAME) {
+    // same owner name as the CNAME test but explicitly query for CNAME RR.
+    // expect the same response as we don't provide a full chain yet.
+    Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_NX_RRSET) {
+    // Leads to www.example.com, it doesn't have TXT
+    // note: with chaining, what should be expected is not trivial:
+    // BIND 9 returns the CNAME in answer and SOA in authority, no additional.
+    // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
+    Query(memory_datasrc, Name("cname.example.com"), RRType::TXT(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+    // same owner name as the NXRRSET test but explicitly query for CNAME RR.
+    Query(memory_datasrc, Name("cname.example.com"), RRType::CNAME(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+    // Leads to nxdomain.example.com
+    // note: with chaining, what should be expected is not trivial:
+    // BIND 9 returns the CNAME in answer and SOA in authority, no additional,
+    // RCODE being NXDOMAIN.
+    // NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
+    // RCODE being NOERROR.
+    Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+    // same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
+    Query(memory_datasrc, Name("cnamenxdom.example.com"), RRType::CNAME(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, CNAME_OUT) {
+    /*
+     * This leads out of zone. This should have only the CNAME even
+     * when we do chaining.
+     *
+     * TODO: We should be able to have two zones in the mock data source.
+     * Then the same test should be done with .org included there and
+     * see what it does (depends on what we want to do)
+     */
+    Query(memory_datasrc, Name("cnameout.example.com"), RRType::A(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_out_txt, zone_ns_txt, ns_addrs_txt);
+}
+
+TEST_F(QueryTest, explicitCNAME_OUT) {
+    // same owner name as the OUT test but explicitly query for CNAME RR.
+    Query(memory_datasrc, Name("cnameout.example.com"), RRType::CNAME(),
+        response).process();
+
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
+        cname_out_txt, zone_ns_txt, ns_addrs_txt);
+}
+
 }

+ 108 - 12
src/bin/msgq/msgq.py.in

@@ -127,6 +127,7 @@ class MsgQ:
         self.hostname = socket.gethostname()
         self.subs = SubscriptionManager()
         self.lnames = {}
+        self.sendbuffs = {}
 
     def setup_poller(self):
         """Set up the poll thing.  Internal function."""
@@ -135,12 +136,29 @@ class MsgQ:
         except AttributeError:
             self.kqueue = select.kqueue()
     
-    def add_kqueue_socket(self, socket):
-        event = select.kevent(socket.fileno(),
-                              select.KQ_FILTER_READ,
+    def add_kqueue_socket(self, socket, write_filter=False):
+        """Add a kquque filter for a socket.  By default the read
+        filter is used; if write_filter is set to True, the write
+        filter is used.  We use a boolean value instead of a specific
+        filter constant, because kqueue filter values do not seem to
+        be defined on some systems.  The use of boolean makes the
+        interface restrictive because there are other filters, but this
+        method is mostly only for our internal use, so it should be
+        acceptable at least for now."""
+        filter_type = select.KQ_FILTER_WRITE if write_filter else \
+            select.KQ_FILTER_READ
+        event = select.kevent(socket.fileno(), filter_type,
                               select.KQ_EV_ADD | select.KQ_EV_ENABLE)
         self.kqueue.control([event], 0)
 
+    def delete_kqueue_socket(self, socket, write_filter=False):
+        """Delete a kqueue filter for socket.  See add_kqueue_socket()
+        for the semantics and notes about write_filter."""
+        filter_type = select.KQ_FILTER_WRITE if write_filter else \
+            select.KQ_FILTER_READ
+        event = select.kevent(socket.fileno(), filter_type,
+                              select.KQ_EV_DELETE)
+        self.kqueue.control([event], 0)
 
     def setup_listener(self):
         """Set up the listener socket.  Internal function."""
@@ -187,6 +205,12 @@ class MsgQ:
         # TODO: When we have logging, we might want
         # to add a debug message here that a new connection
         # was made
+        self.register_socket(newsocket)
+
+    def register_socket(self, newsocket):
+        """
+        Internal function to insert a socket. Used by process_accept and some tests.
+        """
         self.sockets[newsocket.fileno()] = newsocket
         lname = self.newlname()
         self.lnames[lname] = newsocket
@@ -198,10 +222,10 @@ class MsgQ:
 
     def process_socket(self, fd):
         """Process a read on a socket."""
-        sock = self.sockets[fd]
-        if sock == None:
+        if not fd in self.sockets:
             sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
             return
+        sock = self.sockets[fd]
 #        sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
         self.process_packet(fd, sock)
 
@@ -213,7 +237,9 @@ class MsgQ:
         lname = [ k for k, v in self.lnames.items() if v == sock ][0]
         del self.lnames[lname]
         sock.close()
-        self.sockets[fd] = None
+        del self.sockets[fd]
+        if fd in self.sendbuffs:
+            del self.sendbuffs[fd]
         sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
 
     def getbytes(self, fd, sock, length):
@@ -287,6 +313,9 @@ class MsgQ:
             self.process_command_unsubscribe(sock, routing, data)
         elif cmd == 'getlname':
             self.process_command_getlname(sock, routing, data)
+        elif cmd == 'ping':
+            # Command for testing purposes
+            self.process_command_ping(sock, routing, data)
         else:
             sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
 
@@ -305,10 +334,67 @@ class MsgQ:
         return ret
 
     def sendmsg(self, sock, env, msg = None):
-        sock.send(self.preparemsg(env, msg))
+        self.send_prepared_msg(sock, self.preparemsg(env, msg))
+
+    def __send_data(self, sock, data):
+        try:
+            # We set the socket nonblocking, MSG_DONTWAIT doesn't exist
+            # on some OSes
+            sock.setblocking(0)
+            return sock.send(data)
+        except socket.error as e:
+            if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+                return 0
+            else:
+                raise e
+        finally:
+            # And set it back again
+            sock.setblocking(1)
 
     def send_prepared_msg(self, sock, msg):
-        sock.send(msg)
+        # Try to send the data, but only if there's nothing waiting
+        fileno = sock.fileno()
+        if fileno in self.sendbuffs:
+            amount_sent = 0
+        else:
+            amount_sent = self.__send_data(sock, msg)
+
+        # Still something to send
+        if amount_sent < len(msg):
+            now = time.clock()
+            # Append it to buffer (but check the data go away)
+            if fileno in self.sendbuffs:
+                (last_sent, buff) = self.sendbuffs[fileno]
+                if now - last_sent > 0.1:
+                    self.kill_socket(fileno, sock)
+                    return
+                buff += msg
+            else:
+                buff = msg[amount_sent:]
+                last_sent = now
+                if self.poller:
+                    self.poller.register(fileno, select.POLLIN |
+                        select.POLLOUT)
+                else:
+                    self.add_kqueue_socket(sock, True)
+            self.sendbuffs[fileno] = (last_sent, buff)
+
+    def __process_write(self, fileno):
+        # Try to send some data from the buffer
+        (_, msg) = self.sendbuffs[fileno]
+        sock = self.sockets[fileno]
+        amount_sent = self.__send_data(sock, msg)
+        # Keep the rest
+        msg = msg[amount_sent:]
+        if len(msg) == 0:
+            # If there's no more, stop requesting for write availability
+            if self.poller:
+                self.poller.register(fileno, select.POLLIN)
+            else:
+                self.delete_kqueue_socket(sock, True)
+            del self.sendbuffs[fileno]
+        else:
+            self.sendbuffs[fileno] = (time.clock(), msg)
 
     def newlname(self):
         """Generate a unique connection identifier for this socket.
@@ -317,6 +403,9 @@ class MsgQ:
         self.connection_counter += 1
         return "%x_%x@%s" % (time.time(), self.connection_counter, self.hostname)
 
+    def process_command_ping(self, sock, routing, data):
+        self.sendmsg(sock, { "type" : "pong" }, data)
+
     def process_command_getlname(self, sock, routing, data):
         lname = [ k for k, v in self.lnames.items() if v == sock ][0]
         self.sendmsg(sock, { "type" : "getlname" }, { "lname" : lname })
@@ -379,22 +468,29 @@ class MsgQ:
                 if fd == self.listen_socket.fileno():
                     self.process_accept()
                 else:
-                    self.process_socket(fd)
+                    if event & select.POLLOUT:
+                        self.__process_write(fd)
+                    if event & select.POLLIN:
+                        self.process_socket(fd)
 
     def run_kqueue(self):
         while True:
             events = self.kqueue.control(None, 10)
             if not events:
                 raise RuntimeError('serve: kqueue returned no events')
-            
+
             for event in events:
                 if event.ident == self.listen_socket.fileno():
                     self.process_accept()
                 else:
-                    if event.flags & select.KQ_FILTER_READ and event.data > 0:
+                    if event.filter == select.KQ_FILTER_WRITE:
+                        self.__process_write(event.ident)
+                    if event.filter == select.KQ_FILTER_READ and \
+                            event.data > 0:
                         self.process_socket(event.ident)
                     elif event.flags & select.KQ_EV_EOF:
-                        self.kill_socket(event.ident, self.sockets[event.ident])
+                        self.kill_socket(event.ident,
+                                         self.sockets[event.ident])
 
     def shutdown(self):
         """Stop the MsgQ master."""

+ 127 - 2
src/bin/msgq/tests/msgq_test.py

@@ -3,10 +3,14 @@ from msgq import SubscriptionManager, MsgQ
 import unittest
 import os
 import socket
+import signal
+import sys
+import time
+import isc.cc
 
 #
-# Currently only the subscription part is implemented...  I'd have to mock
-# out a socket, which, while not impossible, is not trivial.
+# Currently only the subscription part and some sending is implemented...
+# I'd have to mock out a socket, which, while not impossible, is not trivial.
 #
 
 class TestSubscriptionManager(unittest.TestCase):
@@ -108,5 +112,126 @@ class TestSubscriptionManager(unittest.TestCase):
         msgq = MsgQ("/does/not/exist")
         self.assertRaises(socket.error, msgq.setup)
 
+class SendNonblock(unittest.TestCase):
+    """
+    Tests that the whole thing will not get blocked if someone does not read.
+    """
+
+    def terminate_check(self, task, timeout = 10):
+        """
+        Runs task in separate process (task is a function) and checks
+        it terminates sooner than timeout.
+        """
+        task_pid = os.fork()
+        if task_pid == 0:
+            # Kill the forked process after timeout by SIGALRM
+            signal.alarm(timeout)
+            # Run the task
+            # If an exception happens or we run out of time, we terminate
+            # with non-zero
+            task()
+            # If we got here, then everything worked well and in time
+            # In that case, we terminate successfully
+            sys.exit()
+        else:
+            (pid, status) = os.waitpid(task_pid, 0)
+            self.assertEqual(0, status,
+                "The task did not complete successfully in time")
+
+    def infinite_sender(self, sender):
+        """
+        Sends data until an exception happens. socket.error is caught,
+        as it means the socket got closed. Sender is called to actually
+        send the data.
+        """
+        msgq = MsgQ()
+        # We do only partial setup, so we don't create the listening socket
+        msgq.setup_poller()
+        (read, write) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        msgq.register_socket(write)
+        # Keep sending while it is not closed by the msgq
+        try:
+            while True:
+                sender(msgq, write)
+        except socket.error:
+            pass
+
+    def test_infinite_sendmsg(self):
+        """
+        Tries sending messages (and not reading them) until it either times
+        out (in blocking call, wrong) or closes it (correct).
+        """
+        data = "data"
+        for i in range(1, 10):
+            data += data
+        self.terminate_check(lambda: self.infinite_sender(
+            lambda msgq, socket: msgq.sendmsg(socket, {}, {"message" : data})))
+
+    def test_infinite_sendprepared(self):
+        """
+        Tries sending data (and not reading them) until it either times
+        out (in blocking call, wrong) or closes it (correct).
+        """
+        data = b"data"
+        for i in range(1, 10):
+            data += data
+        self.terminate_check(lambda: self.infinite_sender(
+            lambda msgq, socket: msgq.send_prepared_msg(socket, data)))
+
+    def send_many(self, data):
+        """
+        Tries that sending a command many times and getting an answer works.
+        """
+        msgq = MsgQ()
+        # msgq.run needs to compare with the listen_socket, so we provide
+        # a replacement
+        class DummySocket:
+            def fileno():
+                return -1
+        msgq.listen_socket = DummySocket
+        (queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+        def run():
+            length = len(data)
+            queue_pid = os.fork()
+            if queue_pid == 0:
+                signal.alarm(30)
+                msgq.setup_poller()
+                msgq.register_socket(queue)
+                msgq.run()
+            else:
+                try:
+                    def killall(signum, frame):
+                        os.kill(queue_pid, signal.SIGTERM)
+                        sys.exit(1)
+                    signal.signal(signal.SIGALRM, killall)
+                    msg = msgq.preparemsg({"type" : "ping"}, data)
+                    now = time.clock()
+                    while time.clock() - now < 0.2:
+                        out.sendall(msg)
+                        # Check the answer
+                        (routing, received) = msgq.read_packet(out.fileno(),
+                            out)
+                        self.assertEqual({"type" : "pong"},
+                            isc.cc.message.from_wire(routing))
+                        self.assertEqual(data, received)
+                finally:
+                    os.kill(queue_pid, signal.SIGTERM)
+        self.terminate_check(run)
+
+    def test_small_sends(self):
+        """
+        Tests sending small data many times.
+        """
+        self.send_many(b"data")
+
+    def test_large_sends(self):
+        """
+        Tests sending large data many times.
+        """
+        data = b"data"
+        for i in range(1, 20):
+            data = data + data
+        self.send_many(data)
+
 if __name__ == '__main__':
     unittest.main()

+ 1 - 0
src/bin/resolver/Makefile.am

@@ -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

+ 184 - 127
src/bin/resolver/resolver.cc

@@ -64,7 +64,9 @@ private:
 public:
     ResolverImpl() :
         config_session_(NULL),
-        timeout_(2000),
+        query_timeout_(2000),
+        client_timeout_(4000),
+        lookup_timeout_(30000),
         retries_(3),
         rec_query_(NULL)
     {}
@@ -76,7 +78,12 @@ public:
     void querySetup(DNSService& dnss) {
         assert(!rec_query_); // queryShutdown must be called first
         dlog("Query setup");
-        rec_query_ = new RecursiveQuery(dnss, upstream_, timeout_, retries_);
+        rec_query_ = new RecursiveQuery(dnss, upstream_,
+                                        upstream_root_,
+                                        query_timeout_,
+                                        client_timeout_,
+                                        lookup_timeout_,
+                                        retries_);
     }
 
     void queryShutdown() {
@@ -95,20 +102,37 @@ public:
     {
         upstream_ = upstream;
         if (dnss) {
-            if (upstream_.empty()) {
-                dlog("Asked to do full recursive, but not implemented yet. "
-                    "I'll do nothing.",true);
-            } else {
+            if (!upstream_.empty()) {
                 dlog("Setting forward addresses:");
                 BOOST_FOREACH(const addr_t& address, upstream) {
                     dlog(" " + address.first + ":" +
                         boost::lexical_cast<string>(address.second));
                 }
+            } else {
+                dlog("No forward addresses, running in recursive mode");
+            }
+        }
+    }
+
+    void setRootAddresses(const vector<addr_t>& upstream_root,
+                          DNSService *dnss)
+    {
+        upstream_root_ = upstream_root;
+        if (dnss) {
+            if (!upstream_root_.empty()) {
+                dlog("Setting root addresses:");
+                BOOST_FOREACH(const addr_t& address, upstream_root) {
+                    dlog(" " + address.first + ":" +
+                        boost::lexical_cast<string>(address.second));
+                }
+            } else {
+                dlog("No root addresses");
             }
         }
     }
 
-    void processNormalQuery(const Question& question, MessagePtr message,
+    void processNormalQuery(const Question& question,
+                            MessagePtr answer_message,
                             OutputBufferPtr buffer,
                             DNSServer* server);
 
@@ -117,13 +141,20 @@ public:
 
     /// These members are public because Resolver accesses them directly.
     ModuleCCSession* config_session_;
+    /// Addresses of the root nameserver(s)
+    vector<addr_t> upstream_root_;
     /// Addresses of the forward nameserver
     vector<addr_t> upstream_;
     /// Addresses we listen on
     vector<addr_t> listen_;
 
-    /// Time in milliseconds, to timeout
-    int timeout_;
+    /// Timeout for outgoing queries in milliseconds
+    int query_timeout_;
+    /// Timeout for incoming client queries in milliseconds
+    int client_timeout_;
+    /// Timeout for lookup processing in milliseconds
+    int lookup_timeout_;
+    
     /// Number of retries after timeout
     unsigned retries_;
 
@@ -149,20 +180,6 @@ public:
     MessagePtr message_;
 };
 
-class SectionInserter {
-public:
-    SectionInserter(MessagePtr message, const Message::Section sect) :
-        message_(message), section_(sect)
-    {}
-    void operator()(const RRsetPtr rrset) {
-        //dlog("Adding RRSet to message section " +
-        //    boost::lexical_cast<string>(section_));
-        message_->addRRset(section_, rrset, true);
-    }
-    MessagePtr message_;
-    const Message::Section section_;
-};
-
 void
 makeErrorMessage(MessagePtr message, OutputBufferPtr buffer,
                  const Rcode& rcode)
@@ -210,10 +227,14 @@ public:
     MessageLookup(Resolver* srv) : server_(srv) {}
 
     // \brief Handle the DNS Lookup
-    virtual void operator()(const IOMessage& io_message, MessagePtr message,
-                            OutputBufferPtr buffer, DNSServer* server) const
+    virtual void operator()(const IOMessage& io_message,
+                            MessagePtr query_message,
+                            MessagePtr answer_message,
+                            OutputBufferPtr buffer,
+                            DNSServer* server) const
     {
-        server_->processMessage(io_message, message, buffer, server);
+        server_->processMessage(io_message, query_message,
+                                answer_message, buffer, server);
     }
 private:
     Resolver* server_;
@@ -226,76 +247,62 @@ private:
 class MessageAnswer : public DNSAnswer {
 public:
     virtual void operator()(const IOMessage& io_message,
-                            MessagePtr message,
+                            MessagePtr query_message,
+                            MessagePtr answer_message,
                             OutputBufferPtr buffer) const
     {
-        const qid_t qid = message->getQid();
-        const bool rd = message->getHeaderFlag(Message::HEADERFLAG_RD);
-        const bool cd = message->getHeaderFlag(Message::HEADERFLAG_CD);
-        const Opcode& opcode = message->getOpcode();
-        const Rcode& rcode = message->getRcode();
-        vector<QuestionPtr> questions;
-        questions.assign(message->beginQuestion(), message->endQuestion());
+        const qid_t qid = query_message->getQid();
+        const bool rd = query_message->getHeaderFlag(Message::HEADERFLAG_RD);
+        const bool cd = query_message->getHeaderFlag(Message::HEADERFLAG_CD);
+        const Opcode& opcode = query_message->getOpcode();
 
-        message->clear(Message::RENDER);
-        message->setQid(qid);
-        message->setOpcode(opcode);
-        message->setRcode(rcode);
+        // Fill in the final details of the answer message
+        answer_message->setQid(qid);
+        answer_message->setOpcode(opcode);
 
-        message->setHeaderFlag(Message::HEADERFLAG_QR);
-        message->setHeaderFlag(Message::HEADERFLAG_RA);
+        answer_message->setHeaderFlag(Message::HEADERFLAG_QR);
+        answer_message->setHeaderFlag(Message::HEADERFLAG_RA);
         if (rd) {
-            message->setHeaderFlag(Message::HEADERFLAG_RD);
+            answer_message->setHeaderFlag(Message::HEADERFLAG_RD);
         }
         if (cd) {
-            message->setHeaderFlag(Message::HEADERFLAG_CD);
-        }
-
-
-        // Copy the question section.
-        for_each(questions.begin(), questions.end(), QuestionInserter(message));
-
-        // If the buffer already has an answer in it, copy RRsets from
-        // that into the new message, then clear the buffer and render
-        // the new message into it.
-        if (buffer->getLength() != 0) {
-            try {
-                Message incoming(Message::PARSE);
-                InputBuffer ibuf(buffer->getData(), buffer->getLength());
-                incoming.fromWire(ibuf);
-                message->setRcode(incoming.getRcode());
-                for_each(incoming.beginSection(Message::SECTION_ANSWER),
-                         incoming.endSection(Message::SECTION_ANSWER),
-                         SectionInserter(message, Message::SECTION_ANSWER));
-                for_each(incoming.beginSection(Message::SECTION_AUTHORITY),
-                         incoming.endSection(Message::SECTION_AUTHORITY),
-                         SectionInserter(message, Message::SECTION_AUTHORITY));
-                for_each(incoming.beginSection(Message::SECTION_ADDITIONAL),
-                         incoming.endSection(Message::SECTION_ADDITIONAL),
-                         SectionInserter(message, Message::SECTION_ADDITIONAL));
-            } catch (const Exception& ex) {
-                // Incoming message couldn't be read, we just SERVFAIL
-                message->setRcode(Rcode::SERVFAIL());
-            }
+            answer_message->setHeaderFlag(Message::HEADERFLAG_CD);
         }
 
+        vector<QuestionPtr> questions;
+        questions.assign(query_message->beginQuestion(), query_message->endQuestion());
+        for_each(questions.begin(), questions.end(), QuestionInserter(answer_message));
+        
         // Now we can clear the buffer and render the new message into it
         buffer->clear();
         MessageRenderer renderer(*buffer);
 
+        ConstEDNSPtr edns(query_message->getEDNS());
+        const bool dnssec_ok = edns && edns->getDNSSECAwareness();
+        if (edns) {
+            EDNSPtr edns_response(new EDNS());
+            edns_response->setDNSSECAwareness(dnssec_ok);
+
+            // TODO: We should make our own edns bufsize length configurable
+            edns_response->setUDPSize(Message::DEFAULT_MAX_EDNS0_UDPSIZE);
+            answer_message->setEDNS(edns_response);
+        }
+        
         if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-            ConstEDNSPtr edns(message->getEDNS());
-            renderer.setLengthLimit(edns ? edns->getUDPSize() :
-                Message::DEFAULT_MAX_UDPSIZE);
+            if (edns) {
+                renderer.setLengthLimit(edns->getUDPSize());
+            } else {
+                renderer.setLengthLimit(Message::DEFAULT_MAX_UDPSIZE);
+            }
         } else {
             renderer.setLengthLimit(65535);
         }
 
-        message->toWire(renderer);
+        answer_message->toWire(renderer);
 
         dlog(string("sending a response (") +
             boost::lexical_cast<string>(renderer.getLength()) + "bytes): \n" +
-            message->toText());
+            answer_message->toText());
     }
 };
 
@@ -345,18 +352,21 @@ Resolver::getConfigSession() const {
 }
 
 void
-Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
-                        OutputBufferPtr buffer, DNSServer* server)
+Resolver::processMessage(const IOMessage& io_message,
+                         MessagePtr query_message,
+                         MessagePtr answer_message,
+                         OutputBufferPtr buffer,
+                         DNSServer* server)
 {
     dlog("Got a DNS message");
     InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
     // First, check the header part.  If we fail even for the base header,
     // just drop the message.
     try {
-        message->parseHeader(request_buffer);
+        query_message->parseHeader(request_buffer);
 
         // Ignore all responses.
-        if (message->getHeaderFlag(Message::HEADERFLAG_QR)) {
+        if (query_message->getHeaderFlag(Message::HEADERFLAG_QR)) {
             dlog("Received unexpected response, ignoring");
             server->resume(false);
             return;
@@ -369,52 +379,53 @@ Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
 
     // Parse the message.  On failure, return an appropriate error.
     try {
-        message->fromWire(request_buffer);
+        query_message->fromWire(request_buffer);
     } catch (const DNSProtocolError& error) {
         dlog(string("returning ") + error.getRcode().toText() + ": " + 
             error.what());
-        makeErrorMessage(message, buffer, error.getRcode());
+        makeErrorMessage(query_message, buffer, error.getRcode());
         server->resume(true);
         return;
     } catch (const Exception& ex) {
         dlog(string("returning SERVFAIL: ") + ex.what());
-        makeErrorMessage(message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(query_message, buffer, Rcode::SERVFAIL());
         server->resume(true);
         return;
     } // other exceptions will be handled at a higher layer.
 
-    dlog("received a message:\n" + message->toText());
+    dlog("received a message:\n" + query_message->toText());
 
     // Perform further protocol-level validation.
     bool sendAnswer = true;
-    if (message->getOpcode() == Opcode::NOTIFY()) {
-        makeErrorMessage(message, buffer, Rcode::NOTAUTH());
+    if (query_message->getOpcode() == Opcode::NOTIFY()) {
+        makeErrorMessage(query_message, buffer, Rcode::NOTAUTH());
         dlog("Notify arrived, but we are not authoritative");
-    } else if (message->getOpcode() != Opcode::QUERY()) {
-        dlog("Unsupported opcode (got: " + message->getOpcode().toText() +
+    } else if (query_message->getOpcode() != Opcode::QUERY()) {
+        dlog("Unsupported opcode (got: " + query_message->getOpcode().toText() +
             ", expected: " + Opcode::QUERY().toText());
-        makeErrorMessage(message, buffer, Rcode::NOTIMP());
-    } else if (message->getRRCount(Message::SECTION_QUESTION) != 1) {
+        makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
+    } else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) {
         dlog("The query contained " +
-            boost::lexical_cast<string>(message->getRRCount(
+            boost::lexical_cast<string>(query_message->getRRCount(
             Message::SECTION_QUESTION) + " questions, exactly one expected"));
-        makeErrorMessage(message, buffer, Rcode::FORMERR());
+        makeErrorMessage(query_message, buffer, Rcode::FORMERR());
     } else {
-        ConstQuestionPtr question = *message->beginQuestion();
+        ConstQuestionPtr question = *query_message->beginQuestion();
         const RRType &qtype = question->getType();
         if (qtype == RRType::AXFR()) {
             if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-                makeErrorMessage(message, buffer, Rcode::FORMERR());
+                makeErrorMessage(query_message, buffer, Rcode::FORMERR());
             } else {
-                makeErrorMessage(message, buffer, Rcode::NOTIMP());
+                makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
             }
         } else if (qtype == RRType::IXFR()) {
-            makeErrorMessage(message, buffer, Rcode::NOTIMP());
+            makeErrorMessage(query_message, buffer, Rcode::NOTIMP());
         } else {
             // The RecursiveQuery object will post the "resume" event to the
             // DNSServer when an answer arrives, so we don't have to do it now.
             sendAnswer = false;
-            impl_->processNormalQuery(*question, message, buffer, server);
+            impl_->processNormalQuery(*question, answer_message,
+                                      buffer, server);
         }
     }
 
@@ -424,23 +435,13 @@ Resolver::processMessage(const IOMessage& io_message, MessagePtr message,
 }
 
 void
-ResolverImpl::processNormalQuery(const Question& question, MessagePtr message,
-                                 OutputBufferPtr buffer, DNSServer* server)
+ResolverImpl::processNormalQuery(const Question& question,
+                                 MessagePtr answer_message,
+                                 OutputBufferPtr buffer,
+                                 DNSServer* server)
 {
     dlog("Processing normal query");
-    ConstEDNSPtr edns(message->getEDNS());
-    const bool dnssec_ok = edns && edns->getDNSSECAwareness();
-
-    message->makeResponse();
-    message->setHeaderFlag(Message::HEADERFLAG_RA);
-    message->setRcode(Rcode::NOERROR());
-    if (edns) {
-        EDNSPtr edns_response(new EDNS());
-        edns_response->setDNSSECAwareness(dnssec_ok);
-        edns_response->setUDPSize(ResolverImpl::DEFAULT_LOCAL_UDPSIZE);
-        message->setEDNS(edns_response);
-    }
-    rec_query_->sendQuery(question, buffer, server);
+    rec_query_->sendQuery(question, answer_message, buffer, server);
 }
 
 namespace {
@@ -475,7 +476,7 @@ parseAddresses(ConstElementPtr addresses) {
             }
         } else if (addresses->getType() != Element::null) {
             isc_throw(TypeError,
-                "forward_addresses config element must be a list");
+                "root_addresses, forward_addresses, and listen_on config element must be a list");
         }
     }
     return (result);
@@ -489,21 +490,41 @@ Resolver::updateConfig(ConstElementPtr config) {
 
     try {
         // Parse forward_addresses
+        ConstElementPtr rootAddressesE(config->get("root_addresses"));
+        vector<addr_t> rootAddresses(parseAddresses(rootAddressesE));
         ConstElementPtr forwardAddressesE(config->get("forward_addresses"));
         vector<addr_t> forwardAddresses(parseAddresses(forwardAddressesE));
         ConstElementPtr listenAddressesE(config->get("listen_on"));
         vector<addr_t> listenAddresses(parseAddresses(listenAddressesE));
         bool set_timeouts(false);
-        int timeout = impl_->timeout_;
+        int qtimeout = impl_->query_timeout_;
+        int ctimeout = impl_->client_timeout_;
+        int ltimeout = impl_->lookup_timeout_;
         unsigned retries = impl_->retries_;
-        ConstElementPtr timeoutE(config->get("timeout")),
-            retriesE(config->get("retries"));
-        if (timeoutE) {
+        ConstElementPtr qtimeoutE(config->get("timeout_query")),
+                        ctimeoutE(config->get("timeout_client")),
+                        ltimeoutE(config->get("timeout_lookup")),
+                        retriesE(config->get("retries"));
+        if (qtimeoutE) {
             // It should be safe to just get it, the config manager should
             // check for us
-            timeout = timeoutE->intValue();
-            if (timeout < -1) {
-                isc_throw(BadValue, "Timeout too small");
+            qtimeout = qtimeoutE->intValue();
+            if (qtimeout < -1) {
+                isc_throw(BadValue, "Query timeout too small");
+            }
+            set_timeouts = true;
+        }
+        if (ctimeoutE) {
+            ctimeout = ctimeoutE->intValue();
+            if (ctimeout < -1) {
+                isc_throw(BadValue, "Client timeout too small");
+            }
+            set_timeouts = true;
+        }
+        if (ltimeoutE) {
+            ltimeout = ltimeoutE->intValue();
+            if (ltimeout < -1) {
+                isc_throw(BadValue, "Lookup timeout too small");
             }
             set_timeouts = true;
         }
@@ -526,8 +547,12 @@ Resolver::updateConfig(ConstElementPtr config) {
             setForwardAddresses(forwardAddresses);
             need_query_restart = true;
         }
+        if (rootAddressesE) {
+            setRootAddresses(rootAddresses);
+            need_query_restart = true;
+        }
         if (set_timeouts) {
-            setTimeouts(timeout, retries);
+            setTimeouts(qtimeout, ctimeout, ltimeout, retries);
             need_query_restart = true;
         }
 
@@ -548,6 +573,12 @@ Resolver::setForwardAddresses(const vector<addr_t>& addresses)
     impl_->setForwardAddresses(addresses, dnss_);
 }
 
+void
+Resolver::setRootAddresses(const vector<addr_t>& addresses)
+{
+    impl_->setRootAddresses(addresses, dnss_);
+}
+
 bool
 Resolver::isForwarding() const {
     return (!impl_->upstream_.empty());
@@ -558,6 +589,11 @@ Resolver::getForwardAddresses() const {
     return (impl_->upstream_);
 }
 
+vector<addr_t>
+Resolver::getRootAddresses() const {
+    return (impl_->upstream_root_);
+}
+
 namespace {
 
 void
@@ -603,15 +639,36 @@ Resolver::setListenAddresses(const vector<addr_t>& addresses) {
 }
 
 void
-Resolver::setTimeouts(int timeout, unsigned retries) {
-    dlog("Setting timeout to " + boost::lexical_cast<string>(timeout) +
-        " and retry count to " + boost::lexical_cast<string>(retries));
-    impl_->timeout_ = timeout;
+Resolver::setTimeouts(int query_timeout, int client_timeout,
+                      int lookup_timeout, unsigned retries) {
+    dlog("Setting query timeout to " + boost::lexical_cast<string>(query_timeout) +
+         ", client timeout to " + boost::lexical_cast<string>(client_timeout) +
+         ", lookup timeout to " + boost::lexical_cast<string>(lookup_timeout) +
+         " and retry count to " + boost::lexical_cast<string>(retries));
+    impl_->query_timeout_ = query_timeout;
+    impl_->client_timeout_ = client_timeout;
+    impl_->lookup_timeout_ = lookup_timeout;
     impl_->retries_ = retries;
 }
-pair<int, unsigned>
-Resolver::getTimeouts() const {
-    return (pair<int, unsigned>(impl_->timeout_, impl_->retries_));
+
+int
+Resolver::getQueryTimeout() const {
+    return impl_->query_timeout_;
+}
+
+int
+Resolver::getClientTimeout() const {
+    return impl_->client_timeout_;
+}
+
+int
+Resolver::getLookupTimeout() const {
+    return impl_->lookup_timeout_;
+}
+
+int
+Resolver::getRetries() const {
+    return impl_->retries_;
 }
 
 vector<addr_t>

+ 57 - 2
src/bin/resolver/resolver.h

@@ -63,7 +63,8 @@ public:
     /// \param buffer Pointer to an \c OutputBuffer for the resposne
     /// \param server Pointer to the \c DNSServer
     void processMessage(const asiolink::IOMessage& io_message,
-                        isc::dns::MessagePtr message,
+                        isc::dns::MessagePtr query_message,
+                        isc::dns::MessagePtr answer_message,
                         isc::dns::OutputBufferPtr buffer,
                         asiolink::DNSServer* server);
 
@@ -111,6 +112,24 @@ public:
     bool isForwarding() const;
 
     /**
+     * \brief Specify the list of root nameservers.
+     *
+     * Specify the list of addresses of root nameservers
+     *
+     * @param addresses The list of addresses to use (each one is the address
+     * and port pair).
+     */
+    void setRootAddresses(const std::vector<std::pair<std::string,
+                          uint16_t> >& addresses);
+
+    /**
+     * \short Get list of root addresses.
+     *
+     * \see setRootAddresses.
+     */
+    std::vector<std::pair<std::string, uint16_t> > getRootAddresses() const;
+
+    /**
      * Set and get the addresses we listen on.
      */
     void setListenAddresses(const std::vector<std::pair<std::string,
@@ -125,7 +144,10 @@ public:
      * \param retries The number of retries (0 means try the first time only,
      *     do not retry).
      */
-    void setTimeouts(int timeout = -1, unsigned retries = 0);
+    void setTimeouts(int query_timeout = 2000,
+                     int client_timeout = 4000,
+                     int lookup_timeout = 30000,
+                     unsigned retries = 3);
 
     /**
      * \short Get info about timeouts.
@@ -134,6 +156,39 @@ public:
      */
     std::pair<int, unsigned> getTimeouts() const;
 
+    /**
+     * \brief Get the timeout for outgoing queries
+     *
+     * \returns Timeout for outgoing queries
+     */
+    int getQueryTimeout() const;
+
+    /**
+     * \brief Get the timeout for incoming client queries
+     *
+     * After this timeout, a SERVFAIL shall be sent back
+     * (internal resolving on the query will continue, see
+     * \c getLookupTimeout())
+     * 
+     * \returns Timeout for outgoing queries
+     */
+    int getClientTimeout() const;
+
+    /**
+     * \brief Get the timeout for lookups
+     *
+     * After this timeout, internal processing shall stop
+     */
+    int getLookupTimeout() const;
+
+    /**
+     * \brief Get the number of retries for outgoing queries
+     *
+     * If a query times out (value of \c getQueryTimeout()), we
+     * will retry this number of times
+     */
+    int getRetries() const;
+
 private:
     ResolverImpl* impl_;
     asiolink::DNSService* dnss_;

+ 39 - 1
src/bin/resolver/resolver.spec.pre.in

@@ -4,12 +4,24 @@
     "module_description": "Recursive service",
     "config_data": [
       {
-        "item_name": "timeout",
+        "item_name": "timeout_query",
         "item_type": "integer",
         "item_optional": False,
         "item_default": 2000
       },
       {
+        "item_name": "timeout_client",
+        "item_type": "integer",
+        "item_optional": False,
+        "item_default": 4000
+      },
+      {
+        "item_name": "timeout_lookup",
+        "item_type": "integer",
+        "item_optional": False,
+        "item_default": 30000
+      },
+      {
         "item_name": "retries",
         "item_type": "integer",
         "item_optional": False,
@@ -42,6 +54,32 @@
         }
       },
       {
+        "item_name": "root_addresses",
+        "item_type": "list",
+        "item_optional": True,
+        "item_default": [],
+        "list_item_spec" : {
+          "item_name": "address",
+          "item_type": "map",
+          "item_optional": False,
+          "item_default": {},
+          "map_item_spec": [
+            {
+              "item_name": "address",
+              "item_type": "string",
+              "item_optional": False,
+              "item_default": "::1"
+            },
+            {
+              "item_name": "port",
+              "item_type": "integer",
+              "item_optional": False,
+              "item_default": 53
+            }
+          ]
+        }
+      },
+      {
         "item_name": "listen_on",
         "item_type": "list",
         "item_optional": False,

+ 259 - 0
src/bin/resolver/response_classifier.cc

@@ -0,0 +1,259 @@
+// 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);
+}

+ 138 - 0
src/bin/resolver/response_classifier.h

@@ -0,0 +1,138 @@
+// 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

+ 2 - 0
src/bin/resolver/tests/Makefile.am

@@ -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)

+ 62 - 17
src/bin/resolver/tests/resolver_config_unittest.cc

@@ -96,6 +96,31 @@ TEST_F(ResolverConfig, forwardAddressConfig) {
     EXPECT_EQ(0, server.getForwardAddresses().size());
 }
 
+TEST_F(ResolverConfig, rootAddressConfig) {
+    // Try putting there some address
+    ElementPtr config(Element::fromJSON("{"
+        "\"root_addresses\": ["
+        "   {"
+        "       \"address\": \"192.0.2.1\","
+        "       \"port\": 53"
+        "   }"
+        "]"
+        "}"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    ASSERT_EQ(1, server.getRootAddresses().size());
+    EXPECT_EQ("192.0.2.1", server.getRootAddresses()[0].first);
+    EXPECT_EQ(53, server.getRootAddresses()[0].second);
+
+    // And then remove all addresses
+    config = Element::fromJSON("{"
+        "\"root_addresses\": null"
+        "}");
+    result = server.updateConfig(config);
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(0, server.getRootAddresses().size());
+}
+
 void
 ResolverConfig::invalidTest(const string &JOSN) {
     ElementPtr config(Element::fromJSON(JOSN));
@@ -134,8 +159,8 @@ TEST_F(ResolverConfig, listenAddresses) {
 
     // Try putting there some addresses
     vector<pair<string, uint16_t> > addresses;
-    addresses.push_back(pair<string, uint16_t>("127.0.0.1", 5300));
-    addresses.push_back(pair<string, uint16_t>("::1", 5300));
+    addresses.push_back(pair<string, uint16_t>("127.0.0.1", 5321));
+    addresses.push_back(pair<string, uint16_t>("::1", 5321));
     server.setListenAddresses(addresses);
     EXPECT_EQ(2, server.getListenAddresses().size());
     EXPECT_EQ("::1", server.getListenAddresses()[1].first);
@@ -155,7 +180,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
         "\"listen_on\": ["
         "   {"
         "       \"address\": \"127.0.0.1\","
-        "       \"port\": 5300"
+        "       \"port\": 5321"
         "   }"
         "]"
         "}"));
@@ -163,7 +188,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
     EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
     ASSERT_EQ(1, server.getListenAddresses().size());
     EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
-    EXPECT_EQ(5300, server.getListenAddresses()[0].second);
+    EXPECT_EQ(5321, server.getListenAddresses()[0].second);
 
     // As this is example address, the machine should not have it on
     // any interface
@@ -174,7 +199,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
         "\"listen_on\": ["
         "   {"
         "       \"address\": \"192.0.2.0\","
-        "       \"port\": 5300"
+        "       \"port\": 5321"
         "   }"
         "]"
         "}");
@@ -182,7 +207,7 @@ TEST_F(ResolverConfig, DISABLED_listenAddressConfig) {
     EXPECT_FALSE(result->equals(*isc::config::createAnswer()));
     ASSERT_EQ(1, server.getListenAddresses().size());
     EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first);
-    EXPECT_EQ(5300, server.getListenAddresses()[0].second);
+    EXPECT_EQ(5321, server.getListenAddresses()[0].second);
 }
 
 TEST_F(ResolverConfig, invalidListenAddresses) {
@@ -212,31 +237,51 @@ TEST_F(ResolverConfig, invalidListenAddresses) {
 
 // Just test it sets and gets the values correctly
 TEST_F(ResolverConfig, timeouts) {
-    server.setTimeouts(0, 1);
-    EXPECT_EQ(0, server.getTimeouts().first);
-    EXPECT_EQ(1, server.getTimeouts().second);
+    server.setTimeouts(0, 1, 2, 3);
+    EXPECT_EQ(0, server.getQueryTimeout());
+    EXPECT_EQ(1, server.getClientTimeout());
+    EXPECT_EQ(2, server.getLookupTimeout());
+    EXPECT_EQ(3, server.getRetries());
     server.setTimeouts();
-    EXPECT_EQ(-1, server.getTimeouts().first);
-    EXPECT_EQ(0, server.getTimeouts().second);
+    EXPECT_EQ(2000, server.getQueryTimeout());
+    EXPECT_EQ(4000, server.getClientTimeout());
+    EXPECT_EQ(30000, server.getLookupTimeout());
+    EXPECT_EQ(3, server.getRetries());
 }
 
 TEST_F(ResolverConfig, timeoutsConfig) {
     ElementPtr config = Element::fromJSON("{"
-            "\"timeout\": 1000,"
-            "\"retries\": 3"
+            "\"timeout_query\": 1000,"
+            "\"timeout_client\": 2000,"
+            "\"timeout_lookup\": 3000,"
+            "\"retries\": 4"
             "}");
     ConstElementPtr result(server.updateConfig(config));
     EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
-    EXPECT_EQ(1000, server.getTimeouts().first);
-    EXPECT_EQ(3, server.getTimeouts().second);
+    EXPECT_EQ(1000, server.getQueryTimeout());
+    EXPECT_EQ(2000, server.getClientTimeout());
+    EXPECT_EQ(3000, server.getLookupTimeout());
+    EXPECT_EQ(4, server.getRetries());
 }
 
 TEST_F(ResolverConfig, invalidTimeoutsConfig) {
     invalidTest("{"
-        "\"timeout\": \"error\""
+        "\"timeout_query\": \"error\""
+        "}");
+    invalidTest("{"
+        "\"timeout_query\": -2"
+        "}");
+    invalidTest("{"
+        "\"timeout_client\": \"error\""
+        "}");
+    invalidTest("{"
+        "\"timeout_client\": -2"
+        "}");
+    invalidTest("{"
+        "\"timeout_lookup\": \"error\""
         "}");
     invalidTest("{"
-        "\"timeout\": -2"
+        "\"timeout_lookup\": -2"
         "}");
     invalidTest("{"
         "\"retries\": \"error\""

+ 14 - 3
src/bin/resolver/tests/resolver_unittest.cc

@@ -30,7 +30,10 @@ class ResolverTest : public SrvTestBase{
 protected:
     ResolverTest() : server(){}
     virtual void processMessage() {
-        server.processMessage(*io_message, parse_message, response_obuffer,
+        server.processMessage(*io_message,
+                              parse_message,
+                              response_message,
+                              response_obuffer,
                               &dnsserv);
     }
     Resolver server;
@@ -83,7 +86,11 @@ TEST_F(ResolverTest, AXFRFail) {
                                        RRType::AXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
     // AXFR is not implemented and should always send NOTIMP.
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    server.processMessage(*io_message,
+                          parse_message,
+                          response_message,
+                          response_obuffer,
+                          &dnsserv);
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(),
                 QR_FLAG, 1, 0, 0, 0);
@@ -98,7 +105,11 @@ TEST_F(ResolverTest, notifyFail) {
     request_message.setQid(default_qid);
     request_message.setHeaderFlag(Message::HEADERFLAG_AA);
     createRequestPacket(request_message, IPPROTO_UDP);
-    server.processMessage(*io_message, parse_message, response_obuffer, &dnsserv);
+    server.processMessage(*io_message,
+                          parse_message,
+                          response_message,
+                          response_obuffer,
+                          &dnsserv);
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::NOTAUTH(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);

+ 494 - 0
src/bin/resolver/tests/response_classifier_unittest.cc

@@ -0,0 +1,494 @@
+// Copyright (C) 2010  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.
+
+#include <iostream>
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include <resolver/response_classifier.h>
+
+#include <dns/name.h>
+#include <dns/opcode.h>
+#include <dns/question.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rcode.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace rdata;
+using namespace isc::dns::rdata::generic;
+using namespace isc::dns::rdata::in;
+
+namespace {
+class ResponseClassifierTest : public ::testing::Test {
+public:
+    /// \brief Constructor
+    ///
+    /// The naming convention is:
+    ///
+    /// <category>_<class>_<type>_<name>
+    ///
+    /// <category> is "qu" (question), "rrs" (rrset),
+    /// <qclass> is self-explanatory
+    /// <qtype> is self-explanatory
+    /// <name> is the first part of the domain name (all expected to be in
+    /// example.com)
+    ///
+    /// Message variables
+    ///
+    /// msg_<qtype>  Where <qtype> is the type of query.  These are only used
+    /// in the early tests where simple messages are required.
+
+    ResponseClassifierTest() :
+        msg_a(new Message(Message::RENDER)),
+        msg_any(new Message(Message::RENDER)),
+        qu_ch_a_www(Name("www.example.com"), RRClass::CH(), RRType::A()),
+        qu_in_any_www(Name("www.example.com"), RRClass::IN(), RRType::ANY()),
+        qu_in_a_www2(Name("www2.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_a_www(Name("www.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_cname_www1(Name("www1.example.com"), RRClass::IN(), RRType::A()),
+        qu_in_ns_(Name("example.com"), RRClass::IN(), RRType::NS()),
+        qu_in_txt_www(Name("www.example.com"), RRClass::IN(), RRType::TXT()),
+        rrs_hs_txt_www(new RRset(Name("www.example.com"), RRClass::HS(),
+            RRType::TXT(), RRTTL(300))),
+        rrs_in_a_mail(new RRset(Name("mail.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_a_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::A(), RRTTL(300))),
+        rrs_in_cname_www1(new RRset(Name("www1.example.com"), RRClass::IN(),
+            RRType::CNAME(), RRTTL(300))),
+        rrs_in_cname_www2(new RRset(Name("www2.example.com"), RRClass::IN(),
+            RRType::CNAME(), RRTTL(300))),
+        rrs_in_ns_(new RRset(Name("example.com"), RRClass::IN(),
+            RRType::NS(), RRTTL(300))),
+        rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
+            RRType::TXT(), RRTTL(300)))
+    {
+        // Set up the message to indicate a successful response to the question
+        // "www.example.com A", but don't add in any response sections.
+        msg_a->setHeaderFlag(Message::HEADERFLAG_QR);
+        msg_a->setOpcode(Opcode::QUERY());
+        msg_a->setRcode(Rcode::NOERROR());
+        msg_a->addQuestion(qu_in_a_www);
+
+        // ditto for the query "www.example.com ANY"
+        msg_any->setHeaderFlag(Message::HEADERFLAG_QR);
+        msg_any->setOpcode(Opcode::QUERY());
+        msg_any->setRcode(Rcode::NOERROR());
+        msg_any->addQuestion(qu_in_any_www);
+
+        // The next set of assignments set up the following zone records
+        //
+        // example.com           NS     ns0.isc.org
+        //                       NS     ns0.example.org
+        //
+        // www.example.com       A      1.2.3.4
+        //                       TXT    "An example text string"
+        //
+        // mail.example.com      A      4.5.6.7
+        //
+        // www1.example.com      CNAME  www.example.com
+        //
+        // www2.example.com      CNAME  www1.example.com
+
+        // Set up an imaginary NS RRset for an authority section
+        rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.isc.org"))));
+        rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.example.org"))));
+
+        // Set up the records for the www host
+        rrs_in_a_www->addRdata(ConstRdataPtr(new A("1.2.3.4")));
+        rrs_in_txt_www->addRdata(ConstRdataPtr(
+            new TXT("An example text string")));
+
+        // ... for the mail host
+        rrs_in_a_mail->addRdata(ConstRdataPtr(new A("5.6.7.8")));
+
+        // ... the CNAME records
+        rrs_in_cname_www1->addRdata(ConstRdataPtr(
+            new CNAME("www.example.com")));
+        rrs_in_cname_www2->addRdata(ConstRdataPtr(
+            new CNAME("www1.example.com")));
+    }
+
+    MessagePtr  msg_a;              // Pointer to message in RENDER state
+    MessagePtr  msg_any;            // Pointer to message in RENDER state
+    Question    qu_ch_a_www;        // www.example.com CH A
+    Question    qu_in_any_www;      // www.example.com IN ANY
+    Question    qu_in_a_www2;       // www.example.com IN ANY
+    Question    qu_in_a_www;        // www.example.com IN A
+    Question    qu_in_cname_www1;   // www1.example.com IN CNAME
+    Question    qu_in_ns_;          // example.com IN NS
+    Question    qu_in_txt_www;      // www.example.com IN TXT
+    RRsetPtr    rrs_hs_txt_www;     // www.example.com HS TXT
+    RRsetPtr    rrs_in_a_mail;      // mail.example.com IN A
+    RRsetPtr    rrs_in_a_www;       // www.example.com IN A
+    RRsetPtr    rrs_in_cname_www1;  // www1.example.com IN CNAME
+    RRsetPtr    rrs_in_cname_www2;  // www2.example.com IN CNAME
+    RRsetPtr    rrs_in_ns_;         // example.com IN NS
+    RRsetPtr    rrs_in_txt_www;     // www.example.com IN TXT
+};
+
+// Test that the error() function categorises the codes correctly.
+
+TEST_F(ResponseClassifierTest, StatusCodes) {
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWER));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::ANSWERCNAME));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::CNAME));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::NXDOMAIN));
+    EXPECT_FALSE(ResponseClassifier::error(ResponseClassifier::REFERRAL));
+
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EMPTY));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::EXTRADATA));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVNAMCLASS));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::INVTYPE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MISMATQUEST));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::MULTICLASS));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTONEQUEST));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTRESPONSE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::NOTSINGLE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::OPCODE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::RCODE));
+    EXPECT_TRUE(ResponseClassifier::error(ResponseClassifier::TRUNCATED));
+}
+
+// Test that the system will reject a message which is a query.
+
+TEST_F(ResponseClassifierTest, Query) {
+
+    // Set up message to indicate a query (QR flag = 0, one question).  By
+    // default the opcode will be 0 (query)
+    msg_a->setHeaderFlag(Message::HEADERFLAG_QR, false);
+
+    // Should be rejected as it is a query, not a response
+    EXPECT_EQ(ResponseClassifier::NOTRESPONSE,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+}
+
+// Check that we get an OPCODE error on all but QUERY opcodes.
+
+TEST_F(ResponseClassifierTest, Opcode) {
+
+    uint8_t query = static_cast<uint8_t>(Opcode::QUERY().getCode());
+
+    for (uint8_t i = 0; i < (1 << 4); ++i) {
+        msg_a->setOpcode(Opcode(i));
+        if (i == query) {
+            EXPECT_NE(ResponseClassifier::OPCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        } else {
+            EXPECT_EQ(ResponseClassifier::OPCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        }
+    }
+}
+
+// Test that the system will reject a response with anything other than one
+// question.
+
+TEST_F(ResponseClassifierTest, MultipleQuestions) {
+
+    // Create a message object for this test that has no question section.
+    MessagePtr message(new Message(Message::RENDER));
+    message->setHeaderFlag(Message::HEADERFLAG_QR);
+    message->setOpcode(Opcode::QUERY());
+    message->setRcode(Rcode::NOERROR());
+
+    // Zero questions
+    EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+
+    // One question
+    message->addQuestion(qu_in_a_www);
+    EXPECT_NE(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+
+    // Two questions
+    message->addQuestion(qu_in_ns_);
+    EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+
+    // And finish the check with three questions
+    message->addQuestion(qu_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::NOTONEQUEST,
+        ResponseClassifier::classify(qu_in_a_www, message));
+}
+
+// Test that the question in the question section in the message response
+// is equal to the question supplied.
+
+TEST_F(ResponseClassifierTest, SameQuestion) {
+
+    EXPECT_EQ(ResponseClassifier::MISMATQUEST,
+        ResponseClassifier::classify(qu_in_ns_, msg_a));
+    EXPECT_NE(ResponseClassifier::MISMATQUEST,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+}
+
+// Should get an NXDOMAIN response only on an NXDOMAIN RCODE.
+
+TEST_F(ResponseClassifierTest, NXDOMAIN) {
+
+    uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+
+    for (uint8_t i = 0; i < (1 << 4); ++i) {
+        msg_a->setRcode(Rcode(i));
+        if (i == nxdomain) {
+            EXPECT_EQ(ResponseClassifier::NXDOMAIN,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        } else {
+            EXPECT_NE(ResponseClassifier::NXDOMAIN,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        }
+    }
+}
+
+// Check that we get an RCODE error on all but NXDOMAIN and NOERROR responses.
+
+TEST_F(ResponseClassifierTest, RCODE) {
+
+    uint16_t nxdomain = static_cast<uint16_t>(Rcode::NXDOMAIN().getCode());
+    uint16_t noerror = static_cast<uint16_t>(Rcode::NOERROR().getCode());
+
+    for (uint8_t i = 0; i < (1 << 4); ++i) {
+        msg_a->setRcode(Rcode(i));
+        if ((i == nxdomain) || (i == noerror)) {
+            EXPECT_NE(ResponseClassifier::RCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        } else {
+            EXPECT_EQ(ResponseClassifier::RCODE,
+                ResponseClassifier::classify(qu_in_a_www, msg_a));
+        }
+    }
+}
+
+// Test that the code will detect a truncated message.  Even if nothing else
+// is wrong, we'll want to retry the query if we receive a truncated code.
+// However, we give the option to the user of the code aws to whether they
+// want to take into account the truncated bit.
+
+TEST_F(ResponseClassifierTest, Truncated) {
+
+    // Don't expect the truncated code whatever option we ask for if the TC
+    // bit is not set.
+    msg_a->setHeaderFlag(Message::HEADERFLAG_TC, false);
+    EXPECT_NE(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, true));
+    EXPECT_NE(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, false));
+
+    // Expect the truncated code if the TC bit is set, only if we don't ignore
+    // it.
+    msg_a->setHeaderFlag(Message::HEADERFLAG_TC, true);
+    EXPECT_NE(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, true));
+    EXPECT_EQ(ResponseClassifier::TRUNCATED,
+        ResponseClassifier::classify(qu_in_a_www, msg_a, false));
+}
+
+// Check for an empty packet (i.e. no error, but with the answer and additional
+// sections empty).
+
+TEST_F(ResponseClassifierTest, Empty) {
+
+    EXPECT_EQ(ResponseClassifier::EMPTY,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+}
+
+// Anything where we have an empty answer section but something in the
+// authority section is a referral (if the status is NOERROR).
+
+TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
+
+    msg_a->addRRset(Message::SECTION_AUTHORITY, rrs_in_ns_);
+    EXPECT_EQ(ResponseClassifier::REFERRAL,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+
+}
+
+// Check the case where we have a simple answer in the answer section.  This
+// occurs when the QNAME/QTYPE/QCLASS matches one of the RRsets in the
+// answer section - expect when the QTYPE is ANY, in which case the match
+// must be on the QNAME/QCLASS alone.
+
+TEST_F(ResponseClassifierTest, SingleAnswer) {
+
+    // Check a question that matches the answer
+    msg_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::ANSWER,
+        ResponseClassifier::classify(qu_in_a_www, msg_a));
+
+    // Check an ANY question that matches the answer
+    msg_any->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::ANSWER,
+        ResponseClassifier::classify(qu_in_any_www, msg_any));
+
+    // Check a CNAME response that matches the QNAME.
+    MessagePtr message_a(new Message(Message::RENDER));
+    message_a->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_a->setOpcode(Opcode::QUERY());
+    message_a->setRcode(Rcode::NOERROR());
+    message_a->addQuestion(qu_in_cname_www1);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+    EXPECT_EQ(ResponseClassifier::CNAME,
+        ResponseClassifier::classify(qu_in_cname_www1, message_a));
+
+    // Check if the answer QNAME does not match the question
+    // Q: www.example.com  IN A
+    // A: mail.example.com IN A
+    MessagePtr message_b(new Message(Message::RENDER));
+    message_b->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_b->setOpcode(Opcode::QUERY());
+    message_b->setRcode(Rcode::NOERROR());
+    message_b->addQuestion(qu_in_a_www);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+    EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+        ResponseClassifier::classify(qu_in_a_www, message_b));
+
+    // Check if the answer class does not match the question
+    // Q: www.example.com CH A
+    // A: www.example.com IN A
+    MessagePtr message_c(new Message(Message::RENDER));
+    message_c->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_c->setOpcode(Opcode::QUERY());
+    message_c->setRcode(Rcode::NOERROR());
+    message_c->addQuestion(qu_ch_a_www);
+    message_c->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::INVNAMCLASS,
+        ResponseClassifier::classify(qu_ch_a_www, message_c));
+
+    // Check if the answer type does not match the question
+    // Q: www.example.com IN A
+    // A: www.example.com IN TXT
+    MessagePtr message_d(new Message(Message::RENDER));
+    message_d->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_d->setOpcode(Opcode::QUERY());
+    message_d->setRcode(Rcode::NOERROR());
+    message_d->addQuestion(qu_in_a_www);
+    message_d->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::INVTYPE,
+        ResponseClassifier::classify(qu_in_a_www, message_d));
+}
+
+// Check what happens if we have multiple RRsets in the answer.
+
+TEST_F(ResponseClassifierTest, MultipleAnswerRRsets) {
+
+    // All the same QNAME but different types is only valid on an ANY query.
+    MessagePtr message_a(new Message(Message::RENDER));
+    message_a->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_a->setOpcode(Opcode::QUERY());
+    message_a->setRcode(Rcode::NOERROR());
+    message_a->addQuestion(qu_in_any_www);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::ANSWER,
+        ResponseClassifier::classify(qu_in_any_www, message_a));
+
+    // On another type of query, it results in an EXTRADATA error
+    MessagePtr message_b(new Message(Message::RENDER));
+    message_b->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_b->setOpcode(Opcode::QUERY());
+    message_b->setRcode(Rcode::NOERROR());
+    message_b->addQuestion(qu_in_a_www);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_a_www, message_b));
+
+    // Same QNAME on an ANY query is not valid with mixed classes
+    MessagePtr message_c(new Message(Message::RENDER));
+    message_c->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_c->setOpcode(Opcode::QUERY());
+    message_c->setRcode(Rcode::NOERROR());
+    message_c->addQuestion(qu_in_any_www);
+    message_c->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_c->addRRset(Message::SECTION_ANSWER, rrs_hs_txt_www);
+    EXPECT_EQ(ResponseClassifier::MULTICLASS,
+        ResponseClassifier::classify(qu_in_any_www, message_c));
+
+    // Mixed QNAME is not valid unless QNAME requested is a CNAME.
+    MessagePtr message_d(new Message(Message::RENDER));
+    message_d->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_d->setOpcode(Opcode::QUERY());
+    message_d->setRcode(Rcode::NOERROR());
+    message_d->addQuestion(qu_in_a_www);
+    message_d->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_d->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_a_www, message_d));
+
+    // Mixed QNAME is not valid when the query is an ANY.
+    MessagePtr message_e(new Message(Message::RENDER));
+    message_e->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_e->setOpcode(Opcode::QUERY());
+    message_e->setRcode(Rcode::NOERROR());
+    message_e->addQuestion(qu_in_any_www);
+    message_e->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    message_e->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    message_e->addRRset(Message::SECTION_ANSWER, rrs_in_a_mail);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_any_www, message_e));
+}
+
+// CNAME chain is CNAME if it terminates in a CNAME, answer if it
+// does not, and error if there are RRs left over.
+TEST_F(ResponseClassifierTest, CNAMEChain) {
+
+    // Answer contains a single CNAME
+    MessagePtr message_a(new Message(Message::RENDER));
+    message_a->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_a->setOpcode(Opcode::QUERY());
+    message_a->setRcode(Rcode::NOERROR());
+    message_a->addQuestion(qu_in_a_www2);
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+    EXPECT_EQ(ResponseClassifier::CNAME,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Add a CNAME for www1, and it should still return a CNAME
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+    EXPECT_EQ(ResponseClassifier::CNAME,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Add the A record for www and it should be an answer
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_a_www);
+    EXPECT_EQ(ResponseClassifier::ANSWERCNAME,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Adding an unrelated TXT record should result in EXTRADATA
+    message_a->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+    EXPECT_EQ(ResponseClassifier::EXTRADATA,
+        ResponseClassifier::classify(qu_in_a_www2, message_a));
+
+    // Recreate the chain, but this time end with a TXT RR and not the A
+    // record.  This should return INVTYPE.
+    MessagePtr message_b(new Message(Message::RENDER));
+    message_b->setHeaderFlag(Message::HEADERFLAG_QR);
+    message_b->setOpcode(Opcode::QUERY());
+    message_b->setRcode(Rcode::NOERROR());
+    message_b->addQuestion(qu_in_a_www2);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www2);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_cname_www1);
+    message_b->addRRset(Message::SECTION_ANSWER, rrs_in_txt_www);
+
+    EXPECT_EQ(ResponseClassifier::INVTYPE,
+        ResponseClassifier::classify(qu_in_a_www2, message_b));
+}
+
+} // Anonymous namespace

+ 259 - 15
src/lib/asiolink/asiolink.cc

@@ -30,6 +30,7 @@
 
 #include <dns/buffer.h>
 #include <dns/message.h>
+#include <dns/rcode.h>
 
 #include <asiolink/asiolink.h>
 #include <asiolink/internal/tcpdns.h>
@@ -37,6 +38,7 @@
 
 #include <log/dummylog.h>
 
+
 using namespace asio;
 using asio::ip::udp;
 using asio::ip::tcp;
@@ -46,8 +48,46 @@ using namespace isc::dns;
 using isc::log::dlog;
 using namespace boost;
 
+// Is this something we can use in libdns++?
+namespace {
+    class SectionInserter {
+    public:
+        SectionInserter(MessagePtr message, const Message::Section sect) :
+            message_(message), section_(sect)
+        {}
+        void operator()(const RRsetPtr rrset) {
+            message_->addRRset(section_, rrset, true);
+        }
+        MessagePtr message_;
+        const Message::Section section_;
+    };
+
+
+    /// \brief Copies the parts relevant for a DNS answer to the
+    /// target message
+    ///
+    /// This adds all the RRsets in the answer, authority and
+    /// additional sections to the target, as well as the response
+    /// code
+    void copyAnswerMessage(const Message& source, MessagePtr target) {
+        target->setRcode(source.getRcode());
+
+        for_each(source.beginSection(Message::SECTION_ANSWER),
+                 source.endSection(Message::SECTION_ANSWER),
+                 SectionInserter(target, Message::SECTION_ANSWER));
+        for_each(source.beginSection(Message::SECTION_AUTHORITY),
+                 source.endSection(Message::SECTION_AUTHORITY),
+                 SectionInserter(target, Message::SECTION_AUTHORITY));
+        for_each(source.beginSection(Message::SECTION_ADDITIONAL),
+                 source.endSection(Message::SECTION_ADDITIONAL),
+                 SectionInserter(target, Message::SECTION_ADDITIONAL));
+    }
+}
+
 namespace asiolink {
 
+typedef pair<string, uint16_t> addr_t;
+
 class IOServiceImpl {
 private:
     IOServiceImpl(const IOService& source);
@@ -243,9 +283,14 @@ typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
 }
 
 RecursiveQuery::RecursiveQuery(DNSService& dns_service,
-    const AddressVector& upstream, int timeout, unsigned retries) :
+    const AddressVector& upstream,
+    const AddressVector& upstream_root,
+    int query_timeout, int client_timeout, int lookup_timeout,
+    unsigned retries) :
     dns_service_(dns_service), upstream_(new AddressVector(upstream)),
-    timeout_(timeout), retries_(retries)
+    upstream_root_(new AddressVector(upstream_root)),
+    query_timeout_(query_timeout), client_timeout_(client_timeout),
+    lookup_timeout_(lookup_timeout), retries_(retries)
 {}
 
 namespace {
@@ -296,8 +341,17 @@ private:
 
     // Info for (re)sending the query (the question and destination)
     Question question_;
+
+    // This is where we build and store our final answer
+    MessagePtr answer_message_;
+
+    // currently we use upstream as the current list of NS records
+    // we should differentiate between forwarding and resolving
     shared_ptr<AddressVector> upstream_;
 
+    // root servers...just copied over to the zone_servers_
+    shared_ptr<AddressVector> upstream_root_;
+
     // Buffer to store the result.
     OutputBufferPtr buffer_;
 
@@ -309,12 +363,33 @@ private:
      *     computation of average RTT, increase with each retry, etc.
      */
     // Timeout information
-    int timeout_;
+    int query_timeout_;
     unsigned retries_;
 
+    // normal query state
+
+    // Not using NSAS at this moment, so we keep a list
+    // of 'current' zone servers
+    vector<addr_t> zone_servers_;
+
+    // Update the question that will be sent to the server
+    void setQuestion(const Question& new_question) {
+        question_ = new_question;
+    }
+
+    deadline_timer client_timer;
+    deadline_timer lookup_timer;
+
+    size_t queries_out_;
+
+    // If we timed out ourselves (lookup timeout), stop issuing queries
+    bool done_;
+
     // (re)send the query to the server.
     void send() {
         const int uc = upstream_->size();
+        const int zs = zone_servers_.size();
+        buffer_->clear();
         if (uc > 0) {
             int serverIndex = rand() % uc;
             dlog("Sending upstream query (" + question_.toText() +
@@ -322,37 +397,203 @@ private:
             UDPQuery query(io_, question_,
                 upstream_->at(serverIndex).first,
                 upstream_->at(serverIndex).second, buffer_, this,
-                timeout_);
+                query_timeout_);
+            ++queries_out_;
+            io_.post(query);
+        } else if (zs > 0) {
+            int serverIndex = rand() % zs;
+            dlog("Sending query to zone server (" + question_.toText() +
+                ") to " + zone_servers_.at(serverIndex).first);
+            UDPQuery query(io_, question_,
+                zone_servers_.at(serverIndex).first,
+                zone_servers_.at(serverIndex).second, buffer_, this,
+                query_timeout_);
+            ++queries_out_;
             io_.post(query);
         } else {
             dlog("Error, no upstream servers to send to.");
         }
     }
+    
+    // This function is called by operator() if there is an actual
+    // answer from a server and we are in recursive mode
+    // depending on the contents, we go on recursing or return
+    //
+    // Note that the footprint may change as this function may
+    // need to append data to the answer we are building later.
+    //
+    // returns true if we are done
+    // returns false if we are not done
+    bool handleRecursiveAnswer(const Message& incoming) {
+        if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) {
+            dlog("Got final result, copying answer.");
+            copyAnswerMessage(incoming, answer_message_);
+            return true;
+        } else {
+            dlog("Got delegation, continuing");
+            // ok we need to do some more processing.
+            // the ns list should contain all nameservers
+            // while the additional may contain addresses for
+            // them.
+            // this needs to tie into NSAS of course
+            // for this very first mockup, hope there is an
+            // address in additional and just use that
+
+            // send query to the addresses in the delegation
+            bool found_ns_address = false;
+            zone_servers_.clear();
+
+            for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_ADDITIONAL);
+                 rrsi != incoming.endSection(Message::SECTION_ADDITIONAL) && !found_ns_address;
+                 rrsi++) {
+                ConstRRsetPtr rrs = *rrsi;
+                if (rrs->getType() == RRType::A()) {
+                    // found address
+                    RdataIteratorPtr rdi = rrs->getRdataIterator();
+                    // just use the first for now
+                    if (!rdi->isLast()) {
+                        std::string addr_str = rdi->getCurrent().toText();
+                        dlog("[XX] first address found: " + addr_str);
+                        // now we have one address, simply
+                        // resend that exact same query
+                        // to that address and yield, when it
+                        // returns, loop again.
+                        
+                        // should use NSAS
+                        zone_servers_.push_back(addr_t(addr_str, 53));
+                        found_ns_address = true;
+                    }
+                }
+            }
+            if (found_ns_address) {
+                // next resolver round
+                send();
+                return false;
+            } else {
+                dlog("[XX] no ready-made addresses in additional. need nsas.");
+                // this will result in answering with the delegation. oh well
+                copyAnswerMessage(incoming, answer_message_);
+                return true;
+            }
+        }
+    }
+    
+
 public:
     RunningQuery(asio::io_service& io, const Question &question,
-        shared_ptr<AddressVector> upstream,
-        OutputBufferPtr buffer, DNSServer* server, int timeout,
+        MessagePtr answer_message, shared_ptr<AddressVector> upstream,
+        shared_ptr<AddressVector> upstream_root,
+        OutputBufferPtr buffer, DNSServer* server,
+        int query_timeout, int client_timeout, int lookup_timeout,
         unsigned retries) :
         io_(io),
         question_(question),
+        answer_message_(answer_message),
         upstream_(upstream),
+        upstream_root_(upstream_root),
         buffer_(buffer),
         server_(server->clone()),
-        timeout_(timeout),
-        retries_(retries)
+        query_timeout_(query_timeout),
+        retries_(retries),
+        client_timer(io),
+        lookup_timer(io),
+        queries_out_(0),
+        done_(false)
     {
+        // Setup the timer to stop trying (lookup_timeout)
+        if (lookup_timeout >= 0) {
+            lookup_timer.expires_from_now(
+                boost::posix_time::milliseconds(lookup_timeout));
+            lookup_timer.async_wait(boost::bind(&RunningQuery::stop, this, false));
+        }
+        
+        // Setup the timer to send an answer (client_timeout)
+        if (client_timeout >= 0) {
+            client_timer.expires_from_now(
+                boost::posix_time::milliseconds(client_timeout));
+            client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
+        }
+        
+        // should use NSAS for root servers
+        // Adding root servers if not a forwarder
+        if (upstream_->empty()) {
+            if (upstream_root_->empty()) { //if no root ips given, use this
+                zone_servers_.push_back(addr_t("192.5.5.241", 53));
+            }
+            else
+            {
+              //copy the list
+              dlog("Size is " + 
+                    boost::lexical_cast<string>(upstream_root_->size()) + 
+                    "\n");
+              //Use BOOST_FOREACH here? Is it faster?
+              for(AddressVector::iterator it = upstream_root_->begin();
+                   it < upstream_root_->end(); it++) {
+                zone_servers_.push_back(addr_t(it->first,it->second));
+                dlog("Put " + zone_servers_.back().first + "into root list\n");
+              }
+            }
+        }
+
         send();
     }
 
+    virtual void clientTimeout() {
+        // right now, just stop (should make SERVFAIL and send that
+        // back, but not stop)
+        stop(false);
+    }
+
+    virtual void stop(bool resume) {
+        // if we cancel our timers, we will still get an event for
+        // that, so we cannot delete ourselves just yet (those events
+        // would be bound to a deleted object)
+        // cancel them one by one, both cancels should get us back
+        // here again.
+        // same goes if we have an outstanding query (can't delete
+        // until that one comes back to us)
+        done_ = true;
+        server_->resume(resume);
+        if (lookup_timer.cancel() != 0) {
+            return;
+        }
+        if (client_timer.cancel() != 0) {
+            return;
+        }
+        if (queries_out_ > 0) {
+            return;
+        }
+        delete this;
+    }
+
     // This function is used as callback from DNSQuery.
     virtual void operator()(UDPQuery::Result result) {
-        if (result == UDPQuery::TIME_OUT && retries_ --) {
-            dlog("Resending query");
+        // XXX is this the place for TCP retry?
+        --queries_out_;
+        if (!done_ && result != UDPQuery::TIME_OUT) {
+            // we got an answer
+            Message incoming(Message::PARSE);
+            InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+            incoming.fromWire(ibuf);
+
+            if (upstream_->size() == 0 &&
+                incoming.getRcode() == Rcode::NOERROR()) {
+                done_ = handleRecursiveAnswer(incoming);
+            } else {
+                copyAnswerMessage(incoming, answer_message_);
+                done_ = true;
+            }
+            
+            if (done_) {
+                stop(result == UDPQuery::SUCCESS);
+            }
+        } else if (!done_ && retries_--) {
             // We timed out, but we have some retries, so send again
+            dlog("Timeout, resending query");
             send();
         } else {
-            server_->resume(result == UDPQuery::SUCCESS);
-            delete this;
+            // We are done
+            stop(false);
         }
     }
 };
@@ -360,7 +601,9 @@ public:
 }
 
 void
-RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
+RecursiveQuery::sendQuery(const Question& question,
+                          MessagePtr answer_message,
+                          OutputBufferPtr buffer,
                           DNSServer* server)
 {
     // XXX: eventually we will need to be able to determine whether
@@ -369,8 +612,9 @@ RecursiveQuery::sendQuery(const Question& question, OutputBufferPtr buffer,
     // we're only going to handle UDP.
     asio::io_service& io = dns_service_.get_io_service();
     // It will delete itself when it is done
-    new RunningQuery(io, question, upstream_, buffer, server,
-         timeout_, retries_);
+    new RunningQuery(io, question, answer_message, upstream_, upstream_root_,
+                         buffer, server, query_timeout_, client_timeout_,
+                         lookup_timeout_, retries_);
 }
 
 class IntervalTimerImpl {

+ 18 - 3
src/lib/asiolink/asiolink.h

@@ -412,10 +412,11 @@ public:
     /// \param DNSServer DNSServer object to use
     virtual void operator()(const IOMessage& io_message,
                             isc::dns::MessagePtr message,
+                            isc::dns::MessagePtr answer_message,
                             isc::dns::OutputBufferPtr buffer,
                             DNSServer* server) const
     {
-        (*self_)(io_message, message, buffer, server);
+        (*self_)(io_message, message, answer_message, buffer, server);
     }
 private:
     DNSLookup* self_;
@@ -465,6 +466,7 @@ public:
     /// \param buffer The result is put here
     virtual void operator()(const IOMessage& io_message,
                             isc::dns::MessagePtr message,
+                            isc::dns::MessagePtr answer_message,
                             isc::dns::OutputBufferPtr buffer) const = 0;
 };
 
@@ -536,6 +538,8 @@ public:
     ///        query on.
     /// \param upstream Addresses and ports of the upstream servers
     ///        to forward queries to.
+    /// \param upstream_root Addresses and ports of the root servers
+    ///        to use when resolving.
     /// \param timeout How long to timeout the query, in ms
     ///     -1 means never timeout (but do not use that).
     ///     TODO: This should be computed somehow dynamically in future
@@ -543,7 +547,13 @@ public:
     ///     and return if it returs).
     RecursiveQuery(DNSService& dns_service,
                    const std::vector<std::pair<std::string, uint16_t> >&
-                   upstream, int timeout = -1, unsigned retries = 0);
+                   upstream, 
+                   const std::vector<std::pair<std::string, uint16_t> >&
+                   upstream_root, 
+                   int query_timeout = 2000,
+                   int client_timeout = 4000,
+                   int lookup_timeout = 30000,
+                   unsigned retries = 3);
     //@}
 
     /// \brief Initiates an upstream query in the \c RecursiveQuery object.
@@ -557,13 +567,18 @@ public:
     /// \param buffer An output buffer into which the response can be copied
     /// \param server A pointer to the \c DNSServer object handling the client
     void sendQuery(const isc::dns::Question& question,
+                   isc::dns::MessagePtr answer_message,
                    isc::dns::OutputBufferPtr buffer,
                    DNSServer* server);
 private:
     DNSService& dns_service_;
     boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
         upstream_;
-    int timeout_;
+    boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+        upstream_root_;
+    int query_timeout_;
+    int client_timeout_;
+    int lookup_timeout_;
     unsigned retries_;
 };
 

+ 2 - 1
src/lib/asiolink/internal/tcpdns.h

@@ -195,7 +195,8 @@ private:
     // \c IOMessage and \c Message objects to be passed to the
     // DNS lookup and answer providers
     boost::shared_ptr<asiolink::IOMessage> io_message_;
-    isc::dns::MessagePtr message_;
+    isc::dns::MessagePtr query_message_;
+    isc::dns::MessagePtr answer_message_;
 
     // The buffer into which the query packet is written
     boost::shared_array<char>data_;

+ 6 - 1
src/lib/asiolink/internal/udpdns.h

@@ -208,7 +208,12 @@ private:
     // \c IOMessage and \c Message objects to be passed to the
     // DNS lookup and answer providers
     boost::shared_ptr<asiolink::IOMessage> io_message_;
-    isc::dns::MessagePtr message_;
+
+    // The original query as sent by the client
+    isc::dns::MessagePtr query_message_;
+
+    // The response message we are building
+    isc::dns::MessagePtr answer_message_;
 
     // The buffer into which the response is written
     isc::dns::OutputBufferPtr respbuf_;

+ 6 - 3
src/lib/asiolink/tcpdns.cc

@@ -142,7 +142,8 @@ TCPServer::operator()(error_code ec, size_t length) {
         // Reset or instantiate objects that will be needed by the
         // DNS lookup and the write call.
         respbuf_.reset(new OutputBuffer(0));
-        message_.reset(new Message(Message::PARSE));
+        query_message_.reset(new Message(Message::PARSE));
+        answer_message_.reset(new Message(Message::RENDER));
 
         // Schedule a DNS lookup, and yield.  When the lookup is
         // finished, the coroutine will resume immediately after
@@ -157,7 +158,8 @@ TCPServer::operator()(error_code ec, size_t length) {
 
         // Call the DNS answer provider to render the answer into
         // wire format
-        (*answer_callback_)(*io_message_, message_, respbuf_);
+        (*answer_callback_)(*io_message_, query_message_,
+                            answer_message_, respbuf_);
 
         // Set up the response, beginning with two length bytes.
         lenbuf.writeUint16(respbuf_->getLength());
@@ -176,7 +178,8 @@ TCPServer::operator()(error_code ec, size_t length) {
 /// AsyncLookup<TCPServer> handler.)
 void
 TCPServer::asyncLookup() {
-    (*lookup_callback_)(*io_message_, message_, respbuf_, this);
+    (*lookup_callback_)(*io_message_, query_message_,
+                        answer_message_, respbuf_, this);
 }
 
 /// Post this coroutine on the ASIO service queue so that it will

+ 216 - 36
src/lib/asiolink/tests/asiolink_unittest.cc

@@ -28,6 +28,7 @@
 #include <exceptions/exceptions.h>
 
 #include <dns/tests/unittest_util.h>
+#include <dns/rcode.h>
 
 #include <dns/buffer.h>
 #include <dns/message.h>
@@ -439,6 +440,7 @@ protected:
                             DNSAnswer* answer = NULL) :
             io_(io_service),
             message_(new Message(Message::PARSE)),
+            answer_message_(new Message(Message::RENDER)),
             respbuf_(new OutputBuffer(0)),
             checkin_(checkin), lookup_(lookup), answer_(answer)
         {}
@@ -447,7 +449,8 @@ protected:
                         size_t length = 0)
         {}
 
-        void resume(const bool) { // in our test this shouldn't be called
+        void resume(const bool) {
+          // should never be called in our tests
         }
 
         DNSServer* clone() {
@@ -457,7 +460,8 @@ protected:
 
         inline void asyncLookup() {
             if (lookup_) {
-                (*lookup_)(*io_message_, message_, respbuf_, this);
+                (*lookup_)(*io_message_, message_, answer_message_,
+                           respbuf_, this);
             }
         }
 
@@ -470,6 +474,7 @@ protected:
         // asynchronous lookup calls via the asyncLookup() method
         boost::shared_ptr<asiolink::IOMessage> io_message_;
         isc::dns::MessagePtr message_;
+        isc::dns::MessagePtr answer_message_;
         isc::dns::OutputBufferPtr respbuf_;
 
         // Callback functions provided by the caller
@@ -640,15 +645,17 @@ singleAddress(const string &address, uint16_t port) {
 TEST_F(ASIOLinkTest, recursiveSetupV4) {
     setDNSService(true, false);
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    EXPECT_NO_THROW(RecursiveQuery(*dns_service_, singleAddress(TEST_IPV6_ADDR,
-        port)));
+    EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+                                   singleAddress(TEST_IPV4_ADDR, port),
+                                   singleAddress(TEST_IPV4_ADDR, port)));
 }
 
 TEST_F(ASIOLinkTest, recursiveSetupV6) {
     setDNSService(false, true);
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    EXPECT_NO_THROW(RecursiveQuery(*dns_service_, singleAddress(TEST_IPV6_ADDR,
-        port)));
+    EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+                                   singleAddress(TEST_IPV6_ADDR, port),
+                                   singleAddress(TEST_IPV6_ADDR,port)));
 }
 
 // XXX:
@@ -656,7 +663,7 @@ TEST_F(ASIOLinkTest, recursiveSetupV6) {
 // a routine that can do this with variable address family, address, and
 // port, and with the various callbacks defined in such a way as to ensure
 // full code coverage including error cases.
-TEST_F(ASIOLinkTest, recursiveSend) {
+TEST_F(ASIOLinkTest, forwarderSend) {
     setDNSService(true, false);
 
     // Note: We use the test prot plus one to ensure we aren't binding
@@ -664,11 +671,14 @@ TEST_F(ASIOLinkTest, recursiveSend) {
     uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
 
     MockServer server(*io_service_);
-    RecursiveQuery rq(*dns_service_, singleAddress(TEST_IPV4_ADDR, port));
+    RecursiveQuery rq(*dns_service_,
+                      singleAddress(TEST_IPV4_ADDR, port),
+                      singleAddress(TEST_IPV4_ADDR, port));
 
     Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    rq.sendQuery(q, buffer, &server);
+    MessagePtr answer(new Message(Message::RENDER));
+    rq.sendQuery(q, answer, buffer, &server);
 
     char data[4096];
     size_t size = sizeof(data);
@@ -688,20 +698,59 @@ TEST_F(ASIOLinkTest, recursiveSend) {
     EXPECT_EQ(q.getClass(), q2->getClass());
 }
 
-// Test it tries the correct amount of times before giving up
-TEST_F(ASIOLinkTest, recursiveTimeout) {
-    // Prepare the service (we do not use the common setup, we do not answer
-    setDNSService();
-
-    // Prepare the socket
-    res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
-    sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+int
+createTestSocket()
+{
+    struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
+    int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
     if (sock_ < 0) {
         isc_throw(IOError, "failed to open test socket");
     }
     if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
         isc_throw(IOError, "failed to bind test socket");
     }
+    return sock_;
+}
+
+int
+setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
+    const struct timeval timeo = { tv_sec, tv_usec };
+    int recv_options = 0;
+    if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
+        if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP()
+            recv_options = MSG_DONTWAIT;
+        } else {
+            isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+        }
+    }
+    return recv_options;
+}
+
+// try to read from the socket max time
+// *num is incremented for every succesfull read
+// returns true if it can read max times, false otherwise
+bool tryRead(int sock_, int recv_options, size_t max, int* num) {
+    size_t i = 0;
+    do {
+        char inbuff[512];
+        if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
+            return false;
+        } else {
+            ++i;
+            ++*num;
+        }
+    } while (i < max);
+    return true;
+}
+
+
+// Test it tries the correct amount of times before giving up
+TEST_F(ASIOLinkTest, forwardQueryTimeout) {
+    // Prepare the service (we do not use the common setup, we do not answer
+    setDNSService();
+
+    // Prepare the socket
+    sock_ = createTestSocket();
 
     // Prepare the server
     bool done(true);
@@ -709,40 +758,171 @@ TEST_F(ASIOLinkTest, recursiveTimeout) {
 
     // Do the answer
     const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-    RecursiveQuery query(*dns_service_, singleAddress(TEST_IPV4_ADDR, port),
-        10, 2);
+    RecursiveQuery query(*dns_service_,
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         10, 4000, 3000, 2);
     Question question(Name("example.net"), RRClass::IN(), RRType::A());
     OutputBufferPtr buffer(new OutputBuffer(0));
-    query.sendQuery(question, buffer, &server);
+    MessagePtr answer(new Message(Message::RENDER));
+    query.sendQuery(question, answer, buffer, &server);
 
     // Run the test
     io_service_->run();
 
     // Read up to 3 packets.  Use some ad hoc timeout to prevent an infinite
     // block (see also recvUDP()).
-    const struct timeval timeo = { 10, 0 };
-    int recv_options = 0;
-    if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
-        if (errno == ENOPROTOOPT) { // see ASIOLinkTest::recvUDP()
-            recv_options = MSG_DONTWAIT;
-        } else {
-            isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
-        }
-    }
+    int recv_options = setSocketTimeout(sock_, 10, 0);
     int num = 0;
-    do {
-        char inbuff[512];
-        if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
-            num = -1;
-            break;
-        }
-    } while (++num < 3);
+    bool read_success = tryRead(sock_, recv_options, 3, &num);
 
     // The query should fail
     EXPECT_FALSE(done);
     EXPECT_EQ(3, num);
+    EXPECT_TRUE(read_success);
 }
 
+// If we set client timeout to lower than querytimeout, we should
+// get a failure answer, but still see retries
+// (no actual answer is given here yet)
+TEST_F(ASIOLinkTest, forwardClientTimeout) {
+    // Prepare the service (we do not use the common setup, we do not answer
+    setDNSService();
+
+    sock_ = createTestSocket();
+
+    // Prepare the server
+    bool done(true);
+    MockServerStop server(*io_service_, &done);
+
+    MessagePtr answer(new Message(Message::RENDER));
+
+    // Do the answer
+    const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+    // Set it up to retry twice before client timeout fires
+    // Since the lookup timer has not fired, it should retry
+    // a third time
+    RecursiveQuery query(*dns_service_,
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         50, 120, 1000, 3);
+    Question question(Name("example.net"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    query.sendQuery(question, answer, buffer, &server);
+
+    // Run the test
+    io_service_->run();
+
+    // we know it'll fail, so make it a shorter timeout
+    int recv_options = setSocketTimeout(sock_, 1, 0);
+
+    // Try to read 5 times, should stop after 3 reads
+    int num = 0;
+    bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+    // The query should fail (for resolver it should send back servfail,
+    // but currently, and perhaps for forwarder in general, the effect
+    // will be the same as on a lookup timeout, i.e. no answer is sent
+    // back)
+    EXPECT_FALSE(done);
+    EXPECT_EQ(3, num);
+    EXPECT_FALSE(read_success);
+}
+
+// If we set lookup timeout to lower than querytimeout*retries, we should
+// fail before the full amount of retries
+TEST_F(ASIOLinkTest, forwardLookupTimeout) {
+    // Prepare the service (we do not use the common setup, we do not answer
+    setDNSService();
+
+    // Prepare the socket
+    sock_ = createTestSocket();
+
+    // Prepare the server
+    bool done(true);
+    MockServerStop server(*io_service_, &done);
+
+    MessagePtr answer(new Message(Message::RENDER));
+
+    // Do the answer
+    const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+    // Set up the test so that it will retry 5 times, but the lookup
+    // timeout will fire after only 3 normal timeouts
+    RecursiveQuery query(*dns_service_,
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         singleAddress(TEST_IPV4_ADDR, port),
+                         50, 4000, 120, 5);
+    Question question(Name("example.net"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    query.sendQuery(question, answer, buffer, &server);
+
+    // Run the test
+    io_service_->run();
+
+    int recv_options = setSocketTimeout(sock_, 1, 0);
+
+    // Try to read 5 times, should stop after 3 reads
+    int num = 0;
+    bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+    // The query should fail
+    EXPECT_FALSE(done);
+    EXPECT_EQ(3, num);
+    EXPECT_FALSE(read_success);
+}
+
+// as mentioned above, we need a more better framework for this,
+// in addition to that, this sends out queries into the world
+// (which we should catch somehow and fake replies for)
+// for the skeleton code, it shouldn't be too much of a problem
+// Ok so even we don't all have access to the DNS world right now,
+// so disabling these tests too.
+TEST_F(ASIOLinkTest, DISABLED_recursiveSendOk) {
+    setDNSService(true, false);
+    bool done;
+    
+    MockServerStop server(*io_service_, &done);
+    vector<pair<string, uint16_t> > empty_vector;
+    RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
+
+    Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    MessagePtr answer(new Message(Message::RENDER));
+    rq.sendQuery(q, answer, buffer, &server);
+    io_service_->run();
+
+    // Check that the answer we got matches the one we wanted
+    EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
+    ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
+    RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
+    EXPECT_EQ(q.getName(), a->getName());
+    EXPECT_EQ(q.getType(), a->getType());
+    EXPECT_EQ(q.getClass(), a->getClass());
+    EXPECT_EQ(1, a->getRdataCount());
+}
+
+// see comments at previous test
+TEST_F(ASIOLinkTest, DISABLED_recursiveSendNXDOMAIN) {
+    setDNSService(true, false);
+    bool done;
+    
+    MockServerStop server(*io_service_, &done);
+    vector<pair<string, uint16_t> > empty_vector;
+    RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
+
+    Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
+    OutputBufferPtr buffer(new OutputBuffer(0));
+    MessagePtr answer(new Message(Message::RENDER));
+    rq.sendQuery(q, answer, buffer, &server);
+    io_service_->run();
+
+    // Check that the answer we got matches the one we wanted
+    EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
+    EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
+}
+
+
+
 // This fixture is for testing IntervalTimer. Some callback functors are 
 // registered as callback function of the timer to test if they are called
 // or not.

+ 9 - 3
src/lib/asiolink/udpdns.cc

@@ -130,13 +130,16 @@ UDPServer::operator()(error_code ec, size_t length) {
         // Instantiate objects that will be needed by the
         // asynchronous DNS lookup and/or by the send call.
         respbuf_.reset(new OutputBuffer(0));
-        message_.reset(new Message(Message::PARSE));
+        query_message_.reset(new Message(Message::PARSE));
+        answer_message_.reset(new Message(Message::RENDER));
 
         // Schedule a DNS lookup, and yield.  When the lookup is
         // finished, the coroutine will resume immediately after
         // this point.
         CORO_YIELD io_.post(AsyncLookup<UDPServer>(*this));
 
+        dlog("[XX] got an answer");
+
         // The 'done_' flag indicates whether we have an answer
         // to send back.  If not, exit the coroutine permanently.
         if (!done_) {
@@ -145,7 +148,8 @@ UDPServer::operator()(error_code ec, size_t length) {
 
         // Call the DNS answer provider to render the answer into
         // wire format
-        (*answer_callback_)(*io_message_, message_, respbuf_);
+        (*answer_callback_)(*io_message_, query_message_,
+                            answer_message_, respbuf_);
 
         // Begin an asynchronous send, and then yield.  When the
         // send completes, we will resume immediately after this point
@@ -161,7 +165,8 @@ UDPServer::operator()(error_code ec, size_t length) {
 /// AsyncLookup<UDPServer> handler.)
 void
 UDPServer::asyncLookup() {
-    (*lookup_callback_)(*io_message_, message_, respbuf_, this);
+    (*lookup_callback_)(*io_message_, query_message_, answer_message_,
+                        respbuf_, this);
 }
 
 /// Post this coroutine on the ASIO service queue so that it will
@@ -251,6 +256,7 @@ UDPQuery::operator()(error_code ec, size_t length) {
                 data_->remote.address().to_string());
         }
 
+
         // If we timeout, we stop, which will shutdown everything and
         // cancel all other attempts to run inside the coroutine
         if (data_->timeout != -1) {

+ 17 - 3
src/lib/datasrc/memory_datasrc.cc

@@ -19,6 +19,7 @@
 
 #include <dns/name.h>
 #include <dns/rrclass.h>
+#include <dns/rrsetlist.h>
 #include <dns/masterload.h>
 
 #include <datasrc/memory_datasrc.h>
@@ -213,9 +214,10 @@ struct MemoryZone::MemoryZoneImpl {
         return (false);
     }
 
+
     // Implementation of MemoryZone::find
     FindResult find(const Name& name, RRType type,
-                    const FindOptions options) const
+                    RRsetList* target, const FindOptions options) const
     {
         // Get the node
         DomainNode* node(NULL);
@@ -250,6 +252,18 @@ struct MemoryZone::MemoryZoneImpl {
             }
         }
 
+        // handle type any query
+        if (target && !node->getData()->empty()) {
+            // Empty domain will be handled as NXRRSET by normal processing
+            for (found = node->getData()->begin();
+                 found != node->getData()->end(); found++)
+            {
+                target->addRRset(
+                    boost::const_pointer_cast<RRset>(found->second));
+            }
+            return (FindResult(SUCCESS, ConstRRsetPtr()));
+        }
+
         found = node->getData()->find(type);
         if (found != node->getData()->end()) {
             // Good, it is here
@@ -287,9 +301,9 @@ MemoryZone::getClass() const {
 
 Zone::FindResult
 MemoryZone::find(const Name& name, const RRType& type,
-                 const FindOptions options) const
+                 RRsetList* target, const FindOptions options) const
 {
-    return (impl_->find(name, type, options));
+    return (impl_->find(name, type, target, options));
 }
 
 result::Result

+ 6 - 1
src/lib/datasrc/memory_datasrc.h

@@ -22,6 +22,7 @@
 namespace isc {
 namespace dns {
 class Name;
+class RRsetList;
 };
 
 namespace datasrc {
@@ -55,16 +56,20 @@ public:
 
     /// \brief Returns the origin of the zone.
     virtual const isc::dns::Name& getOrigin() const;
+
     /// \brief Returns the class of the zone.
     virtual const isc::dns::RRClass& getClass() const;
+
     /// \brief Looks up an RRset in the zone.
     ///
     /// See documentation in \c Zone.
     ///
-    /// It returns NULL pointer in case of NXDOMAIN and NXRRSET
+    /// It returns NULL pointer in case of NXDOMAIN and NXRRSET,
+    /// and also SUCCESS if target is not NULL(TYPE_ANY query).
     /// (the base class documentation does not seem to require that).
     virtual FindResult find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
+                            isc::dns::RRsetList* target = NULL,
                             const FindOptions options = FIND_DEFAULT) const;
 
     /// \brief Inserts an rrset into the zone.

+ 3 - 3
src/lib/datasrc/static_datasrc.h

@@ -74,9 +74,9 @@ public:
                             isc::dns::Name& target,
                             const isc::dns::Name* zonename) const;
 
-   Result findCoveringNSEC3(const isc::dns::Name& zonename,
-                            std::string& hash,
-                            isc::dns::RRsetList& target) const;
+    Result findCoveringNSEC3(const isc::dns::Name& zonename,
+                             std::string& hash,
+                             isc::dns::RRsetList& target) const;
 
     Result init();
     Result init(isc::data::ConstElementPtr config);

+ 66 - 16
src/lib/datasrc/tests/memory_datasrc_unittest.cc

@@ -18,6 +18,7 @@
 #include <dns/rdata.h>
 #include <dns/rdataclass.h>
 #include <dns/rrclass.h>
+#include <dns/rrsetlist.h>
 #include <dns/rrttl.h>
 #include <dns/masterload.h>
 
@@ -219,7 +220,8 @@ public:
     void findTest(const Name& name, const RRType& rrtype, Zone::Result result,
                   bool check_answer = true,
                   const ConstRRsetPtr& answer = ConstRRsetPtr(),
-                  MemoryZone *zone = NULL,
+                  RRsetList* target = NULL,
+                  MemoryZone* zone = NULL,
                   Zone::FindOptions options = Zone::FIND_DEFAULT)
     {
         if (!zone) {
@@ -228,7 +230,7 @@ public:
         // The whole block is inside, because we need to check the result and
         // we can't assign to FindResult
         EXPECT_NO_THROW({
-                Zone::FindResult find_result(zone->find(name, rrtype,
+                Zone::FindResult find_result(zone->find(name, rrtype, target,
                                                         options));
                 // Check it returns correct answers
                 EXPECT_EQ(result, find_result.code);
@@ -312,7 +314,8 @@ TEST_F(MemoryZoneTest, findCNAMEUnderZoneCut) {
                                            RRTTL(300)));
     EXPECT_EQ(SUCCESS, zone_.add(rr_cname_under_cut_));
     findTest(Name("cname.child.example.org"), RRType::AAAA(),
-             Zone::CNAME, true, rr_cname_under_cut_, NULL, Zone::FIND_GLUE_OK);
+             Zone::CNAME, true, rr_cname_under_cut_, NULL, NULL,
+             Zone::FIND_GLUE_OK);
 }
 
 // Test adding child zones and zone cut handling
@@ -343,6 +346,51 @@ TEST_F(MemoryZoneTest, delegationNS) {
              Zone::DELEGATION, true, rr_child_ns_); // note: !rr_grandchild_ns_
 }
 
+TEST_F(MemoryZoneTest, findAny) {
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_a_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_ns_)));
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_glue_)));
+
+    // origin
+    RRsetList origin_rrsets;
+    findTest(origin_, RRType::ANY(), Zone::SUCCESS, true,
+             ConstRRsetPtr(), &origin_rrsets);
+    EXPECT_EQ(2, origin_rrsets.size());
+    EXPECT_EQ(rr_a_, origin_rrsets.findRRset(RRType::A(), RRClass::IN()));
+    EXPECT_EQ(rr_ns_, origin_rrsets.findRRset(RRType::NS(), RRClass::IN()));
+
+    // out zone name
+    RRsetList out_rrsets;
+    findTest(Name("example.com"), RRType::ANY(), Zone::NXDOMAIN, true,
+             ConstRRsetPtr(), &out_rrsets);
+    EXPECT_EQ(0, out_rrsets.size());
+
+    RRsetList glue_child_rrsets;
+    findTest(child_glue_name_, RRType::ANY(), Zone::SUCCESS, true,
+                ConstRRsetPtr(), &glue_child_rrsets);
+    EXPECT_EQ(rr_child_glue_, glue_child_rrsets.findRRset(RRType::A(),
+                                                     RRClass::IN()));
+    EXPECT_EQ(1, glue_child_rrsets.size());
+
+    // TODO: test NXRRSET case after rbtree non-terminal logic has
+    // been implemented
+
+    // add zone cut
+    EXPECT_NO_THROW(EXPECT_EQ(SUCCESS, zone_.add(rr_child_ns_)));
+
+    // zone cut
+    RRsetList child_rrsets;
+    findTest(child_ns_name_, RRType::ANY(), Zone::DELEGATION, true,
+             rr_child_ns_, &child_rrsets);
+    EXPECT_EQ(0, child_rrsets.size());
+
+    // glue for this zone cut
+    RRsetList new_glue_child_rrsets;
+    findTest(child_glue_name_, RRType::ANY(), Zone::DELEGATION, true,
+                rr_child_ns_, &new_glue_child_rrsets);
+    EXPECT_EQ(0, new_glue_child_rrsets.size());
+}
+
 TEST_F(MemoryZoneTest, glue) {
     // install zone data:
     // a zone cut
@@ -361,15 +409,15 @@ TEST_F(MemoryZoneTest, glue) {
 
     // If we do it in the "glue OK" mode, we should find the exact match.
     findTest(child_glue_name_, RRType::A(), Zone::SUCCESS, true,
-             rr_child_glue_, NULL, Zone::FIND_GLUE_OK);
+             rr_child_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
 
     // glue OK + NXRRSET case
     findTest(child_glue_name_, RRType::AAAA(), Zone::NXRRSET, true,
-             ConstRRsetPtr(), NULL, Zone::FIND_GLUE_OK);
+             ConstRRsetPtr(), NULL, NULL, Zone::FIND_GLUE_OK);
 
     // glue OK + NXDOMAIN case
     findTest(Name("www.child.example.org"), RRType::A(), Zone::DELEGATION,
-             true, rr_child_ns_, NULL, Zone::FIND_GLUE_OK);
+             true, rr_child_ns_, NULL, NULL, Zone::FIND_GLUE_OK);
 
     // TODO:
     // glue name would match a wildcard under a zone cut: wildcard match
@@ -378,12 +426,13 @@ TEST_F(MemoryZoneTest, glue) {
 
     // nested cut case.  The glue should be found.
     findTest(grandchild_glue_name_, RRType::AAAA(), Zone::SUCCESS,
-             true, rr_grandchild_glue_, NULL, Zone::FIND_GLUE_OK);    
+             true, rr_grandchild_glue_, NULL, NULL, Zone::FIND_GLUE_OK);
 
     // A non-existent name in nested cut.  This should result in delegation
     // at the highest zone cut.
     findTest(Name("www.grand.child.example.org"), RRType::TXT(),
-             Zone::DELEGATION, true, rr_child_ns_, NULL, Zone::FIND_GLUE_OK);
+             Zone::DELEGATION, true, rr_child_ns_, NULL, NULL,
+             Zone::FIND_GLUE_OK);
 }
 
 // Test adding DNAMEs and resulting delegation handling
@@ -442,14 +491,14 @@ TEST_F(MemoryZoneTest, load) {
 
     // Now see there are some rrsets (we don't look inside, though)
     findTest(Name("."), RRType::SOA(), Zone::SUCCESS, false, ConstRRsetPtr(),
-        &rootzone);
+        NULL, &rootzone);
     findTest(Name("."), RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
-        &rootzone);
+        NULL, &rootzone);
     findTest(Name("a.root-servers.net."), RRType::A(), Zone::SUCCESS, false,
-        ConstRRsetPtr(), &rootzone);
+        ConstRRsetPtr(), NULL, &rootzone);
     // But this should no longer be here
     findTest(ns_name_, RRType::AAAA(), Zone::NXDOMAIN, true, ConstRRsetPtr(),
-        &rootzone);
+        NULL, &rootzone);
 
     // Try loading zone that is wrong in a different way
     EXPECT_THROW(zone_.load(TEST_DATA_DIR "/duplicate_rrset.zone"),
@@ -478,13 +527,13 @@ TEST_F(MemoryZoneTest, swap) {
     EXPECT_EQ(RRClass::IN(), zone2.getClass());
     // make sure the zone data is swapped, too
     findTest(origin_, RRType::NS(), Zone::NXDOMAIN, false, ConstRRsetPtr(),
-             &zone1);
+             NULL, &zone1);
     findTest(other_origin, RRType::TXT(), Zone::SUCCESS, false,
-             ConstRRsetPtr(), &zone1);
+             ConstRRsetPtr(), NULL, &zone1);
     findTest(origin_, RRType::NS(), Zone::SUCCESS, false, ConstRRsetPtr(),
-             &zone2);
+             NULL, &zone2);
     findTest(other_origin, RRType::TXT(), Zone::NXDOMAIN, false,
-             ConstRRsetPtr(), &zone2);
+             ConstRRsetPtr(), NULL, &zone2);
 }
 
 TEST_F(MemoryZoneTest, getFileName) {
@@ -510,4 +559,5 @@ TEST_F(MemoryZoneTest, getFileName) {
     EXPECT_EQ(TEST_DATA_DIR "/root.zone", zone_.getFileName());
     EXPECT_TRUE(rootzone.getFileName().empty());
 }
+
 }

+ 12 - 5
src/lib/datasrc/zone.h

@@ -17,6 +17,7 @@
 #define __ZONE_H 1
 
 #include <datasrc/result.h>
+#include <dns/rrsetlist.h>
 
 namespace isc {
 namespace datasrc {
@@ -162,6 +163,12 @@ public:
     ///   a successful match, and the code of \c SUCCESS will be returned.
     /// - If the search name matches a delegation point of DNAME, it returns
     ///   the code of \c DNAME and that DNAME RR.
+    /// - If the target isn't NULL, all RRsets under the domain are inserted
+    ///   there and SUCCESS (or NXDOMAIN, in case of empty domain) is returned
+    ///   instead of normall processing. This is intended to handle ANY query.
+    ///   \note: this behavior is controversial as we discussed in
+    ///   https://lists.isc.org/pipermail/bind10-dev/2011-January/001918.html
+    ///   We should revisit the interface before we heavily rely on it.
     ///
     /// The \c options parameter specifies customized behavior of the search.
     /// Their semantics is as follows:
@@ -177,19 +184,19 @@ public:
     /// A derived version of this method may involve internal resource
     /// allocation, especially for constructing the resulting RRset, and may
     /// throw an exception if it fails.
+    /// It throws DuplicateRRset exception if there are duplicate rrsets under
+    /// the same domain.
     /// It should not throw other types of exceptions.
     ///
-    /// Note: It's quite likely that we'll need to specify search options.
-    /// For example, we should be able to specify whether to allow returning
-    /// glue records at or under a zone cut.  We leave this interface open
-    /// at this moment.
-    ///
     /// \param name The domain name to be searched for.
     /// \param type The RR type to be searched for.
+    /// \param target If target is not NULL, insert all RRs under the domain
+    /// into it.
     /// \param options The search options.
     /// \return A \c FindResult object enclosing the search result (see above).
     virtual FindResult find(const isc::dns::Name& name,
                             const isc::dns::RRType& type,
+                            isc::dns::RRsetList* target = NULL,
                             const FindOptions options
                             = FIND_DEFAULT) const = 0;
     //@}

+ 3 - 0
src/lib/dns/message.h

@@ -509,6 +509,9 @@ public:
     ///
     /// With EDNS the maximum size can be increased per message.
     static const uint16_t DEFAULT_MAX_UDPSIZE = 512;
+
+    /// \brief The default maximum size of UDP DNS messages we can handle
+    static const uint16_t DEFAULT_MAX_EDNS0_UDPSIZE = 4096;
     //@}
 
 private:

+ 22 - 2
src/lib/dns/question.h

@@ -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:

+ 21 - 0
src/lib/dns/rrsetlist.h

@@ -73,6 +73,27 @@ private:
     T it_;
 };
 
+/// A set of RRsets.
+///
+/// \note Do not use this class unless you really understand what
+/// you're doing and you're 100% sure that this class is the best choice
+/// for your purpose.
+///
+/// Counter intuitively, this class is not a "list" of RRsets but a
+/// "set" of them; it doesn't allow multiple RRsets of the same RR
+/// type and RR class to be added at the same time.  And, for that
+/// reason, adding an RRset is more expensive than you'd expect.  The
+/// class name is confusing, but was named so as a result of
+/// compromise: "RRsetset" would look awkward; RRsets would be
+/// confusing (with RRset).
+///
+/// In any case, if you want a list like container of RRsets, your best choice
+/// would be \c std::vector<RRset> or \c std::list<RRset>, not this class.
+/// In fact, in many cases \c RRsetList will be a suboptimal choice.
+/// This class is defined publicly as part of libdns++ for a historical
+/// reason and is actually quite specific to a particular need for libdatasrc.
+/// If you are tempted to use it, think twice to assess if this class
+/// is really what you want.  Again, in many cases the answer will be no.
 class RRsetList {
 private:
     RRsetList(const RRsetList& source);

+ 33 - 0
src/lib/dns/tests/question_unittest.cc

@@ -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));
+
 }
 
 }

+ 1 - 0
src/lib/testutils/srv_test.h

@@ -89,6 +89,7 @@ protected:
     MockSession notify_session;
     MockServer dnsserv;
     isc::dns::Message request_message;
+    isc::dns::MessagePtr response_message;
     isc::dns::MessagePtr parse_message;
     const isc::dns::qid_t default_qid;
     const isc::dns::Opcode opcode;