Browse Source

Merge branch 'master' into trac2087

Mukund Sivaraman 12 years ago
parent
commit
49ad6346f5

+ 1 - 1
configure.ac

@@ -2,7 +2,7 @@
 # Process this file with autoconf to produce a configure script.
 # Process this file with autoconf to produce a configure script.
 
 
 AC_PREREQ([2.59])
 AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20120405, bind10-dev@isc.org)
+AC_INIT(bind10-devel, 20120712, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
 AC_CONFIG_SRCDIR(README)
 AM_INIT_AUTOMAKE
 AM_INIT_AUTOMAKE
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible

+ 64 - 60
doc/guide/bind10-guide.xml

@@ -1243,20 +1243,18 @@ or accounts database -->
       <title>Configuration specification for b10-cmdctl</title>
       <title>Configuration specification for b10-cmdctl</title>
       <para>
       <para>
         The configuration items for <command>b10-cmdctl</command> are:
         The configuration items for <command>b10-cmdctl</command> are:
-key_file
+        <varname>accounts_file</varname> which defines the path to the
-cert_file
+        user accounts database (the default is
-accounts_file
+        <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>);
+        <varname>cert_file</varname> which defines the path to the
+        PEM certificate file (the default is
+        <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>);
+        and
+	<varname>key_file</varname> which defines the path to the
+	PEM private key file (the default is
+        <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>).
       </para>
       </para>
-<!-- TODO -->
-
-      <para>
-        The control commands are:
-print_settings
-<!-- TODO: remove that -->
 
 
-shutdown
-      </para>
-<!-- TODO -->
     </section>
     </section>
 
 
 <!--
 <!--
@@ -1309,7 +1307,8 @@ TODO
 
 
     <para>
     <para>
       The <command>b10-auth</command> is the authoritative DNS server.
       The <command>b10-auth</command> is the authoritative DNS server.
-      It supports EDNS0 and DNSSEC. It supports IPv6.
+      It supports EDNS0, DNSSEC, IPv6, and SQLite3 and in-memory zone
+      data backends.
       Normally it is started by the <command>bind10</command> master
       Normally it is started by the <command>bind10</command> master
       process.
       process.
     </para>
     </para>
@@ -1334,8 +1333,8 @@ since we used bind10 -->
               <simpara>This is an optional string to define the path to find
               <simpara>This is an optional string to define the path to find
                  the SQLite3 database file.
                  the SQLite3 database file.
 <!-- TODO: -->
 <!-- TODO: -->
-Note: Later the DNS server will use various data source backends.
+Note: This may be a temporary setting because the DNS server
-This may be a temporary setting until then.
+can use various data source backends.
               </simpara>
               </simpara>
             </listitem>
             </listitem>
           </varlistentry>
           </varlistentry>
@@ -1356,7 +1355,9 @@ This may be a temporary setting until then.
       and
       and
       <varname>zones</varname> to define
       <varname>zones</varname> to define
       the <varname>file</varname> path name,
       the <varname>file</varname> path name,
-      the <varname>filetype</varname> (e.g., <varname>sqlite3</varname>),
+      the <varname>filetype</varname> (<quote>sqlite3</quote> to load
+      from a SQLite3 database file or <quote>text</quote> to
+      load from a master text file),
       and the <varname>origin</varname> (default domain).
       and the <varname>origin</varname> (default domain).
 
 
       By default, this is empty.
       By default, this is empty.
@@ -1528,13 +1529,13 @@ This may be a temporary setting until then.
 &gt; <userinput>config commit</userinput></screen>
 &gt; <userinput>config commit</userinput></screen>
 
 
 	  The authoritative server will begin serving it immediately
 	  The authoritative server will begin serving it immediately
-	  after it is loaded.
+	  after the zone data is loaded from the master text file.
 	</para>
 	</para>
 
 
       </section>
       </section>
 
 
       <section id="in-memory-datasource-with-sqlite3-backend">
       <section id="in-memory-datasource-with-sqlite3-backend">
-	<title>In-memory Data Source With SQLite3 Backend</title>
+	<title>In-memory Data Source with SQLite3 Backend</title>
 
 
 	<para>
 	<para>
 <!--	  How to configure it. -->
 <!--	  How to configure it. -->
@@ -1556,7 +1557,7 @@ This may be a temporary setting until then.
 &gt; <userinput>config commit</userinput></screen>
 &gt; <userinput>config commit</userinput></screen>
 
 
 	  The authoritative server will begin serving it immediately
 	  The authoritative server will begin serving it immediately
-	  after it is loaded.
+	  after the zone data is loaded from the database file.
 	</para>
 	</para>
 
 
       </section>
       </section>
@@ -1707,7 +1708,7 @@ TODO
       <command>b10-auth</command>.
       <command>b10-auth</command>.
       In combination with <command>b10-zonemgr</command> (for
       In combination with <command>b10-zonemgr</command> (for
       automated SOA checks), this allows the BIND 10 server to
       automated SOA checks), this allows the BIND 10 server to
-      provide <quote>secondary</quote> service.
+      provide <emphasis>secondary</emphasis> service.
     </para>
     </para>
 
 
     <para>
     <para>
@@ -1809,11 +1810,8 @@ what if a NOTIFY is sent?
 
 
       <screen>&gt; <userinput>config add Zonemgr/secondary_zones</userinput>
       <screen>&gt; <userinput>config add Zonemgr/secondary_zones</userinput>
 &gt; <userinput>config set Zonemgr/secondary_zones[0]/name "<option>example.com</option>"</userinput>
 &gt; <userinput>config set Zonemgr/secondary_zones[0]/name "<option>example.com</option>"</userinput>
-&gt; <userinput>config set Zonemgr/secondary_zones[0]/class "<option>IN</option>"</userinput>
 &gt; <userinput>config commit</userinput></screen>
 &gt; <userinput>config commit</userinput></screen>
 
 
-<!-- TODO: remove the IN class example above when it is the default -->
-
       </para>
       </para>
 
 
       <para>
       <para>
@@ -1848,6 +1846,9 @@ what if a NOTIFY is sent?
         automatically sent a <varname>loadzone</varname> command to
         automatically sent a <varname>loadzone</varname> command to
         reload the corresponding zone into memory from the backend.
         reload the corresponding zone into memory from the backend.
       </para>
       </para>
+<!-- TODO: currently it delays the queries; see
+http://bind10.isc.org/wiki/ScalableZoneLoadDesign#a7.2UpdatingaZone
+-->
 
 
       <para>
       <para>
 	The administrator doesn't have to do anything for
 	The administrator doesn't have to do anything for
@@ -1870,7 +1871,7 @@ what if a NOTIFY is sent?
       When the <command>b10-auth</command> authoritative DNS server
       When the <command>b10-auth</command> authoritative DNS server
       receives an AXFR or IXFR request, <command>b10-auth</command>
       receives an AXFR or IXFR request, <command>b10-auth</command>
       internally forwards the request to <command>b10-xfrout</command>,
       internally forwards the request to <command>b10-xfrout</command>,
-      which handles the rest of request processing.
+      which handles the rest of this request processing.
       This is used to provide primary DNS service to share zones
       This is used to provide primary DNS service to share zones
       to secondary name servers.
       to secondary name servers.
       The <command>b10-xfrout</command> is also used to send
       The <command>b10-xfrout</command> is also used to send
@@ -1919,8 +1920,9 @@ Xfrout/transfer_acl[0]	{"action": "ACCEPT"}	any	(default)</screen>
 &gt; <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]</userinput>
 &gt; <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1", "key": "key.example"}]</userinput>
 &gt; <userinput>config commit</userinput></screen>
 &gt; <userinput>config commit</userinput></screen>
 
 
-    <para>Both Xfrout and Auth will use the system wide keyring to check
+    <para>Both <command>b10-xfrout</command> and <command>b10-auth</command>
-    TSIGs in the incoming messages and to sign responses.</para>
+      will use the system wide keyring to check
+      TSIGs in the incoming messages and to sign responses.</para>
 
 
     <note><simpara>
     <note><simpara>
         The way to specify zone specific configuration (ACLs, etc) is
         The way to specify zone specific configuration (ACLs, etc) is
@@ -1954,14 +1956,14 @@ what is XfroutClient xfr_client??
       When the <command>b10-auth</command> authoritative DNS server
       When the <command>b10-auth</command> authoritative DNS server
       receives an UPDATE request, it internally forwards the request
       receives an UPDATE request, it internally forwards the request
       to <command>b10-ddns</command>, which handles the rest of
       to <command>b10-ddns</command>, which handles the rest of
-      request processing.
+      this request processing.
-      When the processing is completed <command>b10-ddns</command>
+      When the processing is completed, <command>b10-ddns</command>
-      will send a response to the client with the RCODE set to the
+      will send a response to the client as specified in RFC 2136
-      value as specified in RFC 2136 (NOERROR for successful update,
+      (NOERROR for successful update, REFUSED if rejected due to
-      REFUSED if rejected due to ACL check, etc).
+      ACL check, etc).
       If the zone has been changed as a result, it will internally
       If the zone has been changed as a result, it will internally
       notify <command>b10-xfrout</command> so that other secondary
       notify <command>b10-xfrout</command> so that other secondary
-      servers will be notified via the DNS notify protocol.
+      servers will be notified via the DNS NOTIFY protocol.
       In addition, if <command>b10-auth</command> serves the updated
       In addition, if <command>b10-auth</command> serves the updated
       zone from its in-memory cache (as described in
       zone from its in-memory cache (as described in
       <xref linkend="in-memory-datasource-with-sqlite3-backend" />),
       <xref linkend="in-memory-datasource-with-sqlite3-backend" />),
@@ -1987,10 +1989,10 @@ what is XfroutClient xfr_client??
       As of this writing <command>b10-ddns</command> does not support
       As of this writing <command>b10-ddns</command> does not support
       update forwarding for secondary zones.
       update forwarding for secondary zones.
       If it receives an update request for a secondary zone, it will
       If it receives an update request for a secondary zone, it will
-      immediately return a response with an RCODE of NOTIMP.
+      immediately return a <quote>not implemented</quote> response.
       <note><simpara>
       <note><simpara>
