Browse Source

[master] Merge branch 'trac999'

with fixing Conflicts:
	ChangeLog
	src/bin/resolver/main.cc
	src/bin/resolver/resolver.cc
	src/bin/resolver/resolver_messages.mes
	src/lib/acl/Makefile.am
	src/lib/acl/tests/Makefile.am
JINMEI Tatuya 14 years ago
parent
commit
e074437292

+ 4 - 0
src/bin/resolver/main.cc

@@ -218,6 +218,10 @@ main(int argc, char* argv[]) {
         }
 
         resolver->setConfigSession(config_session);
+        // Install all initial configurations.  If loading configuration
+        // fails, it will be logged, but we start the server anyway, giving
+        // the user a second chance to correct the configuration.
+        resolver->updateConfig(config_session->getFullConfig());
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_CONFIG_LOADED);
 
         LOG_INFO(resolver_logger, RESOLVER_STARTED);

+ 147 - 71
src/bin/resolver/resolver.cc

@@ -20,12 +20,18 @@
 #include <vector>
 #include <cassert>
 
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/foreach.hpp>
+
+#include <exceptions/exceptions.h>
+
+#include <acl/acl.h>
+#include <acl/loader.h>
+
 #include <asiodns/asiodns.h>
 #include <asiolink/asiolink.h>
 
-#include <boost/foreach.hpp>
-#include <boost/lexical_cast.hpp>
-
 #include <config/ccsession.h>
 
 #include <exceptions/exceptions.h>
@@ -41,6 +47,8 @@
 #include <dns/rrttl.h>
 #include <dns/message.h>
 #include <dns/messagerenderer.h>
+
+#include <server_common/client.h>
 #include <server_common/portconfig.h>
 
 #include <resolve/recursive_query.h>
@@ -49,14 +57,17 @@
 #include "resolver_log.h"
 
 using namespace std;
+using namespace boost;
 
 using namespace isc;
 using namespace isc::util;
+using namespace isc::acl;
 using namespace isc::dns;
 using namespace isc::data;
 using namespace isc::config;
 using namespace isc::asiodns;
 using namespace isc::asiolink;
+using namespace isc::server_common;
 using namespace isc::server_common::portconfig;
 
 class ResolverImpl {
@@ -71,6 +82,7 @@ public:
         client_timeout_(4000),
         lookup_timeout_(30000),
         retries_(3),
+        query_acl_(new Resolver::ClientACL(REJECT)),
         rec_query_(NULL)
     {}
 
@@ -141,10 +153,20 @@ public:
     void resolve(const isc::dns::QuestionPtr& question,
         const isc::resolve::ResolverInterface::CallbackPtr& callback);
 
-    void processNormalQuery(ConstMessagePtr query_message,
-                            MessagePtr answer_message,
-                            OutputBufferPtr buffer,
-                            DNSServer* server);
+    enum NormalQueryResult { RECURSION, DROPPED, ERROR };
+    NormalQueryResult processNormalQuery(const IOMessage& io_message,
+                                         MessagePtr query_message,
+                                         MessagePtr answer_message,
+                                         OutputBufferPtr buffer,
+                                         DNSServer* server);
+
+    const Resolver::ClientACL& getQueryACL() const {
+        return (*query_acl_);
+    }
+
+    void setQueryACL(shared_ptr<const Resolver::ClientACL> new_acl) {
+        query_acl_ = new_acl;
+    }
 
     /// Currently non-configurable, but will be.
     static const uint16_t DEFAULT_LOCAL_UDPSIZE = 4096;
@@ -169,6 +191,8 @@ public:
     unsigned retries_;
 
 private:
+    /// ACL on incoming queries
+    shared_ptr<const Resolver::ClientACL> query_acl_;
 
     /// Object to handle upstream queries
     RecursiveQuery* rec_query_;
@@ -401,7 +425,6 @@ Resolver::processMessage(const IOMessage& io_message,
             server->resume(false);
             return;
         }
-
     } catch (const Exception& ex) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_HEADER_ERROR)
                   .arg(ex.what());
@@ -435,76 +458,40 @@ Resolver::processMessage(const IOMessage& io_message,
               RESOLVER_DNS_MESSAGE_RECEIVED).arg(*query_message);
 
     // Perform further protocol-level validation.
-    bool sendAnswer = true;
+    bool send_answer = true;
     if (query_message->getOpcode() == Opcode::NOTIFY()) {
 
         makeErrorMessage(query_message, answer_message,
                          buffer, Rcode::NOTAUTH());
         // Notify arrived, but we are not authoritative.
-        LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
-                  RESOLVER_NOTIFY_RECEIVED);
-
+        LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NFYNOTAUTH);
     } else if (query_message->getOpcode() != Opcode::QUERY()) {
-
         // Unsupported opcode.
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
                   RESOLVER_UNSUPPORTED_OPCODE).arg(query_message->getOpcode());
         makeErrorMessage(query_message, answer_message,
                          buffer, Rcode::NOTIMP());
-
     } else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) {
-
         // Not one question
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
                   RESOLVER_NOT_ONE_QUESTION)
                   .arg(query_message->getRRCount(Message::SECTION_QUESTION));
-        makeErrorMessage(query_message, answer_message,
-                         buffer, Rcode::FORMERR());
+        makeErrorMessage(query_message, answer_message, buffer,
+                         Rcode::FORMERR());
     } else {
-        ConstQuestionPtr question = *query_message->beginQuestion();
-        const RRType &qtype = question->getType();
-        if (qtype == RRType::AXFR()) {
-            if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
-
-                // Can't process AXFR request receoved over UDP
-                LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
-                          RESOLVER_AXFR_UDP);
-                makeErrorMessage(query_message, answer_message,
-                                 buffer, Rcode::FORMERR());
-            } else {
-
-                // ... or over TCP for that matter
-                LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS,
-                          RESOLVER_AXFR_TCP);
-                makeErrorMessage(query_message, answer_message,
-                                 buffer, Rcode::NOTIMP());
-            }
-        } else if (qtype == RRType::IXFR()) {
-
-            // Can't process IXFR request
-            LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_IXFR);
-            makeErrorMessage(query_message, answer_message,
-                             buffer, Rcode::NOTIMP());
-
-        } else if (question->getClass() != RRClass::IN()) {
-
-            // Non-IN message received, refuse it.
-            LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NON_IN_PACKET)
-                      .arg(question->getClass());
-            makeErrorMessage(query_message, answer_message,
-                             buffer, Rcode::REFUSED());
-        } else {
+        const ResolverImpl::NormalQueryResult result =
+            impl_->processNormalQuery(io_message, query_message,
+                                      answer_message, buffer, server);
+        if (result == ResolverImpl::RECURSION) {
             // 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(query_message, answer_message,
-                                      buffer, server);
+            return;
+        } else if (result == ResolverImpl::DROPPED) {
+            send_answer = false;
         }
     }
 
-    if (sendAnswer) {
-        server->resume(true);
-    }
+    server->resume(send_answer);
 }
 
 void
@@ -514,24 +501,102 @@ ResolverImpl::resolve(const QuestionPtr& question,
     rec_query_->resolve(question, callback);
 }
 
