BIND 10 master, updated. e0744372924442ec75809d3964e917680c57a2ce [master] Merge branch 'trac999'

BIND 10 source code commits bind10-changes at lists.isc.org
Tue Jun 28 07:52:28 UTC 2011


The branch, master has been updated
       via  e0744372924442ec75809d3964e917680c57a2ce (commit)
       via  041c3ec8a0768c513131f47467652ab2aa75a07a (commit)
       via  ab31e2fbf10950084d9cda73c0b4fc7d36296817 (commit)
       via  21a333f512f2a11ce0c770b7d72aacfb623d0c14 (commit)
       via  0c1589a0842cefe0793b538c53c1cb102080b570 (commit)
       via  166b4747ffadbc6b3a94647f1470ba776aeb8c51 (commit)
       via  181283a52982eaf9f8637bd09a2e1dfaef5ce302 (commit)
       via  a46b0d4ab62e16c4096bcb8790659bee93205470 (commit)
       via  aba816f40efe336b20ae56871a531c87117ad24c (commit)
       via  217c09751aab2dc84f49e7942b2c081a0381945f (commit)
       via  ca924dafc7902bbc2a22660fb00f70c0d34c6471 (commit)
       via  679d8390f4fb1253ac26a86a47a9279f3d88174e (commit)
       via  250100ecf6468ad2cbc47663d1f6e83f1fe10f9a (commit)
       via  5aabfb971d4338d3e488d05f8c06a9db973ede5d (commit)
       via  e9a1e75b3d83fe811d9a4e32d6d9a21f446a37d3 (commit)
       via  9f2167d3b5878a5709fd9f1ba2cd200f29f057c8 (commit)
       via  a6e68091deaf13986355b8763c7348b2da71d7d5 (commit)
       via  362d429a32fecd1b59f309466e098935242f9054 (commit)
       via  430f3e516852ee9a8af655626bbab16b03e4cf72 (commit)
       via  1b47d1cf3e0bc8e3c6166d049070dda2f298c7bf (commit)
       via  d86526508726fc2941a7d35730013b75f49ab4a5 (commit)
       via  8d07954792d35120580d9d94fedd642d4797cd53 (commit)
       via  bc8bb2e13305ae879b31a6accbd3f5f1855bf327 (commit)
       via  112aa5ce69ec2440db83d89196144b782f064564 (commit)
       via  42177c3c2dabc2b46adb7133374a4c2da46b1f9e (commit)
       via  d3a000535b506ae8af54119af3dde7d14509654e (commit)
       via  2048643593b3e9c8f34af40bbd00342c2c0c1318 (commit)
      from  499ca0aa022f72674c29a2c66050a5cf1ee9f192 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit e0744372924442ec75809d3964e917680c57a2ce
Merge: 499ca0a 041c3ec
Author: JINMEI Tatuya <jinmei at isc.org>
Date:   Tue Jun 28 00:41:02 2011 -0700

    [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

-----------------------------------------------------------------------

Summary of changes:
 src/bin/resolver/main.cc                           |    4 +
 src/bin/resolver/resolver.cc                       |  218 +++++++++++++-------
 src/bin/resolver/resolver.h                        |   31 +++
 src/bin/resolver/resolver.spec.pre.in              |   35 +++
 src/bin/resolver/resolver_messages.mes             |   21 ++
 src/bin/resolver/tests/Makefile.am                 |    1 +
 src/bin/resolver/tests/resolver_config_unittest.cc |  169 ++++++++++++++-
 src/bin/resolver/tests/resolver_unittest.cc        |   61 ++++++-
 .../resolver/tests/response_scrubber_unittest.cc   |    6 +
 src/lib/Makefile.am                                |    2 +-
 src/lib/acl/Makefile.am                            |    2 +-
 src/lib/acl/ip_check.cc                            |   36 +++-
 src/lib/acl/ip_check.h                             |   63 ++++++
 src/lib/acl/tests/Makefile.am                      |    8 +
 src/lib/acl/tests/ip_check_unittest.cc             |   52 +++++
 src/lib/asiolink/io_endpoint.h                     |   44 ++++
 src/lib/asiolink/tcp_endpoint.h                    |    8 +
 src/lib/asiolink/tests/io_endpoint_unittest.cc     |  204 +++++++++++++++----
 src/lib/asiolink/udp_endpoint.h                    |    8 +
 src/lib/server_common/Makefile.am                  |    4 +-
 src/lib/server_common/client.cc                    |   75 +++++++
 src/lib/server_common/client.h                     |  165 +++++++++++++++
 src/lib/server_common/tests/Makefile.am            |    1 +
 src/lib/server_common/tests/client_unittest.cc     |  127 ++++++++++++
 24 files changed, 1214 insertions(+), 131 deletions(-)
 create mode 100644 src/lib/server_common/client.cc
 create mode 100644 src/lib/server_common/client.h
 create mode 100644 src/lib/server_common/tests/client_unittest.cc

-----------------------------------------------------------------------
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index e7cc4cf..d9c30b9 100644
--- a/src/bin/resolver/main.cc
+++ b/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);
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 68c1b20..951d0fd 100644
--- a/src/bin/resolver/resolver.cc
+++ b/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);
+}
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 2890dd3..9c78126 100644
--- a/src/bin/resolver/resolver.h
+++ b/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_;
diff --git a/src/bin/resolver/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
index 9df1e75..076ef85 100644
--- a/src/bin/resolver/resolver.spec.pre.in
+++ b/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": [
diff --git a/src/bin/resolver/resolver_messages.mes b/src/bin/resolver/resolver_messages.mes
index 4ce0f7d..6c5be64 100644
--- a/src/bin/resolver/resolver_messages.mes
+++ b/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.
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index e0d9fd4..7027ae9 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/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
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index 70e856d..9006301 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/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\"}]}"))));
+}
+
 }