-	  For feature completeness update forwarding should be
+	  For feature completeness, update forwarding should be
-	  eventually supported.  But right now it's considered a lower
+	  eventually supported.  But currently it's considered a lower
 	  priority task and there is no specific plan of implementing
 	  priority task and there is no specific plan of implementing
 	  this feature.
 	  this feature.
 <!-- See Trac #2063 -->
 <!-- See Trac #2063 -->
@@ -2020,9 +2022,9 @@ what is XfroutClient xfr_client??
         underlying data source storing the zone data be writable.
         underlying data source storing the zone data be writable.
         In the current implementation this means the zone must be stored
         In the current implementation this means the zone must be stored
         in an SQLite3-based data source.
         in an SQLite3-based data source.
-        Also, right now, the <command>b10-ddns</command> component
+	Also, in this development version, the <command>b10-ddns</command>
-        configures itself with the data source referring to the
+	component configures itself with the data source referring to the
-        <quote>database_file</quote> configuration parameter of
+        <varname>database_file</varname> configuration parameter of
         <command>b10-auth</command>.
         <command>b10-auth</command>.
         So this information must be configured correctly before starting
         So this information must be configured correctly before starting
         <command>b10-ddns</command>.
         <command>b10-ddns</command>.
@@ -2056,14 +2058,16 @@ what is XfroutClient xfr_client??
 &gt; <userinput>config commit</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
 </screen>
       <note><simpara>
       <note><simpara>
-	  In theory "kind" could be omitted because "dispensable" is its
+	  In theory <varname>kind</varname> could be omitted because
-	  default.  But there's some peculiar behavior (which should
+	  "dispensable" is its default.
-	  be a bug and should be fixed eventually; see Trac ticket
+	  But there's some peculiar behavior (which should be a
-	  #2064) with bindctl and you'll still need to specify that explicitly.
+	  bug and should be fixed eventually; see Trac ticket #2064)
-	  Likewise, "address" may look unnecessary because
+	  with <command>bindctl</command> and you'll still need to
-	  <command>b10-ddns</command> would start and work without
+	  specify that explicitly.  Likewise, <varname>address</varname>
-	  specifying it.  But for it to shutdown gracefully this
+	  may look unnecessary because <command>b10-ddns</command>
-	  parameter should also be specified.
+	  would start and work without specifying it.  But for it
+	  to shutdown gracefully this parameter should also be
+	  specified.
       </simpara></note>
       </simpara></note>
       </para>
       </para>
     </section>
     </section>
@@ -2071,14 +2075,13 @@ what is XfroutClient xfr_client??
     <section>
     <section>
       <title>Access Control</title>
       <title>Access Control</title>
       <para>
       <para>
-        By default <command>b10-ddns</command> rejects any update
+        By default, <command>b10-ddns</command> rejects any update
-        requests from any clients by returning a response with an RCODE
+        requests from any clients by returning a REFUSED response.
-        of REFUSED.
         To allow updates to take effect, an access control rule
         To allow updates to take effect, an access control rule
         (called update ACL) with a policy allowing updates must explicitly be
         (called update ACL) with a policy allowing updates must explicitly be
         configured.
         configured.
         Update ACL must be configured per zone basis in the
         Update ACL must be configured per zone basis in the
-        <quote>zones</quote> configuration parameter of
+        <varname>zones</varname> configuration parameter of
         <command>b10-ddns</command>.
         <command>b10-ddns</command>.
         This is a list of per-zone configurations regarding DDNS.
         This is a list of per-zone configurations regarding DDNS.
         Each list element consists of the following parameters:
         Each list element consists of the following parameters:
@@ -2113,14 +2116,12 @@ what is XfroutClient xfr_client??
         In general, an update ACL rule that allows an update request
         In general, an update ACL rule that allows an update request
         should be configured with a TSIG key.
         should be configured with a TSIG key.
         This is an example update ACL that allows updates to the zone
         This is an example update ACL that allows updates to the zone
-        named <quote>example.org</quote> of RR class <quote>IN</quote>
+        named <quote>example.org</quote> (of default RR class <quote>IN</quote>)
         from clients that send requests signed with a TSIG whose
         from clients that send requests signed with a TSIG whose
         key name is "key.example.org" (and refuses all others):
         key name is "key.example.org" (and refuses all others):
       <screen>
       <screen>
 &gt; <userinput>config add DDNS/zones</userinput>
 &gt; <userinput>config add DDNS/zones</userinput>
 &gt; <userinput>config set DDNS/zones[0]/origin example.org</userinput>
 &gt; <userinput>config set DDNS/zones[0]/origin example.org</userinput>
-&gt; <userinput>config set DDNS/zones[0]/class IN</userinput>
-(Note: "class" can be omitted)
 &gt; <userinput>config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "key": "key.example.org"}</userinput>
 &gt; <userinput>config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "key": "key.example.org"}</userinput>
 &gt; <userinput>config commit</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
 </screen>
@@ -2145,11 +2146,13 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
 &gt; <userinput>config commit</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
 </screen>
       (Note the "add" in the first line.  Before this sequence, we
       (Note the "add" in the first line.  Before this sequence, we
-      have had only entry in zones[0]/update_acl.  The "add" command
+      have had only entry in <varname>zones[0]/update_acl</varname>.
-      with a value (rule) adds a new entry and sets it to the given rule.
+      The <command>add</command> command with a value (rule) adds
+      a new entry and sets it to the given rule.
+
       Due to a limitation of the current implementation, it doesn't
       Due to a limitation of the current implementation, it doesn't
       work if you first try to just add a new entry and then set it to
       work if you first try to just add a new entry and then set it to
-      a given rule).
+      a given rule.)
       </para>
       </para>
 
 
       <note><simpara>
       <note><simpara>
@@ -2171,6 +2174,7 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
 	which is rejecting any requests in the case of
 	which is rejecting any requests in the case of
 	<command>b10-ddns</command>.
 	<command>b10-ddns</command>.
       </para>
       </para>
+<!-- TODO: what are the other defaults? -->
 
 
       <para>
       <para>
 	Other actions than "ACCEPT", namely "REJECT" and "DROP", can be
 	Other actions than "ACCEPT", namely "REJECT" and "DROP", can be
@@ -2209,8 +2213,8 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
       <title>Miscellaneous Operational Issues</title>
       <title>Miscellaneous Operational Issues</title>
       <para>
       <para>
         Unlike BIND 9, BIND 10 currently does not support automatic
         Unlike BIND 9, BIND 10 currently does not support automatic
-        resigning of DNSSEC-signed zone when it's updated via DDNS.
+        re-signing of DNSSEC-signed zone when it's updated via DDNS.
-        It could be possible to resign the updated zone afterwards
+        It could be possible to re-sign the updated zone afterwards
         or make sure the update request also updates related DNSSEC
         or make sure the update request also updates related DNSSEC
         records, but that will be pretty error-prone operation.
         records, but that will be pretty error-prone operation.
         In general, it's not advisable to allow DDNS for a signed zone
         In general, it's not advisable to allow DDNS for a signed zone
@@ -2234,8 +2238,8 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
         <command>b10-zonemgr</command>.  Zones listed in
         <command>b10-zonemgr</command>.  Zones listed in
         <quote>secondary_zones</quote> will never be updated via DDNS
         <quote>secondary_zones</quote> will never be updated via DDNS
         regardless of the update ACL configuration;
         regardless of the update ACL configuration;
-	<command>b10-ddns</command> will return a response with an
+	<command>b10-ddns</command> will return a NOTAUTH (server
-	RCODE of NOTAUTH as specified in RFC 2136.
+        not authoritative for the zone) response.
         If you have a "conceptual" secondary zone whose content is a
         If you have a "conceptual" secondary zone whose content is a
         copy of some external source but is not updated via the
         copy of some external source but is not updated via the
         standard zone transfers and therefore not listed in
         standard zone transfers and therefore not listed in

+ 80 - 28
src/lib/datasrc/client_list.cc

@@ -16,6 +16,7 @@
 #include "client.h"
 #include "client.h"
 #include "factory.h"
 #include "factory.h"
 #include "memory_datasrc.h"
 #include "memory_datasrc.h"