-void
-ResolverImpl::processNormalQuery(ConstMessagePtr query_message,
+ResolverImpl::NormalQueryResult
+ResolverImpl::processNormalQuery(const IOMessage& io_message,
+                                 MessagePtr query_message,
                                  MessagePtr answer_message,
                                  OutputBufferPtr buffer,
                                  DNSServer* server)
 {
+    const ConstQuestionPtr question = *query_message->beginQuestion();
+    const RRType qtype = question->getType();
+    const RRClass qclass = question->getClass();
+
+    // Apply query ACL
+    Client client(io_message);
+    const BasicAction query_action(getQueryACL().execute(client));
+    if (query_action == isc::acl::REJECT) {
+        LOG_INFO(resolver_logger, RESOLVER_QUERY_REJECTED)
+            .arg(question->getName()).arg(qtype).arg(qclass).arg(client);
+        makeErrorMessage(query_message, answer_message, buffer,
+                         Rcode::REFUSED());
+        return (ERROR);
+    } else if (query_action == isc::acl::DROP) {
+        LOG_INFO(resolver_logger, RESOLVER_QUERY_DROPPED)
+            .arg(question->getName()).arg(qtype).arg(qclass).arg(client);
+        return (DROPPED);
+    }
+    LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_QUERY_ACCEPTED)
+        .arg(question->getName()).arg(qtype).arg(question->getClass())
+        .arg(client);
+
+    // ACL passed.  Reject inappropriate queries for the resolver.
+    if (qtype == RRType::AXFR()) {
+        if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
+            // Can't process AXFR request receoved over UDP
+            LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_AXFR_UDP);
+            makeErrorMessage(query_message, answer_message, buffer,
+                             Rcode::FORMERR());
+        } else {
+            // ... or over TCP for that matter
+            LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_AXFR_TCP);
+            makeErrorMessage(query_message, answer_message, buffer,
+                             Rcode::NOTIMP());
+        }
+        return (ERROR);
+    } else if (qtype == RRType::IXFR()) {
+        // Can't process IXFR request
+        LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_IXFR);
+        makeErrorMessage(query_message, answer_message, buffer,
+                         Rcode::NOTIMP());
+        return (ERROR);
+    } else if (qclass != RRClass::IN()) {
+        // Non-IN message received, refuse it.
+        LOG_DEBUG(resolver_logger, RESOLVER_DBG_PROCESS, RESOLVER_NON_IN_PACKET)
+            .arg(question->getClass());
+        makeErrorMessage(query_message, answer_message, buffer,
+                         Rcode::REFUSED());
+        return (ERROR);
+    }
+
+    // Everything is okay.  Start resolver.
     if (upstream_.empty()) {
         // Processing normal query
-        LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_NORMAL_QUERY);
-        ConstQuestionPtr question = *query_message->beginQuestion();
+        LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_NORMQUERY);
         rec_query_->resolve(*question, answer_message, buffer, server);
-
     } else {
-
         // Processing forward query
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO, RESOLVER_FORWARD_QUERY);
         rec_query_->forward(query_message, answer_message, buffer, server);
     }
+
+    return (RECURSION);
+}
+
+namespace {
+// This is a simplified ACL parser for the initial implementation with minimal
+// external dependency.  For a longer term we'll switch to a more generic
+// loader with allowing more complicated ACL syntax.
+shared_ptr<const Resolver::ClientACL>
+createQueryACL(isc::data::ConstElementPtr acl_config) {
+    if (!acl_config) {
+        return (shared_ptr<const Resolver::ClientACL>());
+    }
+
+    shared_ptr<Resolver::ClientACL> new_acl(
+        new Resolver::ClientACL(REJECT));
+    BOOST_FOREACH(ConstElementPtr rule, acl_config->listValue()) {
+        ConstElementPtr action = rule->get("action");
+        ConstElementPtr from = rule->get("from");
+        if (!action || !from) {
+            isc_throw(BadValue, "query ACL misses mandatory parameter");
+        }
+        new_acl->append(shared_ptr<IPCheck<Client> >(
+                            new IPCheck<Client>(from->stringValue())),
+                        defaultActionLoader(action));
+    }
+    return (new_acl);
+}
 }
 
 ConstElementPtr