diff --git a/src/bin/resolver/tests/resolver_unittest.cc b/src/bin/resolver/tests/resolver_unittest.cc
index 97edf12..9bcc261 100644
--- a/src/bin/resolver/tests/resolver_unittest.cc
+++ b/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());
+}
+
+
 }
diff --git a/src/bin/resolver/tests/response_scrubber_unittest.cc b/src/bin/resolver/tests/response_scrubber_unittest.cc
index eff5598..1570def 100644
--- a/src/bin/resolver/tests/response_scrubber_unittest.cc
+++ b/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
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index c8827e2..f4bef6b 100644
--- a/src/lib/Makefile.am
+++ b/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
diff --git a/src/lib/acl/Makefile.am b/src/lib/acl/Makefile.am
index defaf13..ffca59f 100644
--- a/src/lib/acl/Makefile.am
+++ b/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
diff --git a/src/lib/acl/ip_check.cc b/src/lib/acl/ip_check.cc
index 08c8431..76aacca 100644
--- a/src/lib/acl/ip_check.cc
+++ b/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
diff --git a/src/lib/acl/ip_check.h b/src/lib/acl/ip_check.h
index 5bc70fc..794b943 100644
--- a/src/lib/acl/ip_check.h
+++ b/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:
diff --git a/src/lib/acl/tests/Makefile.am b/src/lib/acl/tests/Makefile.am
index f43e057..03b08bb 100644
--- a/src/lib/acl/tests/Makefile.am
+++ b/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)
 
diff --git a/src/lib/acl/tests/ip_check_unittest.cc b/src/lib/acl/tests/ip_check_unittest.cc
index 3fcb05b..fb24978 100644
--- a/src/lib/acl/tests/ip_check_unittest.cc
+++ b/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) {
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
index 756fa3b..11ea97b 100644
--- a/src/lib/asiolink/io_endpoint.h
+++ b/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:
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
index 3e420f3..a54f6b2 100644
--- a/src/lib/asiolink/tcp_endpoint.h
+++ b/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:
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
index ce21fde..f0279d1 100644
--- a/src/lib/asiolink/tests/io_endpoint_unittest.cc
+++ b/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");
+}
+
+}
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
index 5c8a1fe..c5ba3bd 100644
--- a/src/lib/asiolink/udp_endpoint.h
+++ b/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:
diff --git a/src/lib/server_common/Makefile.am b/src/lib/server_common/Makefile.am
index a3063ba..d576104 100644
--- a/src/lib/server_common/Makefile.am
+++ b/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
diff --git a/src/lib/server_common/client.cc b/src/lib/server_common/client.cc
new file mode 100644
index 0000000..31dee88
--- /dev/null
+++ b/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()));
+}
diff --git a/src/lib/server_common/client.h b/src/lib/server_common/client.h
new file mode 100644
index 0000000..148e069
--- /dev/null
+++ b/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:
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index ecdb2d9..af8c613 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/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
diff --git a/src/lib/server_common/tests/client_unittest.cc b/src/lib/server_common/tests/client_unittest.cc
new file mode 100644
index 0000000..34a90a2
--- /dev/null
+++ b/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());
+}
+}




More information about the bind10-changes mailing list