+#include "logger.h"
 
 
 #include <memory>
 #include <memory>
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
@@ -30,11 +31,19 @@ namespace datasrc {
 
 
 ConfigurableClientList::DataSourceInfo::DataSourceInfo(
 ConfigurableClientList::DataSourceInfo::DataSourceInfo(
     DataSourceClient* data_src_client,
     DataSourceClient* data_src_client,
-    const DataSourceClientContainerPtr& container, bool hasCache) :
+    const DataSourceClientContainerPtr& container, bool has_cache) :
     data_src_client_(data_src_client),
     data_src_client_(data_src_client),
     container_(container)
     container_(container)
 {
 {
-    if (hasCache) {
+    if (has_cache) {
+        cache_.reset(new InMemoryClient);
+    }
+}
+
+ConfigurableClientList::DataSourceInfo::DataSourceInfo(bool has_cache) :
+    data_src_client_(NULL)
+{
+    if (has_cache) {
         cache_.reset(new InMemoryClient);
         cache_.reset(new InMemoryClient);
     }
     }
 }
 }
@@ -58,49 +67,92 @@ ConfigurableClientList::configure(const Element& config, bool allow_cache) {
             if (paramConf == ConstElementPtr()) {
             if (paramConf == ConstElementPtr()) {
                 paramConf.reset(new NullElement());
                 paramConf.reset(new NullElement());
             }
             }
-            // TODO: Special-case the master files type.
-            // Ask the factory to create the data source for us
-            const DataSourcePair ds(this->getDataSourceClient(type,
-                                                              paramConf));
             const bool want_cache(allow_cache &&
             const bool want_cache(allow_cache &&
                                   dconf->contains("cache-enable") &&
                                   dconf->contains("cache-enable") &&
                                   dconf->get("cache-enable")->boolValue());
                                   dconf->get("cache-enable")->boolValue());
-            // And put it into the vector
+
-            new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
+            if (type == "MasterFiles") {
-                                                      want_cache));
+                // In case the cache is not allowed, we just skip the master
+                // files (at least for now)
+                if (!allow_cache) {
+                    // We're not going to load these zones. Issue warnings about it.
+                    const map<string, ConstElementPtr>
+                        zones_files(paramConf->mapValue());
+                    for (map<string, ConstElementPtr>::const_iterator
+                         it(zones_files.begin()); it != zones_files.end();
+                         ++it) {
+                        LOG_WARN(logger, DATASRC_LIST_NOT_CACHED).
+                            arg(it->first).arg(rrclass_);
+                    }
+                    continue;
+                }
+                if (!want_cache) {
+                    isc_throw(ConfigurationError, "The cache must be enabled "
+                              "for the MasterFiles type");
+                }
+                new_data_sources.push_back(DataSourceInfo(true));
+            } else {
+                // Ask the factory to create the data source for us
+                const DataSourcePair ds(this->getDataSourceClient(type,
+                                                                  paramConf));
+                // And put it into the vector
+                new_data_sources.push_back(DataSourceInfo(ds.first, ds.second,
+                                                          want_cache));
+            }
+
             if (want_cache) {
             if (want_cache) {
-                if (!dconf->contains("cache-zones")) {
+                if (!dconf->contains("cache-zones") && type != "MasterFiles") {
                     isc_throw(isc::NotImplemented, "Auto-detection of zones "
                     isc_throw(isc::NotImplemented, "Auto-detection of zones "
                               "to cache is not yet implemented, supply "
                               "to cache is not yet implemented, supply "
                               "cache-zones parameter");
                               "cache-zones parameter");
                     // TODO: Auto-detect list of all zones in the
                     // TODO: Auto-detect list of all zones in the
                     // data source.
                     // data source.
                 }
                 }
-                const ConstElementPtr zones(dconf->get("cache-zones"));
+
+                // List the zones we are loading
+                vector<string> zones_origins;
+                if (type == "MasterFiles") {
+                    const map<string, ConstElementPtr>
+                        zones_files(paramConf->mapValue());
+                    for (map<string, ConstElementPtr>::const_iterator
+                         it(zones_files.begin()); it != zones_files.end();
+                         ++it) {
+                        zones_origins.push_back(it->first);
+                    }
+                } else {
+                    const ConstElementPtr zones(dconf->get("cache-zones"));
+                    for (size_t i(0); i < zones->size(); ++i) {
+                        zones_origins.push_back(zones->get(i)->stringValue());
+                    }
+                }
+
                 const shared_ptr<InMemoryClient>
                 const shared_ptr<InMemoryClient>
                     cache(new_data_sources.back().cache_);
                     cache(new_data_sources.back().cache_);
                 const DataSourceClient* const
                 const DataSourceClient* const
                     client(new_data_sources.back().data_src_client_);
                     client(new_data_sources.back().data_src_client_);
-                for (size_t i(0); i < zones->size(); ++i) {
+                for (vector<string>::const_iterator it(zones_origins.begin());
-                    const Name origin(zones->get(i)->stringValue());
+                     it != zones_origins.end(); ++it) {
-                    const DataSourceClient::FindResult
+                    const Name origin(*it);
-                        zone(client->findZone(origin));
-                    if (zone.code != result::SUCCESS) {
-                        // The data source does not contain the zone, it can't
-                        // be cached.
-                        isc_throw(ConfigurationError, "Unable to cache "
-                                  "non-existent zone " << origin);
-                    }
                     shared_ptr<InMemoryZoneFinder>
                     shared_ptr<InMemoryZoneFinder>
                         finder(new
                         finder(new
-                            InMemoryZoneFinder(zone.zone_finder->getClass(),
+                            InMemoryZoneFinder(rrclass_, origin));
-                                               origin));
+                    if (type == "MasterFiles") {
-                    ZoneIteratorPtr iterator(client->getIterator(origin));
+                        finder->load(paramConf->get(*it)->stringValue());
-                    if (!iterator) {
+                    } else {
-                        isc_throw(isc::Unexpected, "Got NULL iterator for "
+                        ZoneIteratorPtr iterator;
-                                  "zone " << origin);
+                        try {
+                            iterator = client->getIterator(origin);
+                        }
+                        catch (const DataSourceError&) {
+                            isc_throw(ConfigurationError, "Unable to cache "
+                                      "non-existent zone " << origin);
+                        }
+                        if (!iterator) {
+                            isc_throw(isc::Unexpected, "Got NULL iterator for "
+                                      "zone " << origin);
+                        }
+                        finder->load(*iterator);
                     }
                     }
-                    finder->load(*iterator);
                     cache->addZone(finder);
                     cache->addZone(finder);
                 }
                 }
             }
             }

+ 12 - 8
src/lib/datasrc/client_list.h

@@ -16,6 +16,7 @@
 #define DATASRC_CONTAINER_H
 #define DATASRC_CONTAINER_H
 
 
 #include <dns/name.h>
 #include <dns/name.h>