@@ -550,6 +615,8 @@ Resolver::updateConfig(ConstElementPtr config) {
         ConstElementPtr listenAddressesE(config->get("listen_on"));
         AddressList listenAddresses(parseAddresses(listenAddressesE,
                                                       "listen_on"));
+        shared_ptr<const ClientACL> query_acl(createQueryACL(
+                                                  config->get("query_acl")));
         bool set_timeouts(false);
         int qtimeout = impl_->query_timeout_;
         int ctimeout = impl_->client_timeout_;
@@ -607,15 +674,6 @@ Resolver::updateConfig(ConstElementPtr config) {
         if (listenAddressesE) {
             setListenAddresses(listenAddresses);
             need_query_restart = true;
-        } else {
-            if (!configured_) {
-                // TODO: ModuleSpec needs getDefault()
-                AddressList initial_addresses;
-                initial_addresses.push_back(AddressPair("127.0.0.1", 53));
-                initial_addresses.push_back(AddressPair("::1", 53));
-                setListenAddresses(initial_addresses);
-                need_query_restart = true;
-            }
         }
         if (forwardAddressesE) {
             setForwardAddresses(forwardAddresses);
@@ -629,6 +687,9 @@ Resolver::updateConfig(ConstElementPtr config) {
             setTimeouts(qtimeout, ctimeout, ltimeout, retries);
             need_query_restart = true;
         }
+        if (query_acl) {
+            setQueryACL(query_acl);
+        }
 
         if (need_query_restart) {
             impl_->queryShutdown();
@@ -714,3 +775,18 @@ AddressList
 Resolver::getListenAddresses() const {
     return (impl_->listen_);
 }
+
+const Resolver::ClientACL&
+Resolver::getQueryACL() const {
+    return (impl_->getQueryACL());
+}
+
+void
+Resolver::setQueryACL(shared_ptr<const ClientACL> new_acl) {
+    if (!new_acl) {
+        isc_throw(InvalidParameter, "NULL pointer is passed to setQueryACL");
+    }
+
+    LOG_INFO(resolver_logger, RESOLVER_SET_QUERY_ACL);
+    impl_->setQueryACL(new_acl);
+}

+ 31 - 0
src/bin/resolver/resolver.h

@@ -19,6 +19,10 @@
 #include <vector>
 #include <utility>
 
+#include <boost/shared_ptr.hpp>
+
+#include <acl/acl.h>
+
 #include <cc/data.h>
 #include <config/ccsession.h>
 #include <dns/message.h>
@@ -37,6 +41,12 @@
 
 #include <resolve/resolver_interface.h>
 
+namespace isc {
+namespace server_common {
+class Client;
+}
+}
+
 class ResolverImpl;
 
 /**
@@ -236,6 +246,27 @@ public:
      */
     int getRetries() const;
 
+    // Shortcut typedef used for query ACL.
+    typedef isc::acl::ACL<isc::server_common::Client> ClientACL;
+
+    /// Get the query ACL.
+    ///
+    /// \exception None
+    const ClientACL& getQueryACL() const;
+
+    /// Set the new query ACL.
+    ///
+    /// This method replaces the existing query ACL completely.
+    /// Normally this method will be called via the configuration handler,
+    /// but is publicly available for convenience of tests (and other
+    /// experimental purposes).
+    /// \c new_acl must not be a NULL pointer.
+    ///
+    /// \exception InvalidParameter The given pointer is NULL
+    ///
+    /// \param new_acl The new ACL to replace the existing one.
+    void setQueryACL(boost::shared_ptr<const ClientACL> new_acl);
+
 private:
     ResolverImpl* impl_;
     isc::asiodns::DNSService* dnss_;

+ 35 - 0
src/bin/resolver/resolver.spec.pre.in

@@ -113,6 +113,41 @@
             }
           ]
         }
+      },
+      {
+        "item_name": "query_acl",
+	"item_type": "list",
+	"item_optional": false,
+	"item_default": [
+	  {
+	    "action": "ACCEPT",
+	    "from": "127.0.0.1"
+	  },
+	  {
+	    "action": "ACCEPT",
+	    "from": "::1"
+	  }
+	],
+	"list_item_spec": {
+	  "item_name": "rule",
+	  "item_type": "map",
+	  "item_optional": false,
+	  "item_default": {},
+	  "map_item_spec": [
+	    {
+	      "item_name": "action",
+	      "item_type": "string",
+	      "item_optional": false,
+	      "item_default": ""
+	    },
+	    {
+	      "item_name": "from",
+	      "item_type": "string",
+	      "item_optional": false,
+	      "item_default": ""
+	    }
+	  ]
+        }
       }
     ],
     "commands": [

+ 21 - 0
src/bin/resolver/resolver_messages.mes

@@ -196,3 +196,24 @@ query and is ignoring it.
 A debug message, the resolver received a message with an unsupported opcode
 (it can only process QUERY opcodes).  It will return a message to the sender
 with the RCODE set to NOTIMP.
+
+% RESOLVER_SET_QUERY_ACL   query ACL is configured
+A debug message that appears when a new query ACL is configured for the
+resolver.
+
+% RESOLVER_QUERY_ACCEPTED   query accepted: '%1/%2/%3' from %4
+A debug message that indicates an incoming query is accepted in terms of
+the query ACL.  The log message shows the query in the form of
+<query name>/<query type>/<query class>, and the client that sends the
+query in the form of <Source IP address>#<source port>.
+
+% RESOLVER_QUERY_REJECTED   query rejected: '%1/%2/%3' from %4
+An informational message that indicates an incoming query is rejected
+in terms of the query ACL.  This results in a response with an RCODE of
+REFUSED.  See QUERYACCEPTED for the information given in the message.
+
+% RESOLVER_QUERY_DROPPED    query dropped: '%1/%2/%3' from %4
+An informational message that indicates an incoming query is dropped
+in terms of the query ACL.  Unlike the QUERYREJECTED case, the server does
+not return any response.  See QUERYACCEPTED for the information given in
+the message.

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

@@ -47,6 +47,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
 run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
 run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
 run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
+run_unittests_LDADD += $(top_builddir)/src/lib/acl/libacl.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 
 # Note the ordering matters: -Wno-... must follow -Wextra (defined in

+ 158 - 11
src/bin/resolver/tests/resolver_config_unittest.cc

@@ -16,12 +16,23 @@
 
 #include <string>
 
+#include <boost/scoped_ptr.hpp>
+
 #include <gtest/gtest.h>
 
 #include <cc/data.h>
 
+#include <config/ccsession.h>
+
 #include <asiodns/asiodns.h>
 #include <asiolink/asiolink.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_message.h>
+
+#include <acl/acl.h>
+
+#include <server_common/client.h>
 
 #include <resolver/resolver.h>
 
@@ -30,25 +41,37 @@
 #include <testutils/portconfig.h>
 
 using namespace std;
+using boost::scoped_ptr;
+using namespace isc::acl;
 using namespace isc::data;
 using namespace isc::testutils;
 using namespace isc::asiodns;
 using namespace isc::asiolink;
+using namespace isc::server_common;
 using isc::UnitTestUtil;
 
 namespace {
 class ResolverConfig : public ::testing::Test {
-    public:
-        IOService ios;
-        DNSService dnss;
-        Resolver server;
-        ResolverConfig() :
-            dnss(ios, NULL, NULL, NULL)
-        {
-            server.setDNSService(dnss);
-            server.setConfigured();
-        }
-        void invalidTest(const string &JSON, const string& name);
+protected:
+    IOService ios;
+    DNSService dnss;
+    Resolver server;
+    scoped_ptr<const IOEndpoint> endpoint;
+    scoped_ptr<const IOMessage> request;
+    scoped_ptr<const Client> client;
+    ResolverConfig() : dnss(ios, NULL, NULL, NULL) {
+        server.setDNSService(dnss);
+        server.setConfigured();
+    }
+    const Client& createClient(const string& source_addr) {
+        endpoint.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress(source_addr),
+                                          53210));
+        request.reset(new IOMessage(NULL, 0, IOSocket::getDummyUDPSocket(),
+                                    *endpoint));
+        client.reset(new Client(*request));
+        return (*client);
+    }
+    void invalidTest(const string &JSON, const string& name);
 };
 
 TEST_F(ResolverConfig, forwardAddresses) {
@@ -228,4 +251,128 @@ TEST_F(ResolverConfig, invalidTimeoutsConfig) {
         "}", "Negative number of retries");
 }
 
+TEST_F(ResolverConfig, defaultQueryACL) {
+    // If no configuration is loaded, the default ACL should reject everything.
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.1")));
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(
+                  createClient("2001:db8::1")));
+
+    // The following would be allowed if the server had loaded the default
+    // configuration from the spec file.  In this context it should not have
+    // happened, and they should be rejected just like the above cases.
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("127.0.0.1")));
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("::1")));
+}
+
+TEST_F(ResolverConfig, emptyQueryACL) {
+    // Explicitly configured empty ACL should have the same effect.
+    ElementPtr config(Element::fromJSON("{ \"query_acl\": [] }"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.1")));
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(
+                  createClient("2001:db8::1")));
+}
+
+TEST_F(ResolverConfig, queryACLIPv4) {
+    // A simple "accept" query for a specific IPv4 address
+    ElementPtr config(Element::fromJSON(
+                          "{ \"query_acl\": "
+                          "  [ {\"action\": \"ACCEPT\","
+                          "     \"from\": \"192.0.2.1\"} ] }"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(ACCEPT, server.getQueryACL().execute(createClient("192.0.2.1")));
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(
+                  createClient("2001:db8::1")));
+}
+
+TEST_F(ResolverConfig, queryACLIPv6) {
+    // same for IPv6
+    ElementPtr config(Element::fromJSON(
+                          "{ \"query_acl\": "
+                          "  [ {\"action\": \"ACCEPT\","
+                          "     \"from\": \"2001:db8::1\"} ] }"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.1")));
+    EXPECT_EQ(ACCEPT, server.getQueryACL().execute(
+                  createClient("2001:db8::1")));
+}
+
+TEST_F(ResolverConfig, multiEntryACL) {
+    // A bit more complicated one: mixture of IPv4 and IPv6 with 3 rules
+    // in total.  We shouldn't have to check so many variations of rules
+    // as it should have been tested in the underlying ACL module.  All we
+    // have to do to check is a reasonably complicated ACL configuration is
+    // loaded as expected.
+    ElementPtr config(Element::fromJSON(
+                          "{ \"query_acl\": "
+                          "  [ {\"action\": \"ACCEPT\","
+                          "     \"from\": \"192.0.2.1\"},"
+                          "    {\"action\": \"REJECT\","
+                          "     \"from\": \"192.0.2.0/24\"},"
+                          "    {\"action\": \"DROP\","
+                          "     \"from\": \"2001:db8::1\"},"
+                          "] }"));
+    ConstElementPtr result(server.updateConfig(config));
+    EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire());
+    EXPECT_EQ(ACCEPT, server.getQueryACL().execute(createClient("192.0.2.1")));
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(createClient("192.0.2.2")));
+    EXPECT_EQ(DROP, server.getQueryACL().execute(
+                  createClient("2001:db8::1")));
+    EXPECT_EQ(REJECT, server.getQueryACL().execute(
+                  createClient("2001:db8::2"))); // match the default rule
+}
+
+int
+getResultCode(ConstElementPtr result) {
+    int rcode;
+    isc::config::parseAnswer(rcode, result);
+    return (rcode);
+}
+
+TEST_F(ResolverConfig, badQueryACL) {
+    // Most of these cases shouldn't happen in practice because the syntax
+    // check should be performed before updateConfig().  But we check at
+    // least the server code won't crash even if an unexpected input is given.
+
+    // ACL must be a list
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\": 1 }"))));
+    // Each rule must have "action" and "from"
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\":"
+                                        " [ {\"from\": \"192.0.2.1\"} ] }"))));
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\":"
+                                        " [ {\"action\": \"DROP\"} ] }"))));
+    // invalid "action"
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\":"
+                                        " [ {\"action\": 1,"
+                                        "    \"from\": \"192.0.2.1\"}]}"))));
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\":"
+                                        " [ {\"action\": \"BADACTION\","
+                                        "    \"from\": \"192.0.2.1\"}]}"))));
+
+    // invalid "from"
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\":"
+                                        " [ {\"action\": \"ACCEPT\","
+                                        "    \"from\": 53}]}"))));
+    EXPECT_EQ(1, getResultCode(
+                  server.updateConfig(
+                      Element::fromJSON("{ \"query_acl\":"
+                                        " [ {\"action\": \"ACCEPT\","
+                                        "    \"from\": \"1922.0.2.1\"}]}"))));
+}
+
 }

+ 60 - 1
src/bin/resolver/tests/resolver_unittest.cc

@@ -12,14 +12,21 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <string>
+
+#include <exceptions/exceptions.h>
+
 #include <dns/name.h>
 
+#include <cc/data.h>
 #include <resolver/resolver.h>
 #include <dns/tests/unittest_util.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 
+using namespace std;
 using namespace isc::dns;
+using namespace isc::data;
 using namespace isc::testutils;
 using isc::UnitTestUtil;
 
@@ -28,7 +35,17 @@ const char* const TEST_PORT = "53535";
 
 class ResolverTest : public SrvTestBase{
 protected:
-    ResolverTest() : server(){}
+    ResolverTest() : server() {
+        // By default queries from the "default remote address" will be
+        // rejected, so we'll need to add an explicit ACL entry to allow that.
+        server.setConfigured();
+        server.updateConfig(Element::fromJSON(
+                                "{ \"query_acl\": "
+                                "  [ {\"action\": \"ACCEPT\","
+                                "     \"from\": \"" +
+                                string(DEFAULT_REMOTE_ADDRESS) +
+                                "\"} ] }"));
+    }
     virtual void processMessage() {
         server.processMessage(*io_message,
                               parse_message,
@@ -136,4 +153,46 @@ TEST_F(ResolverTest, notifyFail) {
                 Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
 }
 
+TEST_F(ResolverTest, setQueryACL) {
+    // valid cases are tested through other tests.  We only explicitly check
+    // an invalid case: passing a NULL shared pointer.
+    EXPECT_THROW(server.setQueryACL(
+                     boost::shared_ptr<const Resolver::ClientACL>()),
+                 isc::InvalidParameter);
+}
+
+TEST_F(ResolverTest, queryACL) {
+    // The "ACCEPT" cases are covered in other tests.  Here we explicitly
+    // test "REJECT" and "DROP" cases.
+
+    // Clear the existing ACL, reverting to the "default reject" rule.
+
+    // AXFR over UDP.  This would otherwise result in FORMERR.
+    server.updateConfig(Element::fromJSON("{ \"query_acl\": [] }"));
+    UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
+                                       Name("example.com"), RRClass::IN(),
+                                       RRType::AXFR());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, parse_message, response_message,
+                          response_obuffer, &dnsserv);
+    EXPECT_TRUE(dnsserv.hasAnswer());
+    headerCheck(*parse_message, default_qid, Rcode::REFUSED(),
+                Opcode::QUERY().getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    // Same query, but with an explicit "DROP" ACL entry.  There should be
+    // no response.
+    server.updateConfig(Element::fromJSON("{ \"query_acl\": "
+                                          "  [ {\"action\": \"DROP\","
+                                          "     \"from\": \"" +
+                                          string(DEFAULT_REMOTE_ADDRESS) +
+                                          "\"} ] }"));
+    parse_message->clear(Message::PARSE);
+    response_message->clear(Message::RENDER);
+    response_obuffer->clear();
+    server.processMessage(*io_message, parse_message, response_message,
+                          response_obuffer, &dnsserv);
+    EXPECT_FALSE(dnsserv.hasAnswer());
+}
+
+
 }

+ 6 - 0
src/bin/resolver/tests/response_scrubber_unittest.cc

@@ -68,6 +68,12 @@ public:
         return address_.getFamily();
     }
 
+    // This is completely dummy and unused.  Define it just for build.
+    virtual const struct sockaddr& getSockAddr() const {
+        static struct sockaddr sa;
+        return (sa);
+    }
+
 private:
     IOAddress   address_;        // Address of endpoint
     uint16_t    port_;          // Port number of endpoint

+ 1 - 1
src/lib/Makefile.am

@@ -1,3 +1,3 @@
 SUBDIRS = exceptions util log cryptolink dns cc config python xfr \
           bench asiolink asiodns nsas cache resolve testutils datasrc \
-          server_common acl
+          acl server_common

+ 1 - 1
src/lib/acl/Makefile.am

@@ -2,7 +2,6 @@ SUBDIRS = . tests
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
-
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
 # The core library
@@ -12,6 +11,7 @@ libacl_la_SOURCES += check.h
 libacl_la_SOURCES += ip_check.h ip_check.cc
 libacl_la_SOURCES += logic_check.h
 libacl_la_SOURCES += loader.h loader.cc
+libacl_la_SOURCES += ip_check.h ip_check.cc
 
 libacl_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libacl_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la

+ 33 - 3
src/lib/acl/ip_check.cc

@@ -12,13 +12,16 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <sys/socket.h>
+
+#include <exceptions/exceptions.h>
+
 #include <boost/lexical_cast.hpp>
 
 #include <acl/ip_check.h>
 
 using namespace std;