+#include <dns/rrclass.h>
 #include <cc/data.h>
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -186,6 +187,12 @@ typedef boost::shared_ptr<const ClientList> ConstClientListPtr;
 /// inherited except for tests.
 /// inherited except for tests.
 class ConfigurableClientList : public ClientList {
 class ConfigurableClientList : public ClientList {
 public:
 public:
+    /// \brief Constructor
+    ///
+    /// \param rrclass For which class the list should work.
+    ConfigurableClientList(const isc::dns::RRClass &rrclass) :
+        rrclass_(rrclass)
+    {}
     /// \brief Exception thrown when there's an error in configuration.
     /// \brief Exception thrown when there's an error in configuration.
     class ConfigurationError : public Exception {
     class ConfigurationError : public Exception {
     public:
     public:
@@ -229,16 +236,11 @@ public:
     ///
     ///
     /// \todo The content yet to be defined.
     /// \todo The content yet to be defined.
     struct DataSourceInfo {
     struct DataSourceInfo {
-        /// \brief Default constructor.
+        // Plays a role of default constructor too (for vector)
-        ///
+        DataSourceInfo(bool has_cache = false);
-        /// Don't use directly. It is here so the structure can live in
-        /// a vector.
-        DataSourceInfo() :
-            data_src_client_(NULL)
-        {}
         DataSourceInfo(DataSourceClient* data_src_client,
         DataSourceInfo(DataSourceClient* data_src_client,
                        const DataSourceClientContainerPtr& container,
                        const DataSourceClientContainerPtr& container,
-                       bool hasCache);
+                       bool has_cache);
         DataSourceClient* data_src_client_;
         DataSourceClient* data_src_client_;
         DataSourceClientContainerPtr container_;
         DataSourceClientContainerPtr container_;
         boost::shared_ptr<InMemoryClient> cache_;
         boost::shared_ptr<InMemoryClient> cache_;
@@ -286,6 +288,8 @@ public:
     /// it might be, so it is just made public (there's no real reason to
     /// it might be, so it is just made public (there's no real reason to
     /// hide it).
     /// hide it).
     const DataSources& getDataSources() const { return (data_sources_); }
     const DataSources& getDataSources() const { return (data_sources_); }
+private:
+    const isc::dns::RRClass rrclass_;
 };
 };
 
 
 } // namespace datasrc
 } // namespace datasrc

+ 7 - 0
src/lib/datasrc/datasrc_messages.mes

@@ -298,6 +298,13 @@ not contain RRs the requested type.  AN NXRRSET indication is returned.
 A debug message indicating that a query for the given name and RR type is being
 A debug message indicating that a query for the given name and RR type is being
 processed.
 processed.
 
 
+% DATASRC_LIST_NOT_CACHED zone %1/%2 not cached, cache disabled globally. Will not be available.
+The process disabled caching of RR data completely. However, the given zone
+is provided as a master file and it can be served from memory cache only.
+Therefore, the zone will not be available for this process. If this is
+a problem, you should move the zone to some database backend (sqlite3, for
+example) and use it from there.
+
 % DATASRC_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
 % DATASRC_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
 Debug information. An RRset is being added to the in-memory data source.
 Debug information. An RRset is being added to the in-memory data source.
 
 

+ 80 - 3
src/lib/datasrc/tests/client_list_unittest.cc

@@ -45,7 +45,7 @@ public:
         Name getOrigin() const { return (origin_); }
         Name getOrigin() const { return (origin_); }
         // The rest is not to be called, so just have them
         // The rest is not to be called, so just have them
         RRClass getClass() const {
         RRClass getClass() const {
-            return (RRClass::IN());
+            isc_throw(isc::NotImplemented, "Not implemented");
         }
         }
         shared_ptr<Context> find(const Name&, const RRType&,
         shared_ptr<Context> find(const Name&, const RRType&,
                                  const FindOptions)
                                  const FindOptions)
@@ -106,6 +106,8 @@ public:
         type_(type),
         type_(type),
         configuration_(configuration)
         configuration_(configuration)
     {
     {
+        EXPECT_NE("MasterFiles", type) << "MasterFiles is a special case "
+            "and it never should be created as a data source client";
         if (configuration_->getType() == Element::list) {
         if (configuration_->getType() == Element::list) {
             for (size_t i(0); i < configuration_->size(); ++i) {
             for (size_t i(0); i < configuration_->size(); ++i) {
                 zones.insert(Name(configuration_->get(i)->stringValue()));
                 zones.insert(Name(configuration_->get(i)->stringValue()));
@@ -148,7 +150,12 @@ public:
         } else if (name == Name("null.org")) {
         } else if (name == Name("null.org")) {
             return (ZoneIteratorPtr());
             return (ZoneIteratorPtr());
         } else {
         } else {
-            return (ZoneIteratorPtr(new Iterator(name)));
+            FindResult result(findZone(name));
+            if (result.code == isc::datasrc::result::SUCCESS) {
+                return (ZoneIteratorPtr(new Iterator(name)));
+            } else {
+                isc_throw(DataSourceError, "No such zone");
+            }
         }
         }
     }
     }
     const string type_;
     const string type_;
@@ -162,6 +169,9 @@ private:
 // some methods to dig directly in the internals, for the tests.
 // some methods to dig directly in the internals, for the tests.
 class TestedList : public ConfigurableClientList {
 class TestedList : public ConfigurableClientList {
 public:
 public:
+    TestedList(const RRClass& rrclass) :
+        ConfigurableClientList(rrclass)
+    {}
     DataSources& getDataSources() { return (data_sources_); }
     DataSources& getDataSources() { return (data_sources_); }
     // Overwrite the list's method to get a data source with given type
     // Overwrite the list's method to get a data source with given type
     // and configuration. We mock the data source and don't create the
     // and configuration. We mock the data source and don't create the
@@ -210,7 +220,7 @@ class ListTest : public ::testing::Test {
 public:
 public:
     ListTest() :
     ListTest() :
         // The empty list corresponds to a list with no elements inside
         // The empty list corresponds to a list with no elements inside
-        list_(new TestedList()),
+        list_(new TestedList(RRClass::IN())),
         config_elem_(Element::fromJSON("["
         config_elem_(Element::fromJSON("["
             "{"
             "{"
             "   \"type\": \"test_type\","
             "   \"type\": \"test_type\","
@@ -498,6 +508,47 @@ TEST_F(ListTest, wrongConfig) {
          "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": 13}]",
          "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": 13}]",
         "[{\"type\": \"test_type\", \"params\": 13}, "
         "[{\"type\": \"test_type\", \"params\": 13}, "
          "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": {}}]",
          "{\"type\": \"x\", \"cache-enable\": true, \"cache-zones\": {}}]",
+        // Some bad inputs for MasterFiles special case
+
+        // It must have the cache enabled
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": {}}]",
+        // No cache-zones allowed here
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": true,"
+         "\"param\": {}, \"cache-zones\": []}]",
+        // Some bad types of params
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": []}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": 13}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": true}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": null}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": \"x\"}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": {\".\": 13}}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": {\".\": true}}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": {\".\": null}}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": {\".\": []}}]",
+        "[{\"type\": \"test_type\", \"params\": 13}, "
+         "{\"type\": \"MasterFiles\", \"cache-enable\": false,"
+         "\"params\": {\".\": {}}}]",
         NULL
         NULL
     };
     };
     // Put something inside to see it survives the exception
     // Put something inside to see it survives the exception
@@ -611,6 +662,8 @@ TEST_F(ListTest, cacheZones) {
     EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code);
     EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.org")).code);
     EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.com")).code);
     EXPECT_EQ(result::SUCCESS, cache->findZone(Name("example.com")).code);
     EXPECT_EQ(result::NOTFOUND, cache->findZone(Name("example.cz")).code);
     EXPECT_EQ(result::NOTFOUND, cache->findZone(Name("example.cz")).code);
+    EXPECT_EQ(RRClass::IN(),
+              cache->findZone(Name("example.org")).zone_finder->getClass());
 
 
     // These are cached and answered from the cache
     // These are cached and answered from the cache
     positiveResult(list_->find(Name("example.com.")), ds_[0],
     positiveResult(list_->find(Name("example.com.")), ds_[0],
@@ -670,4 +723,28 @@ TEST_F(ListTest, badCache) {
     checkDS(0, "test_type", "{}", false);
     checkDS(0, "test_type", "{}", false);
 }
 }
 
 
+TEST_F(ListTest, masterFiles) {
+    const ConstElementPtr elem(Element::fromJSON("["
+        "{"
+        "   \"type\": \"MasterFiles\","
+        "   \"cache-enable\": true,"
+        "   \"params\": {"
+        "       \".\": \"" TEST_DATA_DIR "/root.zone\""
+        "   }"
+        "}]"));
+    list_->configure(*elem, true);
+
+    // It has only the cache
+    EXPECT_EQ(static_cast<const DataSourceClient*>(NULL),
+              list_->getDataSources()[0].data_src_client_);
+
+    // And it can search
+    positiveResult(list_->find(Name(".")), ds_[0], Name("."), true, "com",
+                   true);
+
+    // If cache is not enabled, nothing is loaded
+    list_->configure(*elem, false);
+    EXPECT_EQ(0, list_->getDataSources().size());
+}
+
 }
 }

+ 126 - 14
src/lib/dhcp/iface_mgr.cc

@@ -19,11 +19,14 @@
 #include <netinet/in.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <arpa/inet.h>
 #include <sys/select.h>
 #include <sys/select.h>
+#include <asio.hpp>
 
 
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcp/iface_mgr.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/io_error.h>
 #include <util/io/pktinfo_utilities.h>
 #include <util/io/pktinfo_utilities.h>
 
 
 using namespace std;
 using namespace std;
@@ -197,7 +200,7 @@ void IfaceMgr::stubDetectIfaces() {
         iface.flag_up_ = true;
         iface.flag_up_ = true;
         iface.flag_running_ = true;
         iface.flag_running_ = true;
 
 
-        // note that we claim that this is not a loopback. iface_mgr tries to open a
+        // Note that we claim that this is not a loopback. iface_mgr tries to open a
         // socket on all interaces that are up, running and not loopback. As this is
         // socket on all interaces that are up, running and not loopback. As this is
         // the only interface we were able to detect, let's pretend this is a normal
         // the only interface we were able to detect, let's pretend this is a normal
         // interface.
         // interface.
@@ -228,8 +231,8 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
     int sock;
     int sock;
     int count = 0;
     int count = 0;
 
 
-    for (IfaceCollection::iterator iface=ifaces_.begin();
+    for (IfaceCollection::iterator iface = ifaces_.begin();
-         iface!=ifaces_.end();
+         iface != ifaces_.end();
          ++iface) {
          ++iface) {
 
 
         cout << "Trying opening socket on interface " << iface->getFullName() << endl;
         cout << "Trying opening socket on interface " << iface->getFullName() << endl;
@@ -243,18 +246,17 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
         }
         }
 
 
         AddressCollection addrs = iface->getAddresses();
         AddressCollection addrs = iface->getAddresses();
-
+        for (AddressCollection::iterator addr = addrs.begin();
-        for (AddressCollection::iterator addr= addrs.begin();
              addr != addrs.end();
              addr != addrs.end();
              ++addr) {
              ++addr) {
 
 
-            // skip IPv6 addresses
+            // Skip IPv6 addresses
             if (addr->getFamily() != AF_INET) {
             if (addr->getFamily() != AF_INET) {
                 continue;
                 continue;
             }
             }
 
 
             sock = openSocket(iface->getName(), *addr, port);
             sock = openSocket(iface->getName(), *addr, port);
-            if (sock<0) {
+            if (sock < 0) {
                 cout << "Failed to open unicast socket." << endl;
                 cout << "Failed to open unicast socket." << endl;
                 return (false);
                 return (false);
             }
             }
@@ -270,8 +272,8 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
     int sock;
     int sock;
     int count = 0;
     int count = 0;
 
 
-    for (IfaceCollection::iterator iface=ifaces_.begin();
+    for (IfaceCollection::iterator iface = ifaces_.begin();
-         iface!=ifaces_.end();
+         iface != ifaces_.end();
          ++iface) {
          ++iface) {
 
 
         if (iface->flag_loopback_ ||
         if (iface->flag_loopback_ ||
@@ -281,8 +283,7 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
         }
         }
 
 
         AddressCollection addrs = iface->getAddresses();
         AddressCollection addrs = iface->getAddresses();
-
+        for (AddressCollection::iterator addr = addrs.begin();
-        for (AddressCollection::iterator addr= addrs.begin();
              addr != addrs.end();
              addr != addrs.end();
              ++addr) {
              ++addr) {
 
 
@@ -292,7 +293,7 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
             }
             }
 
 
             sock = openSocket(iface->getName(), *addr, port);
             sock = openSocket(iface->getName(), *addr, port);
-            if (sock<0) {
+            if (sock < 0) {
                 cout << "Failed to open unicast socket." << endl;
                 cout << "Failed to open unicast socket." << endl;
                 return (false);
                 return (false);
             }
             }
@@ -301,7 +302,7 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
             // works well on Mac OS (and possibly other BSDs), but does not work
             // works well on Mac OS (and possibly other BSDs), but does not work
             // on Linux.
             // on Linux.
             if ( !joinMulticast(sock, iface->getName(),
             if ( !joinMulticast(sock, iface->getName(),
-                                string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
+                                string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS))) {
                 close(sock);
                 close(sock);
                 isc_throw(Unexpected, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
                 isc_throw(Unexpected, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
                           << " multicast group.");
                           << " multicast group.");
@@ -317,7 +318,7 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
             int sock2 = openSocket(iface->getName(),
             int sock2 = openSocket(iface->getName(),
                                    IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
                                    IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
                                    port);
                                    port);
-            if (sock2<0) {
+            if (sock2 < 0) {
                 isc_throw(Unexpected, "Failed to open multicast socket on "
                 isc_throw(Unexpected, "Failed to open multicast socket on "
                           << " interface " << iface->getFullName());
                           << " interface " << iface->getFullName());
                 iface->delSocket(sock); // delete previously opened socket
                 iface->delSocket(sock); // delete previously opened socket
@@ -397,6 +398,117 @@ int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
     }
     }
 }
 }
 
 
+int IfaceMgr::openSocketFromIface(const std::string& ifname,
+                                  const uint16_t port,
+                                  const uint8_t family) {
+    // Search for specified interface among detected interfaces.
+    for (IfaceCollection::iterator iface = ifaces_.begin();
+         iface != ifaces_.end();
+         ++iface) {
+
+        if ((iface->getFullName() != ifname) &&
+            (iface->getName() != ifname)) {
+            continue;
+        }
+
+        // Interface is now detected. Search for address on interface
+        // that matches address family (v6 or v4).
+        AddressCollection addrs = iface->getAddresses();
+        AddressCollection::iterator addr_it = addrs.begin();
+        while (addr_it != addrs.end()) {
+            if (addr_it->getFamily() == family) {
+                // We have interface and address so let's open socket.
+                // This may cause isc::Unexpected exception.
+                return (openSocket(iface->getName(), *addr_it, port));
+            }
+            ++addr_it;
+        }
+        // If we are at the end of address collection it means that we found
+        // interface but there is no address for family specified.
+        if (addr_it == addrs.end()) {
+            // Stringify the family value to append it to exception string.
+            std::string family_name("AF_INET");
+            if (family == AF_INET6) {
+                family_name = "AF_INET6";
+            }
+            // We did not find address on the interface.
+            isc_throw(BadValue, "There is no address for interface: "
+                      << ifname << ", port: " << port << ", address "
+                      " family: " << family_name);
+        }
+    }
+    // If we got here it means that we had not found the specified interface.
+    // Otherwise we would have returned from previous exist points.
+    isc_throw(BadValue, "There is no " << ifname << " interface present.");
+}
+
+int IfaceMgr::openSocketFromAddress(const IOAddress& addr,
+                                    const uint16_t port) {
+    // Search through detected interfaces and addresses to match
+    // local address we got.
+    for (IfaceCollection::iterator iface = ifaces_.begin();
+         iface != ifaces_.end();
+         ++iface) {
+
+        AddressCollection addrs = iface->getAddresses();
+
+        for (AddressCollection::iterator addr_it = addrs.begin();
+             addr_it != addrs.end();
+             ++addr_it) {
+
+            // Local address must match one of the addresses
+            // on detected interfaces. If it does, we have
+            // address and interface detected so we can open
+            // socket.
+            if (*addr_it == addr) {
+                // Open socket using local interface, address and port.
+                // This may cause isc::Unexpected exception.
+                return (openSocket(iface->getName(), *addr_it, port));
+            }
+        }
+    }
+    // If we got here it means that we did not find specified address
+    // on any available interface.
+    isc_throw(BadValue, "There is no such address " << addr.toText());
+}
+
+int IfaceMgr::openSocketFromRemoteAddress(const IOAddress& remote_addr,
+                                          const uint16_t port) {
+    // Get local address to be used to connect to remote location.
+    IOAddress local_address(getLocalAddress(remote_addr, port).getAddress());
+    return openSocketFromAddress(local_address, port);
+}
+
+isc::asiolink::IOAddress
+IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
+    // Create remote endpoint, we will be connecting to it.
+    boost::scoped_ptr<const UDPEndpoint>
+        remote_endpoint(static_cast<const UDPEndpoint*>
+                        (UDPEndpoint::create(IPPROTO_UDP, remote_addr, port)));
+    if (!remote_endpoint) {
+        isc_throw(Unexpected, "Unable to create remote endpoint");
+    }
+
+    // Create socket that will be used to connect to remote endpoint.
+    asio::io_service io_service;
+    asio::ip::udp::socket sock(io_service);
+
+    // Try to connect to remote endpoint and check if attempt is successful.
+    asio::error_code err_code;
+    sock.connect(remote_endpoint->getASIOEndpoint(), err_code);
+    if (err_code) {
+        isc_throw(Unexpected,"Failed to connect to remote endpoint.");
+    }
+
+    // Once we are connected socket object holds local endpoint.
+    asio::ip::udp::socket::endpoint_type local_endpoint =
+        sock.local_endpoint();
+    asio::ip::address local_address(local_endpoint.address());
+
+    // Return address of local endpoint.
+    return IOAddress(local_address);
+}
+
 int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, uint16_t port) {
 int IfaceMgr::openSocket4(Iface& iface, const IOAddress& addr, uint16_t port) {
 
 
     cout << "Creating UDP4 socket on " << iface.getFullName()
     cout << "Creating UDP4 socket on " << iface.getFullName()

+ 69 - 1
src/lib/dhcp/iface_mgr.h

@@ -374,7 +374,58 @@ public:
     /// @return socket descriptor, if socket creation, binding and multicast
     /// @return socket descriptor, if socket creation, binding and multicast
     /// group join were all successful.
     /// group join were all successful.
     int openSocket(const std::string& ifname,
     int openSocket(const std::string& ifname,
-                   const isc::asiolink::IOAddress& addr, const uint16_t port);
+                   const isc::asiolink::IOAddress& addr,
+                   const uint16_t port);
+
+    /// @brief Opens UDP/IP socket and binds it to interface specified.
+    ///
+    /// This method differs from \ref openSocket in that it does not require
+    /// the specification of a local address to which socket will be bound.
+    /// Instead, the method searches through the addresses on the specified
+    /// interface and selects one that matches the address family.
+    ///
+    /// @param ifname name of the interface
+    /// @param port UDP port
+    /// @param family address family (AF_INET or AF_INET6)
+    /// @return socket descriptor, if socket creation, binding and multicast
+    /// group join were all successful.
+    /// @throw isc::Unexpected if failed to create and bind socket.
+    /// @throw isc::BadValue if there is no address on specified interface
+    /// that belongs to given family.
+    int openSocketFromIface(const std::string& ifname,
+                            const uint16_t port,
+                            const uint8_t family);
+
+    /// @brief Opens UDP/IP socket and binds to address specified
+    ///
+    /// This methods differs from \ref openSocket in that it does not require
+    /// the specification of the interface to which the socket will be bound.
+    ///
+    /// @param addr address to be bound
+    /// @param port UDP port
+    /// @return socket descriptor, if socket creation, binding and multicast
+    /// group join were all successful.
+    /// @throw isc::Unexpected if failed to create and bind socket
+    /// @throw isc::BadValue if specified address is not available on
+    /// any interface
+    int openSocketFromAddress(const isc::asiolink::IOAddress& addr,
+                              const uint16_t port);
+
+    /// @brief Opens UDP/IP socket to be used to connect to remote address
+    ///
+    /// This method identifies the local address to be used to connect to the
+    /// remote address specified as argument.  Once the local address is
+    /// identified, \ref openSocket is called to open a socket and bind it to
+    /// the interface, address and port.
+    ///
+    /// @param remote_addr remote address to connect to
+    /// @param port UDP port
+    /// @return socket descriptor, if socket creation, binding and multicast
+    /// group join were all successful.
+    /// @throw isc::Unexpected if failed to create and bind socket
+    int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr,
+                                    const uint16_t port);
+
 
 
     /// Opens IPv6 sockets on detected interfaces.
     /// Opens IPv6 sockets on detected interfaces.
     ///
     ///
@@ -548,6 +599,23 @@ private:
     joinMulticast(int sock, const std::string& ifname,
     joinMulticast(int sock, const std::string& ifname,
                   const std::string& mcast);
                   const std::string& mcast);
 
 
+    /// @brief Identifies local network address to be used to
+    /// connect to remote address.
+    ///
+    /// This method identifies local network address that can be used
+    /// to connect to remote address specified.
+    /// It first creates socket and makes attempt to connect
+    /// to remote location via this socket. If connection
+    /// is established successfully, the local address to which
+    /// socket is bound is returned.
+    ///
+    /// @param remote_addr remote address to connect to
+    /// @param port port to be used
+    /// @return local address to be used to connect to remote address
+    /// @throw isc::Unexpected if unable to indentify local address
+    isc::asiolink::IOAddress
+    getLocalAddress(const isc::asiolink::IOAddress& remote_addr,
+                    const uint16_t port);
 };
 };
 
 
 }; // namespace isc::dhcp
 }; // namespace isc::dhcp

+ 85 - 7
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -19,6 +19,7 @@
 
 
 #include <unistd.h>
 #include <unistd.h>
 #include <arpa/inet.h>
 #include <arpa/inet.h>
+#include <boost/scoped_ptr.hpp>
 #include <gtest/gtest.h>
 #include <gtest/gtest.h>
 
 
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
@@ -31,12 +32,17 @@ using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 
 
-// name of loopback interface detection
-const size_t buf_size = 32;
-char LOOPBACK[buf_size] = "lo";
-
 namespace {
 namespace {
 
 
+// Name of loopback interface detection
+const size_t BUF_SIZE = 32;
+char LOOPBACK[BUF_SIZE] = "lo";
+
+// Ports used during testing
+const uint16_t PORT1 = 10547;   // V6 socket
+const uint16_t PORT2 = 10548;   // V4 socket
+
+
 class NakedIfaceMgr: public IfaceMgr {
 class NakedIfaceMgr: public IfaceMgr {
     // "naked" Interface Manager, exposes internal fields
     // "naked" Interface Manager, exposes internal fields
 public:
 public:
@@ -69,10 +75,10 @@ TEST_F(IfaceMgrTest, loDetect) {
     // is implemented
     // is implemented
     if (if_nametoindex("lo") > 0) {
     if (if_nametoindex("lo") > 0) {
         cout << "This is Linux, using lo as loopback." << endl;
         cout << "This is Linux, using lo as loopback." << endl;
-        snprintf(LOOPBACK, buf_size - 1, "lo");
+        snprintf(LOOPBACK, BUF_SIZE - 1, "lo");
     } else if (if_nametoindex("lo0") > 0) {
     } else if (if_nametoindex("lo0") > 0) {
         cout << "This is BSD, using lo0 as loopback." << endl;
         cout << "This is BSD, using lo0 as loopback." << endl;
-        snprintf(LOOPBACK, buf_size - 1, "lo0");
+        snprintf(LOOPBACK, BUF_SIZE - 1, "lo0");
     } else {
     } else {
         cout << "Failed to detect loopback interface. Neither "
         cout << "Failed to detect loopback interface. Neither "
              << "lo nor lo0 worked. I give up." << endl;
              << "lo nor lo0 worked. I give up." << endl;
@@ -80,7 +86,7 @@ TEST_F(IfaceMgrTest, loDetect) {
     }
     }
 }
 }
 
 
-// uncomment this test to create packet writer. It will
+// Uncomment this test to create packet writer. It will
 // write incoming DHCPv6 packets as C arrays. That is useful
 // write incoming DHCPv6 packets as C arrays. That is useful
 // for generating test sequences based on actual traffic
 // for generating test sequences based on actual traffic
 //
 //
@@ -241,6 +247,78 @@ TEST_F(IfaceMgrTest, sockets6) {
     delete ifacemgr;
     delete ifacemgr;
 }
 }
 
 
+TEST_F(IfaceMgrTest, socketsFromIface) {
+    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    // Open v6 socket on loopback interface and bind to port
+    int socket1 = 0;
+    EXPECT_NO_THROW(
+        socket1 = ifacemgr->openSocketFromIface(LOOPBACK, PORT1, AF_INET6);
+    );
+    // Socket descriptor must be positive integer
+    EXPECT_GT(socket1, 0);
+    close(socket1);
+
+    // Open v4 socket on loopback interface and bind to different port
+    int socket2 = 0;
+    EXPECT_NO_THROW(
+        socket2 = ifacemgr->openSocketFromIface(LOOPBACK, PORT2, AF_INET);
+    );
+    // socket descriptor must be positive integer
+    EXPECT_GT(socket2, 0);
+    close(socket2);
+
+}
+
+
+TEST_F(IfaceMgrTest, socketsFromAddress) {
+    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    // Open v6 socket on loopback interface and bind to port
+    int socket1 = 0;
+    IOAddress loAddr6("::1");
+    EXPECT_NO_THROW(
+        socket1 = ifacemgr->openSocketFromAddress(loAddr6, PORT1);
+    );
+    // socket descriptor must be positive integer
+    EXPECT_GT(socket1, 0);
+    close(socket1);
+
+    // Open v4 socket on loopback interface and bind to different port
+    int socket2 = 0;
+    IOAddress loAddr("127.0.0.1");
+    EXPECT_NO_THROW(
+        socket2 = ifacemgr->openSocketFromAddress(loAddr, PORT2);
+    );
+    // socket descriptor must be positive integer
+    EXPECT_GT(socket2, 0);
+    close(socket2);
+}
+
+TEST_F(IfaceMgrTest, socketsFromRemoteAddress) {
+    boost::scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    // Open v6 socket to connect to remote address.
+    // Loopback address is the only one that we know
+    // so let's treat it as remote address.
+    int socket1 = 0;
+    IOAddress loAddr6("::1");
+    EXPECT_NO_THROW(
+        socket1 = ifacemgr->openSocketFromRemoteAddress(loAddr6, PORT1);
+    );
+    EXPECT_GT(socket1, 0);
+    close(socket1);
+
+    // Open v4 socket to connect to remote address.
+    int socket2 = 0;
+    IOAddress loAddr("127.0.0.1");
+    EXPECT_NO_THROW(
+        socket2 = ifacemgr->openSocketFromRemoteAddress(loAddr, PORT2);
+    );
+    EXPECT_GT(socket2, 0);
+    close(socket2);
+}
+
 // TODO: disabled due to other naming on various systems
 // TODO: disabled due to other naming on various systems
 // (lo in Linux, lo0 in BSD systems)
 // (lo in Linux, lo0 in BSD systems)
 TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {
 TEST_F(IfaceMgrTest, DISABLED_sockets6Mcast) {

+ 29 - 1
src/lib/dns/labelsequence.cc

@@ -23,10 +23,38 @@
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 
 
+LabelSequence::LabelSequence(const uint8_t* data,
+                             const uint8_t* offsets,
+                             size_t offsets_size) : data_(data),
+                                                    offsets_(offsets),
+                                                    offsets_size_(offsets_size),
+                                                    first_label_(0),
+                                                    last_label_(offsets_size_)
+{
+    if (data == NULL || offsets == NULL) {
+        isc_throw(BadValue, "Null pointer passed to LabelSequence constructor");
+    }
+    if (offsets_size == 0) {
+        isc_throw(BadValue, "Zero offsets to LabelSequence constructor");
+    }
+    if (offsets_size > Name::MAX_LABELS) {
+        isc_throw(BadValue, "MAX_LABELS exceeded");
+    }
+    for (size_t cur_offset = 0; cur_offset < offsets_size; ++cur_offset) {
+        if (offsets[cur_offset] > Name::MAX_LABELLEN) {
+            isc_throw(BadValue, "MAX_LABEL_LEN exceeded");
+        }
+        if (cur_offset > 0 && offsets[cur_offset] <= offsets[cur_offset - 1]) {
+            isc_throw(BadValue, "Offset smaller than previous offset");
+        }
+    }
+}
+
+
 const uint8_t*
 const uint8_t*
 LabelSequence::getData(size_t *len) const {
 LabelSequence::getData(size_t *len) const {
     *len = getDataLength();
     *len = getDataLength();
-    return &(data_[offsets_[first_label_]]);
+    return (&data_[offsets_[first_label_]]);
 }
 }
 
 
 void
 void

+ 21 - 21
src/lib/dns/labelsequence.h

@@ -21,24 +21,25 @@
 namespace isc {
 namespace isc {
 namespace dns {
 namespace dns {
 
 
-/// \brief Light-weight Accessor to Name object
+/// \brief Light-weight Accessor to data of Name object
 ///
 ///
 /// The purpose of this class is to easily match Names and parts of Names,
 /// The purpose of this class is to easily match Names and parts of Names,
 /// without needing to copy the underlying data on each label strip.
 /// without needing to copy the underlying data on each label strip.
 ///
 ///
-/// It can only work on existing Name objects, and the Name object MUST
+/// It can only work on existing Name objects, or data as provided by the
+/// Name object or another LabelSequence, and the data or Name MUST
 /// remain in scope during the entire lifetime of its associated
 /// remain in scope during the entire lifetime of its associated
 /// LabelSequence(s).
 /// LabelSequence(s).
 ///
 ///
 /// Upon creation of a LabelSequence, it records the offsets of the
 /// Upon creation of a LabelSequence, it records the offsets of the
 /// labels in the wireformat data of the Name. When stripLeft() or
 /// labels in the wireformat data of the Name. When stripLeft() or
 /// stripRight() is called on the LabelSequence, no changes in the
 /// stripRight() is called on the LabelSequence, no changes in the
-/// Name's data occur, but the internal pointers of the
+/// original data occur, but the internal pointers of the
 /// LabelSequence are modified.
 /// LabelSequence are modified.
 ///
 ///
 /// LabelSequences can be compared to other LabelSequences, and their
 /// LabelSequences can be compared to other LabelSequences, and their
 /// data can be requested (which then points to part of the original
 /// data can be requested (which then points to part of the original
-/// data of the associated Name object).
+/// data of the original Name object).
 ///
 ///
 class LabelSequence {
 class LabelSequence {
     // Name calls the private toText(bool) method of LabelSequence.
     // Name calls the private toText(bool) method of LabelSequence.
@@ -86,27 +87,26 @@ public:
     /// \note No validation is done on the given data upon construction;
     /// \note No validation is done on the given data upon construction;
     ///       use with care.
     ///       use with care.
     ///
     ///
-    /// \param data The Name data, in wire format
+    /// \exception isc::BadValue if basic checks for the input data, or
-    /// \param offsets The offsets of the labels in the Name, as given
+    ///            offsets fails.
-    ///        by the Name object or another LabelSequence
+    ///
+    /// \param data The raw data for the domain name, in wire format
+    /// \param offsets The offsets of the labels in the domain name data,
+    ///        as given by a Name object or another LabelSequence
     /// \param offsets_size The size of the offsets data
     /// \param offsets_size The size of the offsets data
     LabelSequence(const uint8_t* data,
     LabelSequence(const uint8_t* data,
                   const uint8_t* offsets,
                   const uint8_t* offsets,
-                  size_t offsets_size) : data_(data),
+                  size_t offsets_size);
-                                         offsets_(offsets),
-                                         offsets_size_(offsets_size),
-                                         first_label_(0),
-                                         last_label_(offsets_size_)
-    {}
 
 
     /// \brief Return the wire-format data for this LabelSequence
     /// \brief Return the wire-format data for this LabelSequence
     ///
     ///
-    /// The data, is returned as a pointer to the original wireformat
+    /// The data is returned as a pointer to (the part of) the original
-    /// data of the original Name object, and the given len value is
+    /// wireformat data, from either the original Name object, or the
+    /// raw data given in the constructor, and the given len value is
     /// set to the number of octets that match this labelsequence.
     /// set to the number of octets that match this labelsequence.
     ///
     ///
     /// \note The data pointed to is only valid if the original Name
     /// \note The data pointed to is only valid if the original Name
-    /// object is still in scope
+    /// object or data is still in scope
     ///
     ///
     /// \param len Pointer to a size_t where the length of the data
     /// \param len Pointer to a size_t where the length of the data
     ///        will be stored (in number of octets)
     ///        will be stored (in number of octets)
@@ -134,7 +134,7 @@ public:
     /// versa.
     /// versa.
     ///
     ///
     /// \note The data pointed to is only valid if the original Name
     /// \note The data pointed to is only valid if the original Name
-    /// object is still in scope
+    /// object or data is still in scope
     ///
     ///
     /// \return The length of the data of the label sequence in octets.
     /// \return The length of the data of the label sequence in octets.
     size_t getDataLength() const;
     size_t getDataLength() const;
@@ -176,7 +176,7 @@ public:
     /// \brief Remove labels from the end of this LabelSequence
     /// \brief Remove labels from the end of this LabelSequence
     ///
     ///
     /// \note No actual memory is changed, this operation merely updates the
     /// \note No actual memory is changed, this operation merely updates the
-    /// internal pointers based on the offsets in the Name object.
+    /// internal pointers based on the offsets originally provided.
     ///
     ///
     /// \exception OutOfRange if i is greater than or equal to the number
     /// \exception OutOfRange if i is greater than or equal to the number
     ///           of labels currently pointed to by this LabelSequence
     ///           of labels currently pointed to by this LabelSequence
@@ -195,13 +195,13 @@ public:
     /// LabelSequence as a string.  The returned string ends with a dot
     /// LabelSequence as a string.  The returned string ends with a dot
     /// '.' if the label sequence is absolute.
     /// '.' if the label sequence is absolute.
     ///
     ///
-    /// This function assumes the underlying name is in proper
+    /// This function assumes the underlying data is in proper
     /// uncompressed wire format.  If it finds an unexpected label
     /// uncompressed wire format.  If it finds an unexpected label
     /// character including compression pointer, an exception of class
     /// character including compression pointer, an exception of class
     /// \c BadLabelType will be thrown.  In addition, if resource
     /// \c BadLabelType will be thrown.  In addition, if resource
     /// allocation for the result string fails, a corresponding standard
     /// allocation for the result string fails, a corresponding standard
     /// exception will be thrown.
     /// exception will be thrown.
-    //
+    ///
     /// \return a string representation of the <code>LabelSequence</code>.
     /// \return a string representation of the <code>LabelSequence</code>.
     std::string toText() const;
     std::string toText() const;
 
 
@@ -271,7 +271,7 @@ private:
 ///
 ///
 /// \param os A \c std::ostream object on which the insertion operation is
 /// \param os A \c std::ostream object on which the insertion operation is
 /// performed.
 /// performed.
-/// \param name The \c LabelSequence object output by the operation.
+/// \param label_sequence The \c LabelSequence object output by the operation.
 /// \return A reference to the same \c std::ostream object referenced by
 /// \return A reference to the same \c std::ostream object referenced by
 /// parameter \c os after the insertion operation.
 /// parameter \c os after the insertion operation.
 std::ostream&
 std::ostream&

+ 3 - 2
src/lib/dns/python/name_python.cc

@@ -522,8 +522,9 @@ Name_isWildCard(s_Name* self) {
 
 
 Py_hash_t
 Py_hash_t
 Name_hash(PyObject* pyself) {
 Name_hash(PyObject* pyself) {
-    s_Name* const self = static_cast<s_Name*>(pyself);
+    const s_Name* const self = static_cast<s_Name*>(pyself);
-    return (LabelSequence(*self->cppobj).getHash(false));
+    return (convertToPyHash<size_t>(
+                LabelSequence(*self->cppobj).getHash(false)));
 }
 }
 
 
 } // end of unnamed namespace
 } // end of unnamed namespace

+ 55 - 0
src/lib/dns/python/pydnspp_common.h

@@ -17,6 +17,9 @@
 
 
 #include <Python.h>
 #include <Python.h>
 
 
+#include <boost/static_assert.hpp>
+
+#include <climits>  // for CHAR_BIT
 #include <stdexcept>
 #include <stdexcept>
 #include <string>
 #include <string>
 
 
@@ -48,6 +51,58 @@ int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
 #if PY_MINOR_VERSION < 2
 #if PY_MINOR_VERSION < 2
 typedef long Py_hash_t;
 typedef long Py_hash_t;
 #endif
 #endif
+
+/// \brief Convert a hash value of arbitrary type to a Python hash value.
+///
+/// This templated function is a convenient wrapper to produce a valid hash
+/// value of type Py_hash_t, which is expected to be used as the return value
+/// of a PyTypeObject.tp_hash implementation.  Py_hash_t is a signed integer
+/// the same size as Py_ssize_t.
+///
+/// The major concern is that -1 means an error in tp_hash and so we need to
+/// ensure that this value is never returned.
+///
+/// If the size of the original hash type is small enough, we convert
+/// the original hash value to a value of Py_hash_t, resetting all higher bits
+/// to 0.  Due to the assumption on the sizes, the conversion to HashvalType
+/// is safe, and (after clearing the higher bits) results in a valid positive
+/// value.
+///
+/// If the size of the input and return types is the same, we clear the
+/// highest bit of the original hash so the resulting value will be in a valid
+/// positive range of Py_hash_t.  If the original type is unsigned, there's
+/// probably no better conversion than this because otherwise the conversion
+/// to Py_hash_t could result in an undefined behavior.  If the original type
+/// is signed, this conversion reduces the effective value range of the
+/// original hash.  If this is not desired, the caller should convert it
+/// by itself (note that -1 should be still avoided).
+///
+/// This function does not support the case where the size of the original
+/// hash type is larger than that of Py_hash_t.
+template <typename HashvalType>
+Py_hash_t
+convertToPyHash(HashvalType val) {
+    BOOST_STATIC_ASSERT(sizeof(HashvalType) <= sizeof(Py_hash_t));
+
+    // Some versions of g++ doesn't ignore the impossible case of if/else
+    // below (depending on the size of HashvalType) and triggers a false
+    // warning.
+    // To work around it we use an intermediate mutable variable.
+    // See Trac #1883 for details.
+    size_t hash_val_bits = CHAR_BIT * sizeof(HashvalType);
+
+    if (sizeof(HashvalType) < sizeof(Py_hash_t)) {
+        // The original hash type is small enough.  Do trivial conversion.
+        const Py_hash_t mask = ~(static_cast<Py_hash_t>(-1) << hash_val_bits);
+        return (static_cast<Py_hash_t>(val) & mask);
+    } else {
+        // Clear the highest bit of the original hash so the conversion is
+        // safe and avoids -1.
+        HashvalType mask = ~(static_cast<HashvalType>(1) <<
+                             (hash_val_bits - 1));
+        return (val & mask);
+    }
+}
 } // namespace python
 } // namespace python
 } // namespace dns
 } // namespace dns
 } // namespace isc
 } // namespace isc

+ 2 - 2
src/lib/dns/python/rrclass_python.cc

@@ -267,8 +267,8 @@ PyObject* RRClass_ANY(s_RRClass*) {
 
 
 Py_hash_t
 Py_hash_t
 RRClass_hash(PyObject* pyself) {
 RRClass_hash(PyObject* pyself) {
-    s_RRClass* const self = static_cast<s_RRClass*>(pyself);
+    const s_RRClass* const self = static_cast<s_RRClass*>(pyself);
-    return (self->cppobj->getCode());
+    return (convertToPyHash<uint16_t>(self->cppobj->getCode()));
 }
 }
 
 
 } // end anonymous namespace
 } // end anonymous namespace

+ 7 - 1
src/lib/dns/python/rrtype_python.cc

@@ -49,6 +49,7 @@ PyObject* RRType_str(PyObject* self);
 PyObject* RRType_toWire(s_RRType* self, PyObject* args);
 PyObject* RRType_toWire(s_RRType* self, PyObject* args);
 PyObject* RRType_getCode(s_RRType* self);
 PyObject* RRType_getCode(s_RRType* self);
 PyObject* RRType_richcmp(s_RRType* self, s_RRType* other, int op);
 PyObject* RRType_richcmp(s_RRType* self, s_RRType* other, int op);
+Py_hash_t RRType_hash(PyObject* pyself);
 PyObject* RRType_NSEC3PARAM(s_RRType *self);
 PyObject* RRType_NSEC3PARAM(s_RRType *self);
 PyObject* RRType_DNAME(s_RRType *self);
 PyObject* RRType_DNAME(s_RRType *self);
 PyObject* RRType_PTR(s_RRType *self);
 PyObject* RRType_PTR(s_RRType *self);
@@ -368,6 +369,11 @@ RRType_ANY(s_RRType*) {
     return (RRType_createStatic(RRType::ANY()));
     return (RRType_createStatic(RRType::ANY()));
 }
 }
 
 
+Py_hash_t
+RRType_hash(PyObject* pyself) {
+    const s_RRType* const self = static_cast<s_RRType*>(pyself);
+    return (convertToPyHash<uint16_t>(self->cppobj->getCode()));
+}
 } // end anonymous namespace
 } // end anonymous namespace
 
 
 namespace isc {
 namespace isc {
@@ -394,7 +400,7 @@ PyTypeObject rrtype_type = {
     NULL,                               // tp_as_number
     NULL,                               // tp_as_number
     NULL,                               // tp_as_sequence
     NULL,                               // tp_as_sequence
     NULL,                               // tp_as_mapping
     NULL,                               // tp_as_mapping
-    NULL,                               // tp_hash
+    RRType_hash,                        // tp_hash
     NULL,                               // tp_call
     NULL,                               // tp_call
     RRType_str,                         // tp_str
     RRType_str,                         // tp_str
     NULL,                               // tp_getattro
     NULL,                               // tp_getattro

+ 9 - 0
src/lib/dns/python/tests/rrtype_python_test.py

@@ -116,6 +116,15 @@ class TestModuleSpec(unittest.TestCase):
 
 
         self.assertFalse(self.rrtype_1 == 1)
         self.assertFalse(self.rrtype_1 == 1)
 
 
+    def test_hash(self):
+        # Exploiting the knowledge that the hash value is the numeric class
+        # value, we can predict the comparison result.
+        self.assertEqual(hash(RRType.AAAA()), hash(RRType("AAAA")))
+        self.assertEqual(hash(RRType("aaaa")), hash(RRType("AAAA")))
+        self.assertEqual(hash(RRType(28)), hash(RRType("AAAA")))
+        self.assertNotEqual(hash(RRType.A()), hash(RRType.NS()))
+        self.assertNotEqual(hash(RRType.AAAA()), hash(RRType("Type65535")))
+
     def test_statics(self):
     def test_statics(self):
         self.assertEqual(RRType("NSEC3PARAM"), RRType.NSEC3PARAM())
         self.assertEqual(RRType("NSEC3PARAM"), RRType.NSEC3PARAM())
         self.assertEqual(RRType("DNAME"), RRType.DNAME())
         self.assertEqual(RRType("DNAME"), RRType.DNAME())

+ 28 - 0
src/lib/dns/tests/labelsequence_unittest.cc

@@ -794,6 +794,7 @@ TEST(LabelSequence, rawConstruction) {
     result = s1.compare(s3);
     result = s1.compare(s3);
     EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
     EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
               result.getRelation());
               result.getRelation());
+    EXPECT_EQ(2, result.getCommonLabels());
 
 
     s1.stripRight(1);
     s1.stripRight(1);
     s3.stripRight(1);
     s3.stripRight(1);
@@ -801,11 +802,38 @@ TEST(LabelSequence, rawConstruction) {
     result = s1.compare(s3);
     result = s1.compare(s3);
     EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
     EXPECT_EQ(isc::dns::NameComparisonResult::COMMONANCESTOR,
               result.getRelation());
               result.getRelation());
+    EXPECT_EQ(1, result.getCommonLabels());
 
 
     data[9] = 'f';
     data[9] = 'f';
     result = s1.compare(s3);
     result = s1.compare(s3);
     EXPECT_EQ(isc::dns::NameComparisonResult::NONE,
     EXPECT_EQ(isc::dns::NameComparisonResult::NONE,
               result.getRelation());
               result.getRelation());
+    EXPECT_EQ(0, result.getCommonLabels());
+}
+
+// Test with some data that exceeds limits (MAX_LABELS and MAX_LABEL_LEN)
+TEST(LabelSequence, badRawConstruction) {
+    uint8_t data[1] = { 0 };
+    uint8_t offsets[1] = { 0 };
+
+    EXPECT_THROW(LabelSequence(NULL, offsets, 1), isc::BadValue);
+    EXPECT_THROW(LabelSequence(data, NULL, 1), isc::BadValue);
+    EXPECT_THROW(LabelSequence(data, offsets, 0), isc::BadValue);
+
+    // exceed MAX_LABELS
+    EXPECT_THROW(LabelSequence(data, offsets, 127), isc::BadValue);
+
+    // exceed MAX_LABEL_LEN
+    uint8_t offsets_toolonglabel[1] = { 64 };
+    EXPECT_THROW(LabelSequence(data, offsets_toolonglabel, 1), isc::BadValue);
+
+    // Add an offset that is lower than the previous offset
+    uint8_t offsets_lower[3] = { 0, 8, 4 };
+    EXPECT_THROW(LabelSequence(data, offsets_lower, 3), isc::BadValue);
+
+    // Add an offset that is equal to the previous offset
+    uint8_t offsets_noincrease[3] = { 0, 8, 8 };
+    EXPECT_THROW(LabelSequence(data, offsets_noincrease, 3), isc::BadValue);
 }
 }
 
 
 }
 }

+ 1 - 0
src/lib/python/isc/ddns/tests/.gitignore

@@ -0,0 +1 @@
+/rwtest.sqlite3.copied

+ 2 - 0
src/lib/util/Makefile.am

@@ -16,6 +16,8 @@ libutil_la_SOURCES += time_utilities.h time_utilities.cc
 libutil_la_SOURCES += interprocess_sync.h
 libutil_la_SOURCES += interprocess_sync.h
 libutil_la_SOURCES += interprocess_sync_file.h interprocess_sync_file.cc
 libutil_la_SOURCES += interprocess_sync_file.h interprocess_sync_file.cc
 libutil_la_SOURCES += interprocess_sync_null.h interprocess_sync_null.cc
 libutil_la_SOURCES += interprocess_sync_null.h interprocess_sync_null.cc
+libutil_la_SOURCES += memory_segment.h
+libutil_la_SOURCES += memory_segment_local.h memory_segment_local.cc
 libutil_la_SOURCES += range_utilities.h
 libutil_la_SOURCES += range_utilities.h
 libutil_la_SOURCES += hash/sha1.h hash/sha1.cc
 libutil_la_SOURCES += hash/sha1.h hash/sha1.cc
 libutil_la_SOURCES += encode/base16_from_binary.h
 libutil_la_SOURCES += encode/base16_from_binary.h

+ 69 - 0
src/lib/util/memory_segment.h

@@ -0,0 +1,69 @@
+// Copyright (C) 2012  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 __MEMORY_SEGMENT_H__
+#define __MEMORY_SEGMENT_H__
+
+#include <stdlib.h>
+
+namespace isc {
+namespace util {
+
+/// \brief Memory Segment Class
+///
+/// This class specifies an interface for allocating memory
+/// segments. This is an abstract class and a real
+/// implementation such as MemorySegmentLocal should be used
+/// in code.
+class MemorySegment {
+public:
+    /// \brief Destructor
+    virtual ~MemorySegment() {}
+
+    /// \brief Allocate/acquire a segment of memory. The source of the
+    /// memory is dependent on the implementation used.
+    ///
+    /// Throws <code>std::bad_alloc</code> if the implementation cannot
+    /// allocate the requested storage.
+    ///
+    /// \param size The size of the memory requested in bytes.
+    /// \return Returns pointer to the memory allocated.
+    virtual void* allocate(size_t size) = 0;
+
+    /// \brief Free/release a segment of memory.
+    ///
+    /// This method may throw <code>isc::OutOfRange</code> if \c size is
+    /// not equal to the originally allocated size. \c size could be
+    /// used by some implementations such as a slice allocator, where
+    /// freeing memory also requires the size to be specified. We also
+    /// use this argument in some implementations to test if all allocated
+    /// memory was deallocated properly.
+    ///
+    /// \param ptr Pointer to the block of memory to free/release. This
+    /// should be equal to a value returned by <code>allocate()</code>.
+    /// \param size The size of the memory to be freed in bytes. This
+    /// should be equal to the number of bytes originally allocated.
+    virtual void deallocate(void* ptr, size_t size) = 0;
+
+    /// \brief Check if all allocated memory was deallocated.
+    ///
+    /// \return Returns <code>true</code> if all allocated memory was
+    /// deallocated, <code>false</code> otherwise.
+    virtual bool allMemoryDeallocated() const = 0;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // __MEMORY_SEGMENT_H__

+ 55 - 0
src/lib/util/memory_segment_local.cc

@@ -0,0 +1,55 @@
+// Copyright (C) 2012  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 "memory_segment_local.h"
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+void*
+MemorySegmentLocal::allocate(size_t size) {
+    void* ptr = malloc(size);
+    if (ptr == NULL) {
+        throw std::bad_alloc();
+    }
+
+    allocated_size_ += size;
+    return (ptr);
+}
+
+void
+MemorySegmentLocal::deallocate(void* ptr, size_t size) {
+    if (ptr == NULL) {
+        // Return early if NULL is passed to be deallocated (without
+        // modifying allocated_size, or comparing against it).
+        return;
+    }
+
+    if (size > allocated_size_) {
+      isc_throw(OutOfRange, "Invalid size to deallocate: " << size
+                << "; currently allocated size: " << allocated_size_);
+    }
+
+    allocated_size_ -= size;
+    free(ptr);
+}
+
+bool
+MemorySegmentLocal::allMemoryDeallocated() const {
+    return (allocated_size_ == 0);
+}
+
+} // namespace util
+} // namespace isc

+ 76 - 0
src/lib/util/memory_segment_local.h

@@ -0,0 +1,76 @@
+// Copyright (C) 2012  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 __MEMORY_SEGMENT_LOCAL_H__
+#define __MEMORY_SEGMENT_LOCAL_H__
+
+#include <util/memory_segment.h>
+
+namespace isc {
+namespace util {
+
+/// \brief malloc/free based Memory Segment class
+///
+/// This class specifies a concrete implementation for a malloc/free
+/// based MemorySegment. Please see the MemorySegment class
+/// documentation for usage.
+class MemorySegmentLocal : public MemorySegment {
+public:
+    /// \brief Constructor
+    ///
+    /// Creates a local memory segment object
+    MemorySegmentLocal() : allocated_size_(0) {
+    }
+
+    /// \brief Destructor
+    virtual ~MemorySegmentLocal() {}
+
+    /// \brief Allocate/acquire a segment of memory. The source of the
+    /// memory is libc's malloc().
+    ///
+    /// Throws <code>std::bad_alloc</code> if the implementation cannot
+    /// allocate the requested storage.
+    ///
+    /// \param size The size of the memory requested in bytes.
+    /// \return Returns pointer to the memory allocated.
+    virtual void* allocate(size_t size);
+
+    /// \brief Free/release a segment of memory.
+    ///
+    /// This method may throw <code>isc::OutOfRange</code> if \c size is
+    /// not equal to the originally allocated size.
+    ///
+    /// \param ptr Pointer to the block of memory to free/release. This
+    /// should be equal to a value returned by <code>allocate()</code>.
+    /// \param size The size of the memory to be freed in bytes. This
+    /// should be equal to the number of bytes originally allocated.
+    virtual void deallocate(void* ptr, size_t size);
+
+    /// \brief Check if all allocated memory was deallocated.
+    ///
+    /// \return Returns <code>true</code> if all allocated memory was
+    /// deallocated, <code>false</code> otherwise.
+    virtual bool allMemoryDeallocated() const;
+
+private:
+    // allocated_size_ can underflow, wrap around to max size_t (which
+    // is unsigned). But because we only do a check against 0 and not a
+    // relation comparison, this is okay.
+    size_t allocated_size_;
+};
+
+} // namespace util
+} // namespace isc
+
+#endif // __MEMORY_SEGMENT_LOCAL_H__

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

@@ -33,6 +33,7 @@ run_unittests_SOURCES += io_utilities_unittest.cc
 run_unittests_SOURCES += lru_list_unittest.cc
 run_unittests_SOURCES += lru_list_unittest.cc
 run_unittests_SOURCES += interprocess_sync_file_unittest.cc
 run_unittests_SOURCES += interprocess_sync_file_unittest.cc
 run_unittests_SOURCES += interprocess_sync_null_unittest.cc
 run_unittests_SOURCES += interprocess_sync_null_unittest.cc
+run_unittests_SOURCES += memory_segment_local_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += qid_gen_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += random_number_generator_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc
 run_unittests_SOURCES += sha1_unittest.cc

+ 108 - 0
src/lib/util/tests/memory_segment_local_unittest.cc

@@ -0,0 +1,108 @@
+// Copyright (C) 2012  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 "util/memory_segment_local.h"
+#include <exceptions/exceptions.h>
+#include <gtest/gtest.h>
+#include <memory>
+
+using namespace std;
+using namespace isc::util;
+
+namespace {
+
+TEST(MemorySegmentLocal, TestLocal) {
+    auto_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+    // By default, nothing is allocated.
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+
+    void* ptr = segment->allocate(1024);
+
+    // Now, we have an allocation:
+    EXPECT_FALSE(segment->allMemoryDeallocated());
+
+    void* ptr2 = segment->allocate(42);
+
+    // Still:
+    EXPECT_FALSE(segment->allMemoryDeallocated());
+
+    // These should not fail, because the buffers have been allocated.
+    EXPECT_NO_FATAL_FAILURE(memset(ptr, 0, 1024));
+    EXPECT_NO_FATAL_FAILURE(memset(ptr, 0, 42));
+
+    segment->deallocate(ptr, 1024);
+
+    // Still:
+    EXPECT_FALSE(segment->allMemoryDeallocated());
+
+    segment->deallocate(ptr2, 42);
+
+    // Now, we have an deallocated everything:
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+TEST(MemorySegmentLocal, TestTooMuchMemory) {
+    auto_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+    EXPECT_THROW(segment->allocate(0x7fffffffffffffff), bad_alloc);
+}
+
+TEST(MemorySegmentLocal, TestBadDeallocate) {
+    auto_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+    // By default, nothing is allocated.
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+
+    void* ptr = segment->allocate(1024);
+
+    // Now, we have an allocation:
+    EXPECT_FALSE(segment->allMemoryDeallocated());
+
+    // This should not throw
+    EXPECT_NO_THROW(segment->deallocate(ptr, 1024));
+
+    // Now, we have an deallocated everything:
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+
+    ptr = segment->allocate(1024);
+
+    // Now, we have another allocation:
+    EXPECT_FALSE(segment->allMemoryDeallocated());
+
+    // This should throw as the size passed to deallocate() is larger
+    // than what was allocated.
+    EXPECT_THROW(segment->deallocate(ptr, 2048), isc::OutOfRange);
+
+    // This should not throw
+    EXPECT_NO_THROW(segment->deallocate(ptr, 1024));
+
+    // Now, we have an deallocated everything:
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+TEST(MemorySegmentLocal, TestNullDeallocate) {
+    auto_ptr<MemorySegment> segment(new MemorySegmentLocal());
+
+    // By default, nothing is allocated.
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+
+    // NULL deallocation is a no-op.
+    EXPECT_NO_THROW(segment->deallocate(NULL, 1024));
+
+    // This should still return true.
+    EXPECT_TRUE(segment->allMemoryDeallocated());
+}
+
+} // anonymous namespace