-
-// Split the IP Address prefix
+using namespace isc;
 
 namespace isc {
 namespace acl {
@@ -105,7 +108,34 @@ splitIPAddress(const string& ipprefix) {
 
     return (make_pair(address, prefix_size));
 }
-
 } // namespace internal
+
+namespace {
+const uint8_t*
+getSockAddrData(const struct sockaddr& sa) {
+    const void* sa_ptr = &sa;
+    const void* data_ptr;
+    if (sa.sa_family == AF_INET) {
+        const struct sockaddr_in* sin =
+            static_cast<const struct sockaddr_in*>(sa_ptr);
+        data_ptr = &sin->sin_addr;
+    } else if (sa.sa_family == AF_INET6) {
+        const struct sockaddr_in6* sin6 =
+            static_cast<const struct sockaddr_in6*>(sa_ptr);
+        data_ptr = &sin6->sin6_addr;
+    } else {
+        isc_throw(BadValue, "Unsupported address family for IPAddress: " <<
+                  static_cast<int>(sa.sa_family));
+    }
+    return (static_cast<const uint8_t*>(data_ptr));
+}
+}
+
+IPAddress::IPAddress(const struct sockaddr& sa) :
+    family(sa.sa_family),
+    data(getSockAddrData(sa)),
+    length(family == AF_INET ?
+           sizeof(struct in_addr) : sizeof(struct in6_addr))
+{}
 } // namespace acl
 } // namespace isc

+ 63 - 0
src/lib/acl/ip_check.h

@@ -15,7 +15,10 @@
 #ifndef __IP_CHECK_H
 #define __IP_CHECK_H
 
+#include <sys/socket.h>
+
 #include <algorithm>
+#include <cassert>
 #include <functional>
 #include <vector>
 
@@ -80,7 +83,63 @@ splitIPAddress(const std::string& ipprefix);
 
 } // namespace internal
 
+/// \brief A simple representation of IP address.
+///
+/// This structure provides address family independent interfaces of an
+/// IP(v4 or v6) address, so that the application can perform
+/// \c IPCheck::matches without knowing which version of address it is
+/// handling.  (For example, consider the standard socket API: it uses
+/// the generic \c sockaddr structure to represent endpoints).
+///
+/// An object of this class could be constructed from various types of
+/// sources, but in the initial implementation there's only one constructor,
+/// which takes a \c sockaddr structure.  For efficiency the \c IPAddress
+/// object only retains a reference to the necessary part of \c sockaddr.
+/// Therefore the corresponding \c sockaddr instance must be valid while the
+/// \c IPAddress object is used.
+///
+/// This class is copyable so that a fixed object can be easily reused for
+/// different addresses.  To ensure internal integrity, specific member
+/// variables are kept private and only accessible via read-only accessor
+/// methods.  Due to this, it is ensured, for example, that if \c getFamily()
+/// returns \c AF_INET6, \c getLength() always returns 16.
+///
+/// All accessor methods are straightforward and exception free.
+///
+/// In future, we may introduce the default constructor to further improve
+/// reusability.
+struct IPAddress {
+    /// The constructor from socket address structure.
+    ///
+    /// This constructor set up the internal data based on the actual type
+    /// \c sa.  For example, if \c sa.sa_family is \c AF_INET, it assumes
+    /// \c sa actually refers to a \c sockaddr_in structure.
+    /// The behavior when this assumption isn't held is undefined.
+    ///
+    /// \param sa A reference to the socket address structure from which the
+    /// \c IPAddress is to be constructed.
+    explicit IPAddress(const struct sockaddr& sa);
+
+    /// Return the address family of the address
+    ///
+    /// It's AF_INET for IPv4 and AF_INET6 for IPv6.
+    int getFamily() const { return (family); }
 
+    /// Return the binary representation of the address in network byte order.
+    ///
+    /// Only the \c getLength() bytes from the returned pointer are ensured
+    /// to be valid.  In addition, if the \c sockaddr structure given on
+    /// construction was dynamically allocated, the data is valid only until
+    /// the \c sockaddr is invalidated.
+    const uint8_t* getData() const { return (data); }
+
+    /// Return the length of the address.
+    size_t getLength() const { return (length); }
+private:
+    int family;
+    const uint8_t* data;
+    size_t length;
+};
 
 /// \brief IP Check
 ///
@@ -352,3 +411,7 @@ const size_t IPCheck<Context>::IPV4_SIZE;
 } // namespace isc
 
 #endif // __IP_CHECK_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 8 - 0
src/lib/acl/tests/Makefile.am

@@ -1,5 +1,12 @@
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
 
 TESTS =
 if HAVE_GTEST
@@ -13,6 +20,7 @@ run_unittests_SOURCES += loader_test.cc
 run_unittests_SOURCES += logcheck.h
 run_unittests_SOURCES += creators.h
 run_unittests_SOURCES += logic_check_test.cc
+
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 

+ 52 - 0
src/lib/acl/tests/ip_check_unittest.cc

@@ -12,6 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <string.h>
 
 #include <gtest/gtest.h>
 #include <acl/ip_check.h>
@@ -155,6 +159,54 @@ TEST(IPFunctionCheck, SplitIPAddress) {
     EXPECT_THROW(splitIPAddress(" 1/ "), isc::InvalidParameter);
 }
 
+const struct sockaddr&
+getSockAddr(const char* const addr) {
+    struct addrinfo hints, *res;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_NUMERICHOST;
+
+    if (getaddrinfo(addr, NULL, &hints, &res) == 0) {
+        static struct sockaddr_storage ss;
+        void* ss_ptr = &ss;
+        memcpy(ss_ptr, res->ai_addr, res->ai_addrlen);
+        freeaddrinfo(res);
+        return (*static_cast<struct sockaddr*>(ss_ptr));
+    }
+
+    // We don't expect getaddrinfo to fail for our tests.  But if that
+    // ever happens we return a dummy value that would make subsequent test
+    // fail.
+    static struct sockaddr sa_dummy;
+    sa_dummy.sa_family = AF_UNSPEC;
+    return (sa_dummy);
+}
+
+TEST(IPAddress, constructIPv4) {
+    IPAddress ipaddr(getSockAddr("192.0.2.1"));
+    const char expected_data[4] = { 192, 0, 2, 1 };
+    EXPECT_EQ(AF_INET, ipaddr.getFamily());
+    EXPECT_EQ(4, ipaddr.getLength());
+    EXPECT_EQ(0, memcmp(expected_data, ipaddr.getData(), 4));
+}
+
+TEST(IPAddress, constructIPv6) {
+    IPAddress ipaddr(getSockAddr("2001:db8:1234:abcd::53"));
+    const char expected_data[16] = { 0x20, 0x01, 0x0d, 0xb8, 0x12, 0x34, 0xab,
+                                     0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                     0x00, 0x53 };
+    EXPECT_EQ(AF_INET6, ipaddr.getFamily());
+    EXPECT_EQ(16, ipaddr.getLength());
+    EXPECT_EQ(0, memcmp(expected_data, ipaddr.getData(), 16));
+}
+
+TEST(IPAddress, badConstruct) {
+    struct sockaddr sa;
+    sa.sa_family = AF_UNSPEC;
+    EXPECT_THROW(IPAddress ipaddr(sa), isc::BadValue);
+}
+
 // *** IPv4 Tests ***
 
 TEST(IPCheck, V4StringConstructor) {

+ 44 - 0
src/lib/asiolink/io_endpoint.h

@@ -20,6 +20,8 @@
 // See the description of the namespace below.
 #include <unistd.h>             // for some network system calls
 
+#include <sys/socket.h>         // for sockaddr
+
 #include <functional>
 #include <string>
 
@@ -90,6 +92,44 @@ public:
     /// \brief Returns the address family of the endpoint.
     virtual short getFamily() const = 0;
 
+    /// \brief Returns the address of the endpoint in the form of sockaddr
+    /// structure.
+    ///
+    /// The actual instance referenced by the returned value of this method
+    /// is of per address family structure: For IPv4 (AF_INET), it's
+    /// \c sockaddr_in; for IPv6 (AF_INET6), it's \c sockaddr_in6.
+    /// The corresponding port and address members of the underlying structure
+    /// will be set in the network byte order.
+    ///
+    /// This method is "redundant" in that all information to construct the
+    /// \c sockaddr is available via the other "get" methods.
+    /// It is still defined for performance sensitive applications that need
+    /// to get the address information, such as for address based access
+    /// control at a high throughput.  Internally it is implemented with
+    /// minimum overhead such as data copy (this is another reason why this
+    /// method returns a reference).
+    ///
+    /// As a tradeoff, this method is more fragile; it assumes that the
+    /// underlying ASIO implementation stores the address information in
+    /// the form of \c sockaddr and it can be accessed in an efficient way.
+    /// This is the case as of this writing, but if the underlying
+    /// implementation changes this method may become much slower or its
+    /// interface may have to be changed, too.
+    ///
+    /// It is therefore discouraged for normal applications to use this
+    /// method.  Unless the application is very performance sensitive, it
+    /// should use the other "get" method to retrieve specific information
+    /// of the endpoint.
+    ///
+    /// The returned reference is only valid while the corresponding
+    /// \c IOEndpoint is valid.  Once it's destructed the reference will
+    /// become invalid.
+    ///
+    /// \exception None
+    /// \return Reference to a \c sockaddr structure corresponding to the
+    /// endpoint.
+    virtual const struct sockaddr& getSockAddr() const = 0;
+
     bool operator==(const IOEndpoint& other) const;
     bool operator!=(const IOEndpoint& other) const;
 
@@ -121,3 +161,7 @@ public:
 } // namespace asiolink
 } // namespace isc
 #endif // __IO_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 8 - 0
src/lib/asiolink/tcp_endpoint.h

@@ -84,6 +84,10 @@ public:
         return (asio_endpoint_.address());
     }
 
+    virtual const struct sockaddr& getSockAddr() const {
+        return (*asio_endpoint_.data());
+    }
+
     virtual uint16_t getPort() const {
         return (asio_endpoint_.port());
     }
@@ -113,3 +117,7 @@ private:
 } // namespace asiolink
 } // namespace isc
 #endif // __TCP_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 162 - 42
src/lib/asiolink/tests/io_endpoint_unittest.cc

@@ -15,14 +15,25 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <string.h>
+
+#include <boost/shared_ptr.hpp>
+
 #include <asiolink/io_endpoint.h>
 #include <asiolink/io_error.h>
 
+using boost::shared_ptr;
 using namespace isc::asiolink;
 
+namespace {
+typedef shared_ptr<const IOEndpoint> ConstIOEndpointPtr;
+
 TEST(IOEndpointTest, createUDPv4) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 53210);
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("192.0.2.1"), 53210));
     EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
     EXPECT_EQ(53210, ep->getPort());
     EXPECT_EQ(AF_INET, ep->getFamily());
@@ -31,8 +42,8 @@ TEST(IOEndpointTest, createUDPv4) {
 }
 
 TEST(IOEndpointTest, createTCPv4) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5301);
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_TCP,
+                                             IOAddress("192.0.2.1"), 5301));
     EXPECT_EQ("192.0.2.1", ep->getAddress().toText());
     EXPECT_EQ(5301, ep->getPort());
     EXPECT_EQ(AF_INET, ep->getFamily());
@@ -41,8 +52,9 @@ TEST(IOEndpointTest, createTCPv4) {
 }
 
 TEST(IOEndpointTest, createUDPv6) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5302);
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("2001:db8::1234"),
+                                             5302));
     EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
     EXPECT_EQ(5302, ep->getPort());
     EXPECT_EQ(AF_INET6, ep->getFamily());
@@ -51,8 +63,9 @@ TEST(IOEndpointTest, createUDPv6) {
 }
 
 TEST(IOEndpointTest, createTCPv6) {
-    const IOEndpoint* ep;
-    ep = IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303);
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_TCP,
+                                             IOAddress("2001:db8::1234"),
+                                             5303));
     EXPECT_EQ("2001:db8::1234", ep->getAddress().toText());
     EXPECT_EQ(5303, ep->getPort());
     EXPECT_EQ(AF_INET6, ep->getFamily());
@@ -61,23 +74,55 @@ TEST(IOEndpointTest, createTCPv6) {
 }
 
 TEST(IOEndpointTest, equality) {
-    std::vector<const IOEndpoint *> epv;
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5303));
-    epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5304));
-    epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5304));
+    std::vector<ConstIOEndpointPtr> epv;
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1234"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1234"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1234"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1234"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1235"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1235"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("2001:db8::1235"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("2001:db8::1235"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.1"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.1"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.1"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.1"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.2"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.2"), 5303)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_TCP,
+                                         IOAddress("192.0.2.2"), 5304)));
+    epv.push_back(ConstIOEndpointPtr(
+                      IOEndpoint::create(IPPROTO_UDP,
+                                         IOAddress("192.0.2.2"), 5304)));
 
     for (size_t i = 0; i < epv.size(); ++i) {
         for (size_t j = 0; j < epv.size(); ++j) {
@@ -92,23 +137,55 @@ TEST(IOEndpointTest, equality) {
 
     // Create a second array with exactly the same values. We use create()
     // again to make sure we get different endpoints
-    std::vector<const IOEndpoint *> epv2;
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5303));
-    epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5304));
-    epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5304));
+    std::vector<ConstIOEndpointPtr> epv2;
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1234"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1234"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1234"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1234"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1235"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1235"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("2001:db8::1235"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("2001:db8::1235"), 5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP,
+                                          IOAddress("192.0.2.1"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP,
+                                          IOAddress("192.0.2.1"), 5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"),
+                                          5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"),
+                                          5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"),
+                                          5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"),
+                                          5303)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"),
+                                          5304)));
+    epv2.push_back(ConstIOEndpointPtr(
+                       IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"),
+                                          5304)));
 
     for (size_t i = 0; i < epv.size(); ++i) {
         EXPECT_TRUE(*epv[i] == *epv2[i]);
@@ -122,3 +199,46 @@ TEST(IOEndpointTest, createIPProto) {
                  IOError);
 }
 
+void
+sockAddrMatch(const struct sockaddr& actual_sa,
+              const char* const expected_addr_text,
+              const char* const expected_port_text)
+{
+    struct addrinfo hints;
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_DGRAM; // this shouldn't matter
+    hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
+
+    struct addrinfo* res;
+    ASSERT_EQ(0, getaddrinfo(expected_addr_text, expected_port_text, &hints,
+                             &res));
+    EXPECT_EQ(res->ai_family, actual_sa.sa_family);
+#ifdef HAVE_SA_LEN
+    // ASIO doesn't seem to set sa_len, so we set it to the expected value
+    res->ai_addr->sa_len = actual_sa.sa_len;
+#endif
+    EXPECT_EQ(0, memcmp(res->ai_addr, &actual_sa, res->ai_addrlen));
+    free(res);
+}
+
+TEST(IOEndpointTest, getSockAddr) {
+    // UDP/IPv4
+    ConstIOEndpointPtr ep(IOEndpoint::create(IPPROTO_UDP,
+                                             IOAddress("192.0.2.1"), 53210));
+    sockAddrMatch(ep->getSockAddr(), "192.0.2.1", "53210");
+
+    // UDP/IPv6
+    ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::53"), 53));
+    sockAddrMatch(ep->getSockAddr(), "2001:db8::53", "53");
+
+    // TCP/IPv4
+    ep.reset(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 53211));
+    sockAddrMatch(ep->getSockAddr(), "192.0.2.2", "53211");
+
+    // TCP/IPv6
+    ep.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::5300"), 35));
+    sockAddrMatch(ep->getSockAddr(), "2001:db8::5300", "35");
+}
+
+}

+ 8 - 0
src/lib/asiolink/udp_endpoint.h

@@ -84,6 +84,10 @@ public:
         return (asio_endpoint_.address());
     }
 
+    virtual const struct sockaddr& getSockAddr() const {
+        return (*asio_endpoint_.data());
+    }
+
     virtual uint16_t getPort() const {
         return (asio_endpoint_.port());
     }
@@ -113,3 +117,7 @@ private:
 } // namespace asiolink
 } // namespace isc
 #endif // __UDP_ENDPOINT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 3 - 1
src/lib/server_common/Makefile.am

@@ -17,13 +17,15 @@ AM_CXXFLAGS += -Wno-unused-parameter
 endif
 
 lib_LTLIBRARIES = libserver_common.la
-libserver_common_la_SOURCES = portconfig.h portconfig.cc
+libserver_common_la_SOURCES = client.h client.cc
 libserver_common_la_SOURCES += keyring.h keyring.cc
+libserver_common_la_SOURCES += portconfig.h portconfig.cc
 libserver_common_la_LIBADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/cc/libcc.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/config/libcfgclient.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la
+libserver_common_la_LIBADD += $(top_builddir)/src/lib/acl/libacl.la
 libserver_common_la_LIBADD += $(top_builddir)/src/lib/dns/libdns++.la
 
 CLEANFILES = *.gcno *.gcda

+ 75 - 0
src/lib/server_common/client.cc

@@ -0,0 +1,75 @@
+// 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.
+
+#include <string>
+#include <sstream>
+
+#include <acl/ip_check.h>
+
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_message.h>
+
+#include <server_common/client.h>
+
+using namespace isc::acl;
+using namespace isc::server_common;
+using namespace isc::asiolink;
+
+struct Client::ClientImpl {
+    ClientImpl(const IOMessage& request_message) :
+        request_(request_message),
+        request_src_(request_.getRemoteEndpoint().getSockAddr())
+    {}
+
+    const IOMessage& request_;
+    const IPAddress request_src_;
+};
+
+Client::Client(const IOMessage& request_message) :
+    impl_(new ClientImpl(request_message))
+{}
+
+Client::~Client() {
+    delete impl_;
+}
+
+const IOEndpoint&
+Client::getRequestSourceEndpoint() const {
+    return (impl_->request_.getRemoteEndpoint());
+}
+
+const IPAddress&
+Client::getRequestSourceIPAddress() const {
+    return (impl_->request_src_);
+}
+
+std::string
+Client::toText() const {
+    std::stringstream ss;
+    ss << impl_->request_.getRemoteEndpoint().getAddress().toText()
+       << '#' << impl_->request_.getRemoteEndpoint().getPort();
+    return (ss.str());
+}
+
+std::ostream&
+isc::server_common::operator<<(std::ostream& os, const Client& client) {
+    return (os << client.toText());
+}
+
+template <>
+bool
+IPCheck<Client>::matches(const Client& client) const {
+    const IPAddress& request_src(client.getRequestSourceIPAddress());
+    return (compare(request_src.getData(), request_src.getFamily()));
+}

+ 165 - 0
src/lib/server_common/client.h

@@ -0,0 +1,165 @@
+// 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.
+
+#ifndef __CLIENT_H
+#define __CLIENT_H 1
+
+#include <string>
+#include <ostream>
+
+#include <boost/noncopyable.hpp>
+
+#include <acl/ip_check.h>
+
+namespace isc {
+namespace asiolink {
+class IOMessage;
+class IOEndpoint;
+}
+
+namespace acl {
+struct IPAddress;
+}
+
+namespace server_common {
+
+/// A DNS client with a single request context.
+///
+/// The \c Client class represents a DNS client with information of one
+/// DNS request (e.g., a query).  The information includes the source and
+/// destination IP addresses of the request, information of the DNS request
+/// message such as the query name or (if provided) TSIG key information.
+///
+/// A \c Client class object is expected to be constructed on receiving a
+/// new request with lower level information such as IP addresses and is
+/// updated with DNS specific information as the server processes the request.
+/// It is also expected to be used as the primary interface for request
+/// processing such as query handling or access control.
+///
+/// Furthermore, to minimize the overhead, this class would be further
+/// extended so that it can be reusable with an additional method to reset
+/// the internal information.
+///
+/// In the current initial implementation, however, it only contains the
+/// lower level information in the form of \c IOMessage object and cannot
+/// be reused (it must be constructed for every new request).  Also, the
+/// only actual usage of this class at this moment is for ACL handling.
+///
+/// A \c Client class object is generally assumed to be valid throughout
+/// the processing of a single request, and then be destructed or (when
+/// supported) reset.  To avoid it is copied and held accidentally beyond
+/// the expected valid period, it is intentionally made non copyable.
+///
+/// Notes about other possibilities: we may want to abstract it further,
+/// so that it can also be used for DHCP.  In that case, we'd subclass a
+/// base client class for DNS specific clients and DHCP specific clients.
+/// We might also want to separate DNS clients for authoritative servers
+/// and clients for the resolver, especially because the former could be
+/// simpler with performance optimizations.
+class Client : boost::noncopyable {
+public:
+    ///
+    /// \name Constructors and Destructor
+    ///
+    //@{
+    /// The constructor.
+    ///
+    /// This initial version of constructor takes an \c IOMessage object
+    /// that is supposed to represent a DNS request message sent from an
+    /// external client (but the constructor does not perform any assumption
+    /// check on the given \c IOMessage).
+    ///
+    /// If and when we extend the behavior and responsibility
+    /// of this class, this version of constructor will probably be
+    /// deprecated.
+    ///
+    /// \c request_message must be valid throughout the lifetime of the client.
+    ///
+    /// \exception None
+    /// \param request_message Refers to \c IOMessage corresponding to some
+    /// DNS request message.
+    explicit Client(const isc::asiolink::IOMessage& request_message);
+
+    /// The destructor
+    ~Client();
+    //@}
+
+    /// Return the client's endpoint of the request.
+    ///
+    /// This should be identical to the result of \c getRemoteEndpoint()
+    /// called on \c request_message passed to the constructor.
+    ///
+    /// \exception None
+    const isc::asiolink::IOEndpoint& getRequestSourceEndpoint() const;
+
+    /// Return the IP address part of the client request's endpoint.
+    ///
+    /// The resulting \c IPAddress can be constructed using
+    /// \c getRequestSourceEndpoint(), and in that sense this method is
+    /// redundant.  But this implementation internally constructs the
+    /// \c IPAddress on construction and always returns a reference to it,
+    /// and should be more efficient.  It is provided so that it can be
+    /// called multiple times in a complicated ACL with minimum cost.
+    ///
+    /// \exception None
+    const isc::acl::IPAddress& getRequestSourceIPAddress() const;
+
+    /// Convert the Client to a string.
+    ///
+    /// (In the initial implementation) the format of the resulting string
+    /// is as follows:
+    /// \code <IP address>#<port>
+    /// \endcode
+    /// The IP address is the textual representation of the client's IP
+    /// address, which is the source address of the request the client has
+    /// sent.  The port is the UDP or TCP of the client's end of the request.
+    ///
+    /// \exception std::bad_alloc Internal resource allocation fails
+    std::string toText() const;
+
+private:
+    struct ClientImpl;
+    ClientImpl* impl_;
+};
+
+/// \brief Insert the \c Client as a string into stream.
+///
+/// This method convert \c client into a string and inserts it into the
+/// output stream \c os.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param edns A reference to an \c Client object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream& operator<<(std::ostream& os, const Client& client);
+}
+
+namespace acl {
+/// The specialization of \c IPCheck for access control with \c Client.
+///
+/// It returns \c true if the source IP address of the client's request
+/// matches the expression encapsulated in the \c IPCheck, and returns
+/// \c false if not.
+template <>
+bool IPCheck<server_common::Client>::matches(
+    const server_common::Client& client) const;
+}
+}
+
+#endif  // __CLIENT_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 1 - 0
src/lib/server_common/tests/Makefile.am

@@ -26,6 +26,7 @@ TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
+run_unittests_SOURCES += client_unittest.cc
 run_unittests_SOURCES += portconfig_unittest.cc
 run_unittests_SOURCES += keyring_test.cc
 nodist_run_unittests_SOURCES = data_path.h

+ 127 - 0
src/lib/server_common/tests/client_unittest.cc

@@ -0,0 +1,127 @@
+// 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.
+
+#include <sys/socket.h>
+#include <string.h>
+
+#include <string>
+#include <sstream>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <acl/ip_check.h>
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_message.h>
+
+#include <server_common/client.h>
+
+#include <gtest/gtest.h>
+
+using namespace boost;
+using namespace isc::acl;
+using namespace isc::asiolink;
+using namespace isc::server_common;
+
+namespace {
+
+class ClientTest : public ::testing::Test {
+protected:
+    ClientTest() {
+        endpoint4.reset(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"),
+                                           53214));
+        endpoint6.reset(IOEndpoint::create(IPPROTO_TCP,
+                                           IOAddress("2001:db8::1"), 53216));
+        request4.reset(new IOMessage(NULL, 0, IOSocket::getDummyUDPSocket(),
+                                     *endpoint4));
+        request6.reset(new IOMessage(NULL, 0, IOSocket::getDummyTCPSocket(),
+                                     *endpoint6));
+        client4.reset(new Client(*request4));
+        client6.reset(new Client(*request6));
+    }
+    scoped_ptr<const IOEndpoint> endpoint4;
+    scoped_ptr<const IOEndpoint> endpoint6;
+    scoped_ptr<const IOMessage> request4;
+    scoped_ptr<const IOMessage> request6;
+    scoped_ptr<const Client> client4;
+    scoped_ptr<const Client> client6;
+};
+
+TEST_F(ClientTest, constructIPv4) {
+    EXPECT_EQ(AF_INET, client4->getRequestSourceEndpoint().getFamily());
+    EXPECT_EQ(IPPROTO_UDP, client4->getRequestSourceEndpoint().getProtocol());
+    EXPECT_EQ("192.0.2.1",
+              client4->getRequestSourceEndpoint().getAddress().toText());
+    EXPECT_EQ(53214, client4->getRequestSourceEndpoint().getPort());
+
+    const uint8_t expected_data[] = { 192, 0, 2, 1 };
+    EXPECT_EQ(AF_INET, client4->getRequestSourceIPAddress().getFamily());
+    ASSERT_EQ(4, client4->getRequestSourceIPAddress().getLength());
+    EXPECT_EQ(0, memcmp(expected_data,
+                        client4->getRequestSourceIPAddress().getData(), 4));
+}
+
+TEST_F(ClientTest, constructIPv6) {
+    EXPECT_EQ(AF_INET6, client6->getRequestSourceEndpoint().getFamily());
+    EXPECT_EQ(IPPROTO_TCP, client6->getRequestSourceEndpoint().getProtocol());
+    EXPECT_EQ("2001:db8::1",
+              client6->getRequestSourceEndpoint().getAddress().toText());
+    EXPECT_EQ(53216, client6->getRequestSourceEndpoint().getPort());
+
+    const uint8_t expected_data[] = { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00,
+                                      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                                      0x00, 0x01 };
+    EXPECT_EQ(AF_INET6, client6->getRequestSourceIPAddress().getFamily());
+    ASSERT_EQ(16, client6->getRequestSourceIPAddress().getLength());
+    EXPECT_EQ(0, memcmp(expected_data,
+                        client6->getRequestSourceIPAddress().getData(), 16));
+}
+
+TEST_F(ClientTest, ACLCheckIPv4) {
+    // Exact match
+    EXPECT_TRUE(IPCheck<Client>("192.0.2.1").matches(*client4));
+    // Exact match (negative)
+    EXPECT_FALSE(IPCheck<Client>("192.0.2.53").matches(*client4));
+    // Prefix match
+    EXPECT_TRUE(IPCheck<Client>("192.0.2.0/24").matches(*client4));
+    // Prefix match (negative)
+    EXPECT_FALSE(IPCheck<Client>("192.0.1.0/24").matches(*client4));
+    // Address family mismatch (the first 4 bytes of the IPv6 address has the
+    // same binary representation as the client's IPv4 address, which
+    // shouldn't confuse the match logic)
+    EXPECT_FALSE(IPCheck<Client>("c000:0201::").matches(*client4));
+}
+
+TEST_F(ClientTest, ACLCheckIPv6) {
+    // The following are a set of tests of the same concept as ACLCheckIPv4
+    EXPECT_TRUE(IPCheck<Client>("2001:db8::1").matches(*client6));
+    EXPECT_FALSE(IPCheck<Client>("2001:db8::53").matches(*client6));
+    EXPECT_TRUE(IPCheck<Client>("2001:db8::/64").matches(*client6));
+    EXPECT_FALSE(IPCheck<Client>("2001:db8:1::/64").matches(*client6));
+    EXPECT_FALSE(IPCheck<Client>("32.1.13.184").matches(*client6));
+}
+
+TEST_F(ClientTest, toText) {
+    EXPECT_EQ("192.0.2.1#53214", client4->toText());
+    EXPECT_EQ("2001:db8::1#53216", client6->toText());
+}
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST_F(ClientTest, LeftShiftOperator) {
+    std::ostringstream oss;
+    oss << *client4 << "more text";
+    EXPECT_EQ(client4->toText() + std::string("more text"), oss.str());
+}
+}