Browse Source

Merge branch 'master' into trac3599

Tomek Mrugalski 10 years ago
parent
commit
cd9f34a8fe
100 changed files with 5463 additions and 1465 deletions
  1. 4 0
      AUTHORS
  2. 46 2
      ChangeLog
  3. 2 0
      doc/Makefile.am
  4. 1 0
      doc/devel/mainpage.dox
  5. 74 0
      doc/examples/kea4/multiple-options.json
  6. 68 0
      doc/examples/kea6/multiple-options.json
  7. 7 7
      doc/guide/config.xml
  8. 13 13
      doc/guide/ddns.xml
  9. 162 95
      doc/guide/dhcp4-srv.xml
  10. 179 108
      doc/guide/dhcp6-srv.xml
  11. 4 4
      doc/guide/install.xml
  12. 1 1
      doc/guide/keactrl.xml
  13. 2 2
      doc/guide/logging.xml
  14. 1 1
      doc/guide/quickstart.xml
  15. 33 26
      src/bin/d2/Makefile.am
  16. 2 25
      src/bin/d2/tests/Makefile.am
  17. 17 10
      src/bin/dhcp4/Makefile.am
  18. 2 1
      src/bin/dhcp4/dhcp4_srv.cc
  19. 3 10
      src/bin/dhcp4/tests/Makefile.am
  20. 17 10
      src/bin/dhcp6/Makefile.am
  21. 20 35
      src/bin/dhcp6/dhcp6_srv.cc
  22. 1 7
      src/bin/dhcp6/json_config_parser.cc
  23. 2 10
      src/bin/dhcp6/tests/Makefile.am
  24. 77 55
      src/bin/dhcp6/tests/config_parser_unittest.cc
  25. 3 2
      src/bin/dhcp6/tests/confirm_unittest.cc
  26. 4 3
      src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
  27. 1 0
      src/bin/dhcp6/tests/d2_unittest.cc
  28. 4 3
      src/bin/dhcp6/tests/dhcp6_message_test.cc
  29. 65 48
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  30. 5 2
      src/bin/dhcp6/tests/dhcp6_test_utils.cc
  31. 1 1
      src/bin/dhcp6/tests/dhcp6_test_utils.h
  32. 3 2
      src/bin/dhcp6/tests/fqdn_unittest.cc
  33. 8 2
      src/bin/dhcp6/tests/hooks_unittest.cc
  34. 4 2
      src/bin/dhcp6/tests/kea_controller_unittest.cc
  35. 8 8
      src/bin/dhcp6/tests/rebind_unittest.cc
  36. 2 1
      src/bin/dhcp6/tests/sarr_unittest.cc
  37. 5 0
      src/bin/keactrl/kea.conf.pre
  38. 22 15
      src/bin/perfdhcp/Makefile.am
  39. 2 9
      src/bin/perfdhcp/tests/Makefile.am
  40. 9 2
      src/bin/sockcreator/Makefile.am
  41. 2 2
      src/bin/sockcreator/tests/Makefile.am
  42. 29 24
      src/hooks/dhcp/user_chk/Makefile.am
  43. 2 16
      src/hooks/dhcp/user_chk/tests/Makefile.am
  44. 0 5
      src/lib/asiodns/tests/Makefile.am
  45. 1 3
      src/lib/asiodns/tests/run_unittests.cc
  46. 3 1
      src/lib/cryptolink/botan_common.h
  47. 2 3
      src/lib/cryptolink/botan_hash.cc
  48. 1 2
      src/lib/cryptolink/botan_hmac.cc
  49. 4 1
      src/lib/cryptolink/crypto_hmac.h
  50. 86 1
      src/lib/cryptolink/openssl_common.h
  51. 2 2
      src/lib/cryptolink/openssl_hash.cc
  52. 6 81
      src/lib/cryptolink/openssl_hmac.cc
  53. 35 0
      src/lib/dhcp/libdhcp++.cc
  54. 25 1
      src/lib/dhcp/libdhcp++.h
  55. 21 6
      src/lib/dhcp/option4_client_fqdn.cc
  56. 5 2
      src/lib/dhcp/option6_addrlst.h
  57. 20 0
      src/lib/dhcp/option_definition.h
  58. 64 0
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  59. 77 2
      src/lib/dhcp/tests/option4_client_fqdn_unittest.cc
  60. 22 5
      src/lib/dhcp/tests/option6_client_fqdn_unittest.cc
  61. 8 0
      src/lib/dhcpsrv/Makefile.am
  62. 153 0
      src/lib/dhcpsrv/base_host_data_source.h
  63. 200 0
      src/lib/dhcpsrv/cfg_hosts.cc
  64. 256 0
      src/lib/dhcpsrv/cfg_hosts.h
  65. 21 1
      src/lib/dhcpsrv/cfg_option_def.cc
  66. 10 0
      src/lib/dhcpsrv/cfg_option_def.h
  67. 1 7
      src/lib/dhcpsrv/cfg_subnets4.cc
  68. 20 26
      src/lib/dhcpsrv/cfg_subnets4.h
  69. 182 0
      src/lib/dhcpsrv/cfg_subnets6.cc
  70. 211 0
      src/lib/dhcpsrv/cfg_subnets6.h
  71. 0 108
      src/lib/dhcpsrv/cfgmgr.cc
  72. 0 91
      src/lib/dhcpsrv/cfgmgr.h
  73. 197 124
      src/lib/dhcpsrv/dhcp_parsers.cc
  74. 61 11
      src/lib/dhcpsrv/dhcp_parsers.h
  75. 0 4
      src/lib/dhcpsrv/dhcpsrv_messages.mes
  76. 90 8
      src/lib/dhcpsrv/host.cc
  77. 44 7
      src/lib/dhcpsrv/host.h
  78. 84 0
      src/lib/dhcpsrv/host_container.h
  79. 116 0
      src/lib/dhcpsrv/host_mgr.cc
  80. 190 0
      src/lib/dhcpsrv/host_mgr.h
  81. 200 0
      src/lib/dhcpsrv/host_reservation_parser.cc
  82. 110 0
      src/lib/dhcpsrv/host_reservation_parser.h
  83. 34 1
      src/lib/dhcpsrv/libdhcpsrv.dox
  84. 5 3
      src/lib/dhcpsrv/srv_config.cc
  85. 43 0
      src/lib/dhcpsrv/srv_config.h
  86. 75 0
      src/lib/dhcpsrv/subnet_selector.h
  87. 4 0
      src/lib/dhcpsrv/tests/Makefile.am
  88. 9 6
      src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
  89. 405 0
      src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
  90. 12 1
      src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc
  91. 11 10
      src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
  92. 353 0
      src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
  93. 0 322
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  94. 287 2
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  95. 230 0
      src/lib/dhcpsrv/tests/host_mgr_unittest.cc
  96. 354 0
      src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
  97. 83 37
      src/lib/dhcpsrv/tests/host_unittest.cc
  98. 1 17
      src/lib/dhcpsrv/tests/srv_config_unittest.cc
  99. 112 0
      src/lib/dhcpsrv/writable_host_data_source.h
  100. 0 0
      src/lib/hooks/Makefile.am

+ 4 - 0
AUTHORS

@@ -66,6 +66,10 @@ We have received the following contributions:
  - Nicolas Chaigneau, Capgemini
    2014-09: Fix for interfaces with multiple addresses in perfdhcp
 
+ - Marcin Wyszynki, Facebook
+   2014-11: Export CalloutManager headers for testing statically linked
+            libraries.
+
 Kea uses log4cplus (http://sourceforge.net/projects/log4cplus/) for logging,
 Boost (http://www.boost.org/) library for almost everything, and can use Botan
 (http://botan.randombit.net/) or OpenSSL (https://www.openssl.org/) for

+ 46 - 2
ChangeLog

@@ -1,3 +1,47 @@
+861.	[func]		marcin
+	The configuration parameters for a DHCPv4 and DHCPv6 options are now
+	optional.
+	(Trac #3467, git 7bf8cef161e6dd00a7f2b2fe8ec04e1958d6db3f)
+
+860.	[bug]		marcin
+	Fixed calculation of the Client FQDN option length for the ASCII
+	domain name encoding.
+	(Trac #3624, git 5a120d9bf85e27ea5b2674d35af0f2774e4cd2a7)
+
+859.	[func]		marcin
+	Implemented Host Manager, which can retrieve host reservations
+	specified in the server's configuration. Future tickets will
+	extend Host Manager to retrieve reservations from other sources,
+	e.g. SQL databases.
+	(Trac #3561, git faac5e9746dbf82eb04ffef95658e4b4c7d64a4a)
+
+858.	[bug]		marcin
+	Added missing "lease-database" entry to the default DHCPv6
+	server configuration, in kea.conf.
+	(Trac #3630, git 0f7ff732ea2add45a24e040eae8a0dda27532a31)
+
+857.	[func]		fdupont
+	Improve the cryptolink code, for instance use a constant
+	time comparison.
+	(Trac #3602, git 0c1f433da650330b40fe1a67bae4716c9184f636)
+
+856.	[build]		marcinw
+	callout_manager.h and server_hooks.h headers are now exported,
+	so statically linked libraries can be tested.
+	(Github #4, git 00b5f3fa0369c13021bf4fb78c6450e524e4e411)
+
+855.	[build]		fdupont
+	Use convenience archives for objects used in a makefile and
+	its parent makefile: before sources were compiled twice using
+	the broken subdir-objects option of automake, now objects
+	are put into a convenience static library (so an archive).
+	(Trac #3631, git d7954b4234114d8fa41aa51f671d4faa1724b748)
+
+854.	[bug]		marcin
+	Corrected a regression on "make distcheck" which appeared after
+	implementation of #3162 (partial fix).
+	(Trac #3629, git 9bb6b76a24e4356b30e59631e76e32c3096fb515)
+
 853.	[func]		tomek
 	Lease6 now is able to store MAC/hardware address information. Memfile
 	memfile backend has been updated to store/retrieve that additional
@@ -11,8 +55,8 @@
 	(Trac #3549, git d92e76860e6931477b3e60e5be8978302973f88f)
 
 851.	[bug]		tmark
-	Corrected a segmentation fault that was occurring under OS-X during D2 module
-	shutdown.
+	Corrected a segmentation fault that was occurring under OS-X
+	during D2 module shutdown.
 	(Trac #3470, git f7822568abd04c12faa3cde34fadaac238a373d3)
 
 850.	[build]		fdupont

+ 2 - 0
doc/Makefile.am

@@ -4,8 +4,10 @@ EXTRA_DIST = version.ent.in differences.txt Doxyfile Doxyfile-xml
 
 nobase_dist_doc_DATA  = examples/kea4/single-subnet.json
 nobase_dist_doc_DATA += examples/kea4/several-subnets.json
+nobase_dist_doc_DATA += examples/kea4/multiple-options.json
 nobase_dist_doc_DATA += examples/kea6/simple.json
 nobase_dist_doc_DATA += examples/kea6/several-subnets.json
+nobase_dist_doc_DATA += examples/kea6/multiple-options.json
 nobase_dist_doc_DATA += examples/ddns/sample1.json
 nobase_dist_doc_DATA += examples/ddns/template.json
 

+ 1 - 0
doc/devel/mainpage.dox

@@ -88,6 +88,7 @@
  * - @subpage libdhcpsrv
  *   - @subpage leasemgr
  *   - @subpage cfgmgr
+ *   - @subpage hostmgr
  *   - @subpage optionsConfig
  *   - @subpage allocengine
  * - @subpage libdhcp_ddns

+ 74 - 0
doc/examples/kea4/multiple-options.json

@@ -0,0 +1,74 @@
+# This is an example configuration file for the DHCPv4 server in Kea.
+# It demonstrates simple configuration of the options for a subnet.
+
+{ "Dhcp4":
+
+{
+# Kea is told to listen on eth0 interface only.
+  "interfaces": [ "eth0" ],
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with valid lifetimes being 4000. Client
+# is told to start renewing after 1000 seconds. If the server does not respond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "valid-lifetime": 4000,
+
+# Renew and rebind timers are commented out. This implies that options
+# 58 and 59 will not be sent to the client. In this case it is up to
+# the client to pick the timer values according to RFC2131. Uncomment the
+# timers to send these options to the client.
+#  "renew-timer": 1000,
+#  "rebind-timer": 2000,
+
+# Defining a subnet. There are 3 DHCP options returned to the
+# clients connected to this subnet. The first two options are
+# identified by the name. The third option is identified by the
+# option code.
+  "subnet4": [
+    {
+       "pools": [ { "pool":  "192.0.2.10 - 192.0.2.200" } ],
+       "subnet": "192.0.2.0/24",
+       "interface": "eth0",
+       "option-data": [
+         {
+             "name": "domain-name-servers",
+             "data": "192.0.2.1, 192.0.2.2"
+         },
+         {
+             "name": "routers",
+             "data": "192.0.2.1"
+         },
+         {
+             "code": 15,
+             "data": "example.org"
+         }
+       ]
+    } 
+  ]
+},
+
+# The following configures logging. It assumes that messages with at least
+# informational level (info, warn, error) will will be logged to stdout.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp4",
+            "output_options": [
+                {
+                    "output": "stdout"
+                }
+            ],
+            "severity": "INFO"
+        }
+    ]
+}
+
+}

+ 68 - 0
doc/examples/kea6/multiple-options.json

@@ -0,0 +1,68 @@
+# This is an example configuration file for DHCPv6 server in Kea.
+# It demonstrates simple configuration of the options for a subnet.
+
+{ "Dhcp6":
+
+{
+# Kea is told to listen on eth0 interface only.
+  "interfaces": [ "eth0" ],
+
+# We need to specify lease type. As of May 2014, three backends are supported:
+# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
+# any prior set up.
+  "lease-database": {
+    "type": "memfile"
+  },
+
+# Addresses will be assigned with preferred and valid lifetimes
+# being 3000 and 4000, respectively. Client is told to start
+# renewing after 1000 seconds. If the server does not repond
+# after 2000 seconds since the lease was granted, client is supposed
+# to start REBIND procedure (emergency renewal that allows switching
+# to a different server).
+  "preferred-lifetime": 3000,
+  "valid-lifetime": 4000,
+  "renew-timer": 1000,
+  "rebind-timer": 2000,
+
+# Defining a subnet. There are 2 DHCP options returned to the
+# clients connected to this subnet. The first option is identified
+# by the name. The second option is identified by the code.
+  "subnet6": [
+    {
+      "pools": [ { "pool": "2001:db8:1::/80" } ],
+      "subnet": "2001:db8:1::/64",
+      "interface": "eth0",
+      "option-data": [
+        {
+            "name": "dns-servers",
+            "data": "2001:db8:2::45, 2001:db8:2::100"
+        },
+        {
+            "code": 12,
+            "data": "2001:db8:1:0:ff00::1"
+        },
+      ]
+    }
+  ]
+},
+
+# The following configures logging. Kea will log all debug messages
+# to /var/log/kea-debug.log file.
+"Logging": {
+    "loggers": [
+        {
+            "name": "kea-dhcp6",
+            "output_options": [
+                {
+                    "output": "/var/log/kea-debug.log"
+                }
+            ],
+            "debuglevel": 99,
+            "severity": "DEBUG"
+        }
+    ]
+}
+
+}
+

+ 7 - 7
doc/guide/config.xml

@@ -6,8 +6,8 @@
 <chapter id="kea-config">
   <title>Kea configuration</title>
 
-  <para>Depending on configuration backend chosen (see <xref
-  linkend="dhcp-config-backend"/>), configuration mechanisms are different. The
+  <para>Depending on the configuration backend chosen (see <xref
+  linkend="dhcp-config-backend"/>), the configuration mechanisms are different. The
   following sections describe details of the different configuration backends. Note
   that only one configuration backend can be used and its selection is
   made when the configure script is run.</para>
@@ -27,7 +27,7 @@
     It assumes that the servers are started from the command line
     (either directly or using a script, e.g. <filename>keactrl</filename>).
     The JSON backend uses certain signals to influence Kea. The
-    configuration file is specified upon startup using -c parameter.</para>
+    configuration file is specified upon startup using the -c parameter.</para>
 
     <section id="json-format">
       <title>JSON syntax</title>
@@ -106,7 +106,7 @@
         rest of this guide will showcase only the subset of parameters appropriate for a given
         context. For example, when discussing the IPv6 subnets configuration in
         DHCPv6, only subnet6 parameters will be mentioned. It is implied that
-        remaining elements (the global map that holds Dhcp6, Logging and possibly
+        the remaining elements (the global map that holds Dhcp6, Logging and possibly
         DhcpDdns) are present, but they are omitted for clarity. Usually, locations
         where extra parameters may appear are denoted with an ellipsis.</para>
     </section>
@@ -114,11 +114,11 @@
     <section>
       <title>Simplified Notation</title>
 
-        <para>It is sometimes convenient to refer to specific element in the
+        <para>It is sometimes convenient to refer to a specific element in the
         configuration hierarchy. Each hierarchy level is separated by a slash.
-        If there is an array, a specific instance within that array is referred by
+        If there is an array, a specific instance within that array is referenced by
         a number in square brackets (with numbering starting at zero). For example, in the above configuration the
-        valid-lifetime in Dhcp6 component can be referred to as
+        valid-lifetime in the Dhcp6 component can be referred to as
         Dhcp6/valid-lifetime, the first interface for the DHCPv4 server as
         Dhcp4/interfaces[0] and the pool in the first subnet defined in the DHCPv6
         configuration as Dhcp6/subnet6[0]/pool.</para>

+ 13 - 13
doc/guide/ddns.xml

@@ -16,7 +16,7 @@
     necessary conversation with those servers to update the DNS data.
     </para>
     <para>
-    In order to match a request to appropriate DNS servers, D2 must have a
+    In order to match a request to the appropriate DNS servers, D2 must have a
     catalog of servers from which to select. In fact, D2 has two such catalogs,
     one for forward DNS and one for reverse DNS; these catalogs are referred
     to as DDNS Domain Lists.  Each list consists of one or more named DDNS
@@ -55,7 +55,7 @@
       <command>kea-dhcp-ddns</command> is the Kea DHCP-DDNS server
       and, due to the nature of DDNS, it is run alongside either the
       DHCPv4 or DHCPv6 components (or both).  Like other parts of
-      Kea, is a separate binary that can be run on its own or through
+      Kea, it is a separate binary that can be run on its own or through
       <command>keactrl</command> (see <xref linkend="keactrl"/>). In
       normal operation, controlling <command>kea-dhcp-ddns</command>
       with <command>keactrl</command> is recommended. However, it is also
@@ -74,7 +74,7 @@
             <simpara>
             <command>-d</command> - specifies whether the server
             logging should be switched to debug/verbose mode. In verbose mode,
-            the logging severity and debuglevel specified in a configuration
+            the logging severity and debuglevel specified in the configuration
             file are ignored and "debug" severity and the maximum debuglevel
             (99) are assumed. The flag is convenient, for temporarily
             switching the server into maximum verbosity, e.g. when
@@ -330,7 +330,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 "DhcpDdns": {
     "tsig_keys": [
     <userinput>    {
-	    "name": "key.four.example.com",
+	    "name": "key.four.example.com.",
 	    "algorithm": "HMAC-SHA224",
 	    "secret": "bZEG7Ow8OgAUPfLWV3aAUQ=="
 	}</userinput>
@@ -376,7 +376,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 	  by zone and a DDNS Domain only defines a single zone.
 	  </para>
 	  <para>
-	  The section describes how to add Forward DDNS Domains. Repeat these
+	  This section describes how to add Forward DDNS Domains. Repeat these
 	  steps for each Forward DDNS Domain desired.  Each Forward DDNS Domain
 	  has the following parameters:
 	  <itemizedlist>
@@ -418,7 +418,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
     "forward_ddns": {
 	"ddns_domains": [
 	    <userinput>{
-		"name": "other.example.com",
+		"name": "other.example.com.",
 		"key_name": "",
 		"dns_servers": [
 		]
@@ -437,7 +437,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 	<section id="add-forward-dns-servers">
 	  <title>Adding Forward DNS Servers</title>
 	  <para>
-	  The section describes how to add DNS servers to a Forward DDNS Domain.
+	  This section describes how to add DNS servers to a Forward DDNS Domain.
 	  Repeat them for as many servers as desired for a each domain.
 	  </para>
 	  <para>
@@ -468,7 +468,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 	    </listitem>
 	  </itemizedlist>
 	  To create a new forward DNS Server, one must add a new server
-	  element to the domain and fill its parameters.  If for
+	  element to the domain and fill in its parameters.  If for
 	example the service is running at "172.88.99.10", then set it as
 	follows:
 <screen>
@@ -476,7 +476,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
     "forward_ddns": {
 	"ddns_domains": [
 	    {
-		"name": "other.example.com",
+		"name": "other.example.com.",
 		"key_name": "",
 		"dns_servers": [
 		    <userinput>{
@@ -535,7 +535,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 	  single zone.
 	  </para>
 	  <para>
-	  The section describes how to add Reverse DDNS Domains. Repeat these
+	  This section describes how to add Reverse DDNS Domains. Repeat these
 	  steps for each Reverse DDNS Domain desired.  Each Reverse DDNS Domain
 	  has the following parameters:
 	  <itemizedlist>
@@ -603,8 +603,8 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 	<section id="add-reverse-dns-servers">
 	  <title>Adding Reverse DNS Servers</title>
 	  <para>
-	  The section describes how to add DNS servers to a Reverse DDNS Domain.
-	  Repeat them for as many servers as desired for a each domain.
+	  This section describes how to add DNS servers to a Reverse DDNS Domain.
+	  Repeat them for as many servers as desired for each domain.
 	  </para>
 	  <para>
 	  Reverse DNS Server entries represents a actual DNS servers which
@@ -633,7 +633,7 @@ corresponding values in the DHCP servers' "dhcp-ddns" configuration section.
 	    </listitem>
 	  </itemizedlist>
 	  To create a new reverse DNS Server, one must first add a new server
-	  element to the domain and fill its parameters.  If for
+	  element to the domain and fill in its parameters.  If for
 	example the service is running at "172.88.99.10", then set it as
 	follows:
 <screen>

+ 162 - 95
doc/guide/dhcp4-srv.xml

@@ -30,7 +30,7 @@
             <simpara>
             <command>-d</command> - specifies whether the server
             logging should be switched to debug/verbose mode. In verbose mode,
-            the logging severity and debuglevel specified in a configuration
+            the logging severity and debuglevel specified in the configuration
             file are ignored and "debug" severity and the maximum debuglevel
             (99) are assumed. The flag is convenient, for temporarily
             switching the server into maximum verbosity, e.g. when
@@ -85,7 +85,7 @@
         Kea configuration backend. (Kea configuration using any other
         backends is outside of scope of this document.) Before DHCPv4
         is started, its configuration file has to be created. The
-        basic configuration looks as follows:
+        basic configuration is as follows:
 <screen>
 {
 # DHCPv4 configuration starts in this line
@@ -140,15 +140,15 @@ and ends with the corresponding closing brace (in the above example,
 the brace after the last comment).  Everything defined between those
 lines is considered to be the Dhcp4 configuration.</para>
 
-<para>In general case, the order in which those parameters appear does not
+<para>In the general case, the order in which those parameters appear does not
 matter. There are two caveats here though. The first one is to remember that
-the configuration file must be a well formed JSON. That means that parameters
-for any given scope must be separate by a comma and there must not be a comma
-after the last parameter. When reordering configuration file, keep in mind that
-moving a parameter to or from the last position in a given scope may require
-moving the comma as well. The second caveat is that it is uncommon &mdash; although
+the configuration file must be well formed JSON. That means that the parameters
+for any given scope must be separated by a comma and there must not be a comma
+after the last parameter. When reordering a configuration file, keep in mind that
+moving a parameter to or from the last position in a given scope may also require
+moving the comma. The second caveat is that it is uncommon &mdash; although
 legal JSON &mdash; to
-repeat the same parameter multiple times. If that appears, the last occurrence of a
+repeat the same parameter multiple times. If that happens, the last occurrence of a
 given parameter in a given scope is used while all previous instances are
 ignored. This is unlikely to cause any confusion as there are no real life
 reasons to keep multiple copies of the same parameter in your configuration
@@ -166,21 +166,21 @@ look like this:
 As "<command>interfaces</command>" is not the last parameter in the configuration,
 a trailing comma is required.</para>
 <para>A number of other parameters
-follow. <command>valid-lifetime</command> defines how long the addresses (leases) given out by the
-server are valid. If nothing changes, client that got the address is allowed to
+follow. <command>valid-lifetime</command> defines for how long the addresses (leases) given out by the
+server are valid. If nothing changes, a client that got an address is allowed to
 use it for 4000 seconds. (Note that integer numbers are specified as is,
 without any quotes around them.) <command>renew-timer</command> and
 <command>rebind-timer</command> are values that
-define T1 and T2 timers that govern when the client will begin renewal and
+define T1 and T2 timers that govern when the client will begin the renewal and
 rebind procedures. Note that <command>renew-timer</command> and
 <command>rebind-timer</command> are optional. If they are not specified the
 client will select values for T1 and T2 timers according to the
 <ulink url="http://tools.ietf.org/html/rfc2131">RFC 2131</ulink>.</para>
 
-<para>The next couple lines define the lease database, the place where the server
+<para>The next couple of lines define the lease database, the place where the server
 stores its lease information. This particular example tells the server to use
 <command>memfile</command>, which is the simplest (and fastest) database
-backend. It uses in-memory database and stores leases on disk in a CSV
+backend. It uses an in-memory database and stores leases on disk in a CSV
 file. This is a very simple configuration. Usually, lease database configuration
 is more extensive and contains additional parameters.  Note that
 <command>lease-database</command>
@@ -192,12 +192,12 @@ comma is present.</para>
 
 <para>Finally, we need to define a list of IPv4 subnets. This is the
 most important DHCPv4 configuration structure as the server uses that
-information to process clients' requests. It defines all subnets that
-the server is expected to receive DHCP requests from. The subnets are
+information to process clients' requests. It defines all subnets from
+which the server is expected to receive DHCP requests. The subnets are
 specified with the <command>subnet4</command> parameter.  It is a list,
 so it starts and ends with square brackets.  Each subnet definition in
-the list has several attributes associated with it, so is a structure
-and is opened and closed with braces. At minimum, a subnet definition
+the list has several attributes associated with it, so it is a structure
+and is opened and closed with braces. At a minimum, a subnet definition
 has to have at least two parameters: <command>subnet</command> (that
 defines the whole subnet) and <command>pools</command> (which is a list of
 dynamically allocated pools that are governed by the DHCP server).</para>
@@ -301,7 +301,7 @@ url="http://jsonviewer.stack.hu/"/>.
 <screen>
 "Dhcp4": { "lease-database": { <userinput>"type": "mysql"</userinput>, ... }, ... }
 </screen>
-  Next, the name of the database is to hold the leases must be set: this is the
+  Next, the name of the database to hold the leases must be set: this is the
   name used when the lease database was created (see <xref linkend="dhcp-mysql-database-create"/>
   or <xref linkend="dhcp-pgsql-database-create"/>).
 <screen>
@@ -323,7 +323,7 @@ url="http://jsonviewer.stack.hu/"/>.
   access the database should be set:
 <screen>
 "Dhcp4": { "lease-database": { <userinput>"user": "<replaceable>user-name</replaceable>"</userinput>,
-                               <userinput>"password" "<replaceable>password</replaceable>"</userinput>,
+                               <userinput>"password": "<replaceable>password</replaceable>"</userinput>,
                               ... },
            ... }
 </screen>
@@ -357,7 +357,7 @@ temporarily override a list of interface names and listen on all interfaces.
   <title>IPv4 Subnet Identifier</title>
   <para>
     The subnet identifier is a unique number associated with a particular subnet.
-    In principle, it is used to associate clients' leases with respective subnets.
+    In principle, it is used to associate clients' leases with their respective subnets.
     When a subnet identifier is not specified for a subnet being configured, it will
     be automatically assigned by the configuration mechanism. The identifiers
     are assigned from 1 and are monotonically increased for each subsequent
@@ -366,17 +366,17 @@ temporarily override a list of interface names and listen on all interfaces.
   <para>
     If there are multiple subnets configured with auto-generated identifiers and
     one of them is removed, the subnet identifiers may be renumbered. For example:
-    if there are four subnets and third is removed the last subnet will be assigned
-    identifier that the third subnet had before removal. As a result, the leases
+    if there are four subnets and the third is removed the last subnet will be assigned
+    the identifier that the third subnet had before removal. As a result, the leases
     stored in the lease database for subnet 3 are now associated with
     subnet 4, something that may have unexpected consequences. It is planned
-    to implement the mechanism to preserve auto-generated subnet ids in a
+    to implement a mechanism to preserve auto-generated subnet ids in a
     future version of Kea.  However, the only remedy for this issue
     at present is to
     manually specify a unique identifier for each subnet.
   </para>
       <para>
-	The following configuration will assign the arbitrary subnet
+	The following configuration will assign the specified subnet
 	identifier to the newly configured subnet:
 
         <screen>
@@ -425,7 +425,7 @@ temporarily override a list of interface names and listen on all interfaces.
     specified in this example.</para>
 
     <para>Each <command>pool</command> is a structure that contains the parameters
-    th describe a single pool. Currently there is only one parameter,
+    that describe a single pool. Currently there is only one parameter,
     <command>pool</command>, which gives the range of addresses
     in the pool. Additional parameters will be added in future
     releases of Kea.</para>
@@ -450,7 +450,7 @@ temporarily override a list of interface names and listen on all interfaces.
 </screen>
     The number of pools is not limited, but for performance reasons it is recommended to
     use as few as possible. White space in pool definitions is ignored, so
-    spaces before and after hyphen are optional. They can be used to improve readability.
+    spaces before and after the hyphen are optional. They can be used to improve readability.
   </para>
   <para>
     The server may be configured to serve more than one subnet:
@@ -478,8 +478,8 @@ temporarily override a list of interface names and listen on all interfaces.
   </para>
   <para>
     When configuring a DHCPv4 server using prefix/length notation, please pay
-    attention to the boundary values. When specifying that the server should use
-    a given pool, it will be able to allocate also first (typically network
+    attention to the boundary values. When specifying that the server can use
+    a given pool, it will also be able to allocate the first (typically network
     address) and the last (typically broadcast address) address from that pool.
     In the aforementioned example of pool 192.0.3.0/24, both 192.0.3.0 and
     192.0.3.255 addresses may be assigned as well. This may be invalid in some
@@ -491,9 +491,9 @@ temporarily override a list of interface names and listen on all interfaces.
       <title>Standard DHCPv4 options</title>
       <para>
         One of the major features of the DHCPv4 server is to provide configuration
-        options to clients. Although there are several options that require
+        options to clients.  Although there are several options that require
         special behavior, most options are sent by the server only if the client
-        explicitly requested them.  The following example shows how to
+        explicitly requests them.  The following example shows how to
         configure the addresses of DNS servers, which is one of the most frequently used
         options. Options specified in this way are considered global and apply
         to all configured subnets.
@@ -518,13 +518,13 @@ temporarily override a list of interface names and listen on all interfaces.
       option name. For a complete list of currently supported names,
       see <xref linkend="dhcp4-std-options-list"/> below.
       The <command>code</command> parameter specifies the option code, which must match one of the
-      values from that list. The next line specifies option space, which must always
+      values from that list. The next line specifies the option space, which must always
       be set to "dhcp4" as these are standard DHCPv4 options. For
       other option spaces, including custom option spaces, see <xref
       linkend="dhcp4-option-spaces"/>. The next line specifies the format in
       which the data will be entered: use of CSV (comma
       separated values) is recommended. The sixth line gives the actual value to
-      be sent to clients. Data is specified as a normal text, with
+      be sent to clients. Data is specified as normal text, with
       values separated by commas if more than one value is
       allowed.
     </para>
@@ -553,6 +553,12 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
 
       <para>
+        Most of the parameters in the "option-data" structure are optional and
+        can be omitted in some circumstances as discussed in the
+        <xref linkend="dhcp4-option-data-defaults"/>.
+      </para>
+
+      <para>
         It is possible to specify or override options on a per-subnet basis.  If
         clients connected to most of your subnets are expected to get the
         same values of a given option, you should use global options: you
@@ -589,15 +595,6 @@ temporarily override a list of interface names and listen on all interfaces.
 </screen>
       </para>
 
-    <note>
-      <para>
-        In future versions of Kea, it will not be necessary to specify
-        the <command>code</command>, <command>space</command>
-        and <command>csv-format</command> fields, as they will
-        be set automatically.
-      </para>
-    </note>
-
       <para>
         The currently supported standard DHCPv4 options are
         listed in <xref linkend="dhcp4-std-options-list"/>
@@ -625,8 +622,8 @@ temporarily override a list of interface names and listen on all interfaces.
         a server administrator must create a definition as described in
         <xref linkend="dhcp4-custom-options"/> in the 'dhcp4' option space. This
         definition should match the option format described in the relevant
-        RFC but configuration mechanism would allow any option format as it has
-        no means to validate it at the moment.
+        RFC but the configuration mechanism will allow any option format as it has
+        no means to validate the format at the moment.
       </para>
 
       <para>
@@ -837,7 +834,7 @@ temporarily override a list of interface names and listen on all interfaces.
 "Dhcp4": {
     "option-data": [
         {
-            <userinput>name "foo",
+            <userinput>"name": "foo",
             "code": 222,
             "space": "dhcp4",
             "csv-format": true,
@@ -853,7 +850,7 @@ temporarily override a list of interface names and listen on all interfaces.
       primitives (uint8, string, ipv4-address etc): it is possible to
       define an option comprising a number of existing primitives.
       Assume we want to define a new option that will consist of
-      an IPv4 address, followed by unsigned 16 bit integer, followed by
+      an IPv4 address, followed by an unsigned 16 bit integer, followed by
       a boolean value, followed by a text string. Such an option could
       be defined in the following way:
 <screen>
@@ -891,14 +888,14 @@ temporarily override a list of interface names and listen on all interfaces.
     ],
     ...
 }</screen>
-      <command>csv-format</command> is set <command>true</command> to indicate
+      <command>csv-format</command> is set to <command>true</command> to indicate
       that the <command>data</command> field comprises a command-separated list
       of values.  The values in the <command>data</command> must correspond to
       the types set in the <command>record-types</command> field of the option
       definition.
      </para>
      <note>
-       <para>In general case, boolean values are specified as <command>true</command> or
+       <para>In the general case, boolean values are specified as <command>true</command> or
        <command>false</command>, without quotes. Some specific boolean parameters may
        accept also <command>"true"</command>, <command>"false"</command>,
        <command>0</command>, <command>1</command>, <command>"0"</command> and
@@ -1099,6 +1096,76 @@ temporarily override a list of interface names and listen on all interfaces.
     </para>
     </section>
 
+    <section id="dhcp4-option-data-defaults">
+      <title>Unspecified parameters for DHCPv4 option configuration</title>
+      <para>In many cases it is not required to specify all parameters for
+      an option configuration and the default values may be used. However, it is
+      important to understand the implications of not specifing some of them
+      as it may result in configuration errors. The list below explains
+      the behavior of the server when a particular parameter is not explicitly
+      specified:
+
+      <itemizedlist>
+        <listitem>
+          <simpara><command>name</command> - the server requires an option name or
+          option code to identify an option. If this parameter is unspecified, the
+          option code must be specified.
+          </simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>code</command> - the server requires an option name or
+          option code to identify an option. This parameter may be left unspecified if
+          the <command>name</command> parameter is specified. However, this also
+          requires that the particular option has its definition (it is either a
+          standard option or an administrator created a definition for the option
+          using an 'option-def' structure), as the option definition associates an
+          option with a particular name. It is possible to configure an option
+          for which there is no definition (unspecified option format).
+          Configuration of such options requires the use of option code.
+          </simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>space</command> - if the option space is unspecified it
+          will default to 'dhcp4' which is an option space holding DHCPv4 standard
+          options.
+          </simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>data</command> - if the option data is unspecified it
+          defaults to an empty value. The empty value is mostly used for the
+          options which have no payload (boolean options), but it is legal to specify
+          empty values for some options which carry variable length data and which
+          spec allows for the length of 0. For such options, the data parameter
+          may be omitted in the configuration.</simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>csv-format</command> - if this value is not specified
+          and the definition for the particular option exists, the server will assume
+          that the option data is specified as a list of comma separated values to be
+          assigned to individual fields of the DHCP option. If the definition
+          does not exist for this option, the server will assume that the data
+          parameter contains the option payload in the binary format (represented
+          as a string of hexadecimal digits). Note that not specifying this
+          parameter doesn't imply that it defaults to a fixed value, but
+          the configuration data interpretation also depends on the presence
+          of the option definition. An administrator must be aware if the
+          definition for the particular option exists when this parameter
+          is not specified. It is generally recommended to not specify this
+          parameter only for the options for which the definition exists, e.g.
+          standard options. Setting <command>csv-format</command> to an explicit
+          value will cause the server to strictly check the format of the option
+          data specified.
+          </simpara>
+        </listitem>
+      </itemizedlist>
+      </para>
+
+    </section>
+
     <section id="dhcp4-stateless-configuration">
       <title>Stateless Configuration of DHCPv4 clients</title>
       <para>The DHCPv4 server supports the stateless client configuration whereby the
@@ -1172,19 +1239,19 @@ temporarily override a list of interface names and listen on all interfaces.
       </note>
       <para>In certain cases it is useful to differentiate between different
       types of clients and treat them differently. The process of doing
-      classification is conducted in two steps. The first step is to assess
+      classification is conducted in two steps. The first step is to assess an
       incoming packet and assign it to zero or more classes. This classification
       is currently simple, but is expected to grow in capability soon. Currently
-      the server checks whether incoming packet has vendor class identifier
-      option (60). If it has, content of that option is prepended with
-      &quot;VENDOR_CLASS_&quot; then is interpreted as a class. For example,
+      the server checks whether an incoming packet includes the vendor class identifier
+      option (60). If it does, the content of that option is prepended with
+      &quot;VENDOR_CLASS_&quot; then it is interpreted as a class. For example,
       modern cable modems will send this option with value &quot;docsis3.0&quot;
       and as a result the packet will belong to class &quot;VENDOR_CLASS_docsis3.0&quot;.
       </para>
 
       <para>It is envisaged that the client classification will be used for changing the
       behavior of almost any part of the DHCP message processing, including assigning
-      leases from different pools, assigning different option (or different values of
+      leases from different pools, assigning different options (or different values of
       the same options) etc. For now, there are only two mechanisms that are taking
       advantage of client classification: specific processing for cable modems and
       subnet selection.</para>
@@ -1192,10 +1259,10 @@ temporarily override a list of interface names and listen on all interfaces.
       <para>
         For clients that belong to the VENDOR_CLASS_docsis3.0 class, the siaddr
         field is set to the value of next-server (if specified in a subnet). If
-        there is boot-file-name option specified, its value is also set in the
+        there is a boot-file-name option specified, its value is also set in the
         file field in the DHCPv4 packet. For eRouter1.0 class, the siaddr is
         always set to 0.0.0.0. That capability is expected to be moved to
-        external hook library that will be dedicated to cable modems.
+        an external hook library that will be dedicated to cable modems.
       </para>
 
       <para>
@@ -1203,7 +1270,7 @@ temporarily override a list of interface names and listen on all interfaces.
         This is particularly useful for cases where two types of devices share the
         same link and are expected to be served from two different subnets. The
         primary use case for such a scenario is cable networks. There are two
-        classes of devices: the cable modem itself, which should be handled a lease
+        classes of devices: the cable modem itself, which should be handed a lease
         from subnet A and all other devices behind the modem that should get a lease
         from subnet B. That segregation is essential to prevent overly curious
         users from playing with their cable modems. For details on how to set up
@@ -1238,7 +1305,7 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
 
       <para>
-        Care should be taken with client classification as it is easy to prevent
+        Care should be taken with client classification as it is easy for
         clients that do not meet class criteria to be denied any service altogether.
       </para>
     </section>
@@ -1248,7 +1315,7 @@ temporarily override a list of interface names and listen on all interfaces.
       <title>Configuring DHCPv4 for DDNS</title>
       <para>
       As mentioned earlier, kea-dhcp4 can be configured to generate requests to the
-      DHCP-DDNS server to update DNS entries.  These requests are known as
+      DHCP-DDNS server (referred to here as "D2" ) to update DNS entries.  These requests are known as
       NameChangeRequests or NCRs.  Each NCR contains the following information:
       <orderedlist>
       <listitem><para>
@@ -1262,8 +1329,7 @@ temporarily override a list of interface names and listen on all interfaces.
       The FQDN, lease address, and DHCID
       </para></listitem>
       </orderedlist>
-      The parameters for controlling the generation of NCRs for submission to the
-      DHCP-DDNS server
+      The parameters for controlling the generation of NCRs for submission to D2
       are contained in the <command>dhcp-ddns</command> section of the kea-dhcp4 server
       configuration. The default values for this section are as follows:
 <screen>
@@ -1291,9 +1357,9 @@ temporarily override a list of interface names and listen on all interfaces.
       <section id="dhcpv4-d2-io-config">
       <title>DHCP-DDNS Server Connectivity</title>
       <para>
-      In order for NCRs to reach the DHCP-DDNS server, kea-dhcp4 must be able
+      In order for NCRs to reach the D2 server, kea-dhcp4 must be able
       to communicate with it.  kea-dhcp4 uses the following configuration
-      parameters to control how it communications with DHCP-DDNS:
+      parameters to control how it communications with D2:
       <itemizedlist>
       <listitem><simpara>
       <command>enable-updates</command> - determines whether or not kea-dhcp4 will
@@ -1302,54 +1368,54 @@ temporarily override a list of interface names and listen on all interfaces.
       </simpara></listitem>
 
       <listitem><simpara>
-      <command>server-ip</command> - IP address on which DHCP-DDNS listens for requests. The default is
+      <command>server-ip</command> - IP address on which D2 listens for requests. The default is
       the local loopback interface at address 127.0.0.1. You may specify
       either an IPv4 or IPv6 address.
       </simpara></listitem>
 
       <listitem><simpara>
-      <command>server-port</command> - port on which DHCP-DDNS listens for requests.  The default value
+      <command>server-port</command> - port on which D2 listens for requests.  The default value
       is 53001.
       </simpara></listitem>
 
       <listitem><simpara>
-      <command>sender-ip</command> - IP address which kea-dhcp4 should use to send requests to the DHCP-DDNS server.
+      <command>sender-ip</command> - IP address which kea-dhcp4 should use to send requests to D2.
       The default value is blank which instructs kea-dhcp4 to select a suitable
       address.
       </simpara></listitem>
 
       <listitem><simpara>
-      <command>sender-port</command> - port which kea-dhcp4 should use to send requests to the DHCP-DDNS server. The
-      default value of 0 instructs kea-dhcp4 to select suitable port.
+      <command>sender-port</command> - port which kea-dhcp4 should use to send requests to D2. The
+      default value of 0 instructs kea-dhcp4 to select a suitable port.
       </simpara></listitem>
 
       <listitem><simpara>
       <command>max-queue-size</command> - maximum number of requests allowed to queue waiting to
-      be sent to the DHCP-DDNS server. This value guards against requests accumulating
+      be sent to D2. This value guards against requests accumulating
       uncontrollably if they are being generated faster than they can be
       delivered.  If the number of requests queued for transmission reaches
       this value, DDNS updating will be turned off until the queue backlog has
-      been sufficiently reduced.  The intention is allow the kea-dhcp4 server to
+      been sufficiently reduced.  The intention is to allow the kea-dhcp4 server to
       continue lease operations without running the risk that its memory usage
       grows without limit.  The default value is 1024.
       </simpara></listitem>
 
       <listitem><simpara>
-      <command>ncr-format</command> - socket protocol use when sending requests to the DHCP-DDNS server.  Currently
+      <command>ncr-format</command> - socket protocol use when sending requests to D2.  Currently
       only UDP is supported.  TCP may be available in an upcoming release.
       </simpara></listitem>
 
       <listitem><simpara>
-      <command>ncr-protocol</command> - packet format to use when sending requests to the DHCP-DDNS server.
+      <command>ncr-protocol</command> - packet format to use when sending requests to D2.
       Currently only JSON format is supported.  Other formats may be available
       in future releases.
       </simpara></listitem>
 
       </itemizedlist>
-      By default, the DHCP-DDNS server is assumed to running on the same machine as kea-dhcp4, and
+      By default, kea-dhcp-ddns is assumed to be running on the same machine as kea-dhcp4, and
       all of the default values mentioned above should be sufficient.
-      If, however, the DHCP-DDNS server has been configured to listen on a different address or
-      port, these values must altered accordingly. For example, if the DHCP-DDNS server has been
+      If, however, D2 has been configured to listen on a different address or
+      port, these values must be altered accordingly. For example, if D2 has been
       configured to listen on 192.168.1.10 port 900, the following configuration
       would be required:
 <screen>
@@ -1369,9 +1435,9 @@ temporarily override a list of interface names and listen on all interfaces.
       <para>kea-dhcp4 follows the behavior prescribed for DHCP servers in
       <ulink url="http://tools.ietf.org/html/rfc4702">RFC 4702</ulink>.
       It is important to keep in mind that kea-dhcp4 provides the initial decision
-      making of when and what to update and forwards that information to the DHCP-DDNS server in
+      making of when and what to update and forwards that information to D2 in
       the form of NCRs. Carrying out the actual DNS updates and dealing with
-      such things as conflict resolution are within the purview of the DHCP-DDNS server itself (<xref linkend="dhcp-ddns-server"/>).
+      such things as conflict resolution are within the purview of D2 itself (<xref linkend="dhcp-ddns-server"/>).
       This section describes when kea-dhcp4 will generate NCRs and the
       configuration parameters that can be used to influence this decision.
       It assumes that the "enable-updates" parameter is true.
@@ -1458,7 +1524,7 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
       <para>
       (Note that the flag combination N=1, S=1 is prohibited according to
-      <ulink utl="http://tools.ietf.org/html/rfc4702">RFC 4702</ulink>. If such a combination is received from the client, the packet
+      <ulink url="http://tools.ietf.org/html/rfc4702">RFC 4702</ulink>. If such a combination is received from the client, the packet
       will be dropped by kea-dhcp4.)
       </para>
       <para>
@@ -1478,7 +1544,7 @@ temporarily override a list of interface names and listen on all interfaces.
       The third row in the table above describes the case in which the client
       requests that no DNS updates be done. The parameter, <command>override-no-update</command>,
       can be used to instruct the server to disregard the client's wishes. When
-      this parameter is true, kea-dhcp4 will generate DDNS update request to the DHCP-DDNS server
+      this parameter is true, kea-dhcp4 will generate a DDNS update request to kea-dhcp-ddns
       even if the client requests that no updates be done.  The N-S-O flags in the
       server's response to the client will be 0-1-1.
       </para>
@@ -1583,8 +1649,9 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
       <para>
       where address-text is simply the lease IP address converted to a
-      hyphenated string.  For example, if lease address is 172.16.1.10 and
-      assuming default values for <command>generated-prefix</command> and <command>qualifying-suffix</command>, the
+      hyphenated string.  For example, if the lease address is 172.16.1.10 and
+      default values are used for
+      <command>generated-prefix</command> and <command>qualifying-suffix</command>, the
       generated FQDN would be:
       </para>
       <para>
@@ -1595,12 +1662,12 @@ temporarily override a list of interface names and listen on all interfaces.
     <section id="dhcp4-next-server">
       <title>Next Server (siaddr)</title>
       <para>In some cases, clients want to obtain configuration from the TFTP server.
-      Although there is a dedicated option for it, some devices may use siaddr field
+      Although there is a dedicated option for it, some devices may use the siaddr field
       in the DHCPv4 packet for that purpose. That specific field can be configured
-      using <command>next-server</command> directive. It is possible to define it in global scope or
-      for a given subnet only. If both are defined, subnet value takes precedence.
+      using <command>next-server</command> directive. It is possible to define it in the global scope or
+      for a given subnet only. If both are defined, the subnet value takes precedence.
       The value in subnet can be set to 0.0.0.0, which means that <command>next-server</command> should
-      not be sent. It may also be set to empty string, which means the same as if
+      not be sent. It may also be set to an empty string, which means the same as if
       it was not defined at all, i.e. use the global value.
       </para>
 
@@ -1633,11 +1700,11 @@ temporarily override a list of interface names and listen on all interfaces.
       which updated
       <ulink url="http://tools.ietf.org/html/rfc2131">RFC 2131</ulink>.
       That update now states that the server must
-      send client-id if client sent it. That is the default behaviour
+      send client-id if the client sent it. That is the default behaviour
       that Kea offers. However, in some cases older devices that do
       not support
       <ulink url="http://tools.ietf.org/html/rfc6842">RFC 6842</ulink>.
-      may refuse to accept responses that include
+      may refuse to accept responses that include the
       client-id option. To enable backward compatibility, an optional
       configuration parameter has been introduced. To configure it,
       use the following configuration statement:</para>
@@ -1675,7 +1742,7 @@ temporarily override a list of interface names and listen on all interfaces.
         The DHCPv4 server differentiates between the directly connected clients,
         clients trying to renew leases and clients sending their messages through
         relays. For the directly connected clients the server will check the
-        configuration of the interface on which the message has been received, and
+        configuration for the interface on which the message has been received, and
         if the server configuration doesn't match any configured subnet the
         message is discarded.</para>
         <para>Assuming that the server's interface is configured with the
@@ -1686,7 +1753,7 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
       <para>
         The rule above does not apply when the client unicasts its message, i.e.
-        is trying to renew its lease. Such message is accepted through any
+        is trying to renew its lease. Such a message is accepted through any
         interface. The renewing client sets ciaddr to the currently used IPv4
         address. The server uses this address to select the subnet for the client
         (in particular, to extend the lease using this address).
@@ -1703,8 +1770,8 @@ temporarily override a list of interface names and listen on all interfaces.
       <note>
         <para>The subnet selection mechanism described in this section is based
         on the assumption that client classification is not used. The classification
-        mechanism alters the way in which subnet is selected for the client,
-        depending on the classes that the client belongs to.</para>
+        mechanism alters the way in which a subnet is selected for the client,
+        depending on the classes to which the client belongs.</para>
       </note>
 
     <section id="dhcp4-relay-override">
@@ -1712,9 +1779,9 @@ temporarily override a list of interface names and listen on all interfaces.
       <para>
         The relay has to have an interface connected to the link on which
         the clients are being configured. Typically the relay has an IPv4
-        address configured on that interface that belongs to the subnet that
-        the server will assign addresses from. In such typical case, the
-        server is able to use IPv4 address inserted by the relay (in the giaddr
+        address configured on that interface that belongs to the subnet from which
+        the server will assign addresses. In the typical case, the
+        server is able to use the IPv4 address inserted by the relay (in the giaddr
         field of the DHCPv4 packet) to select the appropriate subnet.
       </para>
       <para>
@@ -1725,13 +1792,13 @@ temporarily override a list of interface names and listen on all interfaces.
         network renumbering (where both old and new address space is still being
         used) and a cable network. In a cable network both cable modems and the
         devices behind them are physically connected to the same link, yet
-        they use distinct addressing. In such case, the DHCPv4 server needs
+        they use distinct addressing. In such a case, the DHCPv4 server needs
         additional information (the IPv4 address of the relay) to properly select
         an appropriate subnet.
       </para>
       <para>
         The following example assumes that there is a subnet 192.0.2.0/24
-        that is accessible via relay that uses 10.0.0.1 as its IPv4 address.
+        that is accessible via a relay that uses 10.0.0.1 as its IPv4 address.
         The server will be able to select this subnet for any incoming packets
         that came from a relay that has an address in 192.0.2.0/24 subnet.
         It will also select that subnet for a relay with address 10.0.0.1.

+ 179 - 108
doc/guide/dhcp6-srv.xml

@@ -26,9 +26,9 @@
           </listitem>
           <listitem>
             <simpara>
-            <command>-v</command> - specifies whether the server
+            <command>-d</command> - specifies whether the server
             logging should be switched to verbose mode. In verbose mode,
-            the logging severity and debuglevel specified in a configuration
+            the logging severity and debuglevel specified in the configuration
             file are ignored and "debug" severity and the maximum debuglevel
             (99) are assumed. The flag is convenient, for temporarily
             switching the server into maximum verbosity, e.g. when
@@ -37,8 +37,8 @@
           <listitem>
             <simpara>
             <command>-p <replaceable>port</replaceable></command> -
-            specifies UDP port the server will listen on. This is only
-            useful during testing, as the DHCPv6 server listening on
+            specifies UDP port on which the server will listen. This is only
+            useful during testing, as a DHCPv6 server listening on
             ports other than default DHCPv6 ports will not be able to
             handle regular DHCPv6 queries.</simpara>
           </listitem>
@@ -142,11 +142,11 @@ and ends with the corresponding closing brace (in the above example,
 the brace after the last comment).  Everything defined between those
 lines is considered to be the Dhcp6 configuration.</para>
 
-<para>In general case, the order in which those parameters appear does not
+<para>In the general case, the order in which those parameters appear does not
 matter. There are two caveats here though. The first one is to remember that
-the configuration file must be a well formed JSON. That means that parameters
-for any given scope must be separate by a comma and there must not be a comma
-after the last parameter. When reordering configuration file, keep in mind that
+the configuration file must be well formed JSON. That means that parameters
+for any given scope must be separated by a comma and there must not be a comma
+after the last parameter. When reordering a configuration file, keep in mind that
 moving a parameter to or from the last position in a given scope may require
 moving the comma as well. The second caveat is that it is uncommon &mdash; although
 legal JSON &mdash; to
@@ -169,19 +169,19 @@ look like this:
 As "<command>interfaces</command>" is not the last parameter in the
 configuration, a trailing comma is required.</para>
 <para>A number of other parameters follow. <command>valid-lifetime</command>
-defines how long the addresses (leases) given out by the server are valid. If
-nothing changes, client that got the address is allowed to use it for 4000
+defines for how long the addresses (leases) given out by the server are valid. If
+nothing changes, a client that got an address is allowed to use it for 4000
 seconds. (Note that integer numbers are specified as is, without any quotes
 around them.) The address will become deprecated in 3000 seconds (clients are
 allowed to keep old connections, but can't use this address for creating new
 connections). <command>renew-timer</command> and <command>
 rebind-timer</command> are values that define T1 and T2 timers that govern when
-the client will begin renewal and rebind procedures.</para>
+the client will begin the renewal and rebind procedures.</para>
 
-<para>The next couple lines define the lease database, the place where the server
+<para>The next couple of lines define the lease database, the place where the server
 stores its lease information. This particular example tells the server to use
 <command>memfile</command>, which is the simplest (and fastest) database
-backend. It uses in-memory database and stores leases on disk in a CSV
+backend. It uses an in-memory database and stores leases on disk in a CSV
 file. This is a very simple configuration. Usually, lease database configuration
 is more extensive and contains additional parameters.  Note that
 <command>lease-database</command>
@@ -193,11 +193,11 @@ comma is present.</para>
 
 <para>Finally, we need to define a list of IPv6 subnets. This is the
 most important DHCPv6 configuration structure as the server uses that
-information to process clients' requests. It defines all subnets that
-the server is expected to receive DHCP requests from. The subnets are
+information to process clients' requests. It defines all subnets from
+which the server is expected to receive DHCP requests. The subnets are
 specified with the <command>subnet6</command> parameter.  It is a list,
 so it starts and ends with square brackets.  Each subnet definition in
-the list has several attributes associated with it, so is a structure
+the list has several attributes associated with it, so it is a structure
 and is opened and closed with braces. At minimum, a subnet definition
 has to have at least two parameters: <command>subnet</command> (that
 defines the whole subnet) and <command>pool</command> (which is a list of
@@ -327,7 +327,7 @@ JSON validator is available at <ulink url="http://jsonviewer.stack.hu/"/>.
   access the database should be set:
 <screen>
 "Dhcp6": { "lease-database": { <userinput>"user": "<replaceable>user-name</replaceable>"</userinput>,
-                               <userinput>"password" "<replaceable>password</replaceable>"</userinput>,
+                               <userinput>"password": "<replaceable>password</replaceable>"</userinput>,
                               ... },
            ... }
 </screen>
@@ -360,7 +360,7 @@ temporarily override a list of interface names and listen on all interfaces.
     <section id="ipv6-subnet-id">
       <title>IPv6 Subnet Identifier</title>
       <para>
-        Subnet identifier is a unique number associated with a particular subnet.
+        The subnet identifier is a unique number associated with a particular subnet.
         In principle, it is used to associate clients' leases with respective subnets.
         When the subnet identifier is not specified for a subnet being configured, it will
         be automatically assigned by the configuration mechanism. The identifiers
@@ -370,17 +370,17 @@ temporarily override a list of interface names and listen on all interfaces.
       <para>
        If there are multiple subnets configured with auto-generated identifiers and
        one of them is removed, the subnet identifiers may be renumbered. For example:
-       if there are 4 subnets and 3rd is removed the last subnet will be assigned
-       identifier that the 3rd subnet had before removal. As a result, the leases
-       stored in the lease database for subnet 3 are now associated with the
+       if there are four subnets and the third is removed the last subnet will be assigned
+       the identifier that the third subnet had before removal. As a result, the leases
+       stored in the lease database for subnet 3 are now associated with
        subnet 4, which may have unexpected consequences. In the future it is planned
-       to implement the mechanism to preserve auto-generated subnet ids upon removal
+       to implement a mechanism to preserve auto-generated subnet ids upon removal
        of one of the subnets. Currently, the only remedy for this issue is to
-       manually specify the unique subnet identifier for each subnet.
+       manually specify a unique subnet identifier for each subnet.
       </para>
 
       <para>
-	The following configuration will assign the arbitrary subnet
+	The following configuration will assign the specified subnet
 	identifier to the newly configured subnet:
 
 	<screen>
@@ -403,16 +403,16 @@ temporarily override a list of interface names and listen on all interfaces.
     <section id="dhcp6-unicast">
       <title>Unicast traffic support</title>
       <para>
-        When DHCPv6 server starts up, by default it listens to the DHCP traffic
+        When the DHCPv6 server starts, by default it listens to the DHCP traffic
         sent to multicast address ff02::1:2 on each interface that it is
         configured to listen on (see <xref linkend="dhcp6-interface-selection"/>).
         In some cases it is useful to configure a server to handle incoming
         traffic sent to the global unicast addresses as well. The most common
         reason for that is to have relays send their traffic to the server
-        directly. To configure server to listen on specific unicast address, a
-        notation to specify interfaces has been extended. Interface name can be
-        optionally followed by a slash, followed by global unicast address that
-        server should listen on. That will be done in addition to normal
+        directly. To configure the server to listen on a specific unicast address, the
+        notation to specify interfaces has been extended.  An interface name can be
+        optionally followed by a slash, followed by the global unicast address on which
+        the server should listen. This will be done in addition to normal
         link-local binding + listening on ff02::1:2 address. The sample commands
         listed below show how to listen on 2001:db8::1 (a global address)
         configured on the eth1 interface.
@@ -424,7 +424,7 @@ temporarily override a list of interface names and listen on all interfaces.
     ...
 }</screen>
 
-        When configuration gets committed, the server will start to listen on
+        When this configuration gets committed, the server will start to listen on
         eth1 on link-local address, multicast group (ff02::1:2) and 2001:db8::1.
       </para>
       <para>
@@ -435,8 +435,8 @@ temporarily override a list of interface names and listen on all interfaces.
       <para>
         Care should be taken to specify proper unicast addresses. The server will
         attempt to bind to those addresses specified, without any additional checks.
-        That approach is selected on purpose, so in the software can be used to
-        communicate over uncommon addresses if the administrator desires so.
+        This approach is selected on purpose, so the software can be used to
+        communicate over uncommon addresses if the administrator so desires.
       </para>
     </section>
 
@@ -471,14 +471,14 @@ temporarily override a list of interface names and listen on all interfaces.
         is specified.</para>
 
         <para>Each <command>pool</command> is a structure that contains the
-        parameters th describe a single pool. Currently there is only one
+        parameters that describe a single pool. Currently there is only one
         parameter, <command>pool</command>, which gives the range of addresses
         in the pool. Additional parameters will be added in future releases of
         Kea.</para>
 
         <para>It is possible to define more than one pool in a
         subnet: continuing the previous example, further assume that
-        2001:db8:1:0:5::/80 should be also be managed by the server. It could be written as
+        2001:db8:1:0:5::/80 should also be managed by the server. It could be written as
         2001:db8:1:0:5:: to 2001:db8:1::5:ffff:ffff:ffff, but typing so many 'f's
         is cumbersome. It can be expressed more simply as 2001:db8:1:0:5::/80. Both
         formats are supported by Dhcp6 and can be mixed in the pool list.
@@ -521,16 +521,16 @@ temporarily override a list of interface names and listen on all interfaces.
         ...
     ]
 }</screen>
-        In this example, we allow server to
+        In this example, we allow the server to
         dynamically assign all addresses available in the whole subnet. Although
         rather wasteful, it is certainly a valid configuration to dedicate the
         whole /64 subnet for that purpose. Note that the Kea server does not preallocate
-        the leases, so there is no danger of using gigantic address pools.
+        the leases, so there is no danger in using gigantic address pools.
       </para>
       <para>
         When configuring a DHCPv6 server using prefix/length notation, please pay
-        attention to the boundary values. When specifying that the server should use
-        a given pool, it will be able to allocate also first (typically network
+        attention to the boundary values. When specifying that the server can use
+        a given pool, it will also be able to allocate the first (typically network
         address) address from that pool. For example for pool 2001:db8:2::/64 the
         2001:db8:2:: address may be assigned as well. If you want to avoid this,
         use the "min-max" notation.
@@ -546,8 +546,9 @@ temporarily override a list of interface names and listen on all interfaces.
         A subnet may have one or more prefix delegation pools.  Each pool has
         a prefixed address, which is specified as a prefix and a prefix length,
         as well as a delegated prefix length. <command>delegated-len</command>
-	must not be shorter (numerically greater) than
-	<command>prefix-len</command>. If both <command>delegated-len</command>
+	must not be shorter (that is it must be numerically greater or equal)
+	than <command>prefix-len</command>.
+	If both <command>delegated-len</command>
 	and <command>prefix-len</command> are equal, the server will be able to
 	delegate only one prefix. A sample configuration is shown below:
       <screen>
@@ -572,13 +573,13 @@ temporarily override a list of interface names and listen on all interfaces.
     <section id="dhcp6-std-options">
       <title>Standard DHCPv6 options</title>
       <para>
-        One of the major features of DHCPv6 server is to provide configuration
-        options to clients. Although there are several options that require
+        One of the major features of a DHCPv6 server is to provide configuration
+        options to clients.  Although there are several options that require
         special behavior, most options are sent by the server only if the client
-        explicitly requested them.  The following example shows how to
+        explicitly requests them.  The following example shows how to
         configure DNS servers, which is one of the most frequently used
-        options. Numbers in the first column are added for easier reference and
-        will not appear on screen. Options specified in this way are considered
+        options.  Numbers in the first column are added for easier reference and
+        will not appear on screen.  Options specified in this way are considered
         global and apply to all configured subnets.
 
         <screen>
@@ -598,26 +599,28 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
 
     <para>
-      The first line creates new entry in option-data table. It contains
+      The <command>option-data></command> line creates a new entry in
+      the option-data table.  This table contains
       information on all global options that the server is supposed to configure
-      in all subnets. The second line specifies option name. For a complete list
+      in all subnets.  The <command>name</command> line specifies the option name.
+      (For a complete list
       of currently supported names, see <xref
-      linkend="dhcp6-std-options-list"/>.  The third line specifies option code,
-      which must match one of the values from that list. Line beginning with
-      <command>space</command> specifies option space, which must always be set
-      to "dhcp6" as these are standard DHCPv6 options. For other name spaces,
+      linkend="dhcp6-std-options-list"/>.)  The next line specifies the option code,
+      which must match one of the values from that list. The line beginning with
+      <command>space</command> specifies the option space, which must always be set
+      to "dhcp6" as these are standard DHCPv6 options.  For other name spaces,
       including custom option spaces, see <xref
-      linkend="dhcp6-option-spaces"/>. The fifth line specifies the format in
+      linkend="dhcp6-option-spaces"/>. The next line specifies the format in
       which the data will be entered: use of CSV (comma separated values) is
-      recommended. The sixth line gives the actual value to be sent to
-      clients. Data is specified as a normal text, with values separated by
+      recommended. The <command>data</command> line gives the actual value to be sent to
+      clients.  Data is specified as normal text, with values separated by
       commas if more than one value is allowed.
     </para>
 
     <para>
-      Options can also be configured as hexadecimal values. If csv-format is
+      Options can also be configured as hexadecimal values.  If "csv-format" is
       set to false, the option data must be specified as a string of hexadecimal
-      numbers. The
+      numbers.  The
       following commands configure the DNS-SERVERS option for all
       subnets with the following addresses: 2001:db8:1::cafe and
       2001:db8:1::babe.
@@ -639,11 +642,18 @@ temporarily override a list of interface names and listen on all interfaces.
 
        The value for the setting of the "data" element is split across two
        lines in this document for clarity: when entering the command, the
-       whole string should be entered on the same line. Care should be taken
+       whole string should be entered on the same line.  Care should be taken
        to use proper encoding when using hexadecimal format as Kea's ability
        to validate data correctness in hexadecimal is limited.
       </para>
 
+      <para>
+        Most of the parameters in the "option-data" structure are optional and
+        can be omitted in some circumstances as discussed in the
+        <xref linkend="dhcp6-option-data-defaults"/>.
+      </para>
+
+
     <para>
       It is possible to override options on a per-subnet basis.  If
       clients connected to most of your subnets are expected to get the
@@ -681,16 +691,6 @@ temporarily override a list of interface names and listen on all interfaces.
 </screen>
     </para>
 
-    <note>
-      <para>
-        In future versions of Kea, it will not be necessary to specify
-        the <command>code</command>, <command>space</command>
-        and <command>csv-format</command> fields, as they will
-        be set automatically.
-      </para>
-    </note>
-
-
     <para>
       The currently supported standard DHCPv6 options are
       listed in <xref linkend="dhcp6-std-options-list"/>.
@@ -702,7 +702,7 @@ temporarily override a list of interface names and listen on all interfaces.
     <para>
       Some options are designated as arrays, which means that more than one
       value is allowed in such an option. For example the option dns-servers
-      allows the specification of more than one IPv6 address, so allowing
+      allows the specification of more than one IPv6 address, allowing
       clients to obtain the addresses of multiple DNS servers.
     </para>
 
@@ -718,8 +718,8 @@ temporarily override a list of interface names and listen on all interfaces.
         a server administrator must create a definition as described in
         <xref linkend="dhcp6-custom-options"/> in the 'dhcp6' option space. This
         definition should match the option format described in the relevant
-        RFC but configuration mechanism would allow any option format as it has
-        no means to validate it at the moment.
+        RFC but the configuration mechanism would allow any option format as it has
+        no means to validate the format at the moment.
       </para>
 
 
@@ -831,7 +831,7 @@ temporarily override a list of interface names and listen on all interfaces.
 "Dhcp6": {
     "option-data": [
         {
-            <userinput>name "foo",
+            <userinput>"name": "foo",
             "code": 100,
             "space": "dhcp6",
             "csv-format": true,
@@ -895,7 +895,7 @@ temporarily override a list of interface names and listen on all interfaces.
       </para>
 
       <note>
-       <para>In general case, boolean values are specified as <command>true</command> or
+       <para>In the general case, boolean values are specified as <command>true</command> or
        <command>false</command>, without quotes. Some specific boolean parameters may
        accept also <command>"true"</command>, <command>"false"</command>,
        <command>0</command>, <command>1</command>, <command>"0"</command> and
@@ -973,14 +973,14 @@ temporarily override a list of interface names and listen on all interfaces.
       <title>Nested DHCPv6 options (custom option spaces)</title>
       <para>It is sometimes useful to define completely new option
       spaces.  This is useful if the user wants his new option to
-      convey sub-options that use separate numbering scheme, for
+      convey sub-options that use a separate numbering scheme, for
       example sub-options with codes 1 and 2. Those option codes
       conflict with standard DHCPv6 options, so a separate option
       space must be defined.
       </para>
-      <para>Note that it is not required to create new option space when
-      defining sub-options for a standard option because it is by
-      default created if the standard option is meant to convey
+      <para>Note that it is not required to create a new option space when
+      defining sub-options for a standard option because it is
+      created by default if the standard option is meant to convey
       any sub-options (see <xref linkend="dhcp6-vendor-opts"/>).
       </para>
       <para>
@@ -1036,8 +1036,8 @@ should include options from the isc option space:
 
     The name of the option space in which the sub-options are defined is set in
     the <command>encapsulate</command> field. The <command>type</command> field
-    is set to <command>empty</command> which imposes that this option does not
-    carry any data other than sub-options.
+    is set to <command>empty</command> which limits this option to only carrying
+    data in sub-options.
     </para>
     <para>
     Finally, we can set values for the new options:
@@ -1090,24 +1090,94 @@ should include options from the isc option space:
     </para>
     </section>
 
+    <section id="dhcp6-option-data-defaults">
+      <title>Unspecified parameters for DHCPv6 option configuration</title>
+      <para>In many cases it is not required to specify all parameters for
+      an option configuration and the default values can be used. However, it is
+      important to understand the implications of not specifing some of them
+      as it may result in configuration errors. The list below explains
+      the behavior of the server when a particular parameter is not explicitly
+      specified:
+
+      <itemizedlist>
+        <listitem>
+          <simpara><command>name</command> - the server requires an option name or
+          option code to identify an option. If this parameter is unspecified, the
+          option code must be specified.
+          </simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>code</command> - the server requires an option name or
+          option code to identify an option. This parameter may be left unspecified if
+          the <command>name</command> parameter is specified. However, this also
+          requires that the particular option has its definition (it is either a
+          standard option or an administrator created a definition for the option
+          using an 'option-def' structure), as the option definition associates an
+          option with a particular name. It is possible to configure an option
+          for which there is no definition (unspecified option format).
+          Configuration of such options requires the use of option code.
+          </simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>space</command> - if the option space is unspecified it
+          will default to 'dhcp6' which is an option space holding DHCPv6 standard
+          options.
+          </simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>data</command> - if the option data is unspecified it
+          defaults to an empty value. The empty value is mostly used for the
+          options which have no payload (boolean options), but it is legal to specify
+          empty values for some options which carry variable length data and which
+          spec allows for the length of 0. For such options, the data parameter
+          may be omitted in the configuration.</simpara>
+        </listitem>
+
+        <listitem>
+          <simpara><command>csv-format</command> - if this value is not specified
+          and the definition for the particular option exists, the server will assume
+          that the option data is specified as a list of comma separated values to be
+          assigned to individual fields of the DHCP option. If the definition
+          does not exist for this option, the server will assume that the data
+          parameter contains the option payload in the binary format (represented
+          as a string of hexadecimal digits). Note that not specifying this
+          parameter doesn't imply that it defaults to a fixed value, but
+          the configuration data interpretation also depends on the presence
+          of the option definition. An administrator must be aware if the
+          definition for the particular option exists when this parameter
+          is not specified. It is generally recommended to not specify this
+          parameter only for the options for which the definition exists, e.g.
+          standard options. Setting <command>csv-format</command> to an explicit
+          value will cause the server to strictly check the format of the option
+          data specified.
+          </simpara>
+        </listitem>
+      </itemizedlist>
+      </para>
+
+    </section>
+
     <section id="dhcp6-config-subnets">
       <title>IPv6 Subnet Selection</title>
       <para>
         The DHCPv6 server may receive requests from local (connected to the
         same subnet as the server) and remote (connecting via relays) clients.
-        As server may have many subnet configurations defined, it must select
-        appropriate subnet for a given request.
+        As the server may have many subnet configurations defined, it must select
+        an appropriate subnet for a given request.
       </para>
       <para>
-        The server can not assume which of configured subnets are local. It is
-        possible in IPv4, where there is reasonable expectation that the
+        The server can not assume which of the configured subnets are local. In IPv4
+        it is possible as there is a reasonable expectation that the
         server will have a (global) IPv4 address configured on the interface,
         and can use that information to detect whether a subnet is local or
-        not. That assumption is not true in IPv6, as the DHCPv6 must be able
-        to operate with having link-local addresses only. Therefore an optional
+        not. That assumption is not true in IPv6, the DHCPv6 server must be able
+        to operate while only having link-local addresses. Therefore an optional
         &quot;interface&quot; parameter is available within a subnet definition
         to designate that a given subnet is local, i.e. reachable directly over
-        specified interface. For example the server that is intended to serve
+        the specified interface. For example the server that is intended to serve
         a local subnet over eth0 may be configured as follows:
 	<screen>
 "Dhcp6": {
@@ -1132,7 +1202,7 @@ should include options from the isc option space:
         <title>DHCPv6 Relays</title>
         <para>
           A DHCPv6 server with multiple subnets defined must select the
-          appropriate subnet when it receives a request from client.  For clients
+          appropriate subnet when it receives a request from a client.  For clients
           connected via relays, two mechanisms are used:
         </para>
         <para>
@@ -1209,11 +1279,11 @@ should include options from the isc option space:
       </note>
       <para>In certain cases it is useful to differentiate between different types
       of clients and treat them differently. The process of doing classification
-      is conducted in two steps. The first step is to assess incoming packet and
+      is conducted in two steps. The first step is to assess an incoming packet and
       assign it to zero or more classes. This classification is currently simple,
       but is expected to grow in capability soon. Currently the server checks whether
-      incoming packet has vendor class option (16). If it has, content
-      of that option is prepended with &quot;VENDOR_CLASS_&quot; interpreted as a
+      the incoming packet includes vendor class option (16). If it has, the content
+      of that option is prepended with &quot;VENDOR_CLASS_&quot; then it is interpreted as a
       class. For example, modern cable modems will send this option with value
       &quot;docsis3.0&quot; and as a result the packet will belong to class
       &quot;VENDOR_CLASS_docsis3.0&quot;.
@@ -1230,7 +1300,7 @@ should include options from the isc option space:
         This is particularly useful for cases where two types of devices share the
         same link and are expected to be served from two different subnets. The
         primary use case for such a scenario are cable networks. There are two
-        classes of devices: cable modem itself, which should be handled a lease
+        classes of devices: the cable modem itself, which should be handed a lease
         from subnet A and all other devices behind modems that should get a lease
         from subnet B. That segregation is essential to prevent overly curious
         users from playing with their cable modems. For details on how to set up
@@ -1243,7 +1313,7 @@ should include options from the isc option space:
       <title>Limiting access to IPv6 subnet to certain classes</title>
       <para>
         In certain cases it beneficial to restrict access to certain subnets
-        only to clients that belong to a given subnet. For details on client
+        only to clients that belong to a given class. For details on client
         classes, see <xref linkend="dhcp6-client-classifier"/>. This is an
         extension of a previous example from <xref linkend="dhcp6-address-config"/>.
 
@@ -1273,7 +1343,7 @@ should include options from the isc option space:
       </para>
 
       <para>
-        Care should be taken with client classification as it is easy to prevent
+        Care should be taken with client classification as it is easy for
         clients that do not meet class criteria to be denied any service altogether.
       </para>
     </section>
@@ -1283,7 +1353,7 @@ should include options from the isc option space:
       <title>Configuring DHCPv6 for DDNS</title>
       <para>
       As mentioned earlier, kea-dhcp6 can be configured to generate requests to
-      the DHCP-DDNS server (referred to here as the "D2" server) to update
+      the DHCP-DDNS server (referred to here as "D2") to update
       DNS entries.  These requests are known as NameChangeRequests or NCRs.
       Each NCR contains the following information:
       <orderedlist>
@@ -1352,7 +1422,7 @@ should include options from the isc option space:
       </simpara></listitem>
       <listitem><simpara>
       <command>sender-port</command> - port which kea-dhcp6 should use to send requests to D2. The
-      default value of 0 instructs kea-dhcp6 to select suitable port.
+      default value of 0 instructs kea-dhcp6 to select a suitable port.
       </simpara></listitem>
       <listitem><simpara>
       <command>max-queue-size</command> - maximum number of requests allowed to queue waiting to
@@ -1360,7 +1430,7 @@ should include options from the isc option space:
       uncontrollably if they are being generated faster than they can be
       delivered.  If the number of requests queued for transmission reaches
       this value, DDNS updating will be turned off until the queue backlog has
-      been sufficiently reduced.  The intent is allow kea-dhcp6 to
+      been sufficiently reduced.  The intent is to allow kea-dhcp6 to
       continue lease operations.  The default value is 1024.
       </simpara></listitem>
       <listitem><simpara>
@@ -1373,7 +1443,7 @@ should include options from the isc option space:
       in future releases.
       </simpara></listitem>
       </itemizedlist>
-      By default, D2 is assumed to running on the same machine as kea-dhcp6, and
+      By default, kea-dhcp-ddns is assumed to running on the same machine as kea-dhcp6, and
       all of the default values mentioned above should be sufficient.
       If, however, D2 has been configured to listen on a different address or
       port, these values must altered accordingly. For example, if D2 has been
@@ -1516,9 +1586,9 @@ should include options from the isc option space:
       The third row in the table above describes the case in which the client
       requests that no DNS updates be done. The parameter, "override-no-update",
       can be used to instruct the server to disregard the client's wishes. When
-      this parameter is true, kea-dhcp6 will generate DDNS update request to D2
-      even if the client requests no updates be done.  The N-S-O flags in the
-      server's response to the client will be 0-1-1.
+      this parameter is true, kea-dhcp6 will generate DDNS update requests to 
+      kea-dhcp-ddns even if the client requests no updates be done.  The N-S-O 
+      flags in the server's response to the client will be 0-1-1.
       </para>
       <para>
       To override client delegation, issue the following commands:
@@ -1548,7 +1618,7 @@ should include options from the isc option space:
       FQDN using a configurable prefix and suffix.
       </para></listitem>
       <listitem><para>
-      Otherwise, using is the domain name value from the client FQDN option as
+      Otherwise, using the domain name value from the client FQDN option as
       the candidate name:
       <orderedlist>
       <listitem><para>
@@ -1622,7 +1692,7 @@ should include options from the isc option space:
         some-computer.example.com.
       </para>
       <para>
-      When generating a the entire name, kea-dhcp6 will construct name of the
+      When generating the entire name, kea-dhcp6 will construct name of the
       format:
       </para>
       <para>
@@ -1631,7 +1701,8 @@ should include options from the isc option space:
       <para>
       where address-text is simply the lease IP address converted to a
       hyphenated string.  For example, if lease address is 3001:1::70E and
-      assuming default values for generated-prefix and qualifying-suffix, the
+      default values are used for 
+      <command>generated-prefix</command> and <command>qualifying-suffix</command>, the
       generated FQDN would be:
       </para>
       <para>
@@ -1673,16 +1744,16 @@ should include options from the isc option space:
       <para>
         The relay has to have an interface connected to the link on which
         the clients are being configured. Typically the relay has a global IPv6
-        address configured on that interface that belongs to the subnet that
-        the server will assign addresses from. In such typical case, the
-        server is able to use IPv6 address inserted by the relay (in link-addr
-        field in RELAY-FORW message) to select appropriate subnet.
+        address configured on the interface that belongs to the subnet from which
+        the server will assign addresses. In the typical case, the
+        server is able to use the IPv6 address inserted by the relay (in the link-addr
+        field in RELAY-FORW message) to select the appropriate subnet.
       </para>
       <para>
         However, that is not always the case. The relay
         address may not match the subnet in certain deployments. This
         usually means that there is more than one subnet allocated for a given
-        link. Two most common examples where this is the case are long lasting
+        link. The two most common examples where this is the case are long lasting
         network renumbering (where both old and new address space is still being
         used) and a cable network. In a cable network both cable modems and the
         devices behind them are physically connected to the same link, yet
@@ -1724,7 +1795,7 @@ should include options from the isc option space:
           In certain cases, it is useful to mix relay address information,
           introduced in <xref linkend="dhcp6-relay-override"/> with client
           classification, explained in <xref linkend="dhcp6-subnet-class"/>.
-          One specific example is cable network, where typically modems
+          One specific example is a cable network, where typically modems
           get addresses from a different subnet than all devices connected
           behind them.
         </para>

+ 4 - 4
doc/guide/install.xml

@@ -310,7 +310,7 @@ Debian and Ubuntu:
             <para>
               For additional instructions concerning the building and installation of
               Kea for various databases, see <xref linkend="dhcp-install-configure"/>.
-              For additional instructions concerning configuration backends, see
+              For additional instructions concerning the configuration backends, see
               <xref linkend="dhcp-config-backend" />.
             </para>
           </note>
@@ -389,7 +389,7 @@ Debian and Ubuntu:
     <section id="dhcp-config-backend">
       <title>Selecting the Configuration Backend</title>
       <para>Kea 0.9 introduces configuration backends that are
-      switchable during compilation phase. The backend is chosen using
+      switchable during the compilation phase. The backend is chosen using
       the --with-kea-config switch when running the configure script. It
       currently supports two values: BUNDY and JSON. JSON is the default.
       </para>
@@ -415,8 +415,8 @@ Debian and Ubuntu:
         <varlistentry>
           <term>JSON</term>
           <listitem>
-	    <simpara>JSON is a new default configuration backend
-	    that causes Kea to read JSON configuration file from
+	    <simpara>JSON is the new default configuration backend
+	    that causes Kea to read JSON configuration files from
 	    disk. It does not require any framework and thus is
 	    considered more lightweight. It will allow dynamic
 	    on-line reconfiguration, but will lack remote capabilities

+ 1 - 1
doc/guide/keactrl.xml

@@ -107,7 +107,7 @@ kea_verbose=no
       <para>
         By default, Kea servers managed by <command>keactrl</command> are
         located in <filename>[kea-install-dir]/sbin</filename>. This
-        should work for most of the installations. If the default
+        should work for most installations. If the default
         location needs to be altered for any reason, the paths
         specified with the <parameter>dhcp4_srv</parameter>,
         <parameter>dhcp6_srv</parameter> and <parameter>dhcp_ddns_srv</parameter>

+ 2 - 2
doc/guide/logging.xml

@@ -264,7 +264,7 @@
 
           Each logger can have zero or more
           <option>output_options</option>. These specify where log
-          messages are sent to. These are explained in detail below.
+          messages are sent. These are explained in detail below.
 
         </para>
 
@@ -583,7 +583,7 @@ file be created.</para>
           <varlistentry>
           <term>KEA_LOCKFILE_DIR</term>
           <listitem><para>
-              Specifies a directory where logging system should create its
+              Specifies a directory where the logging system should create its
               lock file. If not specified, it is
               <replaceable>prefix</replaceable>/var/run/kea, where
               <replaceable>prefix</replaceable> defaults to /usr/local.

+ 1 - 1
doc/guide/quickstart.xml

@@ -61,7 +61,7 @@ $ <userinput>./configure [your extra parameters]</userinput></screen>
         </listitem>
 
         <listitem>
-          <para>Edit configuration file which is by default installed in
+          <para>Edit the configuration file which by default is installed in
           <filename>[kea-install-dir]/etc/kea/kea.conf</filename> and contains
           configuration for all Kea services. Configuration choices for DHCPv4
           and DHCPv6 services are described in <xref linkend="dhcp4-configuration"/> and <xref linkend="dhcp6-configuration"/>, respectively.</para>

+ 33 - 26
src/bin/d2/Makefile.am

@@ -45,41 +45,48 @@ s-messages: d2_messages.mes
 
 BUILT_SOURCES = spec_config.h d2_messages.h d2_messages.cc
 
-sbin_PROGRAMS = kea-dhcp-ddns
-
-kea_dhcp_ddns_SOURCES  = main.cc
-kea_dhcp_ddns_SOURCES += d_process.h
-kea_dhcp_ddns_SOURCES += d_controller.cc d_controller.h
-kea_dhcp_ddns_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
-kea_dhcp_ddns_SOURCES += d2_asio.h
-kea_dhcp_ddns_SOURCES += d2_log.cc d2_log.h
-kea_dhcp_ddns_SOURCES += d2_process.cc d2_process.h
-kea_dhcp_ddns_SOURCES += d2_config.cc d2_config.h
-kea_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
-kea_dhcp_ddns_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
-kea_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
-kea_dhcp_ddns_SOURCES += d2_update_mgr.cc d2_update_mgr.h
-kea_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
-kea_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
-kea_dhcp_ddns_SOURCES += io_service_signal.cc io_service_signal.h
-kea_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
-kea_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
-kea_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.h
-kea_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
-kea_dhcp_ddns_SOURCES += state_model.cc state_model.h
+# convenience archive
+
+noinst_LTLIBRARIES = libd2.la
+
+libd2_la_SOURCES  =
+libd2_la_SOURCES += d_process.h
+libd2_la_SOURCES += d_controller.cc d_controller.h
+libd2_la_SOURCES += d_cfg_mgr.cc d_cfg_mgr.h
+libd2_la_SOURCES += d2_asio.h
+libd2_la_SOURCES += d2_log.cc d2_log.h
+libd2_la_SOURCES += d2_process.cc d2_process.h
+libd2_la_SOURCES += d2_config.cc d2_config.h
+libd2_la_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
+libd2_la_SOURCES += d2_queue_mgr.cc d2_queue_mgr.h
+libd2_la_SOURCES += d2_update_message.cc d2_update_message.h
+libd2_la_SOURCES += d2_update_mgr.cc d2_update_mgr.h
+libd2_la_SOURCES += d2_zone.cc d2_zone.h
+libd2_la_SOURCES += dns_client.cc dns_client.h
+libd2_la_SOURCES += io_service_signal.cc io_service_signal.h
+libd2_la_SOURCES += labeled_value.cc labeled_value.h
+libd2_la_SOURCES += nc_add.cc nc_add.h
+libd2_la_SOURCES += nc_remove.cc nc_remove.h
+libd2_la_SOURCES += nc_trans.cc nc_trans.h
+libd2_la_SOURCES += state_model.cc state_model.h
 
 if CONFIG_BACKEND_BUNDY
-kea_dhcp_ddns_SOURCES += bundy_d2_controller.cc bundy_d2_controller.h
+libd2_la_SOURCES += bundy_d2_controller.cc bundy_d2_controller.h
 else
 if CONFIG_BACKEND_JSON
-kea_dhcp_ddns_SOURCES += d2_controller.cc d2_controller.h
+libd2_la_SOURCES += d2_controller.cc d2_controller.h
 endif
 endif
 
-nodist_kea_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
+nodist_libd2_la_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
 
-kea_dhcp_ddns_LDADD = $(top_builddir)/src/lib/log/libkea-log.la
+sbin_PROGRAMS = kea-dhcp-ddns
+
+kea_dhcp_ddns_SOURCES  = main.cc
+
+kea_dhcp_ddns_LDADD  = libd2.la
+kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 kea_dhcp_ddns_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la

+ 2 - 25
src/bin/d2/tests/Makefile.am

@@ -1,5 +1,3 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
 SHTESTS =
 # The test of dynamic reconfiguration based on signals will work only
 # if we are using file based configuration approach.
@@ -52,26 +50,7 @@ if HAVE_GTEST
 
 TESTS += d2_unittests
 
-d2_unittests_SOURCES = ../d2_asio.h
-d2_unittests_SOURCES += ../d2_log.h ../d2_log.cc
-d2_unittests_SOURCES += ../d_process.h
-d2_unittests_SOURCES += ../d_controller.cc ../d_controller.h
-d2_unittests_SOURCES += ../d_cfg_mgr.cc ../d_cfg_mgr.h
-d2_unittests_SOURCES += ../d2_process.cc ../d2_process.h
-d2_unittests_SOURCES += ../d2_config.cc ../d2_config.h
-d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
-d2_unittests_SOURCES += ../d2_queue_mgr.cc ../d2_queue_mgr.h
-d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
-d2_unittests_SOURCES += ../d2_update_mgr.cc ../d2_update_mgr.h
-d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
-d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
-d2_unittests_SOURCES += ../io_service_signal.cc ../io_service_signal.h
-d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
-d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
-d2_unittests_SOURCES += ../nc_remove.cc ../nc_remove.h
-d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
-d2_unittests_SOURCES += ../state_model.cc ../state_model.h
-d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
+d2_unittests_SOURCES = d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
 d2_unittests_SOURCES += d_cfg_mgr_unittests.cc
@@ -88,14 +67,11 @@ d2_unittests_SOURCES += nc_remove_unittests.cc
 d2_unittests_SOURCES += nc_test_utils.cc nc_test_utils.h
 d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += state_model_unittests.cc
-nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 if CONFIG_BACKEND_BUNDY
-d2_unittests_SOURCES += ../bundy_d2_controller.cc ../bundy_d2_controller.h
 d2_unittests_SOURCES += bundy_d2_controller_unittests.cc
 else
 if CONFIG_BACKEND_JSON
-d2_unittests_SOURCES += ../d2_controller.cc ../d2_controller.h
 d2_unittests_SOURCES += d2_controller_unittests.cc
 d2_unittests_SOURCES += d_controller_unittests.cc
 endif
@@ -104,6 +80,7 @@ endif
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 d2_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 d2_unittests_LDADD = $(GTEST_LDADD)
+d2_unittests_LDADD += $(top_builddir)/src/bin/d2/libd2.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 d2_unittests_LDADD += $(top_builddir)/src/lib/asiodns/libkea-asiodns.la

+ 17 - 10
src/bin/dhcp4/Makefile.am

@@ -45,26 +45,33 @@ s-messages: dhcp4_messages.mes
 
 BUILT_SOURCES = spec_config.h dhcp4_messages.h dhcp4_messages.cc
 
-sbin_PROGRAMS = kea-dhcp4
+# convenience archive
 
-kea_dhcp4_SOURCES  = main.cc
-kea_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
-kea_dhcp4_SOURCES += json_config_parser.cc json_config_parser.h
-kea_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
-kea_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
+noinst_LTLIBRARIES = libdhcp4.la
+
+libdhcp4_la_SOURCES  =
+libdhcp4_la_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
+libdhcp4_la_SOURCES += json_config_parser.cc json_config_parser.h
+libdhcp4_la_SOURCES += dhcp4_log.cc dhcp4_log.h
+libdhcp4_la_SOURCES += dhcp4_srv.cc dhcp4_srv.h
 
 if CONFIG_BACKEND_BUNDY
-kea_dhcp4_SOURCES += bundy_controller.cc
+libdhcp4_la_SOURCES += bundy_controller.cc
 endif
 
 if CONFIG_BACKEND_JSON
-kea_dhcp4_SOURCES += kea_controller.cc
+libdhcp4_la_SOURCES += kea_controller.cc
 endif
 
-nodist_kea_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
+nodist_libdhcp4_la_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 EXTRA_DIST += dhcp4_messages.mes
 
-kea_dhcp4_LDADD  = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
+sbin_PROGRAMS = kea-dhcp4
+
+kea_dhcp4_SOURCES  = main.cc
+
+kea_dhcp4_LDADD  = libdhcp4.la
+kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
 kea_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la

+ 2 - 1
src/bin/dhcp4/dhcp4_srv.cc

@@ -33,6 +33,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/utils.h>
 #include <hooks/callout_handle.h>
@@ -1542,7 +1543,7 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
 
     Subnet4Ptr subnet;
 
-    CfgSubnets4::Selector selector;
+    SubnetSelector selector;
     selector.ciaddr_ = question->getCiaddr();
     selector.giaddr_ = question->getGiaddr();
     selector.local_address_ = question->getLocalAddr();

+ 3 - 10
src/bin/dhcp4/tests/Makefile.am

@@ -1,5 +1,3 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
 SHTESTS =
 # The test of dynamic reconfiguration based on signals will work only
 # if we are using file based configuration approach.
@@ -76,10 +74,7 @@ libco2_la_LDFLAGS = -avoid-version -export-dynamic -module -rpath /nowhere
 
 TESTS += dhcp4_unittests
 
-dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
-dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
-dhcp4_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
-dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
+dhcp4_unittests_SOURCES  = d2_unittest.h d2_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
@@ -97,21 +92,19 @@ dhcp4_unittests_SOURCES += dora_unittest.cc
 if CONFIG_BACKEND_BUNDY
 # For Bundy backend, we only need to run the usual tests. There are no
 # Bundy-specific tests yet.
-dhcp4_unittests_SOURCES += ../bundy_controller.cc
 dhcp4_unittests_SOURCES += bundy_controller_unittest.cc
 endif
 
 if CONFIG_BACKEND_JSON
-dhcp4_unittests_SOURCES += ../kea_controller.cc
 dhcp4_unittests_SOURCES += kea_controller_unittest.cc
 endif
 
-nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
-nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
+nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
 
 dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDADD = $(GTEST_LDADD)
+dhcp4_unittests_LDADD += $(top_builddir)/src/bin/dhcp4/libdhcp4.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la

+ 17 - 10
src/bin/dhcp6/Makefile.am

@@ -47,26 +47,33 @@ s-messages: dhcp6_messages.mes
 
 BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
 
-sbin_PROGRAMS = kea-dhcp6
+# convenience archive
 
-kea_dhcp6_SOURCES  = main.cc
-kea_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
-kea_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
-kea_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
-kea_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h
+noinst_LTLIBRARIES = libdhcp6.la
+
+libdhcp6_la_SOURCES  =
+libdhcp6_la_SOURCES += dhcp6_log.cc dhcp6_log.h
+libdhcp6_la_SOURCES += dhcp6_srv.cc dhcp6_srv.h
+libdhcp6_la_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
+libdhcp6_la_SOURCES += json_config_parser.cc json_config_parser.h
 
 if CONFIG_BACKEND_BUNDY
-kea_dhcp6_SOURCES += bundy_controller.cc
+libdhcp6_la_SOURCES += bundy_controller.cc
 endif
 
 if CONFIG_BACKEND_JSON
-kea_dhcp6_SOURCES += kea_controller.cc
+libdhcp6_la_SOURCES += kea_controller.cc
 endif
 
-nodist_kea_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
+nodist_libdhcp6_la_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 EXTRA_DIST += dhcp6_messages.mes
 
-kea_dhcp6_LDADD  = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
+sbin_PROGRAMS = kea-dhcp6
+
+kea_dhcp6_SOURCES  = main.cc
+
+kea_dhcp6_LDADD  = libdhcp6.la
+kea_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 kea_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
 kea_dhcp6_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
 kea_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la

+ 20 - 35
src/bin/dhcp6/dhcp6_srv.cc

@@ -38,6 +38,7 @@
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/utils.h>
 #include <exceptions/exceptions.h>
 #include <hooks/callout_handle.h>
@@ -868,42 +869,24 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
 
 Subnet6Ptr
 Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
+    // Initialize subnet selector with the values used to select the subnet.
+    SubnetSelector selector;
+    selector.iface_name_ = question->getIface();
+    selector.remote_address_ = question->getRemoteAddr();
+    selector.first_relay_linkaddr_ = IOAddress("::");
+    selector.client_classes_ = question->classes_;
+
+    // Initialize fields specific to relayed messages.
+    if (!question->relay_info_.empty()) {
+        selector.first_relay_linkaddr_ = question->relay_info_.back().linkaddr_;
+        selector.interface_id_ =
+            question->getAnyRelayOption(D6O_INTERFACE_ID,
+                                        Pkt6::RELAY_GET_FIRST);
+    }
 
-    Subnet6Ptr subnet;
-
-    if (question->relay_info_.empty()) {
-        // This is a direct (non-relayed) message
-
-        // Try to find a subnet if received packet from a directly connected client
-        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
-                                               question->classes_);
-        if (!subnet) {
-            // If no subnet was found, try to find it based on remote address
-            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
-                                                   question->classes_);
-        }
-    } else {
-
-        // This is a relayed message
-        OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
-                                                             Pkt6::RELAY_GET_FIRST);
-        if (interface_id) {
-            subnet = CfgMgr::instance().getSubnet6(interface_id,
-                                                   question->classes_);
-        }
-
-        if (!subnet) {
-            // If no interface-id was specified (or not configured on server),
-            // let's try address matching
-            IOAddress link_addr = question->relay_info_.back().linkaddr_;
+    Subnet6Ptr subnet = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->selectSubnet(selector);
 
-            // if relay filled in link_addr field, then let's use it
-            if (link_addr != IOAddress("::")) {
-                subnet = CfgMgr::instance().getSubnet6(link_addr,
-                                                       question->classes_, true);
-            }
-        }
-    }
 
     // Let's execute all callouts registered for subnet6_receive
     if (HooksManager::calloutsPresent(Hooks.hook_index_subnet6_select_)) {
@@ -919,7 +902,9 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         // We pass pointer to const collection for performance reasons.
         // Otherwise we would get a non-trivial performance penalty each
         // time subnet6_select is called.
-        callout_handle->setArgument("subnet6collection", CfgMgr::instance().getSubnets6());
+        callout_handle->setArgument("subnet6collection",
+                                    CfgMgr::instance().getCurrentCfg()->
+                                    getCfgSubnets6()->getAll());
 
         // Call user (and server-side) callouts
         HooksManager::callCallouts(Hooks.hook_index_subnet6_select_, *callout_handle);

+ 1 - 7
src/bin/dhcp6/json_config_parser.cc

@@ -344,7 +344,7 @@ public:
             // subnet id is invalid (duplicate). Thus, we catch exceptions
             // here to append a position in the configuration string.
             try {
-                isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
+                CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(sub6ptr);
             } catch (const std::exception& ex) {
                 isc_throw(DhcpConfigError, ex.what() << " ("
                           << subnet->getPosition() << ")");
@@ -534,12 +534,6 @@ public:
     ///
     /// @param subnets_list pointer to a list of IPv6 subnets
     void build(ConstElementPtr subnets_list) {
-        // @todo: Implement more subtle reconfiguration than toss
-        // the old one and replace with the new one.
-
-        // remove old subnets
-        isc::dhcp::CfgMgr::instance().deleteSubnets6();
-
         BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
             ParserPtr parser(new Subnet6ConfigParser("subnet"));
             parser->build(subnet);

+ 2 - 10
src/bin/dhcp6/tests/Makefile.am

@@ -1,5 +1,3 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
 SHTESTS =
 # The test of dynamic reconfiguration based on signals will work only
 # if we are using file based configuration approach.
@@ -80,15 +78,11 @@ dhcp6_unittests_SOURCES += hooks_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_test_utils.cc dhcp6_test_utils.h
 dhcp6_unittests_SOURCES += d2_unittest.cc d2_unittest.h
 dhcp6_unittests_SOURCES += marker_file.cc
-dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
-dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
-dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.h ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += wireshark.cc
 dhcp6_unittests_SOURCES += dhcp6_client.cc dhcp6_client.h
 dhcp6_unittests_SOURCES += rebind_unittest.cc
 dhcp6_unittests_SOURCES += sarr_unittest.cc
-dhcp6_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
 dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += confirm_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h
@@ -96,21 +90,19 @@ dhcp6_unittests_SOURCES += dhcp6_message_test.cc dhcp6_message_test.h
 if CONFIG_BACKEND_BUNDY
 # For Bundy backend, we only need to run the usual tests. There are no
 # Bundy-specific tests yet.
-dhcp6_unittests_SOURCES += ../bundy_controller.cc
 dhcp6_unittests_SOURCES += bundy_controller_unittest.cc
 endif
 
 if CONFIG_BACKEND_JSON
-dhcp6_unittests_SOURCES += ../kea_controller.cc
 dhcp6_unittests_SOURCES += kea_controller_unittest.cc
 endif
 
-nodist_dhcp6_unittests_SOURCES  = ../dhcp6_messages.h ../dhcp6_messages.cc
-nodist_dhcp6_unittests_SOURCES += marker_file.h test_libraries.h
+nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 
 dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
+dhcp6_unittests_LDADD += $(top_builddir)/src/bin/dhcp6/libdhcp6.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la

+ 77 - 55
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -26,6 +26,7 @@
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <hooks/hooks_manager.h>
 
@@ -247,13 +248,13 @@ public:
     getOptionFromSubnet(const IOAddress& subnet_address,
                         const uint16_t option_code,
                         const uint16_t expected_options_count = 1) {
-        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address,
-                                                          classify_);
+        Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+            selectSubnet(subnet_address, classify_);
         if (!subnet) {
             /// @todo replace toText() with the use of operator <<.
             ADD_FAILURE() << "A subnet for the specified address "
                           << subnet_address.toText()
-                          << "does not exist in Config Manager";
+                          << " does not exist in Config Manager";
         }
         OptionContainerPtr options =
             subnet->getCfgOption()->getAll("dhcp6");
@@ -469,6 +470,8 @@ public:
                            const uint16_t option_code,
                            const uint8_t* expected_data,
                            const size_t expected_data_len) {
+        CfgMgr::instance().clear();
+
         std::string config = createConfigWithOption(params);
         ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
 
@@ -557,8 +560,8 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-        classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -605,7 +608,10 @@ TEST_F(Dhcp6ParserTest, multipleSubnets) {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
         checkResult(x, 0);
 
-        const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+        CfgMgr::instance().commit();
+
+        const Subnet6Collection* subnets =
+            CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
         ASSERT_TRUE(subnets);
         ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
 
@@ -660,7 +666,10 @@ TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
         checkResult(x, 0);
 
-        const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+        CfgMgr::instance().commit();
+
+        const Subnet6Collection* subnets =
+            CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
         ASSERT_TRUE(subnets);
         ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
 
@@ -796,7 +805,10 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    CfgMgr::instance().commit();
+
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
 
@@ -805,7 +817,9 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    subnets = CfgMgr::instance().getSubnets6();
+    CfgMgr::instance().commit();
+
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
 
@@ -820,12 +834,16 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
+    CfgMgr::instance().commit();
+
     // Do reconfiguration
     json = Element::fromJSON(config_second_removed);
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    subnets = CfgMgr::instance().getSubnets6();
+    CfgMgr::instance().commit();
+
+    subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
 
@@ -863,8 +881,8 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     // returned value should be 0 (configuration success)
     checkResult(status, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
@@ -898,8 +916,8 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
     // returned value should be 0 (configuration success)
     checkResult(status, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(valid_iface_, subnet->getIface());
 }
@@ -931,8 +949,8 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
     checkResult(status, 1);
     EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     EXPECT_FALSE(subnet);
 }
 
@@ -993,16 +1011,20 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
 
     // Try to get a subnet based on bogus interface-id option
     OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
-    OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("5000::1");
+    selector.interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(selector);
     EXPECT_FALSE(subnet);
 
     // Now try to get subnet for valid interface-id value
     tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
-    ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
+    selector.interface_id_.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(selector);
     ASSERT_TRUE(subnet);
-    EXPECT_TRUE(ifaceid->equals(subnet->getInterfaceId()));
+    EXPECT_TRUE(selector.interface_id_->equals(subnet->getInterfaceId()));
 }
 
 
@@ -1084,7 +1106,8 @@ TEST_F(Dhcp6ParserTest, multiplePools) {
     ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
     checkResult(status, 0);
 
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(2, subnets->size()); // We expect 2 subnets
 
@@ -1163,8 +1186,8 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     // returned value must be 1 (configuration parse error)
     checkResult(x, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -1205,8 +1228,8 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     checkResult(x, 0);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
 
     // Fetch the collection of PD pools.  It should have 1 entry.
@@ -1277,8 +1300,8 @@ TEST_F(Dhcp6ParserTest, pdPoolList) {
     checkResult(x, 0);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
 
     // Fetch the collection of NA pools.  It should have 1 entry.
@@ -1333,8 +1356,8 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     checkResult(x, 0);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
 
     ASSERT_TRUE(subnet);
 
@@ -2027,8 +2050,8 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(2, options->size());
@@ -2122,8 +2145,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp6.
     OptionDescriptor desc1 = subnet->getCfgOption()->get("dhcp6", 38);
@@ -2278,8 +2301,8 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2341,8 +2364,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                       classify_);
+    Subnet6Ptr subnet1 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet1);
     OptionContainerPtr options1 = subnet1->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options1->size());
@@ -2367,8 +2390,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
                sizeof(subid_expected));
 
     // Test another subnet in the same way.
-    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"),
-                                                       classify_);
+    Subnet6Ptr subnet2 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:2::4"), classify_);
     ASSERT_TRUE(subnet2);
     OptionContainerPtr options2 = subnet2->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options2->size());
@@ -2402,8 +2425,6 @@ TEST_F(Dhcp6ParserTest, optionDataBoolean) {
     ASSERT_TRUE(executeConfiguration(config, "parse configuration with a"
                                      " boolean value"));
 
-    CfgMgr::instance().commit();
-
     // The subnet should now hold one option with the code 1000.
     OptionDescriptor desc =
         getOptionFromSubnet(IOAddress("2001:db8:1::5"), 1000);
@@ -2538,8 +2559,8 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options->size());
@@ -2581,8 +2602,8 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
     OptionContainerPtr options = subnet->getCfgOption()->getAll("dhcp6");
     ASSERT_EQ(1, options->size());
@@ -2659,8 +2680,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2721,8 +2742,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2864,8 +2885,8 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::5"), classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -3151,8 +3172,8 @@ TEST_F(Dhcp6ParserTest, subnetRelayInfo) {
     // returned value should be 0 (configuration success)
     checkResult(status, 0);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::1"),
-                                                      classify_);
+    Subnet6Ptr subnet = CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->
+        selectSubnet(IOAddress("2001:db8:1::1"), classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("2001:db8:1::abcd", subnet->getRelayInfo().addr_.toText());
 }
@@ -3191,7 +3212,8 @@ TEST_F(Dhcp6ParserTest, classifySubnets) {
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
 

+ 3 - 2
src/bin/dhcp6/tests/confirm_unittest.cc

@@ -233,7 +233,8 @@ TEST_F(ConfirmTest, relayedClientNoAddress) {
     // Configure the server.
     configure(CONFIRM_CONFIGS[1], *client.getServer());
     // Make sure we ended-up having expected number of subnets configured.
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_EQ(2, subnets->size());
     // Client to send relayed message.
     client.useRelay();
@@ -255,7 +256,7 @@ TEST_F(ConfirmTest, relayedClientNoSubnet) {
     ASSERT_NO_FATAL_FAILURE(requestLease(CONFIRM_CONFIGS[1], 2, client));
     // Now that the client has a lease, let's remove any subnets to check
     // how the server would respond to the Confirm.
-    ASSERT_NO_THROW(CfgMgr::instance().deleteSubnets6());
+    ASSERT_NO_THROW(CfgMgr::instance().clear());
     // Send Confirm message to the server.
     ASSERT_NO_THROW(client.doConfirm());
     // Client should have received a status code option and this option should

+ 4 - 3
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -182,7 +182,7 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
     ElementPtr config = Element::fromJSON(config_txt);
 
     // Make sure there are no subnets configured.
-    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().clear();
 
     // Now send the command
     int rcode = -1;
@@ -192,11 +192,12 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
     EXPECT_EQ(0, rcode); // Expect success
 
     // Check that the config was indeed applied.
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
     EXPECT_EQ(3, subnets->size());
 
     // Clean up after the test.
-    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().clear();
 }
 
 } // End of anonymous namespace

+ 1 - 0
src/bin/dhcp6/tests/d2_unittest.cc

@@ -128,6 +128,7 @@ Dhcp6SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
 
 void
 Dhcp6SrvD2Test::configure(const std::string& config, bool exp_result) {
+    CfgMgr::instance().clear();
     ElementPtr json = Element::fromJSON(config);
     ConstElementPtr status;
 

+ 4 - 3
src/bin/dhcp6/tests/dhcp6_message_test.cc

@@ -53,7 +53,8 @@ Dhcpv6MessageTest::requestLease(const std::string& config,
     // Configure the server.
     configure(config, *client.getServer());
     // Make sure we ended-up having expected number of subnets configured.
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_EQ(subnets_num, subnets->size());
     // Do the actual 4-way exchange.
     ASSERT_NO_THROW(client.doSARR());
@@ -63,8 +64,8 @@ Dhcpv6MessageTest::requestLease(const std::string& config,
     // subnets.
     ASSERT_EQ(1, client.getLeaseNum());
     Lease6 lease_client = client.getLease(0);
-    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client.addr_,
-                                              ClientClasses()));
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client.addr_, ClientClasses()));
     // Check that the client's lease matches the information on the server
     // side.
     Lease6Ptr lease_server = checkLease(lease_client);

+ 65 - 48
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1182,8 +1182,9 @@ TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
 
     // CASE 1: We have only one subnet defined and we received local traffic.
     // The only available subnet used to be picked, but not anymore
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+    CfgMgr::instance().commit();
 
     Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     pkt->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -1196,38 +1197,42 @@ TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
     // We should NOT select it.
 
     // Identical steps as in case 1, but repeated for clarity
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+    CfgMgr::instance().commit();
     pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
     Subnet6Ptr selected = srv.selectSubnet(pkt);
     EXPECT_FALSE(selected);
 
     // CASE 3: We have three subnets defined and we received local traffic.
     // Nothing should be selected.
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
     pkt->setRemoteAddr(IOAddress("fe80::abcd"));
     selected = srv.selectSubnet(pkt);
     EXPECT_FALSE(selected);
 
     // CASE 4: We have three subnets defined and we received relayed traffic
     // that came out of subnet 2. We should select subnet2 then
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
     pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
     selected = srv.selectSubnet(pkt);
     EXPECT_EQ(selected, subnet2);
 
     // CASE 5: We have three subnets defined and we received relayed traffic
     // that came out of undefined subnet. We should select nothing
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
     pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
     EXPECT_FALSE(srv.selectSubnet(pkt));
 }
@@ -1246,8 +1251,9 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
 
     // CASE 1: We have only one subnet defined and it is available via eth0.
     // Packet came from eth0. The only available subnet should be selected
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+    CfgMgr::instance().commit();
 
     Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     pkt->setIface("eth0");
@@ -1257,8 +1263,9 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
 
     // CASE 2: We have only one subnet defined and it is available via eth0.
     // Packet came from eth1. We should not select it
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+    CfgMgr::instance().commit();
 
     pkt->setIface("eth1");
 
@@ -1268,10 +1275,11 @@ TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
     // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
     // one over wifi1.
     // Packet came from eth1. We should not select it
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
 
     pkt->setIface("eth0");
     EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
@@ -1298,8 +1306,9 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
 
     // CASE 1: We have only one subnet defined and we received relayed traffic.
     // The only available subnet should NOT be selected.
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+    CfgMgr::instance().commit();
 
     Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     pkt->relay_info_.push_back(relay);
@@ -1309,19 +1318,21 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
 
     // CASE 2: We have three subnets defined and we received relayed traffic.
     // Nothing should be selected.
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
     selected = srv.selectSubnet(pkt);
     EXPECT_EQ(selected, subnet2);
 
     // CASE 3: We have three subnets defined and we received relayed traffic
     // that came out of subnet 2. We should select subnet2 then
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
 
     // Source of the packet should have no meaning. Selection is based
     // on linkaddr field in the relay
@@ -1331,10 +1342,11 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayLinkaddr) {
 
     // CASE 4: We have three subnets defined and we received relayed traffic
     // that came out of undefined subnet. We should select nothing
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
     pkt->relay_info_.clear();
     relay.linkaddr_ = IOAddress("2001:db8:4::1234");
     pkt->relay_info_.push_back(relay);
@@ -1357,8 +1369,9 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
 
     // CASE 1: We have only one subnet defined and it is for interface-id "relay1"
     // Packet came with interface-id "relay2". We should not select subnet1
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1); // just a single subnet
+    CfgMgr::instance().commit();
 
     Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     Pkt6::RelayInfo relay;
@@ -1374,18 +1387,20 @@ TEST_F(Dhcpv6SrvTest, selectSubnetRelayInterfaceId) {
 
     // CASE 2: We have only one subnet defined and it is for interface-id "relay2"
     // Packet came with interface-id "relay2". We should select it
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet2); // just a single subnet
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2); // just a single subnet
+    CfgMgr::instance().commit();
     selected = srv.selectSubnet(pkt);
     EXPECT_EQ(selected, subnet2);
 
     // CASE 3: We have only 3 subnets defined: one remote for interface-id "relay1",
     // one remote for interface-id "relay2" and third local
     // packet comes with interface-id "relay2". We should select subnet2
-    CfgMgr::instance().deleteSubnets6();
-    CfgMgr::instance().addSubnet6(subnet1);
-    CfgMgr::instance().addSubnet6(subnet2);
-    CfgMgr::instance().addSubnet6(subnet3);
+    CfgMgr::instance().clear();
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet1);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet2);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet3);
+    CfgMgr::instance().commit();
 
     EXPECT_EQ(subnet2, srv.selectSubnet(pkt));
 }
@@ -1938,7 +1953,8 @@ TEST_F(Dhcpv6SrvTest, relayOverride) {
     ASSERT_NO_THROW(configure(config));
 
     // Let's get the subnet configuration objects
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_EQ(2, subnets->size());
 
     // Let's get them for easy reference
@@ -2014,7 +2030,8 @@ TEST_F(Dhcpv6SrvTest, relayOverrideAndClientClass) {
     ASSERT_NO_THROW(configure(config));
 
     // Let's get the subnet configuration objects
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_EQ(2, subnets->size());
 
     // Let's get them for easy reference

+ 5 - 2
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -36,8 +36,9 @@ Dhcpv6SrvTest::Dhcpv6SrvTest()
                               64));
     subnet_->addPool(pool_);
 
-    isc::dhcp::CfgMgr::instance().deleteSubnets6();
-    isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+    isc::dhcp::CfgMgr::instance().clear();
+    isc::dhcp::CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
+    isc::dhcp::CfgMgr::instance().commit();
 
     // configure PD pool
     pd_pool_ = isc::dhcp::Pool6Ptr
@@ -604,6 +605,8 @@ Dhcpv6SrvTest::configure(const std::string& config, NakedDhcpv6Srv& srv) {
     int rcode;
     ConstElementPtr comment = config::parseAnswer(rcode, status);
     ASSERT_EQ(0, rcode);
+
+    CfgMgr::instance().commit();
 }
 
 // Generate IA_NA option with specified parameters

+ 1 - 1
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -348,7 +348,7 @@ public:
     ///
     /// Removes existing configuration.
     ~Dhcpv6SrvTest() {
-        isc::dhcp::CfgMgr::instance().deleteSubnets6();
+        isc::dhcp::CfgMgr::instance().clear();
     };
 
     /// @brief Runs DHCPv6 configuration from the JSON string.

+ 3 - 2
src/bin/dhcp6/tests/fqdn_unittest.cc

@@ -974,13 +974,14 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
     // We are going to configure a subnet with a pool that consists of
     // exactly one address. This address will be handed out to the
     // client, will get expired and then be reused.
-    CfgMgr::instance().deleteSubnets6();
+    CfgMgr::instance().clear();
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1:1::"), 56, 1, 2,
                                      3, 4));
     subnet_->setIface("eth0");
     pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr));
     subnet_->addPool(pool_);
-    CfgMgr::instance().addSubnet6(subnet_);
+    CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
+    CfgMgr::instance().commit();
 
     // Allocate a lease.
     testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",

+ 8 - 2
src/bin/dhcp6/tests/hooks_unittest.cc

@@ -922,6 +922,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet6_select) {
     comment_ = parseAnswer(rcode_, status);
     ASSERT_EQ(0, rcode_);
 
+    CfgMgr::instance().commit();
+
     // Prepare solicit packet. Server should select first subnet for it
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -942,7 +944,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet6_select) {
     // Check that pkt6 argument passing was successful and returned proper value
     EXPECT_TRUE(callback_pkt6_.get() == sol.get());
 
-    const Subnet6Collection* exp_subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* exp_subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
 
     // The server is supposed to pick the first subnet, because of matching
     // interface. Check that the value is reported properly.
@@ -990,6 +993,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet_select_change) {
     comment_ = parseAnswer(rcode_, status);
     ASSERT_EQ(0, rcode_);
 
+    CfgMgr::instance().commit();
+
     // Prepare solicit packet. Server should select first subnet for it
     Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
     sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -1016,7 +1021,8 @@ TEST_F(HooksDhcpv6SrvTest, subnet_select_change) {
     ASSERT_TRUE(addr_opt);
 
     // Get all subnets and use second subnet for verification
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_EQ(2, subnets->size());
 
     // Advertised address must belong to the second pool (in subnet's range,

+ 4 - 2
src/bin/dhcp6/tests/kea_controller_unittest.cc

@@ -106,7 +106,8 @@ TEST_F(JSONFileBackendTest, jsonFile) {
     EXPECT_NO_THROW(srv->init(TEST_FILE));
 
     // Now check if the configuration has been applied correctly.
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
 
@@ -176,7 +177,8 @@ TEST_F(JSONFileBackendTest, comments) {
     EXPECT_NO_THROW(srv->init(TEST_FILE));
 
     // Now check if the configuration has been applied correctly.
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
     ASSERT_TRUE(subnets);
     ASSERT_EQ(1, subnets->size());
 

+ 8 - 8
src/bin/dhcp6/tests/rebind_unittest.cc

@@ -223,8 +223,8 @@ TEST_F(RebindTest, directClient) {
     // subnets.
     ASSERT_EQ(1, client.getLeaseNum());
     Lease6 lease_client2 = client.getLease(0);
-    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
-                                              ClientClasses()));
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client2.addr_, ClientClasses()));
     // The client's lease should have been extended. The client will
     // update the cltt to current time when the lease gets extended.
     ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
@@ -337,8 +337,8 @@ TEST_F(RebindTest, relayedClient) {
     // subnets.
     ASSERT_EQ(1, client.getLeaseNum());
     Lease6 lease_client2 = client.getLease(0);
-    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
-                                              ClientClasses()));
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client2.addr_, ClientClasses()));
     // The client's lease should have been extended. The client will
     // update the cltt to current time when the lease gets extended.
     ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
@@ -498,8 +498,8 @@ TEST_F(RebindTest, directClientPD) {
     // subnets.
     ASSERT_EQ(1, client.getLeaseNum());
     Lease6 lease_client2 = client.getLease(0);
-    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
-                                              ClientClasses()));
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client2.addr_, ClientClasses()));
     // The client's lease should have been extended. The client will
     // update the cltt to current time when the lease gets extended.
     ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);
@@ -675,8 +675,8 @@ TEST_F(RebindTest, relayedUnicast) {
     // subnets.
     ASSERT_EQ(1, client.getLeaseNum());
     Lease6 lease_client2 = client.getLease(0);
-    ASSERT_TRUE(CfgMgr::instance().getSubnet6(lease_client2.addr_,
-                                              ClientClasses()));
+    ASSERT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->
+                selectSubnet(lease_client2.addr_, ClientClasses()));
     // The client's lease should have been extended. The client will
     // update the cltt to current time when the lease gets extended.
     ASSERT_GE(lease_client2.cltt_ - lease_client.cltt_, 1000);

+ 2 - 1
src/bin/dhcp6/tests/sarr_unittest.cc

@@ -76,7 +76,8 @@ TEST_F(SARRTest, directClientPrefixHint) {
     client.usePD();
     configure(CONFIGS[0], *client.getServer());
     // Make sure we ended-up having expected number of subnets configured.
-    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    const Subnet6Collection* subnets = CfgMgr::instance().getCurrentCfg()->
+        getCfgSubnets6()->getAll();
     ASSERT_EQ(1, subnets->size());
     // Append IAPREFIX option to the client's message.
     ASSERT_NO_THROW(client.useHint(100, 200, 64, "2001:db8:3:33::33"));

+ 5 - 0
src/bin/keactrl/kea.conf.pre

@@ -36,6 +36,11 @@
 # Add names of interfaces to listen on.
   "interfaces": [ ],
 
+# Use Memfile lease database backend to store leases in a CSV file.
+  "lease-database": {
+    "type": "memfile"
+  },
+
 # Addresses will be assigned with preferred and valid lifetimes
 # being 3000 and 4000, respectively. Client is told to start
 # renewing after 1000 seconds. If the server does not repond

+ 22 - 15
src/bin/perfdhcp/Makefile.am

@@ -17,27 +17,34 @@ if USE_STATIC_LINK
 AM_LDFLAGS += -static
 endif
 
-sbin_PROGRAMS = perfdhcp
-perfdhcp_SOURCES = main.cc
-perfdhcp_SOURCES += command_options.cc command_options.h
-perfdhcp_SOURCES += localized_option.h
-perfdhcp_SOURCES += perf_pkt6.cc perf_pkt6.h
-perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
-perfdhcp_SOURCES += packet_storage.h
-perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
-perfdhcp_SOURCES += rate_control.cc rate_control.h
-perfdhcp_SOURCES += stats_mgr.h
-perfdhcp_SOURCES += test_control.cc test_control.h
-libkea_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+# convenience archive
+
+noinst_LTLIBRARIES = libperfdhcp.la
+
+libperfdhcp_la_SOURCES  =
+libperfdhcp_la_SOURCES += command_options.cc command_options.h
+libperfdhcp_la_SOURCES += localized_option.h
+libperfdhcp_la_SOURCES += perf_pkt6.cc perf_pkt6.h
+libperfdhcp_la_SOURCES += perf_pkt4.cc perf_pkt4.h
+libperfdhcp_la_SOURCES += packet_storage.h
+libperfdhcp_la_SOURCES += pkt_transform.cc pkt_transform.h
+libperfdhcp_la_SOURCES += rate_control.cc rate_control.h
+libperfdhcp_la_SOURCES += stats_mgr.h
+libperfdhcp_la_SOURCES += test_control.cc test_control.h
 
-perfdhcp_CXXFLAGS = $(AM_CXXFLAGS)
+libperfdhcp_la_CXXFLAGS = $(AM_CXXFLAGS)
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Boost headers when compiling with clang.
-perfdhcp_CXXFLAGS += -Wno-unused-parameter
+libperfdhcp_la_CXXFLAGS += -Wno-unused-parameter
 endif
 
-perfdhcp_LDADD = $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
+sbin_PROGRAMS = perfdhcp
+perfdhcp_SOURCES = main.cc
+libkea_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+
+perfdhcp_LDADD = libperfdhcp.la
+perfdhcp_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 perfdhcp_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 perfdhcp_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 

+ 2 - 9
src/bin/perfdhcp/tests/Makefile.am

@@ -1,5 +1,3 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
 SUBDIRS = . testdata
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
@@ -33,12 +31,6 @@ run_unittests_SOURCES += rate_control_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += command_options_helper.h
-run_unittests_SOURCES += ../command_options.cc
-run_unittests_SOURCES += ../pkt_transform.cc
-run_unittests_SOURCES += ../perf_pkt6.cc
-run_unittests_SOURCES += ../perf_pkt4.cc
-run_unittests_SOURCES += ../rate_control.cc
-run_unittests_SOURCES += ../test_control.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
@@ -49,7 +41,8 @@ if USE_CLANGPP
 run_unittests_CXXFLAGS = -Wno-unused-parameter
 endif
 
-run_unittests_LDADD  = $(top_builddir)/src/lib/util/libkea-util.la
+run_unittests_LDADD  = $(top_builddir)/src/bin/perfdhcp/libperfdhcp.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
 run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la

+ 9 - 2
src/bin/sockcreator/Makefile.am

@@ -29,8 +29,15 @@ $(man_MANS):
 
 endif
 
+# convenience archive
+
+noinst_LTLIBRARIES = libsockcreator.la
+
+libsockcreator_la_SOURCES = sockcreator.cc sockcreator.h
+
 pkglibexec_PROGRAMS = kea-sockcreator
 
-kea_sockcreator_SOURCES = sockcreator.cc sockcreator.h main.cc
-kea_sockcreator_LDADD  = $(top_builddir)/src/lib/util/io/libkea-util-io.la
+kea_sockcreator_SOURCES = main.cc
+kea_sockcreator_LDADD  = libsockcreator.la
+kea_sockcreator_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
 kea_sockcreator_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la

+ 2 - 2
src/bin/sockcreator/tests/Makefile.am

@@ -14,13 +14,13 @@ TESTS_ENVIRONMENT = \
 TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
-run_unittests_SOURCES = ../sockcreator.cc ../sockcreator.h
-run_unittests_SOURCES += sockcreator_tests.cc
+run_unittests_SOURCES = sockcreator_tests.cc
 run_unittests_SOURCES += run_unittests.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 run_unittests_LDADD  = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/bin/sockcreator/libsockcreator.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
 endif

+ 29 - 24
src/hooks/dhcp/user_chk/Makefile.am

@@ -31,41 +31,46 @@ EXTRA_DIST = libdhcp_user_chk.dox
 #CLEANFILES = *.gcno *.gcda user_chk_messages.h user_chk_messages.cc s-messages
 CLEANFILES = *.gcno *.gcda
 
-noinst_LTLIBRARIES = libdhcp_user_chk.la
-libdhcp_user_chk_la_SOURCES  =
-libdhcp_user_chk_la_SOURCES += load_unload.cc
-libdhcp_user_chk_la_SOURCES += pkt_receive_co.cc
-libdhcp_user_chk_la_SOURCES += pkt_send_co.cc
-libdhcp_user_chk_la_SOURCES += subnet_select_co.cc
-libdhcp_user_chk_la_SOURCES += user.cc user.h
-libdhcp_user_chk_la_SOURCES += user_chk.h
+# convenience archive
+
+noinst_LTLIBRARIES = libduc.la
+
+libduc_la_SOURCES  =
+libduc_la_SOURCES += load_unload.cc
+libduc_la_SOURCES += pkt_receive_co.cc
+libduc_la_SOURCES += pkt_send_co.cc
+libduc_la_SOURCES += subnet_select_co.cc
+libduc_la_SOURCES += user.cc user.h
+libduc_la_SOURCES += user_chk.h
 # Until logging in dynamically loaded libraries is fixed, exclude these.
-#libdhcp_user_chk_la_SOURCES += user_chk_log.cc user_chk_log.h
-libdhcp_user_chk_la_SOURCES += user_data_source.h
-libdhcp_user_chk_la_SOURCES += user_file.cc user_file.h
-libdhcp_user_chk_la_SOURCES += user_registry.cc user_registry.h
-libdhcp_user_chk_la_SOURCES += version.cc
+#libduc_la_SOURCES += user_chk_log.cc user_chk_log.h
+libduc_la_SOURCES += user_data_source.h
+libduc_la_SOURCES += user_file.cc user_file.h
+libduc_la_SOURCES += user_registry.cc user_registry.h
+libduc_la_SOURCES += version.cc
 
 # Until logging in dynamically loaded libraries is fixed, exclude these.
-#nodist_libdhcp_user_chk_la_SOURCES = user_chk_messages.cc user_chk_messages.h
+#nodist_libduc_la_SOURCES = user_chk_messages.cc user_chk_messages.h
+
+libduc_la_CXXFLAGS = $(AM_CXXFLAGS)
+libduc_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some of the
+# Boost headers when compiling with clang.
+libduc_la_CXXFLAGS += -Wno-unused-parameter
+endif
 
-libdhcp_user_chk_la_CXXFLAGS = $(AM_CXXFLAGS)
-libdhcp_user_chk_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+noinst_LTLIBRARIES += libdhcp_user_chk.la
+
+libdhcp_user_chk_la_SOURCES  =
 libdhcp_user_chk_la_LDFLAGS  = $(AM_LDFLAGS)
 libdhcp_user_chk_la_LDFLAGS  += -avoid-version -export-dynamic -module
 # -rpath /nowhere is a hack to trigger libtool to not create a
 # convenience archive, resulting in shared modules
 libdhcp_user_chk_la_LDFLAGS  += -rpath /nowhere
-libdhcp_user_chk_la_LIBADD  =
+libdhcp_user_chk_la_LIBADD  = libduc.la
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/log/libkea-log.la
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/util/libkea-util.la
 libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/util/threads/libkea-threads.la
-
-
-if USE_CLANGPP
-# Disable unused parameter warning caused by some of the
-# Boost headers when compiling with clang.
-libdhcp_user_chk_la_CXXFLAGS += -Wno-unused-parameter
-endif

+ 2 - 16
src/hooks/dhcp/user_chk/tests/Makefile.am

@@ -1,5 +1,3 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
 SUBDIRS = .
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
@@ -35,19 +33,6 @@ if HAVE_GTEST
 TESTS += libdhcp_user_chk_unittests
 
 libdhcp_user_chk_unittests_SOURCES  = 
-libdhcp_user_chk_unittests_SOURCES += ../load_unload.cc
-libdhcp_user_chk_unittests_SOURCES += ../pkt_receive_co.cc
-libdhcp_user_chk_unittests_SOURCES += ../pkt_send_co.cc
-libdhcp_user_chk_unittests_SOURCES += ../subnet_select_co.cc
-libdhcp_user_chk_unittests_SOURCES += ../version.cc
-libdhcp_user_chk_unittests_SOURCES += ../user.cc ../user.h
-libdhcp_user_chk_unittests_SOURCES += ../user_chk.h
-# Until logging in dynamically loaded libraries is fixed, exclude these.
-#libdhcp_user_chk_unittests_SOURCES += ../user_chk_log.cc ../user_chk_log.h
-#libdhcp_user_chk_unittests_SOURCES += ../user_chk_messages.cc ../user_chk_messages.h
-libdhcp_user_chk_unittests_SOURCES += ../user_data_source.h
-libdhcp_user_chk_unittests_SOURCES += ../user_file.cc ../user_file.h
-libdhcp_user_chk_unittests_SOURCES += ../user_registry.cc ../user_registry.h
 libdhcp_user_chk_unittests_SOURCES += run_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += userid_unittests.cc
 libdhcp_user_chk_unittests_SOURCES += user_unittests.cc
@@ -66,7 +51,8 @@ if USE_CLANGPP
 libdhcp_user_chk_unittests_CXXFLAGS += -Wno-unused-parameter
 endif
 
-libdhcp_user_chk_unittests_LDADD = $(top_builddir)/src/lib/log/libkea-log.la
+libdhcp_user_chk_unittests_LDADD = $(top_builddir)/src/hooks/dhcp/user_chk/libduc.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la

+ 0 - 5
src/lib/asiodns/tests/Makefile.am

@@ -1,10 +1,7 @@
-AUTOMAKE_OPTIONS = subdir-objects
-
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
 AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc  -I$(top_builddir)/src/lib/util
-AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(srcdir)/testdata\"
 
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
@@ -21,8 +18,6 @@ TESTS =
 if HAVE_GTEST
 TESTS += run_unittests
 run_unittests_SOURCES  = run_unittests.cc
-run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
-run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += dns_service_unittest.cc
 run_unittests_SOURCES += dns_server_unittest.cc
 run_unittests_SOURCES += io_fetch_unittest.cc

+ 1 - 3
src/lib/asiodns/tests/run_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009, 2014  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
@@ -16,14 +16,12 @@
 #include <util/unittests/run_all.h>
 
 #include <log/logger_support.h>
-#include <dns/tests/unittest_util.h>
 
 int
 main(int argc, char* argv[])
 {
     ::testing::InitGoogleTest(&argc, argv);         // Initialize Google test
     isc::log::initLogger();                         // Initialize logging
-    isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);  // Add location of test data
 
     return (isc::util::unittests::run_all());
 }

+ 3 - 1
src/lib/cryptolink/botan_common.h

@@ -14,13 +14,15 @@
 
 namespace isc {
 namespace cryptolink {
+namespace btn {
 
 /// @brief Decode the HashAlgorithm enum into a name usable by Botan
 ///
 /// @param algorithm algorithm to be converted
 /// @return static text representation of the algorithm name
 const char*
-getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm);
+getHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm);
 
+} // namespace btn
 } // namespace cryptolink
 } // namespace isc

+ 2 - 3
src/lib/cryptolink/botan_hash.cc

@@ -34,7 +34,7 @@ namespace cryptolink {
 /// @param algorithm algorithm to be converted
 /// @return text representation of the algorithm name
 const char*
-getBotanHashAlgorithmName(isc::cryptolink::HashAlgorithm algorithm) {
+btn::getHashAlgorithmName(HashAlgorithm algorithm) {
     switch (algorithm) {
     case isc::cryptolink::MD5:
         return ("MD5");
@@ -67,8 +67,7 @@ public:
     explicit HashImpl(const HashAlgorithm hash_algorithm) {
         Botan::HashFunction* hash;
         try {
-            hash = Botan::get_hash(
-                getBotanHashAlgorithmName(hash_algorithm));
+            hash = Botan::get_hash(btn::getHashAlgorithmName(hash_algorithm));
         } catch (const Botan::Algorithm_Not_Found&) {
             isc_throw(isc::cryptolink::UnsupportedAlgorithm,
                       "Unknown hash algorithm: " <<

+ 1 - 2
src/lib/cryptolink/botan_hmac.cc

@@ -45,8 +45,7 @@ public:
                       const HashAlgorithm hash_algorithm) {
         Botan::HashFunction* hash;
         try {
-            hash = Botan::get_hash(
-                getBotanHashAlgorithmName(hash_algorithm));
+            hash = Botan::get_hash(btn::getHashAlgorithmName(hash_algorithm));
         } catch (const Botan::Algorithm_Not_Found&) {
             isc_throw(isc::cryptolink::UnsupportedAlgorithm,
                       "Unknown hash algorithm: " <<

+ 4 - 1
src/lib/cryptolink/crypto_hmac.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011, 2014  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
@@ -125,6 +125,9 @@ public:
     ///            and smaller than the output length of the algorithm,
     ///            only len bytes will be checked
     /// \return true if the signature is correct, false otherwise
+    ///
+    /// \note verify() does not destroy its context so it can be
+    /// called multiple times with different signatures.
     bool verify(const void* sig, size_t len);
 
 private:

+ 86 - 1
src/lib/cryptolink/openssl_common.h

@@ -14,6 +14,7 @@
 
 namespace isc {
 namespace cryptolink {
+namespace ossl {
 
 /// @brief Decode the HashAlgorithm enum into an EVP_MD pointer (or 0)
 ///
@@ -21,7 +22,91 @@ namespace cryptolink {
 /// @param algorithm algorithm to be converted
 /// @return pointer to a static EVP_MD which identifies the algorithm
 const EVP_MD*
-getOpenSSLHashAlgorithm(isc::cryptolink::HashAlgorithm algorithm);
+getHashAlgorithm(isc::cryptolink::HashAlgorithm algorithm);
 
+/// Secure Buffers which are wiped out when released.
+/// Subset of the std::vector interface but not derived from
+/// to avoid unwanted inheritance.
+template<typename T>
+class SecBuf {
+public:
+    typedef typename std::vector<T>::iterator iterator;
+
+    typedef typename std::vector<T>::const_iterator const_iterator;
+
+    explicit SecBuf() : vec_(std::vector<T>()) {}
+
+    explicit SecBuf(size_t n, const T& value = T()) :
+        vec_(std::vector<T>(n, value))
+    {}
+
+    SecBuf(iterator first, iterator last) :
+        vec_(std::vector<T>(first, last))
+    {}
+
+    SecBuf(const_iterator first, const_iterator last) :
+        vec_(std::vector<T>(first, last))
+    {}
+
+    SecBuf(const std::vector<T>& x) : vec_(x) {}
+
+    ~SecBuf() {
+        std::memset(&vec_[0], 0, vec_.capacity() * sizeof(T));
+    };
+
+    iterator begin() {
+        return (vec_.begin());
+    };
+
+    const_iterator begin() const {
+        return (vec_.begin());
+    };
+
+    iterator end() {
+        return (vec_.end());
+    };
+
+    const_iterator end() const {
+        return (vec_.end());
+    };
+
+    size_t size() const {
+        return (vec_.size());
+    };
+
+    void resize(size_t sz) {
+        vec_.resize(sz);
+    };
+
+    SecBuf& operator=(const SecBuf& x) {
+        if (&x != *this) {
+            vec_ = x.vec_;
+        }
+        return (*this);
+    };
+
+    T& operator[](size_t n) {
+        return (vec_[n]);
+    };
+
+    const T& operator[](size_t n) const {
+        return (vec_[n]);
+    };
+
+    // constant time comparison against timing attacks
+    // (same type than XXX::verify() so const void* (vs. const T*) x)
+    bool same(const void* x, size_t len) const {
+        bool ret = true;
+        const T* p = static_cast<const T*>(x);
+        for (size_t i = 0; i < len; ++i)
+            ret = ret && (vec_[i] == p[i]);
+        return ret;
+    };
+
+private:
+    std::vector<T> vec_;
+};
+
+} // namespace ossl
 } // namespace cryptolink
 } // namespace isc

+ 2 - 2
src/lib/cryptolink/openssl_hash.cc

@@ -32,7 +32,7 @@ namespace cryptolink {
 /// @param algorithm algorithm to be converted
 /// @return pointer to EVP_MD which identifies the algorithm
 const EVP_MD*
-getOpenSSLHashAlgorithm(isc::cryptolink::HashAlgorithm algorithm) {
+ossl::getHashAlgorithm(HashAlgorithm algorithm) {
     switch (algorithm) {
     case isc::cryptolink::MD5:
         return (EVP_md5());
@@ -63,7 +63,7 @@ public:
     ///
     /// @param hash_algorithm The hash algorithm
     explicit HashImpl(const HashAlgorithm hash_algorithm) {
-        const EVP_MD* algo = getOpenSSLHashAlgorithm(hash_algorithm);
+        const EVP_MD* algo = ossl::getHashAlgorithm(hash_algorithm);
         if (algo == 0) {
             isc_throw(isc::cryptolink::UnsupportedAlgorithm,
                       "Unknown hash algorithm: " <<

+ 6 - 81
src/lib/cryptolink/openssl_hmac.cc

@@ -23,81 +23,6 @@
 
 #include <cstring>
 
-namespace {
-
-/// Secure Buffers which are wiped out when released.
-template<typename T>
-struct SecBuf {
-public:
-    typedef typename std::vector<T>::iterator iterator;
-
-    typedef typename std::vector<T>::const_iterator const_iterator;
-
-    explicit SecBuf() : vec_(std::vector<T>()) {}
-
-    explicit SecBuf(size_t n, const T& value = T()) :
-        vec_(std::vector<T>(n, value))
-    {}
-
-    SecBuf(iterator first, iterator last) :
-        vec_(std::vector<T>(first, last))
-    {}
-
-    SecBuf(const_iterator first, const_iterator last) :
-        vec_(std::vector<T>(first, last))
-    {}
-
-    SecBuf(const std::vector<T>& x) : vec_(x) {}
-
-    ~SecBuf() {
-        std::memset(&vec_[0], 0, vec_.capacity() * sizeof(T));
-    };
-
-    iterator begin() {
-        return (vec_.begin());
-    };
-
-    const_iterator begin() const {
-        return (vec_.begin());
-    };
-
-    iterator end() {
-        return (vec_.end());
-    };
-
-    const_iterator end() const {
-        return (vec_.end());
-    };
-
-    size_t size() const {
-        return (vec_.size());
-    };
-
-    void resize(size_t sz) {
-        vec_.resize(sz);
-    };
-
-    SecBuf& operator=(const SecBuf& x) {
-        if (&x != *this) {
-            vec_ = x.vec_;
-        }
-        return (*this);
-    };
-
-    T& operator[](size_t n) {
-        return (vec_[n]);
-    };
-
-    const T& operator[](size_t n) const {
-        return (vec_[n]);
-    };
-
-private:
-    std::vector<T> vec_;
-};
-
-} // local namespace
-
 namespace isc {
 namespace cryptolink {
 
@@ -114,7 +39,7 @@ public:
     /// @param hash_algorithm The hash algorithm
     explicit HMACImpl(const void* secret, size_t secret_len,
                       const HashAlgorithm hash_algorithm) {
-        const EVP_MD* algo = getOpenSSLHashAlgorithm(hash_algorithm);
+        const EVP_MD* algo = ossl::getHashAlgorithm(hash_algorithm);
         if (algo == 0) {
             isc_throw(UnsupportedAlgorithm,
                       "Unknown hash algorithm: " <<
@@ -162,7 +87,7 @@ public:
     /// See @ref isc::cryptolink::HMAC::sign() for details.
     void sign(isc::util::OutputBuffer& result, size_t len) {
         size_t size = getOutputLength();
-        SecBuf<unsigned char> digest(size);
+        ossl::SecBuf<unsigned char> digest(size);
         HMAC_Final(md_.get(), &digest[0], NULL);
         if (len == 0 || len > size) {
             len = size;
@@ -175,7 +100,7 @@ public:
     /// See @ref isc::cryptolink::HMAC::sign() for details.
     void sign(void* result, size_t len) {
         size_t size = getOutputLength();
-        SecBuf<unsigned char> digest(size);
+        ossl::SecBuf<unsigned char> digest(size);
         HMAC_Final(md_.get(), &digest[0], NULL);
         if (len > size) {
             len = size;
@@ -188,7 +113,7 @@ public:
     /// See @ref isc::cryptolink::HMAC::sign() for details.
     std::vector<uint8_t> sign(size_t len) {
         size_t size = getOutputLength();
-        SecBuf<unsigned char> digest(size);
+        ossl::SecBuf<unsigned char> digest(size);
         HMAC_Final(md_.get(), &digest[0], NULL);
         if (len != 0 && len < size) {
             digest.resize(len);
@@ -204,12 +129,12 @@ public:
         if (len != 0 && (len < 10 || len < size / 2)) {
             return (false);
         }
-        SecBuf<unsigned char> digest(size);
+        ossl::SecBuf<unsigned char> digest(size);
         HMAC_Final(md_.get(), &digest[0], NULL);
         if (len == 0 || len > size) {
             len = size;
         }
-        return (std::memcmp(&digest[0], sig, len) == 0);
+        return (digest.same(sig, len));
     }
 
 private:

+ 35 - 0
src/lib/dhcp/libdhcp++.cc

@@ -130,6 +130,41 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
 }
 
 OptionDefinitionPtr
+LibDHCP::getOptionDef(const Option::Universe u, const std::string& name) {
+    const OptionDefContainer& defs = getOptionDefs(u);
+    const OptionDefContainerNameIndex& idx = defs.get<2>();
+    const OptionDefContainerNameRange& range = idx.equal_range(name);
+    if (range.first != range.second) {
+        return (*range.first);
+    }
+    return (OptionDefinitionPtr());
+
+}
+
+
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+                            const std::string& name) {
+    const OptionDefContainer* defs = NULL;
+    if (u == Option::V4) {
+        defs = getVendorOption4Defs(vendor_id);
+    } else if (u == Option::V6) {
+        defs = getVendorOption6Defs(vendor_id);
+    }
+
+    if (!defs) {
+        return (OptionDefinitionPtr());
+    }
+
+    const OptionDefContainerNameIndex& idx = defs->get<2>();
+    const OptionDefContainerNameRange& range = idx.equal_range(name);
+    if (range.first != range.second) {
+        return (*range.first);
+    }
+    return (OptionDefinitionPtr());
+}
+
+OptionDefinitionPtr
 LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
                             const uint16_t code) {
     const OptionDefContainer* defs = NULL;

+ 25 - 1
src/lib/dhcp/libdhcp++.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014 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
@@ -20,6 +20,7 @@
 #include <util/buffer.h>
 
 #include <iostream>
+#include <string>
 
 namespace isc {
 namespace dhcp {
@@ -55,6 +56,16 @@ public:
     static OptionDefinitionPtr getOptionDef(const Option::Universe u,
                                             const uint16_t code);
 
+    /// @brief Return the definition of option having a specified name.
+    ///
+    /// @param u universe (v4 or V6)
+    /// @param name Option name.
+    ///
+    /// @return Pointer to the option definition or NULL pointer if option
+    /// definition has not been found.
+    static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+                                            const std::string& name);
+
     /// @brief Returns vendor option definition for a given vendor-id and code
     ///
     /// @param u universe (V4 or V6)
@@ -66,6 +77,19 @@ public:
                                                   const uint32_t vendor_id,
                                                   const uint16_t code);
 
+    /// @brief Returns vendor option definition for a given vendor-id and
+    /// option name.
+    ///
+    /// @param u Universe (V4 or V6)
+    /// @param vendor_id Enterprise-id for a given vendor
+    /// @param name Option name.
+    ///
+    /// @return A pointer to an option definition or NULL pointer if
+    /// no option definition found.
+    static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u,
+                                                  const uint32_t vendor_id,
+                                                  const std::string& name);
+
     /// @brief Check if the specified option is a standard option.
     ///
     /// @param u universe (V4 or V6)

+ 21 - 6
src/lib/dhcp/option4_client_fqdn.cc

@@ -446,8 +446,10 @@ Option4ClientFqdn::packDomainName(isc::util::OutputBuffer& buf) const {
         }
 
     } else {
-        std::string domain_name = impl_->domain_name_->toText();
-        buf.writeData(&domain_name[0], domain_name.size());
+        std::string domain_name = getDomainName();
+        if (!domain_name.empty()) {
+            buf.writeData(&domain_name[0], domain_name.size());
+        }
 
     }
 }
@@ -512,11 +514,24 @@ Option4ClientFqdn::toText(int indent) {
 uint16_t
 Option4ClientFqdn::len() {
     uint16_t domain_name_length = 0;
-    // If domain name is partial, the NULL terminating character
-    // is not included and the option length have to be adjusted.
+    // Try to calculate the length of the domain name only if there is
+    // any domain name specified.
     if (impl_->domain_name_) {
-        domain_name_length = impl_->domain_name_type_ == FULL ?
-            impl_->domain_name_->getLength() : impl_->domain_name_->getLength() - 1;
+        // If the E flag is specified, the domain name is encoded in the
+        // canonical format. The length of the domain name depends on
+        // whether it is a partial or fully qualified names. For the
+        // former the length is 1 octet lesser because it lacks terminating
+        // zero.
+        if (getFlag(FLAG_E)) {
+            domain_name_length = impl_->domain_name_type_ == FULL ?
+                impl_->domain_name_->getLength() :
+                impl_->domain_name_->getLength() - 1;
+
+        // ASCII encoded domain name. Its length is just a length of the
+        // string holding the name.
+        } else {
+            domain_name_length = getDomainName().length();
+        }
     }
 
     return (getHeaderLen() + FIXED_FIELDS_LEN + domain_name_length);

+ 5 - 2
src/lib/dhcp/option6_addrlst.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012, 2014 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
@@ -17,7 +17,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcp/option.h>
-
+#include <boost/shared_ptr.hpp>
 #include <vector>
 
 namespace isc {
@@ -94,6 +94,9 @@ protected:
     AddressContainer addrs_;
 };
 
+/// @brief Pointer to the @c Option6AddrLst object.
+typedef boost::shared_ptr<Option6AddrLst> Option6AddrLstPtr;
+
 } // isc::dhcp namespace
 } // isc namespace
 

+ 20 - 0
src/lib/dhcp/option_definition.h

@@ -717,6 +717,17 @@ typedef boost::multi_index_container<
                 uint16_t,
                 &OptionDefinition::getCode
             >
+        >,
+        // Start definition of index #2
+        boost::multi_index::hashed_non_unique<
+            // Use option name as the index key. This value is
+            // returned by the @c OptionDefinition::getName
+            // method.
+            boost::multi_index::const_mem_fun<
+                OptionDefinition,
+                std::string,
+                &OptionDefinition::getName
+            >
         >
     >
 > OptionDefContainer;
@@ -736,6 +747,15 @@ typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
 typedef std::pair<OptionDefContainerTypeIndex::const_iterator,
                   OptionDefContainerTypeIndex::const_iterator> OptionDefContainerTypeRange;
 
+/// Type of the index #2 - option name.
+typedef OptionDefContainer::nth_index<2>::type OptionDefContainerNameIndex;
+/// Pair of iterators to represent the range of options definitions
+/// having the same option name. The first element in this pair
+/// represents the beginning of the range, the second element
+/// represents the end.
+typedef std::pair<OptionDefContainerNameIndex::const_iterator,
+                  OptionDefContainerNameIndex::const_iterator> OptionDefContainerNameRange;
+
 
 } // namespace isc::dhcp
 } // namespace isc

+ 64 - 0
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -16,6 +16,7 @@
 
 #include <dhcp/dhcp4.h>
 #include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option4_client_fqdn.h>
@@ -1141,6 +1142,69 @@ TEST_F(LibDhcpTest, stdOptionDefs6) {
                                     typeid(Option6AddrLst));
 }
 
+// This test checks if the DHCPv6 option definition can be searched by
+// an option name.
+TEST_F(LibDhcpTest, getOptionDefByName6) {
+    // Get all definitions.
+    const OptionDefContainer& defs = LibDHCP::getOptionDefs(Option::V6);
+    // For each definition try to find it using option name.
+    for (OptionDefContainer::const_iterator def = defs.begin();
+         def != defs.end(); ++def) {
+        OptionDefinitionPtr def_by_name =
+            LibDHCP::getOptionDef(Option::V6, (*def)->getName());
+        ASSERT_TRUE(def_by_name);
+        ASSERT_TRUE(**def == *def_by_name);
+    }
+}
+
+
+// This test checks if the DHCPv4 option definition can be searched by
+// an option name.
+TEST_F(LibDhcpTest, getOptionDefByName4) {
+    // Get all definitions.
+    const OptionDefContainer& defs = LibDHCP::getOptionDefs(Option::V4);
+    // For each definition try to find it using option name.
+    for (OptionDefContainer::const_iterator def = defs.begin();
+         def != defs.end(); ++def) {
+        OptionDefinitionPtr def_by_name =
+            LibDHCP::getOptionDef(Option::V4, (*def)->getName());
+        ASSERT_TRUE(def_by_name);
+        ASSERT_TRUE(**def == *def_by_name);
+    }
+}
+
+// This test checks if the definition of the DHCPv6 vendor option can
+// be searched by option name.
+TEST_F(LibDhcpTest, getVendorOptionDefByName6) {
+    const OptionDefContainer* defs =
+        LibDHCP::getVendorOption6Defs(VENDOR_ID_CABLE_LABS);
+    ASSERT_TRUE(defs != NULL);
+    for (OptionDefContainer::const_iterator def = defs->begin();
+         def != defs->end(); ++def) {
+        OptionDefinitionPtr def_by_name =
+            LibDHCP::getVendorOptionDef(Option::V6, VENDOR_ID_CABLE_LABS,
+                                        (*def)->getName());
+        ASSERT_TRUE(def_by_name);
+        ASSERT_TRUE(**def == *def_by_name);
+    }
+}
+
+// This test checks if the definition of the DHCPv4 vendor option can
+// be searched by option name.
+TEST_F(LibDhcpTest, getVendorOptionDefByName4) {
+    const OptionDefContainer* defs =
+        LibDHCP::getVendorOption4Defs(VENDOR_ID_CABLE_LABS);
+    ASSERT_TRUE(defs != NULL);
+    for (OptionDefContainer::const_iterator def = defs->begin();
+         def != defs->end(); ++def) {
+        OptionDefinitionPtr def_by_name =
+            LibDHCP::getVendorOptionDef(Option::V4, VENDOR_ID_CABLE_LABS,
+                                        (*def)->getName());
+        ASSERT_TRUE(def_by_name);
+        ASSERT_TRUE(**def == *def_by_name);
+    }
+}
+
 // tests whether v6 vendor-class option can be parsed properly.
 TEST_F(LibDhcpTest, vendorClass6) {
 

+ 77 - 2
src/lib/dhcp/tests/option4_client_fqdn_unittest.cc

@@ -694,7 +694,7 @@ TEST(Option4ClientFqdnTest, packASCII) {
 
     // Prepare reference data.
     const uint8_t ref_data[] = {
-        81, 23,                               // header
+        81, 22,                               // header
         Option4ClientFqdn::FLAG_S,            // flags
         0,                                    // RCODE1
         0,                                    // RCODE2
@@ -955,6 +955,81 @@ TEST(Option4ClientFqdnTest, len) {
     );
     ASSERT_TRUE(option);
     EXPECT_EQ(12, option->len());
-    }
+
+    // Another test for partial domain name but this time the domain name
+    // contains two labels.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(Option4ClientFqdn::FLAG_E,
+                                           Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(20, option->len());
+
+}
+
+// This test verifies that the correct length of the option in on-wire
+// format is returned when ASCII encoding for FQDN is in use.
+TEST(Option4ClientFqdnTest, lenAscii) {
+    // Create option instance. Check that constructor doesn't throw.
+    boost::scoped_ptr<Option4ClientFqdn> option;
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    // This option comprises a header (2 octets), flag field (1 octet),
+    // RCODE1 and RCODE2 (2 octets) and the domain name in the ASCII format.
+    // The length of the domain name in the ASCII format is 19 - length
+    // of the string plus terminating dot.
+    EXPECT_EQ(24, option->len());
+
+    // Let's change the domain name and see if the length is different.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "example.com"))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(17, option->len());
+
+    // Let's test the length of the option when the partial domain name is
+    // specified.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    // For partial names, there is no terminating dot, so the length of the
+    // domain name is equal to the length of the "myhost".
+    EXPECT_EQ(11, option->len());
+
+    // Another check for partial domain name but this time the domain name
+    // contains two labels.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "myhost.example",
+                                           Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(19, option->len());
+
+
+    // A special case is an empty domain name for which the returned length
+    // should be a sum of the header length, RCODE1, RCODE2 and flag fields
+    // length.
+    ASSERT_NO_THROW(
+        option.reset(new Option4ClientFqdn(0, Option4ClientFqdn::RCODE_CLIENT(),
+                                           "", Option4ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+
+    EXPECT_EQ(5, option->len());
+}
 
 } // anonymous namespace

+ 22 - 5
src/lib/dhcp/tests/option6_client_fqdn_unittest.cc

@@ -191,17 +191,16 @@ TEST(Option6ClientFqdnTest, constructFromWireTooLongLabel) {
 // is over 255.
 TEST(Option6ClientFqdnTest, constructFromWireTooLongDomainName) {
     OptionBuffer in_buf(Option6ClientFqdn::FLAG_S);
+    // Construct the FQDN from 26 labels, each having a size of 10.
     for (int i = 0; i < 26;  ++i) {
+        // Append the length of each label.
         in_buf.push_back(10);
+        // Append the actual label.
         in_buf.insert(in_buf.end(), 10, 109);
     }
+    // Terminate FQDN with a dot.
     in_buf.push_back(0);
 
-    try {
-        Option6ClientFqdn(in_buf.begin(), in_buf.end());
-    } catch (const Exception& ex) {
-        std::cout << ex.what() << std::endl;
-    }
     EXPECT_THROW(Option6ClientFqdn(in_buf.begin(), in_buf.end()),
                  InvalidOption6FqdnDomainName);
 }
@@ -791,6 +790,14 @@ TEST(Option6ClientFqdnTest, len) {
     // length of the string representation of the domain name + 1).
     EXPECT_EQ(25, option->len());
 
+    // Use different domain name to check if the length also changes
+    // as expected.
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "example.com"))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(18, option->len());
+
     // Let's check that the size will change when domain name of a different
     // size is used.
     ASSERT_NO_THROW(
@@ -805,6 +812,16 @@ TEST(Option6ClientFqdnTest, len) {
     );
     ASSERT_TRUE(option);
     EXPECT_EQ(12, option->len());
+
+    // Another test for partial domain name but this time using
+    // two labels.
+    ASSERT_NO_THROW(
+        option.reset(new Option6ClientFqdn(0, "myhost.example",
+                                           Option6ClientFqdn::PARTIAL))
+    );
+    ASSERT_TRUE(option);
+    EXPECT_EQ(20, option->len());
+
 }
 
 } // anonymous namespace

+ 8 - 0
src/lib/dhcpsrv/Makefile.am

@@ -44,11 +44,14 @@ lib_LTLIBRARIES = libkea-dhcpsrv.la
 libkea_dhcpsrv_la_SOURCES  =
 libkea_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
 libkea_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libkea_dhcpsrv_la_SOURCES += base_host_data_source.h
 libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
+libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
 libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
 libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
+libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
 libkea_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file4.cc csv_lease_file4.h
 libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
@@ -60,6 +63,9 @@ libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
 libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
+libkea_dhcpsrv_la_SOURCES += host_container.h
+libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
+libkea_dhcpsrv_la_SOURCES += host_reservation_parser.cc host_reservation_parser.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
@@ -79,8 +85,10 @@ libkea_dhcpsrv_la_SOURCES += pool.cc pool.h
 libkea_dhcpsrv_la_SOURCES += srv_config.cc srv_config.h
 libkea_dhcpsrv_la_SOURCES += subnet.cc subnet.h
 libkea_dhcpsrv_la_SOURCES += subnet_id.h
+libkea_dhcpsrv_la_SOURCES += subnet_selector.h
 libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h
+libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
 
 nodist_libkea_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
 

+ 153 - 0
src/lib/dhcpsrv/base_host_data_source.h

@@ -0,0 +1,153 @@
+// Copyright (C) 2014 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 BASE_HOST_DATA_SOURCE_H
+#define BASE_HOST_DATA_SOURCE_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/host.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception thrown when the duplicate @c Host object is detected.
+class DuplicateHost : public Exception {
+public:
+    DuplicateHost(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Base interface for the classes implementing simple data source
+/// for host reservations.
+///
+/// This abstract class defines an interface for the classes implementing
+/// basic data source for host reservations. This interface allows for
+/// adding new reservations (represented by @c Host objects) and retrieving
+/// these reservations using various parameters such as HW address or DUID,
+/// subnet identifier (either IPv4 or IPv6) or reserved IP address.
+///
+/// This interface DOES NOT specify the methods to manage existing
+/// host reservations such as to remove one IPv6 reservation but leave
+/// other reservations. It also lacks the methods used for preparing
+/// the data to be added to the SQL database: commit, rollback etc.
+/// Such methods are declared in other interfaces.
+class BaseHostDataSource {
+public:
+
+    /// @brief Default destructor implementation.
+    virtual ~BaseHostDataSource() {
+    }
+
+    /// @brief Return all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const = 0;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const = 0;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const = 0;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) const = 0;
+
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const = 0;
+
+    /// @brief Adds a new host to the collection.
+    ///
+    /// The implementations of this method should guard against duplicate
+    /// reservations for the same host, where possible. For example, when the
+    /// reservation for the same HW address and subnet id is added twice, the
+    /// implementation should throw an exception. Note, that usually it is
+    /// impossible to guard against adding duplicated host, where one instance
+    /// is identified by HW address, another one by DUID.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host) = 0;
+
+};
+
+}
+}
+
+#endif // BASE_HOST_DATA_SOURCE_H

+ 200 - 0
src/lib/dhcpsrv/cfg_hosts.cc

@@ -0,0 +1,200 @@
+// Copyright (C) 2014 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 <dhcpsrv/cfg_hosts.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+ConstHostCollection
+CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    ConstHostCollection collection;
+    getAllInternal<ConstHostCollection>(hwaddr, duid, collection);
+    return (collection);
+}
+
+HostCollection
+CfgHosts::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) {
+    HostCollection collection;
+    getAllInternal<HostCollection>(hwaddr, duid, collection);
+    return (collection);
+}
+
+ConstHostCollection
+CfgHosts::getAll4(const IOAddress&) const {
+    isc_throw(isc::NotImplemented, "getAll4(address) const is not implemented");
+}
+
+HostCollection
+CfgHosts::getAll4(const IOAddress&) {
+    isc_throw(isc::NotImplemented, "getAll4(address) is not implemented");
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal(const std::vector<uint8_t>& identifier,
+                         const Host::IdentifierType& identifier_type,
+                         Storage& storage) const {
+    // Search for the Host using the identifier and identifier type as a
+    // composite key.
+    const HostContainerIndex0& idx = hosts_.get<0>();
+    std::pair<HostContainerIndex0::iterator, HostContainerIndex0::iterator> r =
+        idx.equal_range(boost::make_tuple(identifier, identifier_type));
+    // Append each Host object to the storage.
+    for (HostContainerIndex0::iterator host = r.first; host != r.second;
+         ++host) {
+        storage.push_back(*host);
+    }
+}
+
+template<typename Storage>
+void
+CfgHosts::getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
+                         Storage& storage) const {
+    // Get hosts using HW address.
+    if (hwaddr) {
+        getAllInternal<Storage>(hwaddr->hwaddr_, Host::IDENT_HWADDR, storage);
+    }
+    // Get hosts using DUID.
+    if (duid) {
+        getAllInternal<Storage>(duid->getDuid(), Host::IDENT_DUID, storage);
+    }
+}
+
+ConstHostPtr
+CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+               const DuidPtr& duid) const {
+    // The false value indicates that it is an IPv4 subnet.
+    return (getHostInternal(subnet_id, false, hwaddr, duid));
+}
+
+HostPtr
+CfgHosts::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+               const DuidPtr& duid) {
+    // The false value indicates that it is an IPv4 subnet.
+    return (getHostInternal(subnet_id, false, hwaddr, duid));
+}
+
+ConstHostPtr
+CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) const {
+    // The true value indicates that it is an IPv6 subnet.
+    return (getHostInternal(subnet_id, true, hwaddr, duid));
+}
+
+HostPtr
+CfgHosts::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) {
+    // The true value indicates that it is an IPv6 subnet.
+    return (getHostInternal(subnet_id, true, hwaddr, duid));
+}
+
+ConstHostPtr
+CfgHosts::get6(const IOAddress&, const uint8_t) const {
+    isc_throw(isc::NotImplemented,
+              "get6(prefix, len) const is not implemented");
+}
+
+HostPtr
+CfgHosts::get6(const IOAddress&, const uint8_t) {
+    isc_throw(isc::NotImplemented, "get6(prefix, len) is not implemented");
+}
+
+HostPtr
+CfgHosts::getHostInternal(const SubnetID& subnet_id, const bool subnet6,
+                          const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    // Get all hosts for the HW address and DUID. This may return multiple hosts
+    // for different subnets, but the number of hosts returned should be low
+    // because one host presumably doesn't show up in many subnets.
+    HostCollection hosts;
+    getAllInternal<HostCollection>(hwaddr, duid, hosts);
+
+    HostPtr host;
+    // Iterate over the returned hosts and select those for which the
+    // subnet id matches.
+    for (HostCollection::const_iterator host_it = hosts.begin();
+         host_it != hosts.end(); ++host_it) {
+        // Check if this is IPv4 subnet or IPv6 subnet.
+        SubnetID host_subnet_id = subnet6 ? (*host_it)->getIPv6SubnetID() :
+            (*host_it)->getIPv4SubnetID();
+
+        if (subnet_id == host_subnet_id) {
+            // If this is the first occurrence of the host for this subnet,
+            // remember it. But, if we find that this is second @c Host object
+            // for the same client, it is a misconfiguration. Most likely,
+            // the administrator has specified one reservation for a HW
+            // address and another one for the DUID, which gives an ambiguous
+            // result, and we don't know which reservation we should choose.
+            // Therefore, throw an exception.
+            if (!host) {
+                host = *host_it;
+
+            } else {
+                isc_throw(DuplicateHost,  "more than one reservation found"
+                          " for the host belonging to the subnet with id '"
+                          << subnet_id << "' and using the HW address '"
+                          << (hwaddr ? hwaddr->toText(false) : "(null)")
+                          << "' and DUID '"
+                          << (duid ? duid->toText() : "(null)")
+                          << "'");
+            }
+        }
+    }
+    return (host);
+}
+
+
+void
+CfgHosts::add(const HostPtr& host) {
+    // Sanity check that the host is non-null.
+    if (!host) {
+        isc_throw(BadValue, "specified host object must not be NULL when it"
+                  " is added to the configuration");
+    }
+    // At least one subnet ID must be non-zero
+    if (host->getIPv4SubnetID() == 0 && host->getIPv6SubnetID() == 0) {
+        isc_throw(BadValue, "must not use both IPv4 and IPv6 subnet ids of"
+                  " 0 when adding new host reservation");
+    }
+    /// @todo This may need further sanity checks.
+    HWAddrPtr hwaddr = host->getHWAddress();
+    DuidPtr duid = host->getDuid();
+    // Check for duplicates for the specified IPv4 subnet.
+    if ((host->getIPv4SubnetID() > 0) &&
+        get4(host->getIPv4SubnetID(), hwaddr, duid)) {
+        isc_throw(DuplicateHost, "failed to add new host using the HW"
+                  " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
+                  << " and DUID '" << (duid ? duid->toText() : "(null)")
+                  << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
+                  << "' as this host has already been added");
+
+    // Checek for duplicates for the specified IPv6 subnet.
+    } else if (host->getIPv6SubnetID() &&
+               get6(host->getIPv6SubnetID(), duid, hwaddr)) {
+        isc_throw(DuplicateHost, "failed to add new host using the HW"
+                  " address '" << (hwaddr ? hwaddr->toText(false) : "(null)")
+                  << " and DUID '" << (duid ? duid->toText() : "(null)")
+                  << "' to the IPv6 subnet id '" << host->getIPv6SubnetID()
+                  << "' as this host has already been added");
+    }
+
+    // This is a new instance - add it.
+    hosts_.insert(host);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 256 - 0
src/lib/dhcpsrv/cfg_hosts.h

@@ -0,0 +1,256 @@
+// Copyright (C) 2014 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 CFG_HOSTS_H
+#define CFG_HOSTS_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_container.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/writable_host_data_source.h>
+#include <boost/shared_ptr.hpp>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Represents the host reservations specified in the configuration file.
+///
+/// This class holds a collection of the host reservations (@c Host objects)
+/// which can be retrieved using different search criteria.
+///
+/// In the typical case the reservations are searched using the client's MAC
+/// address of DUID and a subnet that the client is connected to. The
+/// reservations can be also retrieved using other parameters, such as reserved
+/// IP address.
+///
+/// The reservations are added to this object by the configuration parsers,
+/// when the new configuration is applied for the server. The reservations
+/// are retrieved by the @c HostMgr class when the server is allocating or
+/// renewing an address or prefix for the particular client.
+class CfgHosts : public WritableHostDataSource {
+public:
+
+    /// @brief Return all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects which represent reservations
+    /// for the specified HW address or DUID. Note, that this method may
+    /// return multiple reservations because a particular client may have
+    /// reservations in multiple subnets and the same client may be identified
+    /// by HW address or DUID. The server is unable to verify that the specific
+    /// DUID and HW address belong to the same client, until the client sends
+    /// a DHCP message.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr());
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @throw isc::NotImplemented.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @throw isc::NotImplemented
+    virtual HostCollection
+    getAll4(const asiolink::IOAddress& address);
+
+    /// @brief Returns a host connected to the IPv4 subnet and matching
+    /// specified identifiers.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet and matching
+    /// specified identifiers.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr());
+
+    /// @brief Returns a host connected to the IPv6 subnet and matching
+    /// the specified identifiers.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet and matching the
+    /// specified identifiers.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    /// @throw isc::dhcp::DuplicateHost if more than one candidate host has
+    /// been found.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr());
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @throw isc::NotImplemented
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @throw isc::NotImplemented
+    virtual HostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
+
+
+    /// @brief Adds a new host to the collection.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    ///
+    /// @throw DuplicateHost If a host for a particular HW address or DUID
+    /// has already been added to the IPv4 or IPv6 subnet.
+    virtual void add(const HostPtr& host);
+
+private:
+
+    /// @brief Returns @c Host objects for the specific identifier and type.
+    ///
+    /// This private method is called by the @c CfgHosts::getAllInternal
+    /// method which finds the @c Host objects using HW address or DUID.
+    /// The retrieved objects are appended to the @c storage container.
+    ///
+    /// @param identifier Binary representation of the HW addres or DUID (or
+    /// other future identifier).
+    /// @param identifier_type The type of the supplied identifier.
+    /// @param [out] storage Container to which the retreived objects are
+    /// appended.
+    /// @tparam One of the @c ConstHostCollection of @c HostCollection.
+    template<typename Storage>
+    void getAllInternal(const std::vector<uint8_t>& identifier,
+                        const Host::IdentifierType& identifier_type,
+                        Storage& storage) const;
+
+    /// @brief Returns @c Host objects for the specified HW address or DUID.
+    ///
+    /// This private method is called by the @c CfgHosts::getAll methods to
+    /// retrieve the @c Host objects using HW address or DUID. The retrieved
+    /// objects are appended to the @c storage container.
+    ///
+    /// @param hwaddr HW address identifying a host.
+    /// @param duid DUID identifying a host.
+    /// @param [out] storage Container to which the retrieved objects are
+    /// appended.
+    /// @tparam One of the @c ConstHostCollection or @c HostCollection.
+    template<typename Storage>
+    void getAllInternal(const HWAddrPtr& hwaddr, const DuidPtr& duid,
+                        Storage& storage) const;
+
+    /// @brief Returns @c Host object connected to a subnet.
+    ///
+    /// This private method returns a pointer to the @c Host object identified
+    /// by the HW address or DUID and connected to an IPv4 or IPv6 subnet.
+    ///
+    /// @param subnet_id IPv4 or IPv6 subnet identifier.
+    /// @param subnet6 A boolean flag which indicates if the subnet identifier
+    /// points to a IPv4 (if false) or IPv6 subnet (if true).
+    /// @param hwaddr HW address identifying a host.
+    /// @param duid DUID identifying a host.
+    ///
+    /// @return Pointer to the found host, or NULL if no host found.
+    /// @throw isc::dhcp::DuplicateHost if method found more than one matching
+    /// @c Host object.
+    HostPtr getHostInternal(const SubnetID& subnet_id,
+                            const bool subnet6,
+                            const HWAddrPtr& hwaddr,
+                            const DuidPtr& duid) const;
+
+    /// @brief Multi-index container holding @c Host objects.
+    HostContainer hosts_;
+
+};
+
+/// @name Pointers to the @c CfgHosts objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgHosts> CfgHostsPtr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgHosts> ConstCfgHostsPtr;
+
+//@}
+
+}
+}
+
+#endif // CFG_HOSTS_H

+ 21 - 1
src/lib/dhcpsrv/cfg_option_def.cc

@@ -138,7 +138,27 @@ CfgOptionDef::get(const std::string& option_space,
     return (OptionDefinitionPtr());
 }
 
-
+OptionDefinitionPtr
+CfgOptionDef::get(const std::string& option_space,
+                  const std::string& option_name) const {
+    // Get the pointer to collection of the option definitions that belong
+    // to the particular option space.
+    OptionDefContainerPtr defs = getAll(option_space);
+    // If there are any option definitions for this option space, get the
+    // one that has the specified option name.
+    if (defs && !defs->empty()) {
+        const OptionDefContainerNameIndex& idx = defs->get<2>();
+        const OptionDefContainerNameRange& range = idx.equal_range(option_name);
+        // If there is more than one definition matching the option name,
+        // return the first one. In fact, it shouldn't happen that we have
+        // more than one because we check for duplicates when we add them.
+        if (std::distance(range.first, range.second) > 0) {
+            return (*range.first);
+        }
+    }
+    // Nothing found. Return NULL pointer.
+    return (OptionDefinitionPtr());
+}
 
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 10 - 0
src/lib/dhcpsrv/cfg_option_def.h

@@ -110,6 +110,16 @@ public:
     OptionDefinitionPtr get(const std::string& option_space,
                             const uint16_t option_code) const;
 
+    /// @brief Return option definition for the particular option space and name.
+    ///
+    /// @param option_space pption space.
+    /// @param option_name option name.
+    ///
+    /// @return An option definition or NULL pointer if option definition
+    /// has not been found.
+    OptionDefinitionPtr get(const std::string& option_space,
+                            const std::string& option_name) const;
+
 private:
 
     /// @brief A collection of option definitions.

+ 1 - 7
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -32,12 +32,6 @@ const IOAddress BCAST_ADDRESS("255.255.255.255");
 namespace isc {
 namespace dhcp {
 
-CfgSubnets4::Selector::Selector()
-    : ciaddr_(ZERO_ADDRESS), giaddr_(ZERO_ADDRESS),
-      local_address_(ZERO_ADDRESS), remote_address_(ZERO_ADDRESS),
-      client_classes_(ClientClasses()), iface_name_(std::string()) {
-}
-
 void
 CfgSubnets4::add(const Subnet4Ptr& subnet) {
     /// @todo: Check that this new subnet does not cross boundaries of any
@@ -52,7 +46,7 @@ CfgSubnets4::add(const Subnet4Ptr& subnet) {
 }
 
 Subnet4Ptr
-CfgSubnets4::selectSubnet(const Selector& selector) const {
+CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
     // If relayed message has been received, try to match the giaddr with the
     // relay address specified for a subnet. It is also possible that the relay
     // address will not match with any of the relay addresses accross all

+ 20 - 26
src/lib/dhcpsrv/cfg_subnets4.h

@@ -17,6 +17,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
 #include <boost/shared_ptr.hpp>
 
 namespace isc {
@@ -35,30 +36,6 @@ namespace dhcp {
 class CfgSubnets4 {
 public:
 
-    /// @brief Subnet selector used in @c CfgSubnets4::selectSubnet.
-    ///
-    /// This structure holds various parameters extracted from a packet sent
-    /// by a DHCP client used to select the subnet for the client.
-    struct Selector {
-        /// @brief ciaddr from the client's message.
-        asiolink::IOAddress ciaddr_;
-        /// @brief giaddr from the client's message.
-        asiolink::IOAddress giaddr_;
-        /// @brief Address on which the message was received.
-        asiolink::IOAddress local_address_;
-        /// @brief Source address of the message.
-        asiolink::IOAddress remote_address_;
-        /// @brief Classes that the client belongs to.
-        ClientClasses client_classes_;
-        /// @brief Name of the interface on which the message was received.
-        std::string iface_name_;
-
-        /// @brief Default constructor.
-        ///
-        ///  Sets the default values for the @c Selector.
-        Selector();
-    };
-
     /// @brief Adds new subnet to the configuration.
     ///
     /// @param subnet Pointer to the subnet being added.
@@ -107,6 +84,14 @@ public:
     ///
     /// If the address matches with a subnet, the subnet is returned.
     ///
+    /// @todo This method requires performance improvement! It currently
+    /// iterates over all existing subnets (possibly a couple of times)
+    /// to find the one which fulfils the search criteria. The subnet storage
+    /// is implemented as a simple STL vector which precludes fast searches
+    /// using specific keys. Hence, full scan is required. To improve the
+    /// search performance a different container type is required, e.g.
+    /// multi-index container, or something of a similar functionality.
+    ///
     /// @param selector Const reference to the selector structure which holds
     /// various information extracted from the client's packet which are used
     /// to find appropriate subnet.
@@ -114,13 +99,22 @@ public:
     /// @return Pointer to the selected subnet or NULL if no subnet found.
     /// @throw isc::BadValue if the values in the subnet selector are invalid
     /// or they are insufficient to select a subnet.
-    Subnet4Ptr selectSubnet(const Selector& selector) const;
+    Subnet4Ptr selectSubnet(const SubnetSelector& selector) const;
 
     /// @brief Returns pointer to a subnet if provided address is in its range.
     ///
     /// This method returns a pointer to the subnet if the address passed in
     /// parameter is in range with this subnet. This is mainly used for unit
-    /// testing. This method is also called by the @c selectSubnet(Selector).
+    /// testing. This method is also called by the
+    /// @c selectSubnet(SubnetSelector).
+    ///
+    /// @todo This method requires performance improvement! It currently
+    /// iterates over all existing subnets to find the one which fulfils
+    /// the search criteria. The subnet storage is implemented as a simple
+    /// STL vector which precludes fast searches using specific keys.
+    /// Hence, full scan is required. To improve the search performance a
+    /// different container type is required, e.g. multi-index container,
+    /// or something of a similar functionality.
     ///
     /// @param address Address for which the subnet is searched.
     /// @param client_classes Optional parameter specifying the classes that

+ 182 - 0
src/lib/dhcpsrv/cfg_subnets6.cc

@@ -0,0 +1,182 @@
+// Copyright (C) 2014 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 <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/subnet_id.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+void
+CfgSubnets6::add(const Subnet6Ptr& subnet) {
+    /// @todo: Check that this new subnet does not cross boundaries of any
+    /// other already defined subnet.
+    if (isDuplicate(*subnet)) {
+        isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv6 subnet '"
+                  << subnet->getID() << "' is already in use");
+    }
+    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+              .arg(subnet->toText());
+    subnets_.push_back(subnet);
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const SubnetSelector& selector) const {
+    Subnet6Ptr subnet;
+
+    // If relay agent link address is set to zero it means that we're dealing
+    // with a directly connected client.
+    if (selector.first_relay_linkaddr_ == IOAddress("::")) {
+        // If interface name is known try to match it with interface names
+        // specified for configured subnets.
+        if (!selector.iface_name_.empty()) {
+            subnet = selectSubnet(selector.iface_name_,
+                                  selector.client_classes_);
+        }
+
+        // If interface name didn't match, try the client's address.
+        if (!subnet && selector.remote_address_ != IOAddress("::")) {
+            subnet = selectSubnet(selector.remote_address_,
+                                  selector.client_classes_);
+        }
+
+    // If relay agent link address is set, we're dealing with a relayed message.
+    } else {
+
+        // Find the subnet using the Interface Id option, if present.
+        subnet = selectSubnet(selector.interface_id_, selector.client_classes_);
+
+        // If Interface ID option could not be matched for any subnet, try
+        // the relay agent link address.
+        if (!subnet) {
+            subnet = selectSubnet(selector.first_relay_linkaddr_,
+                                  selector.client_classes_,
+                                  true);
+        }
+    }
+
+    // Return subnet found, or NULL if not found.
+    return (subnet);
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const asiolink::IOAddress& address,
+                          const ClientClasses& client_classes,
+                          const bool is_relay_address) const {
+
+    // If the specified address is a relay address we first need to match
+    // it with the relay addresses specified for all subnets.
+    if (is_relay_address) {
+        for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+             subnet != subnets_.end(); ++subnet) {
+
+            // If the specified address matches the relay address, return this
+            // subnet.
+            if (is_relay_address &&
+                ((*subnet)->getRelayInfo().addr_ == address) &&
+                (*subnet)->clientSupported(client_classes)) {
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                          DHCPSRV_CFGMGR_SUBNET6_RELAY)
+                    .arg((*subnet)->toText()).arg(address.toText());
+                return (*subnet);
+            }
+        }
+    }
+
+    // No success so far. Check if the specified address is in range
+    // with any subnet.
+    for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+         subnet != subnets_.end(); ++subnet) {
+        if ((*subnet)->inRange(address) &&
+            (*subnet)->clientSupported(client_classes)) {
+            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET6)
+                      .arg((*subnet)->toText()).arg(address.toText());
+            return (*subnet);
+        }
+    }
+
+    // Nothing found.
+    return (Subnet6Ptr());
+}
+
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const std::string& iface_name,
+                          const ClientClasses& client_classes) const {
+
+    // If empty interface specified, we can't select subnet by interface.
+    if (!iface_name.empty()) {
+        for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+             subnet != subnets_.end(); ++subnet) {
+
+            // If interface name matches with the one specified for the subnet
+            // and the client is not rejected based on the classification,
+            // return the subnet.
+            if ((iface_name == (*subnet)->getIface()) &&
+                (*subnet)->clientSupported(client_classes)) {
+
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                          DHCPSRV_CFGMGR_SUBNET6_IFACE)
+                    .arg((*subnet)->toText()).arg(iface_name);
+                return (*subnet);
+            }
+        }
+    }
+
+    // No subnet found for this interface name.
+    return (Subnet6Ptr());
+}
+
+Subnet6Ptr
+CfgSubnets6::selectSubnet(const OptionPtr& interface_id,
+                          const ClientClasses& client_classes) const {
+    // We can only select subnet using an interface id, if the interface
+    // id is known.
+    if (interface_id) {
+        for (Subnet6Collection::const_iterator subnet = subnets_.begin();
+             subnet != subnets_.end(); ++subnet) {
+
+            // If interface id matches for the subnet and the subnet is not
+            // rejected based on the classification.
+            if ((*subnet)->getInterfaceId() &&
+                (*subnet)->getInterfaceId()->equals(interface_id) &&
+                (*subnet)->clientSupported(client_classes)) {
+
+                LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+                      DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
+                    .arg((*subnet)->toText());
+                return (*subnet);
+            }
+        }
+    }
+    // No subnet found.
+    return (Subnet6Ptr());
+}
+
+bool
+CfgSubnets6::isDuplicate(const Subnet6& subnet) const {
+    for (Subnet6Collection::const_iterator subnet_it = subnets_.begin();
+         subnet_it != subnets_.end(); ++subnet_it) {
+        if ((*subnet_it)->getID() == subnet.getID()) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 211 - 0
src/lib/dhcpsrv/cfg_subnets6.h

@@ -0,0 +1,211 @@
+// Copyright (C) 2014 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 CFG_SUBNETS6_H
+#define CFG_SUBNETS6_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <util/optional_value.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds subnets configured for the DHCPv6 server.
+///
+/// This class holds a collection of subnets configured for the DHCPv6 server.
+/// It allows for retrieving a subnet for the particular client using various
+/// parameters extracted from the DHCPv6 message. These parameters must be
+/// assigned to the appropriate members of the @c SubnetSelector structure.
+///
+/// See @c CfgSubnets6::selectSubnet documentation for more details on how the subnet
+/// is selected for the client.
+class CfgSubnets6 {
+public:
+
+    /// @brief Adds new subnet to the configuration.
+    ///
+    /// @param subnet Pointer to the subnet being added.
+    ///
+    /// @throw isc::DuplicateSubnetID If the subnet id for the new subnet
+    /// duplicates id of an existing subnet.
+    void add(const Subnet6Ptr& subnet);
+
+    /// @brief Returns pointer to the collection of all IPv6 subnets.
+    ///
+    /// This is used in a hook (subnet6_select), where the hook is able
+    /// to choose a different subnet. Server code has to offer a list
+    /// of possible choices (i.e. all subnets).
+    ///
+    /// @return A pointer to const Subnet6 collection
+    const Subnet6Collection* getAll() const {
+        return (&subnets_);
+    }
+
+    /// @brief Selects a subnet using parameters specified in the selector.
+    ///
+    /// This method tries to retrieve the subnet for the client using various
+    /// parameters extracted from the client's message using the following
+    /// logic.
+    ///
+    /// If the relay agent link address is set to zero it is assumed that
+    /// the subnet is selected for the directly connected client.
+    /// In this case it is checked if there is any subnet associated with the
+    /// interface over which the message has been received. If there is no
+    /// subnet explicitly associated with this interface the client's address
+    /// will be used to check if the address is in range with any of the
+    /// subnets.
+    ///
+    /// If the message was relayed it is possible that the relay agent has
+    /// appended an Interface ID option. If this option is present, the method
+    /// will check if it matches with any explicitly specified interface id
+    /// for any subnet. If it does, the subnet is returned. Otherwise, the
+    /// relay agents link address is used to select the subnet. In this case,
+    /// the method will first check if this link address is explicitly
+    /// associated with any subnet. If not, it is checked if the link address
+    /// is in range with any of the subnets.
+    ///
+    /// @todo This method requires performance improvement! It currently
+    /// iterates over all existing subnets (possibly a couple of times)
+    /// to find the one which fulfils the search criteria. The subnet storage
+    /// is implemented as a simple STL vector which precludes fast searches
+    /// using specific keys. Hence, full scan is required. To improve the
+    /// search performance a different container type is required, e.g.
+    /// multi-index container, or something of a similar functionality.
+    ///
+    /// @param selector Const reference to the selector structure which holds
+    /// various information extracted from the client's packet which are used
+    /// to find appropriate subnet.
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr selectSubnet(const SubnetSelector& selector) const;
+
+    /// @brief Selects the subnet using a specified address.
+    ///
+    /// This method searches for the subnet using the specified address. If
+    /// the specified address is a link address on the relay agent (which is
+    /// indicated by the 3rd argument) the method will first try to match the
+    /// specified address with the relay addresses explicitly specified for
+    /// existing subnets. If no match is found, the method will check if the
+    /// address is in range with any of the subnets.
+    ///
+    /// If the address is not a relay agent link address (@c is_relay_address
+    /// is set to false), the method will simply check if the address is in
+    /// range with any of the subnets.
+    ///
+    /// @note This method is mainly to be used in unit tests, which often
+    /// require sanity-checking if the subnet exists for the particular
+    /// address. For other purposes the @c selectSubnet(SubnetSelector) should
+    /// rather be used instead.
+    ///
+    /// @todo This method requires performance improvement! It currently
+    /// iterates over all existing subnets (possibly a couple of times)
+    /// to find the one which fulfils the search criteria. The subnet storage
+    /// is implemented as a simple STL vector which precludes fast searches
+    /// using specific keys. Hence, full scan is required. To improve the
+    /// search performance a different container type is required, e.g.
+    /// multi-index container, or something of a similar functionality.
+    ///
+    /// @param address Address for which the subnet is searched.
+    /// @param client_classes Optional parameter specifying the classes that
+    /// the client belongs to.
+    /// @param is_relay_address Specifies if the provided address is an
+    /// address of the relay agent (true) or not (false).
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr
+    selectSubnet(const asiolink::IOAddress& address,
+                 const ClientClasses& client_classes = ClientClasses(),
+                 const bool is_relay_address = false) const;
+
+private:
+
+    /// @brief Selects a subnet using the interface name.
+    ///
+    /// This method searches for the subnet using the name of the interface.
+    /// If any of the subnets is explicitly associated with the interface
+    /// name, the subnet is returned.
+    ///
+    /// @todo This method requires performance improvement! It currently
+    /// iterates over all existing subnets to find the one which fulfils
+    /// the search criteria. The subnet storage is implemented as a
+    /// simple STL vector which precludes fast searches using specific
+    /// keys. Hence, full scan is required. To improve the search
+    /// performance a different container type is required, e.g.
+    /// multi-index container, or something of a similar functionality.
+    ///
+    /// @param iface_name Interface name.
+    /// @param client_classes Optional parameter specifying the classes that
+    /// the client belongs to.
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr
+    selectSubnet(const std::string& iface_name,
+                 const ClientClasses& client_classes) const;
+
+    /// @brief Selects a subnet using Interface ID option.
+    ///
+    /// This method searches for the subnet using the Interface ID option
+    /// inserted by the relay agent to the message from a client. If any
+    /// of the subnets is explicitly associated with that interface id, the
+    /// subnet is returned.
+    ///
+    /// @todo This method requires performance improvement! It currently
+    /// iterates over all existing subnets to find the one which fulfils
+    /// the search criteria. The subnet storage is implemented as a
+    /// simple STL vector which precludes fast searches using specific
+    /// keys. Hence, full scan is required. To improve the search
+    /// performance a different container type is required, e.g.
+    /// multi-index container, or something of a similar functionality.
+    ///
+    /// @param interface_id An instance of the Interface ID option received
+    /// from the client.
+    /// @param client_classes Optional parameter specifying the classes that
+    /// the client belongs to.
+    ///
+    /// @return Pointer to the selected subnet or NULL if no subnet found.
+    Subnet6Ptr
+    selectSubnet(const OptionPtr& interface_id,
+                 const ClientClasses& client_classes) const;
+
+    /// @brief Checks that the IPv6 subnet with the given id already exists.
+    ///
+    /// @param subnet Subnet for which this function will check if the other
+    /// subnet with equal id already exists.
+    ///
+    /// @return true if the duplicate subnet exists.
+    bool isDuplicate(const Subnet6& subnet) const;
+
+    /// @brief A container for IPv6 subnets.
+    Subnet6Collection subnets_;
+
+};
+
+/// @name Pointer to the @c CfgSubnets6 objects.
+//@{
+/// @brief Non-const pointer.
+typedef boost::shared_ptr<CfgSubnets6> CfgSubnets6Ptr;
+
+/// @brief Const pointer.
+typedef boost::shared_ptr<const CfgSubnets6> ConstCfgSubnets6Ptr;
+
+//@}
+
+}
+}
+
+#endif // CFG_SUBNETS6_H

+ 0 - 108
src/lib/dhcpsrv/cfgmgr.cc

@@ -62,114 +62,6 @@ CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
     spaces6_.insert(make_pair(space->getName(), space));
 }
 
-Subnet6Ptr
-CfgMgr::getSubnet6(const std::string& iface,
-                   const isc::dhcp::ClientClasses& classes) {
-
-    if (!iface.length()) {
-        return (Subnet6Ptr());
-    }
-
-    // If there is more than one, we need to choose the proper one
-    for (Subnet6Collection::iterator subnet = subnets6_.begin();
-         subnet != subnets6_.end(); ++subnet) {
-
-        // If client is rejected because of not meeting client class criteria...
-        if (!(*subnet)->clientSupported(classes)) {
-            continue;
-        }
-
-        if (iface == (*subnet)->getIface()) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                      DHCPSRV_CFGMGR_SUBNET6_IFACE)
-                .arg((*subnet)->toText()).arg(iface);
-            return (*subnet);
-        }
-    }
-    return (Subnet6Ptr());
-}
-
-Subnet6Ptr
-CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint,
-                   const isc::dhcp::ClientClasses& classes,
-                   const bool relay) {
-
-    // If there is more than one, we need to choose the proper one
-    for (Subnet6Collection::iterator subnet = subnets6_.begin();
-         subnet != subnets6_.end(); ++subnet) {
-
-        // If client is rejected because of not meeting client class criteria...
-        if (!(*subnet)->clientSupported(classes)) {
-            continue;
-        }
-
-        // If the hint is a relay address, and there is relay info specified
-        // for this subnet and those two match, then use this subnet.
-        if (relay && ((*subnet)->getRelayInfo().addr_ == hint) ) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                      DHCPSRV_CFGMGR_SUBNET6_RELAY)
-                .arg((*subnet)->toText()).arg(hint.toText());
-            return (*subnet);
-        }
-
-        if ((*subnet)->inRange(hint)) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_SUBNET6)
-                      .arg((*subnet)->toText()).arg(hint.toText());
-            return (*subnet);
-        }
-    }
-
-    // sorry, we don't support that subnet
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET6)
-              .arg(hint.toText());
-    return (Subnet6Ptr());
-}
-
-Subnet6Ptr CfgMgr::getSubnet6(OptionPtr iface_id_option,
-                              const isc::dhcp::ClientClasses& classes) {
-    if (!iface_id_option) {
-        return (Subnet6Ptr());
-    }
-
-    // Let's iterate over all subnets and for those that have interface-id
-    // defined, check if the interface-id is equal to what we are looking for
-    for (Subnet6Collection::iterator subnet = subnets6_.begin();
-         subnet != subnets6_.end(); ++subnet) {
-
-        // If client is rejected because of not meeting client class criteria...
-        if (!(*subnet)->clientSupported(classes)) {
-            continue;
-        }
-
-        if ( (*subnet)->getInterfaceId() &&
-             ((*subnet)->getInterfaceId()->equals(iface_id_option))) {
-            LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
-                      DHCPSRV_CFGMGR_SUBNET6_IFACE_ID)
-                .arg((*subnet)->toText());
-            return (*subnet);
-        }
-    }
-    return (Subnet6Ptr());
-}
-
-void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
-    /// @todo: Check that this new subnet does not cross boundaries of any
-    /// other already defined subnet.
-    /// @todo: Check that there is no subnet with the same interface-id
-    if (isDuplicate(*subnet)) {
-        isc_throw(isc::dhcp::DuplicateSubnetID, "ID of the new IPv6 subnet '"
-                  << subnet->getID() << "' is already in use");
-    }
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
-              .arg(subnet->toText());
-    subnets6_.push_back(subnet);
-}
-
-void CfgMgr::deleteSubnets6() {
-    LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET6);
-    subnets6_.clear();
-}
-
 
 std::string CfgMgr::getDataDir() {
     return (datadir_);

+ 0 - 91
src/lib/dhcpsrv/cfgmgr.h

@@ -126,97 +126,6 @@ public:
         return (spaces6_);
     }
 
-    /// @brief get IPv6 subnet by address
-    ///
-    /// Finds a matching subnet, based on an address. This can be used
-    /// in two cases: when trying to find an appropriate lease based on
-    /// a) relay link address (that must be the address that is on link)
-    /// b) our global address on the interface the message was received on
-    ///    (for directly connected clients)
-    ///
-    /// If there are any classes specified in a subnet, that subnet
-    /// will be selected only if the client belongs to appropriate class.
-    ///
-    /// @note The client classification is checked before any relay
-    /// information checks are conducted.
-    ///
-    /// If relay is true then relay info overrides (i.e. value the sysadmin
-    /// can configure in Dhcp6/subnet6[X]/relay/ip-address) can be used.
-    /// That is applicable only for relays. Those overrides must not be used
-    /// for client address or for client hints. They are for link-addr field
-    /// in the RELAY_FORW message only.
-    ///
-    /// @param hint an address that belongs to a searched subnet
-    /// @param classes classes the client belongs to
-    /// @param relay true if address specified in hint is a relay
-    ///
-    /// @return a subnet object (or NULL if no suitable match was fount)
-    Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint,
-                          const isc::dhcp::ClientClasses& classes,
-                          const bool relay = false);
-
-    /// @brief get IPv6 subnet by interface name
-    ///
-    /// Finds a matching local subnet, based on interface name. This
-    /// is used for selecting subnets that were explicitly marked by the
-    /// user as reachable over specified network interface.
-    ///
-    /// If there are any classes specified in a subnet, that subnet
-    /// will be selected only if the client belongs to appropriate class.
-    ///
-    /// @param iface_name interface name
-    /// @param classes classes the client belongs to
-    ///
-    /// @return a subnet object (or NULL if no suitable match was fount)
-    Subnet6Ptr getSubnet6(const std::string& iface_name,
-                          const isc::dhcp::ClientClasses& classes);
-
-    /// @brief get IPv6 subnet by interface-id
-    ///
-    /// Another possibility to find a subnet is based on interface-id.
-    ///
-    /// If there are any classes specified in a subnet, that subnet
-    /// will be selected only if the client belongs to appropriate class.
-    ///
-    /// @param interface_id content of interface-id option returned by a relay
-    /// @param classes classes the client belongs to
-    ///
-    /// @return a subnet object
-    Subnet6Ptr getSubnet6(OptionPtr interface_id,
-                          const isc::dhcp::ClientClasses& classes);
-
-    /// @brief adds an IPv6 subnet
-    ///
-    /// @param subnet new subnet to be added.
-    void addSubnet6(const Subnet6Ptr& subnet);
-
-    /// @todo: Add subnet6 removal routines. Currently it is not possible
-    /// to remove subnets. The only case where subnet6 removal would be
-    /// needed is a dynamic server reconfiguration - a use case that is not
-    /// planned to be supported any time soon.
-
-    /// @brief removes all IPv6 subnets
-    ///
-    /// This method removes all existing IPv6 subnets. It is used during
-    /// reconfiguration - old configuration is wiped and new definitions
-    /// are used to recreate subnets.
-    ///
-    /// @todo Implement more intelligent approach. Note that comparison
-    /// between old and new configuration is tricky. For example: is
-    /// 2000::/64 and 2000::/48 the same subnet or is it something
-    /// completely new?
-    void deleteSubnets6();
-
-    /// @brief returns const reference to all subnets6
-    ///
-    /// This is used in a hook (subnet6_select), where the hook is able
-    /// to choose a different subnet. Server code has to offer a list
-    /// of possible choices (i.e. all subnets).
-    /// @return a pointer to const Subnet6 collection
-    const Subnet6Collection* getSubnets6() {
-        return (&subnets6_);
-    }
-
     /// @brief returns path do the data directory
     ///
     /// This method returns a path to writeable directory that DHCP servers

+ 197 - 124
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -33,6 +33,7 @@ using namespace std;
 using namespace isc::asiolink;
 using namespace isc::data;
 using namespace isc::hooks;
+using namespace isc::util;
 
 namespace isc {
 namespace dhcp {
@@ -342,128 +343,203 @@ OptionDataParser::commit() {
     // Does nothing
 }
 
-OptionDefinitionPtr
-OptionDataParser::findServerSpaceOptionDefinition(const std::string& option_space,
-                                                  const uint32_t option_code) const {
-    const Option::Universe u = address_family_ == AF_INET ?
-        Option::V4 : Option::V6;
-
-    if ((option_space == DHCP4_OPTION_SPACE) && (u == Option::V6)) {
-        isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
-                  << "' option space name is reserved for DHCPv4 server");
-    } else if ((option_space == DHCP6_OPTION_SPACE) && (u == Option::V4)) {
-        isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
-                  << "' option space name is reserved for DHCPv6 server");
-    }
-
-    OptionDefinitionPtr def;
-    if (((option_space == DHCP4_OPTION_SPACE) || (option_space == DHCP6_OPTION_SPACE)) &&
-        LibDHCP::isStandardOption(u, option_code)) {
-        def = LibDHCP::getOptionDef(u, option_code);
-
-    } else {
-        // Check if this is a vendor-option. If it is, get vendor-specific
-        // definition.
-        uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
-        if (vendor_id) {
-            def = LibDHCP::getVendorOptionDef(u, vendor_id, option_code);
-        }
-    }
-    return (def);
-}
-
-
-void
-OptionDataParser::createOption(ConstElementPtr option_data) {
-    const Option::Universe universe = address_family_ == AF_INET ?
-        Option::V4 : Option::V6;
-    // Check if mandatory parameters are specified.
+OptionalValue<uint32_t>
+OptionDataParser::extractCode(ConstElementPtr parent) const {
     uint32_t code;
-    std::string name;
-    std::string data;
     try {
         code = uint32_values_->getParam("code");
-        name = string_values_->getParam("name");
-        data = string_values_->getParam("data");
-    } catch (const std::exception& ex) {
-        isc_throw(DhcpConfigError,
-                  ex.what() << "(" << option_data->getPosition() << ")");
+
+    } catch (const exception& ex) {
+        // The code parameter was not found. Return an unspecified
+        // value.
+        return (OptionalValue<uint32_t>());
     }
-    // Check parameters having default values.
-    std::string space = string_values_->getOptionalParam("space", universe == Option::V4 ?
-                                                         "dhcp4" : "dhcp6");
-    bool csv_format = boolean_values_->getOptionalParam("csv-format", false);
 
-    // Option code is held in the uint32_t storage but is supposed to
-    // be uint16_t value. We need to check that value in the configuration
-    // does not exceed range of uint8_t for DHCPv4, uint16_t for DHCPv6 and
-    // is not zero.
     if (code == 0) {
         isc_throw(DhcpConfigError, "option code must not be zero "
-                  "(" << uint32_values_->getPosition("code") << ")");
+                  "(" << uint32_values_->getPosition("code", parent) << ")");
 
-    } else if (universe == Option::V4 &&
+    } else if (address_family_ == AF_INET &&
                code > std::numeric_limits<uint8_t>::max()) {
         isc_throw(DhcpConfigError, "invalid option code '" << code
-                << "', it must not exceed '"
+                << "', it must not be greater than '"
                   << static_cast<int>(std::numeric_limits<uint8_t>::max())
-                  << "' (" << uint32_values_->getPosition("code") << ")");
+                  << "' (" << uint32_values_->getPosition("code", parent)
+                  << ")");
 
-    } else if (universe == Option::V6 &&
+    } else if (address_family_ == AF_INET6 &&
                code > std::numeric_limits<uint16_t>::max()) {
         isc_throw(DhcpConfigError, "invalid option code '" << code
                 << "', it must not exceed '"
                   << std::numeric_limits<uint16_t>::max()
-                  << "' (" << uint32_values_->getPosition("code") << ")");
+                  << "' (" << uint32_values_->getPosition("code", parent)
+                  << ")");
+
+    }
+
+    return (OptionalValue<uint32_t>(code, OptionalValueState(true)));
+}
 
+OptionalValue<std::string>
+OptionDataParser::extractName(ConstElementPtr parent) const {
+    std::string name;
+    try {
+        name = string_values_->getParam("name");
+
+    } catch (...) {
+        return (OptionalValue<std::string>());
     }
 
-    // Check that the option name is non-empty and does not contain spaces
-    if (name.empty()) {
-        isc_throw(DhcpConfigError, "name of the option with code '"
-                  << code << "' is empty ("
-                  << string_values_->getPosition("name") << ")");
-    } else if (name.find(" ") != std::string::npos) {
+    if (name.find(" ") != std::string::npos) {
         isc_throw(DhcpConfigError, "invalid option name '" << name
                   << "', space character is not allowed ("
-                  << string_values_->getPosition("name") << ")");
+                  << string_values_->getPosition("name", parent) << ")");
     }
 
-    if (!OptionSpace::validateName(space)) {
-        isc_throw(DhcpConfigError, "invalid option space name '"
-                << space << "' specified for option '"
-                << name << "', code '" << code
-                  << "' (" << string_values_->getPosition("space") << ")");
+    return (OptionalValue<std::string>(name, OptionalValueState(true)));
+}
+
+std::string
+OptionDataParser::extractData() const {
+    std::string data;
+    try {
+        data = string_values_->getParam("data");
+
+    } catch (...) {
+        // The "data" parameter was not found. Return an empty value.
+        return (data);
     }
 
-    // Find the Option Definition for the option by its option code.
-    // findOptionDefinition will throw if not found, no need to test.
-    // Find the definition for the option by its code. This function
-    // may throw so we catch exceptions to log the culprit line of the
-    // configuration.
-    OptionDefinitionPtr def;
+    return (data);
+}
+
+OptionalValue<bool>
+OptionDataParser::extractCSVFormat() const {
+    bool csv_format = true;
     try {
-        def = findServerSpaceOptionDefinition(space, code);
+        csv_format = boolean_values_->getParam("csv-format");
 
-    } catch (const std::exception& ex) {
-        isc_throw(DhcpConfigError, ex.what()
-                  << " (" << string_values_->getPosition("space") << ")");
+    } catch (...) {
+        return (OptionalValue<bool>(csv_format));
+    }
+
+    return (OptionalValue<bool>(csv_format, OptionalValueState(true)));
+}
+
+std::string
+OptionDataParser::extractSpace() const {
+    std::string space = address_family_ == AF_INET ? "dhcp4" : "dhcp6";
+    try {
+        space = string_values_->getParam("space");
+
+    } catch (...) {
+        return (space);
+    }
+
+    try {
+        if (!OptionSpace::validateName(space)) {
+            isc_throw(DhcpConfigError, "invalid option space name '"
+                      << space << "'");
+        }
+
+        if ((space == DHCP4_OPTION_SPACE) && (address_family_ == AF_INET6)) {
+            isc_throw(DhcpConfigError, "'" << DHCP4_OPTION_SPACE
+                      << "' option space name is reserved for DHCPv4 server");
+
+        } else if ((space == DHCP6_OPTION_SPACE) &&
+                   (address_family_ == AF_INET)) {
+            isc_throw(DhcpConfigError, "'" << DHCP6_OPTION_SPACE
+                      << "' option space name is reserved for DHCPv6 server");
+        }
+
+    } catch (std::exception& ex) {
+        // Append position of the option space parameter. Note, that in the case
+        // when 'space' was not specified a default value will be used and we
+        // should never get here. Therefore, it is ok to call getPosition for
+        // the space parameter here as this parameter will always be specified.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << string_values_->getPosition("space") << ")");
+    }
+
+    return (space);
+}
+
+template<typename SearchKey>
+OptionDefinitionPtr
+OptionDataParser::findOptionDefinition(const std::string& option_space,
+                                       const SearchKey& search_key) const {
+    const Option::Universe u = address_family_ == AF_INET ?
+        Option::V4 : Option::V6;
+    OptionDefinitionPtr def;
+
+    if ((option_space == DHCP4_OPTION_SPACE) ||
+        (option_space == DHCP6_OPTION_SPACE)) {
+        def = LibDHCP::getOptionDef(u, search_key);
+
+    }
+
+    if (!def) {
+        // Check if this is a vendor-option. If it is, get vendor-specific
+        // definition.
+        uint32_t vendor_id = CfgOption::optionSpaceToVendorId(option_space);
+        if (vendor_id) {
+            def = LibDHCP::getVendorOptionDef(u, vendor_id, search_key);
+        }
     }
+
     if (!def) {
-        // If we are not dealing with a standard option then we
-        // need to search for its definition among user-configured
-        // options. They are expected to be in the global storage
-        // already.
-        def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()->get(space, code);
-
-        // It's ok if we don't have option format if the option is
-        // specified as hex
-        if (!def && csv_format) {
+        // Check if this is an option specified by a user.
+        def = CfgMgr::instance().getStagingCfg()->getCfgOptionDef()
+            ->get(option_space, search_key);
+    }
+
+    return (def);
+}
+
+void
+OptionDataParser::createOption(ConstElementPtr option_data) {
+    const Option::Universe universe = address_family_ == AF_INET ?
+        Option::V4 : Option::V6;
+
+    OptionalValue<uint32_t> code_param =  extractCode(option_data);
+    OptionalValue<std::string> name_param = extractName(option_data);
+    OptionalValue<bool> csv_format_param = extractCSVFormat();
+    std::string data_param = extractData();
+    std::string space_param = extractSpace();
+
+    // Require that option code or option name is specified.
+    if (!code_param.isSpecified() && !name_param.isSpecified()) {
+        isc_throw(DhcpConfigError, "option data configuration requires one of"
+                  " 'code' or 'name' parameters to be specified"
+                  << " (" << option_data->getPosition() << ")");
+    }
+
+    // Try to find a corresponding option definition using option code or
+    // option name.
+    OptionDefinitionPtr def = code_param.isSpecified() ?
+        findOptionDefinition(space_param, code_param) :
+        findOptionDefinition(space_param, name_param);
+
+    // If there is no definition, the user must not explicitly enable the
+    // use of csv-format.
+    if (!def) {
+        // If explicitly requested that the CSV format is to be used,
+        // the option definition is a must.
+        if (csv_format_param.isSpecified() && csv_format_param) {
             isc_throw(DhcpConfigError, "definition for the option '"
-                      << space << "." << name
-                      << "' having code '" << code
+                      << space_param << "." << name_param
+                      << "' having code '" << code_param
                       << "' does not exist ("
-                      << string_values_->getPosition("name") << ")");
+                      << string_values_->getPosition("name", option_data)
+                      << ")");
+
+        // If there is no option definition and the option code is not specified
+        // we have no means to find the option code.
+        } else if (name_param.isSpecified() && !code_param.isSpecified()) {
+            isc_throw(DhcpConfigError, "definition for the option '"
+                      << space_param << "." << name_param
+                      << " does not exist ("
+                      << string_values_->getPosition("name", option_data));
         }
     }
 
@@ -471,12 +547,15 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
     std::vector<uint8_t> binary;
     std::vector<std::string> data_tokens;
 
-    if (csv_format) {
+    // If the definition is available and csv-format hasn't been explicitly
+    // disabled, we will parse the data as comma separated values.
+    if (def && (!csv_format_param.isSpecified() || csv_format_param)) {
         // If the option data is specified as a string of comma
         // separated values then we need to split this string into
         // individual values - each value will be used to initialize
         // one data field of an option.
-        data_tokens = isc::util::str::tokens(data, ",");
+        data_tokens = isc::util::str::tokens(data_param, ",");
+
     } else {
         // Otherwise, the option data is specified as a string of
         // hexadecimal digits that we have to turn into binary format.
@@ -484,76 +563,70 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
             // The decodeHex function expects that the string contains an
             // even number of digits. If we don't meet this requirement,
             // we have to insert a leading 0.
-            if (!data.empty() && data.length() % 2) {
-                data = data.insert(0, "0");
+            if (!data_param.empty() && data_param.length() % 2) {
+                data_param = data_param.insert(0, "0");
             }
-            util::encode::decodeHex(data, binary);
+            util::encode::decodeHex(data_param, binary);
         } catch (...) {
             isc_throw(DhcpConfigError, "option data is not a valid"
-                      << " string of hexadecimal digits: " << data
-                      << " (" << string_values_->getPosition("data") << ")");
+                      << " string of hexadecimal digits: " << data_param
+                      << " ("
+                      << string_values_->getPosition("data", option_data)
+                      << ")");
         }
     }
 
     OptionPtr option;
     if (!def) {
-        if (csv_format) {
-            isc_throw(DhcpConfigError, "the CSV option data format can be"
-                      " used to specify values for an option that has a"
-                      " definition. The option with code " << code
-                      << " does not have a definition ("
-                      << boolean_values_->getPosition("csv-format") << ")");
-        }
-
         // @todo We have a limited set of option definitions initalized at
         // the moment.  In the future we want to initialize option definitions
         // for all options.  Consequently an error will be issued if an option
         // definition does not exist for a particular option code. For now it is
         // ok to create generic option if definition does not exist.
-        OptionPtr option(new Option(universe,
-                                    static_cast<uint16_t>(code), binary));
+        OptionPtr option(new Option(universe, static_cast<uint16_t>(code_param),
+                                    binary));
         // The created option is stored in option_descriptor_ class member
         // until the commit stage when it is inserted into the main storage.
         // If an option with the same code exists in main storage already the
         // old option is replaced.
         option_descriptor_.option_ = option;
         option_descriptor_.persistent_ = false;
+
     } else {
 
-        // Option name should match the definition. The option name
-        // may seem to be redundant but in the future we may want
-        // to reference options and definitions using their names
-        // and/or option codes so keeping the option name in the
-        // definition of option value makes sense.
-        if (def->getName() != name) {
+        // Option name is specified it should match the name in the definition.
+        if (name_param.isSpecified() && (def->getName() != name_param.get())) {
             isc_throw(DhcpConfigError, "specified option name '"
-                      << name << "' does not match the "
-                      << "option definition: '" << space
+                      << name_param << "' does not match the "
+                      << "option definition: '" << space_param
                       << "." << def->getName() << "' ("
-                      << string_values_->getPosition("name") << ")");
+                      << string_values_->getPosition("name", option_data)
+                      << ")");
         }
 
         // Option definition has been found so let's use it to create
         // an instance of our option.
         try {
-            OptionPtr option = csv_format ?
-                def->optionFactory(universe, code, data_tokens) :
-                def->optionFactory(universe, code, binary);
+            OptionPtr option =
+                !csv_format_param.isSpecified() || csv_format_param ?
+                def->optionFactory(universe, def->getCode(), data_tokens) :
+                def->optionFactory(universe, def->getCode(), binary);
             OptionDescriptor desc(option, false);
             option_descriptor_.option_ = option;
             option_descriptor_.persistent_ = false;
 
         } catch (const isc::Exception& ex) {
             isc_throw(DhcpConfigError, "option data does not match"
-                      << " option definition (space: " << space
-                      << ", code: " << code << "): "
+                      << " option definition (space: " << space_param
+                      << ", code: " << def->getCode() << "): "
                       << ex.what() << " ("
-                      << string_values_->getPosition("data") << ")");
+                      << string_values_->getPosition("data", option_data)
+                      << ")");
         }
     }
 
     // All went good, so we can set the option space name.
-    option_space_ = space;
+    option_space_ = space_param;
 }
 
 // **************************** OptionDataListParser *************************

+ 61 - 11
src/lib/dhcpsrv/dhcp_parsers.h

@@ -25,6 +25,7 @@
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
+#include <util/optional_value.h>
 
 #include <boost/shared_ptr.hpp>
 
@@ -109,15 +110,20 @@ public:
     /// element holding a particular value is located.
     ///
     /// @param name is the name of the parameter which position is desired.
+    /// @param parent Pointer to a data element which position should be
+    /// returned when position of the specified parameter is not found.
     ///
     /// @return Position of the data element or the position holding empty
     /// file name and two zeros if the position hasn't been specified for the
     /// particular value.
-    const data::Element::Position& getPosition(const std::string& name) const {
+    const data::Element::Position&
+    getPosition(const std::string& name, const data::ConstElementPtr parent =
+                data::ConstElementPtr()) const {
         typename std::map<std::string, data::Element::Position>::const_iterator
             pos = positions_.find(name);
         if (pos == positions_.end()) {
-            return (data::Element::ZERO_POSITION());
+            return (parent ? parent->getPosition() :
+                    data::Element::ZERO_POSITION());
         }
 
         return (pos->second);
@@ -538,24 +544,26 @@ public:
 
     /// @brief virtual destructor to ensure orderly destruction of derivations.
     virtual ~OptionDataParser(){};
+private:
 
-protected:
-    /// @brief Finds an option definition within the server's option space
+    /// @brief Finds an option definition within an option space
     ///
     /// Given an option space and an option code, find the correpsonding
-    /// option defintion within the server's option defintion storage.
+    /// option defintion within the option defintion storage.
     ///
     /// @param option_space name of the parameter option space
-    /// @param option_code numeric value of the parameter to find
+    /// @param search_key an option code or name to be used to lookup the
+    /// option definition.
+    /// @tparam A numeric type for searching using an option code or the
+    /// string for searching using the option name.
+    ///
     /// @return OptionDefintionPtr of the option defintion or an
     /// empty OptionDefinitionPtr if not found.
     /// @throw DhcpConfigError if the option space requested is not valid
     /// for this server.
-    virtual OptionDefinitionPtr
-    findServerSpaceOptionDefinition(const std::string& option_space,
-                                    const uint32_t option_code) const;
-
-private:
+    template<typename SearchKey>
+    OptionDefinitionPtr findOptionDefinition(const std::string& option_space,
+                                             const SearchKey& search_key) const;
 
     /// @brief Create option instance.
     ///
@@ -574,6 +582,48 @@ private:
     /// are invalid.
     void createOption(isc::data::ConstElementPtr option_data);
 
+    /// @brief Retrieves parsed option code as an optional value.
+    ///
+    /// @param parent A data element holding full option data configuration.
+    /// It is used here to log a position if the element holding a code
+    /// is not specified and its position is therefore unavailable.
+    ///
+    /// @return Option code, possibly unspecified.
+    /// @throw DhcpConfigError if option code is invalid.
+    util::OptionalValue<uint32_t>
+    extractCode(data::ConstElementPtr parent) const;
+
+    /// @brief Retrieves parsed option name as an optional value.
+    ///
+    /// @param parent A data element holding full option data configuration.
+    /// It is used here to log a position if the element holding a name
+    /// is not specified and its position is therefore unavailable.
+    ///
+    /// @return Option name, possibly unspecified.
+    /// @throw DhcpConfigError if option name is invalid.
+    util::OptionalValue<std::string>
+    extractName(data::ConstElementPtr parent) const;
+
+    /// @brief Retrieves csv-format parameter as an optional value.
+    ///
+    /// @return Value of the csv-format parameter, possibly unspecified.
+    util::OptionalValue<bool> extractCSVFormat() const;
+
+    /// @brief Retrieves option data as a string.
+    ///
+    /// @return Option data as a string. It will return empty string if
+    /// option data is unspecified.
+    std::string extractData() const;
+
+    /// @brief Retrieves option space name.
+    ///
+    /// If option space name is not specified in the configuration the
+    /// 'dhcp4' or 'dhcp6' option space name is returned, depending on
+    /// the universe specified in the parser context.
+    ///
+    /// @return Option space name.
+    std::string extractSpace() const;
+
     /// Storage for boolean values.
     BooleanStoragePtr boolean_values_;
 

+ 0 - 4
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -83,10 +83,6 @@ of active interfaces. This doesn't prevent the server from listening to
 the DHCP traffic through open sockets, but will rather be used by Interface
 Manager to select active interfaces when sockets are re-opened.
 
-% DHCPSRV_CFGMGR_DELETE_SUBNET6 deleting all IPv6 subnets
-A debug message noting that the DHCP configuration manager has deleted all IPv6
-subnets in its database.
-
 % DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
 This debug message is output when the DHCP configuration manager has received
 a request for an IPv4 subnet for the specified address, but no such

+ 90 - 8
src/lib/dhcpsrv/host.cc

@@ -15,36 +15,57 @@
 #include <dhcpsrv/host.h>
 #include <util/strutil.h>
 #include <exceptions/exceptions.h>
+#include <sstream>
 
 namespace isc {
 namespace dhcp {
 
-IPv6Resrv::IPv6Resrv(const asiolink::IOAddress& prefix,
+IPv6Resrv::IPv6Resrv(const Type& type,
+                     const asiolink::IOAddress& prefix,
                      const uint8_t prefix_len)
-    : prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
+    : type_(type), prefix_(asiolink::IOAddress("::")), prefix_len_(128) {
     // Validate and set the actual values.
-    set(prefix, prefix_len);
+    set(type, prefix, prefix_len);
 }
 
 void
-IPv6Resrv::set(const asiolink::IOAddress& prefix, const uint8_t prefix_len) {
+IPv6Resrv::set(const Type& type, const asiolink::IOAddress& prefix,
+               const uint8_t prefix_len) {
     if (!prefix.isV6() || prefix.isV6Multicast()) {
         isc_throw(isc::BadValue, "invalid prefix '" << prefix
-                  << " for new IPv6 reservation");
+                  << "' for new IPv6 reservation");
 
     } else if (prefix_len > 128) {
         isc_throw(isc::BadValue, "invalid prefix length '"
                   << static_cast<int>(prefix_len)
                   << "' for new IPv6 reservation");
+
+    } else if ((type == TYPE_NA) && (prefix_len != 128)) {
+        isc_throw(isc::BadValue, "invalid prefix length '"
+                  << static_cast<int>(prefix_len)
+                  << "' for reserved IPv6 address, expected 128");
     }
 
+    type_ = type;
     prefix_ = prefix;
     prefix_len_ = prefix_len;
 }
 
+std::string
+IPv6Resrv::toText() const {
+    std::ostringstream s;
+    s << prefix_;
+    // For PD, append prefix length.
+    if (getType() == TYPE_PD) {
+        s << "/" << static_cast<int>(prefix_len_);
+    }
+    return (s.str());
+}
+
 bool
 IPv6Resrv::operator==(const IPv6Resrv& other) const {
-    return (prefix_ == other.prefix_ &&
+    return (type_ == other.type_ &&
+            prefix_ == other.prefix_ &&
             prefix_len_ == other.prefix_len_);
 }
 
@@ -61,12 +82,16 @@ Host::Host(const uint8_t* identifier, const size_t identifier_len,
            const std::string& dhcp4_client_classes,
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
-      ipv6_subnet_id_(ipv6_subnet_id), ipv4_reservation_(ipv4_reservation),
+      ipv6_subnet_id_(ipv6_subnet_id),
+      ipv4_reservation_(asiolink::IOAddress("0.0.0.0")),
        hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
        dhcp6_client_classes_(dhcp6_client_classes) {
 
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_len, identifier_type);
+
+    // Validate and set IPv4 address reservation.
+    setIPv4Reservation(ipv4_reservation);
 }
 
 Host::Host(const std::string& identifier, const std::string& identifier_name,
@@ -76,12 +101,37 @@ Host::Host(const std::string& identifier, const std::string& identifier_name,
            const std::string& dhcp4_client_classes,
            const std::string& dhcp6_client_classes)
     : hw_address_(), duid_(), ipv4_subnet_id_(ipv4_subnet_id),
-      ipv6_subnet_id_(ipv6_subnet_id), ipv4_reservation_(ipv4_reservation),
+      ipv6_subnet_id_(ipv6_subnet_id),
+      ipv4_reservation_(asiolink::IOAddress("0.0.0.0")),
       hostname_(hostname), dhcp4_client_classes_(dhcp4_client_classes),
       dhcp6_client_classes_(dhcp6_client_classes) {
 
     // Initialize HWAddr or DUID
     setIdentifier(identifier, identifier_name);
+
+    // Validate and set IPv4 address reservation.
+    setIPv4Reservation(ipv4_reservation);
+}
+
+const std::vector<uint8_t>&
+Host::getIdentifier() const {
+    if (hw_address_) {
+        return (hw_address_->hwaddr_);
+
+    } else if (duid_) {
+        return (duid_->getDuid());
+
+    }
+    static std::vector<uint8_t> empty_vector;
+    return (empty_vector);
+}
+
+Host::IdentifierType
+Host::getIdentifierType() const {
+    if (hw_address_) {
+        return (IDENT_HWADDR);
+    }
+    return (IDENT_DUID);
 }
 
 void
@@ -118,7 +168,23 @@ Host::setIdentifier(const std::string& identifier, const std::string& name) {
 }
 
 void
+Host::setIPv4Reservation(const asiolink::IOAddress& address) {
+    if (!address.isV4()) {
+        isc_throw(isc::BadValue, "address '" << address << "' is not a valid"
+                  " IPv4 address");
+    }
+    ipv4_reservation_ = address;
+}
+
+
+void
 Host::addReservation(const IPv6Resrv& reservation) {
+    // Check if it is not duplicating existing reservation.
+    if (hasReservation(reservation)) {
+        isc_throw(isc::InvalidOperation, "failed on attempt to add a duplicated"
+                  " host reservation for " << reservation.toText());
+    }
+    // Add it.
     ipv6_reservations_.insert(IPv6ResrvTuple(reservation.getType(),
                                              reservation));
 }
@@ -128,6 +194,22 @@ Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
     return (ipv6_reservations_.equal_range(type));
 }
 
+bool
+Host::hasReservation(const IPv6Resrv& reservation) const {
+    IPv6ResrvRange reservations = getIPv6Reservations(reservation.getType());
+    if (std::distance(reservations.first, reservations.second) > 0) {
+        for (IPv6ResrvIterator it = reservations.first;
+             it != reservations.second; ++it) {
+            if (it->second == reservation) {
+                return (true);
+            }
+        }
+    }
+
+    // No matching reservations found.
+    return (false);
+}
+
 void
 Host::addClientClassInternal(ClientClasses& classes,
                              const std::string& class_name) {

+ 44 - 7
src/lib/dhcpsrv/host.h

@@ -20,6 +20,7 @@
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/subnet_id.h>
+#include <boost/shared_ptr.hpp>
 #include <list>
 #include <map>
 #include <string>
@@ -54,12 +55,14 @@ public:
     /// of 128 is used. This value indicates that the reservation is made
     /// for an IPv6 address.
     ///
+    /// @param type Reservation type: NA or PD.
     /// @param prefix Address or prefix to be reserved.
     /// @param prefix_len Prefix length.
     ///
     /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
     /// multicast address or the prefix length is greater than 128.
-    IPv6Resrv(const asiolink::IOAddress& prefix,
+    IPv6Resrv(const Type& type,
+              const asiolink::IOAddress& prefix,
               const uint8_t prefix_len = 128);
 
     /// @brief Returns prefix for the reservation.
@@ -78,17 +81,22 @@ public:
     ///
     /// @return NA for prefix length equal to 128, PD otherwise.
     Type getType() const {
-        return (prefix_len_ == 128 ? TYPE_NA : TYPE_PD);
+        return (type_);
     }
 
     /// @brief Sets a new prefix and prefix length.
     ///
+    /// @param type Reservation type: NA or PD.
     /// @param prefix New prefix.
     /// @param prefix_len New prefix length.
     ///
     /// @throw isc::BadValue if prefix is not IPv6 prefix, is a
     /// multicast address or the prefix length is greater than 128.
-    void set(const asiolink::IOAddress& prefix, const uint8_t prefix_len);
+    void set(const Type& type, const asiolink::IOAddress& prefix,
+             const uint8_t prefix_len);
+
+    /// @brief Returns information about the reservation in the textual format.
+    std::string toText() const;
 
     /// @brief Equality operator.
     ///
@@ -101,6 +109,8 @@ public:
     bool operator!=(const IPv6Resrv& other) const;
 
 private:
+
+    Type type_;                  ///< Reservation type.
     asiolink::IOAddress prefix_; ///< Prefix
     uint8_t prefix_len_;         ///< Prefix length.
 
@@ -133,7 +143,10 @@ typedef std::pair<IPv6ResrvIterator, IPv6ResrvIterator> IPv6ResrvRange;
 /// interfaces. For the MAC address based reservations, each interface on a
 /// network device maps to a single @c Host object as each @c Host object
 /// contains at most one MAC address. So, it is possible that a single
-/// device is associated with multiple distinct @c Host objects.
+/// device is associated with multiple distinct @c Host objects if the
+/// device has multiple interfaces. Under normal circumstances, a non-mobile
+/// dual stack device using one interface should be represented by a single
+/// @c Host object.
 ///
 /// A DHCPv6 DUID is common for all interfaces on a device. Therefore, for
 /// DUID based reservations a @c Host object may represent a network device with
@@ -282,6 +295,10 @@ public:
         return (duid_);
     }
 
+    const std::vector<uint8_t>& getIdentifier() const;
+
+    IdentifierType getIdentifierType() const;
+
     /// @brief Sets new IPv4 subnet identifier.
     ///
     /// @param ipv4_subnet_id New subnet identifier.
@@ -311,9 +328,9 @@ public:
     /// The new reservation removes a previous reservation.
     ///
     /// @param address Address to be reserved for the client.
-    void setIPv4Reservation(const asiolink::IOAddress& address) {
-        ipv4_reservation_ = address;
-    }
+    ///
+    /// @throw isc::BadValue if the provided address is not an IPv4 address.
+    void setIPv4Reservation(const asiolink::IOAddress& address);
 
     /// @brief Returns reserved IPv4 address.
     ///
@@ -335,6 +352,14 @@ public:
     /// the specified type.
     IPv6ResrvRange getIPv6Reservations(const IPv6Resrv::Type& type) const;
 
+    /// @brief Checks if specified IPv6 reservation exists for the host.
+    ///
+    /// @param reservation A reservation to be checked for the host.
+    ///
+    /// @return true if the reservation already exists for the host, false
+    /// otherwise.
+    bool hasReservation(const IPv6Resrv& reservation) const;
+
     /// @brief Sets new hostname.
     ///
     /// @param hostname New hostname.
@@ -408,6 +433,18 @@ private:
     ClientClasses dhcp6_client_classes_;
 };
 
+/// @brief Pointer to the @c Host object.
+typedef boost::shared_ptr<Host> HostPtr;
+
+/// @brief Const pointer to the @c Host object.
+typedef boost::shared_ptr<const Host> ConstHostPtr;
+
+/// @brief Collection of the const Host objects.
+typedef std::vector<ConstHostPtr> ConstHostCollection;
+
+/// @brief Collection of the @c Host objects.
+typedef std::vector<HostPtr> HostCollection;
+
 }
 }
 

+ 84 - 0
src/lib/dhcpsrv/host_container.h

@@ -0,0 +1,84 @@
+// Copyright (C) 2014 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 HOST_CONTAINER_H
+#define HOST_CONTAINER_H
+
+#include <dhcpsrv/host.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/composite_key.hpp>
+#include <boost/multi_index/mem_fun.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Multi-index container holding host reservations.
+///
+/// This container holds a collection of @c Host objects which can be retrieved
+/// using various parameters. The typical use of this container is to search for
+/// all @c Host objects which are identified by a specified identifier, i.e.
+/// HW address or DUID.
+///
+/// @todo This container will be extended to search for @c Host objects
+/// associated with a specific IPv4 address or IPv6 prefix/length.
+///
+/// @see http://www.boost.org/doc/libs/1_56_0/libs/multi_index/doc/index.html
+typedef boost::multi_index_container<
+    // This container stores pointers to Host objects.
+    HostPtr,
+    // Start specification of indexes here.
+    boost::multi_index::indexed_by<
+        // First index is used to search for the host using one of the
+        // identifiers, i.e. HW address or DUID. The elements of this
+        // index are non-unique because there may be multiple reservations
+        // for the same host belonging to a different subnets.
+        boost::multi_index::ordered_non_unique<
+            // The index comprises actual identifier (HW address or DUID) in
+            // a binary form and a type of the identifier which indicates
+            // that it is HW address or DUID.
+            boost::multi_index::composite_key<
+                // Composite key uses members of the Host class.
+                Host,
+                // Binary identifier is retrieved from the Host class using
+                // a getIdentifier method.
+                boost::multi_index::const_mem_fun<
+                    Host, const std::vector<uint8_t>&,
+                    &Host::getIdentifier
+                >,
+                // Identifier type is retrieved from the Host class using
+                // a getIdentifierType method.
+                boost::multi_index::const_mem_fun<
+                    Host, Host::IdentifierType,
+                    &Host::getIdentifierType
+                >
+            >
+        >
+    >
+> HostContainer;
+
+/// @brief First index type in the @c HostContainer.
+///
+/// This index allows for searching for @c Host objects using an
+/// identifier + identifier type tuple.
+typedef HostContainer::nth_index<0>::type HostContainerIndex0;
+
+/// @brief Results range returned using the @c HostContainerIndex0.
+typedef std::pair<HostContainerIndex0::iterator,
+                  HostContainerIndex0::iterator> HostContainerIndex0Range;
+
+}
+}
+
+#endif // HOST_CONTAINER_H

+ 116 - 0
src/lib/dhcpsrv/host_mgr.cc

@@ -0,0 +1,116 @@
+// Copyright (C) 2014 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 <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host_mgr.h>
+
+namespace {
+
+/// @brief Convenience function returning a pointer to the hosts configuration.
+///
+/// This function is called by the @c HostMgr methods requiring access to the
+/// host reservations specified in the DHCP server configuration.
+///
+/// @return A pointer to the const hosts reservation configuration.
+isc::dhcp::ConstCfgHostsPtr getCfgHosts() {
+    return (isc::dhcp::CfgMgr::instance().getCurrentCfg()->getCfgHosts());
+}
+
+} // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+using namespace isc::asiolink;
+
+boost::scoped_ptr<HostMgr>&
+HostMgr::getHostMgrPtr() {
+    static boost::scoped_ptr<HostMgr> host_mgr_ptr;
+    return (host_mgr_ptr);
+}
+
+void
+HostMgr::create(const std::string&) {
+    getHostMgrPtr().reset(new HostMgr());
+
+    /// @todo Initialize alternate_source here, using the parameter.
+    /// For example: alternate_source.reset(new MysqlHostDataSource(access)).
+}
+
+HostMgr&
+HostMgr::instance() {
+    boost::scoped_ptr<HostMgr>& host_mgr_ptr = getHostMgrPtr();
+    if (!host_mgr_ptr) {
+        create();
+    }
+    return (*host_mgr_ptr);
+}
+
+ConstHostCollection
+HostMgr::getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid) const {
+    ConstHostCollection hosts = getCfgHosts()->getAll(hwaddr, duid);
+    if (alternate_source) {
+        ConstHostCollection hosts_plus = alternate_source->getAll(hwaddr, duid);
+        hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+    }
+    return (hosts);
+}
+
+ConstHostCollection
+HostMgr::getAll4(const IOAddress& address) const {
+    ConstHostCollection hosts = getCfgHosts()->getAll4(address);
+    if (alternate_source) {
+        ConstHostCollection hosts_plus = alternate_source->getAll4(address);
+        hosts.insert(hosts.end(), hosts_plus.begin(), hosts_plus.end());
+    }
+    return (hosts);
+}
+
+ConstHostPtr
+HostMgr::get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+              const DuidPtr& duid) const {
+    ConstHostPtr host = getCfgHosts()->get4(subnet_id, hwaddr, duid);
+    if (!host && alternate_source) {
+        host = alternate_source->get4(subnet_id, hwaddr, duid);
+    }
+    return (host);
+}
+
+ConstHostPtr
+HostMgr::get6(const SubnetID& subnet_id, const DuidPtr& duid,
+               const HWAddrPtr& hwaddr) const {
+    ConstHostPtr host = getCfgHosts()->get6(subnet_id, duid, hwaddr);
+    if (!host && alternate_source) {
+        host = alternate_source->get6(subnet_id, duid, hwaddr);
+    }
+    return (host);
+}
+
+ConstHostPtr
+HostMgr::get6(const IOAddress& prefix, const uint8_t prefix_len) const {
+    ConstHostPtr host = getCfgHosts()->get6(prefix, prefix_len);
+    if (!host && alternate_source) {
+        host = alternate_source->get6(prefix, prefix_len);
+    }
+    return (host);
+}
+
+void
+HostMgr::add(const HostPtr&) {
+    isc_throw(isc::NotImplemented, "HostMgr::add is not implemented");
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace

+ 190 - 0
src/lib/dhcpsrv/host_mgr.h

@@ -0,0 +1,190 @@
+// Copyright (C) 2014 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 HOST_MGR_H
+#define HOST_MGR_H
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Host Manager.
+///
+/// This is a singleton class which provides access to multiple sources of
+/// information about static host reservations. These sources are also referred
+/// to as host data sources. Each source derives (directly or indirectly) from
+/// the @c BaseHostDataSource.
+///
+/// The @c HostMgr is a central point for providing information about the host
+/// reservations. Internally, it relays the queries (calls to the appropriate
+/// methods declared in the @c BaseHostDataSource) to the data sources it is
+/// connected to. The @c HostMgr is always connected to the server's
+/// configuration, accessible through the @c CfgHosts object in the @c CfgMgr.
+/// The @c CfgHosts object holds all reservations specified in the DHCP server
+/// configuration file. If a particular reservation is not found in the
+/// @c CfgHosts object, the @c HostMgr will try to find it using the alternate
+/// host data storage. The alternate host data storage is usually a database
+/// (e.g. SQL database), accessible through a dedicated host data source
+/// object (a.k.a. database backend). This datasource is responsible for
+/// managing the connection with the database and forming appropriate queries
+/// to retrieve (or update) the information about the reservations.
+///
+/// The use of the alternate host data source is optional and usually requires
+/// additional configuration to be specified by the server administrator.
+/// For example, for the SQL database the user's credentials, database address,
+/// and database name are required. The @c HostMgr passes these parameters
+/// to an appropriate datasource which is responsible for opening a connection
+/// and maintaining it.
+///
+/// It is possible to switch to a different alternate data source or disable
+/// the use of the alternate datasource, e.g. as a result of server's
+/// reconfiguration. However, the use of the primary host data source (i.e.
+/// reservations specified in the configuration file) can't be disabled.
+///
+/// @todo Implement alternate host data sources: MySQL, PostgreSQL, etc.
+class HostMgr : public boost::noncopyable, BaseHostDataSource {
+public:
+
+    /// @brief Creates new instance of the @c HostMgr.
+    ///
+    /// If an instance of the @c HostMgr already exists, it will be replaced
+    /// by the new instance. Thus, any instances of the alternate host data
+    /// sources will be dropped.
+    ///
+    /// @param access Host data source access parameters for the alternate
+    /// host data source. It holds "keyword=value" pairs, separated by spaces.
+    /// The supported values are specific to the alternate data source in use.
+    /// However, the "type" parameter will be common and it will specify which
+    /// data source is to be used. Currently, no parameters are supported
+    /// and the parameter is ignored.
+    static void create(const std::string& access = "");
+
+    /// @brief Returns a sole instance of the @c HostMgr.
+    ///
+    /// This method should be used to retrieve an instance of the @c HostMgr
+    /// to be used to gather/manage host reservations. It returns an instance
+    /// of the @c HostMgr created by the @c create method. If such instance
+    /// doesn't exist yet, it is created using the @c create method with the
+    /// default value of the data access string, which configures the host
+    /// manager to not use the alternate host data source.
+    static HostMgr& instance();
+
+    /// @brief Returns all hosts for the specified HW address or DUID.
+    ///
+    /// This method returns all @c Host objects representing reservations for
+    /// the specified HW address or/and DUID as documented in the
+    /// @c BaseHostDataSource::getAll.
+    ///
+    /// It retrieves reservations from both primary and alternate host data
+    /// source as a single collection of @c Host objects, i.e. if matching
+    /// reservations are in both sources, all of them are returned.
+    ///
+    /// Note that returned collection may contain duplicates. It is the
+    /// caller's responsibility to check for duplicates.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL of not available.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected to
+    /// different subnets.
+    ///
+    /// If matching reservations are both in the primary and the alternate
+    /// data source, all of them are returned.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of const @c Host objects.
+    virtual ConstHostCollection
+    getAll4(const asiolink::IOAddress& address) const;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// This method returns a single reservation for the particular host
+    /// (identified by the HW address or DUID) as documented in the
+    /// @c BaseHostDataSource::get4.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) const;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// This method returns a host connected to the IPv6 subnet and identified
+    /// by the HW address or DUID, as described in the
+    /// @c BaseHostDataSource::get6.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) const;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// This method returns a host using specified IPv6 prefix, as described
+    /// in the @c BaseHostDataSource::get6.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Const @c Host object using a specified HW address or DUID.
+    virtual ConstHostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) const;
+
+    /// @brief Adds a new host to the alternate data source.
+    ///
+    /// This method will throw an exception if no alternate data source is
+    /// in use.
+    ///
+    /// @param host Pointer to the new @c Host object being added.
+    virtual void add(const HostPtr& host);
+
+private:
+
+    /// @brief Private default constructor.
+    HostMgr() { }
+
+    /// @brief Pointer to an alternate host data source.
+    ///
+    /// If this pointer is NULL, the source is not in use.
+    boost::scoped_ptr<BaseHostDataSource> alternate_source;
+
+    /// @brief Returns a pointer to the currently used instance of the
+    /// @c HostMgr.
+    static boost::scoped_ptr<HostMgr>& getHostMgrPtr();
+
+};
+}
+}
+
+#endif // HOST_MGR_H

+ 200 - 0
src/lib/dhcpsrv/host_reservation_parser.cc

@@ -0,0 +1,200 @@
+// Copyright (C) 2014 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 <asiolink/io_address.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host_reservation_parser.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+HostReservationParser::HostReservationParser(const SubnetID& subnet_id)
+    : DhcpConfigParser(), subnet_id_(subnet_id) {
+}
+
+void
+HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
+    std::string identifier;
+    std::string identifier_name;
+    std::string hostname;
+
+    // Gather those parameters that are common for both IPv4 and IPv6
+    // reservations.
+    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+        try {
+            if (element.first == "hw-address" || element.first == "duid") {
+                if (!identifier_name.empty()) {
+                    isc_throw(DhcpConfigError, "the 'hw-address' and 'duid'"
+                              " parameters are mutually exclusive");
+                }
+                identifier = element.second->stringValue();
+                identifier_name = element.first;
+
+            } else if (element.first == "hostname") {
+                hostname = element.second->stringValue();
+
+            }
+        } catch (const std::exception& ex) {
+            // Append line number where the error occurred.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << element.second->getPosition() << ")");
+        }
+    }
+
+    try {
+        // hw-address or duid is a must.
+        if (identifier_name.empty()) {
+            isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a requirement"
+                      " parameter for host reservation");
+        }
+
+        // Create a host object from the basic parameters we already parsed.
+        host_.reset(new Host(identifier, identifier_name, SubnetID(0),
+                             SubnetID(0), IOAddress("0.0.0.0"), hostname));
+
+    } catch (const std::exception& ex) {
+        // Append line number where the error occurred.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << reservation_data->getPosition() << ")");
+    }
+}
+
+void
+HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
+    try {
+        CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host_);
+
+    } catch (const std::exception& ex) {
+        // Append line number to the exception string.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << reservation_data->getPosition() << ")");
+    }
+}
+
+HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id)
+    : HostReservationParser(subnet_id) {
+}
+
+void
+HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
+    HostReservationParser::build(reservation_data);
+
+    host_->setIPv4SubnetID(subnet_id_);
+
+    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+        try {
+            if (element.first == "ip-address") {
+                host_->setIPv4Reservation(IOAddress(element.second->
+                                                    stringValue()));
+            }
+        }
+        catch (const std::exception& ex) {
+        // Append line number where the error occurred.
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << reservation_data->getPosition() << ")");
+        }
+    }
+
+    addHost(reservation_data);
+}
+
+HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id)
+    : HostReservationParser(subnet_id) {
+}
+
+void
+HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
+    HostReservationParser::build(reservation_data);
+
+    host_->setIPv6SubnetID(subnet_id_);
+
+    BOOST_FOREACH(ConfigPair element, reservation_data->mapValue()) {
+        if (element.first == "ip-addresses" || element.first == "prefixes") {
+            BOOST_FOREACH(ConstElementPtr prefix_element,
+                          element.second->listValue()) {
+                try {
+                    // For the IPv6 address the prefix length is 128 and the
+                    // value specified in the list is a reserved address.
+                    IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
+                    std::string prefix = prefix_element->stringValue();
+                    uint8_t prefix_len = 128;
+
+                    // If we're dealing with prefixes, instead of addresses,
+                    // we will have to extract the prefix length from the value
+                    // specified in the following format: 2001:db8:2000::/64.
+                    if (element.first == "prefixes") {
+                        // The slash is mandatory for prefixes. If there is no
+                        // slash, return an error.
+                        size_t len_pos  = prefix.find('/');
+                        if (len_pos == std::string::npos) {
+                            isc_throw(DhcpConfigError, "prefix reservation"
+                                      " requires prefix length be specified"
+                                      " in '" << prefix << "'");
+
+                        // If there is nothing after the slash, we should also
+                        // report an error.
+                        } else if (len_pos >= prefix.length() - 1) {
+                            isc_throw(DhcpConfigError, "prefix '" <<  prefix
+                                      << "' requires length after '/'");
+
+                        }
+
+                        // Convert the prefix length from the string to the
+                        // number. Note, that we don't use the uint8_t type
+                        // as the lexical cast would expect a chracter, e.g.
+                        // 'a', instead of prefix length, e.g. '64'.
+                        try {
+                            prefix_len = boost::lexical_cast<
+                                unsigned int>(prefix.substr(len_pos + 1));
+
+                        } catch (const boost::bad_lexical_cast&) {
+                            isc_throw(DhcpConfigError, "prefix length value '"
+                                      << prefix.substr(len_pos + 1)
+                                      << "' is invalid");
+                        }
+
+                        // Remove the  slash character and the prefix length
+                        // from the parsed value.
+                        prefix.erase(len_pos);
+
+                        // Finally, set the reservation type.
+                        resrv_type = IPv6Resrv::TYPE_PD;
+                    }
+
+                    // Create a reservation for an address or prefix.
+                    host_->addReservation(IPv6Resrv(resrv_type,
+                                                    IOAddress(prefix),
+                                                    prefix_len));
+
+                } catch (const std::exception& ex) {
+                    // Append line number where the error occurred.
+                    isc_throw(DhcpConfigError, ex.what() << " ("
+                              << prefix_element->getPosition() << ")");
+                }
+            }
+        }
+    }
+
+    // This may fail, but the addHost function will handle this on its own.
+    addHost(reservation_data);
+}
+
+} // end of namespace isc::dhcp
+} // end of namespace isc

+ 110 - 0
src/lib/dhcpsrv/host_reservation_parser.h

@@ -0,0 +1,110 @@
+// Copyright (C) 2014 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 HOST_RESERVATION_PARSER_H
+#define HOST_RESERVATION_PARSER_H
+
+#include <cc/data.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/host.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Parser for a single host reservation entry.
+class HostReservationParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet that the host is
+    /// connected to.
+    HostReservationParser(const SubnetID& subnet_id);
+
+    /// @brief Parses a single entry for host reservation.
+    ///
+    /// @param reservation_data Data element holding map with a host
+    /// reservation configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr reservation_data);
+
+    /// @brief Commit, unused.
+    virtual void commit() { }
+
+protected:
+
+    /// @brief Inserts @c host_ object to the staging configuration.
+    ///
+    /// This method should be called by derived classes to insert the fully
+    /// parsed host reservation configuration to the @c CfgMgr.
+    ///
+    /// @param reservation_data Data element holding host reservation. It
+    /// used by this method to append the line number to the error string.
+    ///
+    /// @throw DhcpConfigError When operation to add a configured host fails.
+    void addHost(isc::data::ConstElementPtr reservation_data);
+
+    /// @brief Identifier of the subnet that the host is connected to.
+    SubnetID subnet_id_;
+
+    /// @brief Holds a pointer to @c Host object representing a parsed
+    /// host reservation configuration.
+    HostPtr host_;
+
+};
+
+/// @brief Parser for a single host reservation for DHCPv4.
+class HostReservationParser4 : public HostReservationParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet that the host is
+    /// connected to.
+    HostReservationParser4(const SubnetID& subnet_id);
+
+    /// @brief Parses a single host reservation for DHCPv4.
+    ///
+    /// @param reservation_data Data element holding map with a host
+    /// reservation configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr reservation_data);
+};
+
+/// @brief Parser for a single host reservation for DHCPv6.
+class HostReservationParser6 : public HostReservationParser {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet that the host is
+    /// connected to.
+    HostReservationParser6(const SubnetID& subnet_id);
+
+    /// @brief Parses a single host reservation for DHCPv6.
+    ///
+    /// @param reservation_data Data element holding map with a host
+    /// reservation configuration.
+    ///
+    /// @throw DhcpConfigError If the configuration is invalid.
+    virtual void build(isc::data::ConstElementPtr reservation_data);
+};
+
+
+}
+} // end of namespace isc
+
+#endif // HOST_RESERVATION_PARSER_H

+ 34 - 1
src/lib/dhcpsrv/libdhcpsrv.dox

@@ -81,6 +81,39 @@ one that occurred before it etc.
 the \ref isc::dhcp::SrvConfig object. Kea developers are actively working
 on migrating the other configuration parameters to it.
 
+@section hostmgr Host Manager
+
+Host Manager implemented by the \ref isc::dhcp::HostMgr is a singleton object
+which provides means to retrieve resources statically assigned to the DHCP
+clients, such as IP addresses, prefixes or hostnames. The statically assigned
+resources are called reservations (or host reservations) and they are
+represented in the code by the \ref isc::dhcp::Host class.
+
+The reservations can be specified in the configuration file or in some
+other storage (typically in a database). A dedicated object, called
+host data source, is needed to retrieve the host reservations from the
+database. This object must implement the \ref isc::dhcp::BaseHostDataSource
+interface and its implementation is specific to the type of storage
+holding the reservations. For example, the host data source managing
+host reservations in the MySQL database is required to establish
+connection to the MySQL databse and issue specific queries. Once
+implemented, the \ref isc::dhcp::HostMgr::create method must be updated
+to create an instance of this datasource. Note, that this instance is
+created as "alternate host data source" as opposed to the primary data
+source which returns host reservations specified in the configuration file.
+The primary data source is implemented internally in the
+\ref isc::dhcp::HostMgr and uses the configuration data structures held by
+the \ref isc::dhcp::CfgMgr to retrieve the reservations. In general, the
+\ref isc::dhcp::HostMgr first searches for the reservations using the
+primary data source and falls back to the use of alternate data source
+when nothing has been found. For those methods which are meant to return
+multiple reservations (e.g. find all reservations for the particular
+client), the \ref isc::dhcp::HostMgr will use both primary and alternate
+data source (if present) and concatenate results.
+
+For more information about the \ref isc::dhcp::HostMgr please refer to its
+documentation.
+
 @section optionsConfig Options Configuration Information
 
 The \ref isc::dhcp::CfgOption object holds a collection of options being
@@ -117,7 +150,7 @@ that handles allocation of new leases. It takes parameters that the client
 provided (client-id, DUID, subnet, a hint if the user provided one, etc.) and
 then attempts to allocate a lease.
 
-There is no single best soluction to the address assignment problem. Server
+There is no single best solution to the address assignment problem. Server
 is expected to pick an address from its available pools is currently not used.
 There are many possible algorithms that can do that, each with its own advantages
 and drawbacks. This allocation engine must provide robust operation is radically

+ 5 - 3
src/lib/dhcpsrv/srv_config.cc

@@ -26,12 +26,14 @@ namespace dhcp {
 
 SrvConfig::SrvConfig()
     : sequence_(0), cfg_option_def_(new CfgOptionDef()),
-      cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()) {
+      cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()),
+      cfg_subnets6_(new CfgSubnets6()), cfg_hosts_(new CfgHosts()) {
 }
 
 SrvConfig::SrvConfig(const uint32_t sequence)
     : sequence_(sequence), cfg_option_def_(new CfgOptionDef()),
-      cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()) {
+      cfg_option_(new CfgOption()), cfg_subnets4_(new CfgSubnets4()),
+      cfg_subnets6_(new CfgSubnets6()), cfg_hosts_(new CfgHosts()) {
 }
 
 std::string
@@ -49,7 +51,7 @@ SrvConfig::getConfigSummary(const uint32_t selection) const {
     }
 
     if ((selection & CFGSEL_SUBNET6) == CFGSEL_SUBNET6) {
-        subnets_num = CfgMgr::instance().getSubnets6()->size();
+        subnets_num = getCfgSubnets6()->getAll()->size();
         if (subnets_num > 0) {
             s << "added IPv6 subnets: " << subnets_num;
         } else {

+ 43 - 0
src/lib/dhcpsrv/srv_config.h

@@ -15,10 +15,12 @@
 #ifndef DHCPSRV_CONFIG_H
 #define DHCPSRV_CONFIG_H
 
+#include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfg_option_def.h>
 #include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/cfg_subnets6.h>
 #include <dhcpsrv/logging_info.h>
 #include <boost/shared_ptr.hpp>
 #include <vector>
@@ -201,6 +203,38 @@ public:
         return (cfg_subnets4_);
     }
 
+    /// @brief Returns pointer to non-const object holding subnets configuration
+    /// for DHCPv6.
+    ///
+    /// @return Pointer to the object holding subnets configuration for DHCPv4.
+    CfgSubnets6Ptr getCfgSubnets6() {
+        return (cfg_subnets6_);
+    }
+
+    /// @brief Returns pointer to const object holding subnets configuration for
+    /// DHCPv4.
+    ///
+    /// @return Pointer to the object holding subnets configuration for DHCPv6.
+    ConstCfgSubnets6Ptr getCfgSubnets6() const {
+        return (cfg_subnets6_);
+    }
+
+    /// @brief Returns pointer to the non-const objects representing host
+    /// reservations for different IPv4 and IPv6 subnets.
+    ///
+    /// @return Pointer to the non-const object holding host reservations.
+    CfgHostsPtr getCfgHosts() {
+        return (cfg_hosts_);
+    }
+
+    /// @brief Returns pointer to the const objects representing host
+    /// reservations for different IPv4 and IPv6 subnets.
+    ///
+    /// @return Pointer to the const object holding host reservations.
+    ConstCfgHostsPtr getCfgHosts() const {
+        return (cfg_hosts_);
+    }
+
     //@}
 
     /// @brief Copies the currnet configuration to a new configuration.
@@ -305,6 +339,15 @@ private:
     /// @brief Pointer to subnets configuration for IPv4.
     CfgSubnets4Ptr cfg_subnets4_;
 
+    /// @brief Pointer to subnets configuration for IPv4.
+    CfgSubnets6Ptr cfg_subnets6_;
+
+    /// @brief Pointer to the configuration for hosts reservation.
+    ///
+    /// This object holds a list of @c Host objects representing host
+    /// reservations for different IPv4 and IPv6 subnets.
+    CfgHostsPtr cfg_hosts_;
+
 };
 
 /// @name Pointers to the @c SrvConfig object.

+ 75 - 0
src/lib/dhcpsrv/subnet_selector.h

@@ -0,0 +1,75 @@
+// Copyright (C) 2014 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 SUBNET_SELECTOR_H
+#define SUBNET_SELECTOR_H
+
+#include <asiolink/io_address.h>
+#include <dhcp/classify.h>
+#include <dhcp/option.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Subnet selector used to specify parameters used to select a subnet.
+///
+/// This structure holds various parameters extracted from a packet sent
+/// by a DHCP client used to select the subnet for the client. This selector
+/// is common for IPv4 and IPv6 subnets.
+struct SubnetSelector {
+    /// @name DHCPv4 specific parameters.
+    //@{
+    /// @brief ciaddr from the client's message.
+    asiolink::IOAddress ciaddr_;
+    /// @brief giaddr from the client's message.
+    asiolink::IOAddress giaddr_;
+    //@}
+
+    /// @name DHCPv6 specific parameters.
+    //@{
+    /// @brief Interface id option.
+    OptionPtr interface_id_;
+    /// @brief First relay link address.
+    asiolink::IOAddress first_relay_linkaddr_;
+    //@}
+
+    /// @brief Address on which the message was received.
+    asiolink::IOAddress local_address_;
+    /// @brief Source address of the message.
+    asiolink::IOAddress remote_address_;
+    /// @brief Classes that the client belongs to.
+    ClientClasses client_classes_;
+    /// @brief Name of the interface on which the message was received.
+    std::string iface_name_;
+
+    /// @brief Default constructor.
+    ///
+    /// Sets the default values for the @c Selector.
+    SubnetSelector()
+        : ciaddr_(asiolink::IOAddress("0.0.0.0")),
+          giaddr_(asiolink::IOAddress("0.0.0.0")),
+          interface_id_(),
+          first_relay_linkaddr_(asiolink::IOAddress("::")),
+          local_address_(asiolink::IOAddress("0.0.0.0")),
+          remote_address_(asiolink::IOAddress("0.0.0.0")),
+          client_classes_(), iface_name_(std::string()) {
+    }
+};
+
+
+}
+}
+
+#endif // SUBNET_SELECTOR_H

+ 4 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -55,10 +55,12 @@ libdhcpsrv_unittests_SOURCES  = run_unittests.cc
 libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
 libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
 libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_hosts_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_iface_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_option_def_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfg_subnets4_unittest.cc
+libdhcpsrv_unittests_SOURCES += cfg_subnets6_unittest.cc
 libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file4_unittest.cc
 libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc
@@ -66,7 +68,9 @@ libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc
 libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc
 libdhcpsrv_unittests_SOURCES += daemon_unittest.cc
 libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_reservation_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc

+ 9 - 6
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc

@@ -91,6 +91,8 @@ public:
     /// in many tests, initializes cfg_mgr configuration and creates
     /// lease database.
     AllocEngine6Test() {
+        CfgMgr::instance().clear();
+
         duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
         iaid_ = 42;
 
@@ -115,7 +117,6 @@ public:
     void initSubnet(const IOAddress& subnet, const IOAddress& pool_start,
                     const IOAddress& pool_end) {
         CfgMgr& cfg_mgr = CfgMgr::instance();
-        cfg_mgr.deleteSubnets6();
 
         subnet_ = Subnet6Ptr(new Subnet6(subnet, 56, 1, 2, 3, 4));
         pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, pool_start, pool_end));
@@ -125,7 +126,8 @@ public:
         pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD, subnet, 56, 64));
         subnet_->addPool(pd_pool_);
 
-        cfg_mgr.addSubnet6(subnet_);
+        cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+        cfg_mgr.commit();
 
     }
 
@@ -855,13 +857,13 @@ TEST_F(AllocEngine6Test, outOfAddresses6) {
 
     IOAddress addr("2001:db8:1::ad");
     CfgMgr& cfg_mgr = CfgMgr::instance();
-    cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+    cfg_mgr.clear(); // Get rid of the default test configuration
 
     // Create configuration similar to other tests, but with a single address pool
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
     subnet_->addPool(pool_);
-    cfg_mgr.addSubnet6(subnet_);
+    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
 
     // Just a different duid
     DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
@@ -939,13 +941,14 @@ TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
 
     IOAddress addr("2001:db8:1::ad");
     CfgMgr& cfg_mgr = CfgMgr::instance();
-    cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+    cfg_mgr.clear(); // Get rid of the default test configuration
 
     // Create configuration similar to other tests, but with a single address pool
     subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
     pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
     subnet_->addPool(pool_);
-    cfg_mgr.addSubnet6(subnet_);
+    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
+    cfg_mgr.commit();
 
     // Let's create an expired lease
     DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));

+ 405 - 0
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -0,0 +1,405 @@
+// Copyright (C) 2014 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 <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/host.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for testing @c CfgHost object holding
+/// host reservations specified in the configuration file.
+class CfgHostsTest : public ::testing::Test {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor allocates a collection of @c HWAddr and @c DuidPtr
+    /// objects used by the unit tests.
+    ///
+    /// The allocated HW addresses use the following pattern: 01:02:0A:BB:03:XX
+    /// where XX is a number between 0 and 0x32. All of them are of the
+    /// HTYPE_ETHER type.
+    ///
+    /// The allocated DUID LLTs use the following pattern:
+    /// 01:02:03:04:05:06:07:08:09:0A:XX where the XX is a number between
+    /// 0 and 0x32.
+    CfgHostsTest();
+
+    /// @brief Increases last byte of an address.
+    ///
+    /// @param address Address to be increased.
+    IOAddress increase(const IOAddress& address, const uint8_t num) const;
+
+    /// @brief Collection of HW address objects allocated for unit tests.
+    std::vector<HWAddrPtr> hwaddrs_;
+    /// @brief Collection of DUIDs allocated for unit tests.
+    std::vector<DuidPtr> duids_;
+};
+
+CfgHostsTest::CfgHostsTest() {
+    const uint8_t mac_template[] = {
+        0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+    };
+    for (int i = 0; i < 50; ++i) {
+        std::vector<uint8_t> vec(mac_template,
+                                 mac_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+        hwaddrs_.push_back(hwaddr);
+    }
+
+    const uint8_t duid_template[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+    };
+    for (int i = 0; i < 50; ++i) {
+        std::vector<uint8_t> vec(duid_template,
+                                 duid_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        DuidPtr duid(new DUID(vec));
+        duids_.push_back(duid);
+    }
+}
+
+IOAddress
+CfgHostsTest::increase(const IOAddress& address, const uint8_t num) const {
+    std::vector<uint8_t> vec = address.toBytes();
+    if (!vec.empty()) {
+        vec[vec.size() - 1] += num;
+        return (IOAddress::fromBytes(address.getFamily(), &vec[0]));
+    }
+    return (address);
+}
+
+// This test checks that hosts with unique HW addresses and DUIDs can be
+// retrieved from the host configuration.
+TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
+    CfgHosts cfg;
+    // Add 25 hosts identified by HW address and 25 hosts identified by
+    // DUID. They are added to different subnets.
+    for (int i = 0; i < 25; ++i) {
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(i % 10 + 1), SubnetID(i % 5 + 1),
+                                 IOAddress("192.0.2.5"))));
+
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(i % 5 + 1), SubnetID(i % 10 + 1),
+                                 IOAddress("192.0.2.10"))));
+
+    }
+
+    // Try to retrieve each added reservation using HW address and DUID. Do it
+    // in the reverse order to make sure that the order doesn't matter.
+    for (int i = 24; i >= 0; --i) {
+        // Get host identified by HW address. The DUID is non-zero but it
+        // points to a host for which the reservation hasn't been added.
+        HostCollection hosts = cfg.getAll(hwaddrs_[i], duids_[i + 25]);
+        ASSERT_EQ(1, hosts.size());
+        EXPECT_EQ(i % 10 + 1, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+
+        // Get host identified by DUID. The HW address is non-null but it
+        // points to a host for which the reservation hasn't been added.
+        hosts = cfg.getAll(hwaddrs_[i + 25], duids_[i]);
+        ASSERT_EQ(1, hosts.size());
+        EXPECT_EQ(i % 5 + 1, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
+    }
+
+    // Make sure that the reservations do not exist for the hardware addresses
+    // and DUIDs from the range of 25 to 49.
+    for (int i = 49; i >= 25; --i) {
+        EXPECT_TRUE(cfg.getAll(hwaddrs_[i]).empty());
+        EXPECT_TRUE(cfg.getAll(HWAddrPtr(), duids_[i]).empty());
+    }
+}
+
+// This test verifies that the host can be added to multiple subnets and
+// that the getAll message retrieves all instances of the host.
+TEST_F(CfgHostsTest, getAllRepeatingHosts) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+        // Add two hosts, using the same HW address to two distnict subnets.
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(1), SubnetID(2),
+                                 IOAddress("192.0.2.5"))));
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(2), SubnetID(3),
+                                 IOAddress("10.0.0.5"))));
+
+        // Add two hosts, using the same DUID to two distnict subnets.
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(1), SubnetID(2),
+                                 IOAddress("192.0.2.10"))));
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(2), SubnetID(3),
+                                 IOAddress("10.0.2.10"))));
+    }
+
+    // Verify that hosts can be retrieved.
+    for (int i = 0; i < 25; ++i) {
+        // Get host by HW address. The DUID is non-null but the reservation
+        // should be returned for the HW address because there are no
+        // reservations for the DUIDs from the range of 25 to 49.
+        HostCollection hosts = cfg.getAll(hwaddrs_[i], duids_[i + 25]);
+        ASSERT_EQ(2, hosts.size());
+        EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ("192.0.2.5", hosts[0]->getIPv4Reservation().toText());
+        EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+        EXPECT_EQ("10.0.0.5", hosts[1]->getIPv4Reservation().toText());
+
+        // Get host by DUID. The HW address is non-null but the reservation
+        // should be returned for the DUID because there are no
+        // reservations for the HW addresses from the range of 25 to 49.
+        hosts = cfg.getAll(hwaddrs_[i + 25], duids_[i]);
+        ASSERT_EQ(2, hosts.size());
+        EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ(2, hosts[1]->getIPv4SubnetID());
+    }
+
+    // The getAll function should return empty containers for the HW addresses
+    //  and DUIDs for which the reservations haven't been added.
+    for (int i = 25; i < 50; ++i) {
+        EXPECT_TRUE(cfg.getAll(hwaddrs_[i]).empty());
+        EXPECT_TRUE(cfg.getAll(HWAddrPtr(), duids_[i]).empty());
+    }
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv4 subnet (by subnet id).
+TEST_F(CfgHostsTest, get4) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+        // Add host identified by HW address.
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(1 + i % 2), SubnetID(13),
+                                 increase(IOAddress("192.0.2.5"), i))));
+
+        // Add host identified by DUID.
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(1 + i % 2), SubnetID(13),
+                                 increase(IOAddress("192.0.2.100"), i))));
+    }
+
+    for (int i = 0; i < 25; ++i) {
+        // Retrieve host by HW address. The DUID is non-null but there is no
+        // reservation made for the DUID so the reservation is returned for
+        // HW address.
+        HostPtr host = cfg.get4(SubnetID(1 + i % 2), hwaddrs_[i],
+                                duids_[i + 25]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+        EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+                  host->getIPv4Reservation());
+
+        // Retrieve host by DUID. The HW address is non-null but there is no
+        // reservation made for the HW address so the reservation is returned
+        // for the DUID.
+        host = cfg.get4(SubnetID(1 + i % 2), hwaddrs_[i + 25], duids_[i]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv4SubnetID());
+        EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+                  host->getIPv4Reservation());
+
+    }
+
+    // Also check that when the get4 finds multiple Host objects that fulfil
+    // search criteria, it will throw an exception. Note that the reservation
+    // exist both for hwaddrs_[0] and duids_[0].
+    EXPECT_THROW(cfg.get4(SubnetID(1), hwaddrs_[0], duids_[0]), DuplicateHost);
+}
+
+// This test checks that the reservations can be retrieved for the particular
+// host connected to the specific IPv6 subnet (by subnet id).
+TEST_F(CfgHostsTest, get6) {
+    CfgHosts cfg;
+    // Add hosts.
+    for (int i = 0; i < 25; ++i) {
+        // Add host identified by HW address.
+        HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                        "hw-address",
+                                        SubnetID(10), SubnetID(1 + i % 2),
+                                        IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:1::1"),
+                                                i)));
+        cfg.add(host);
+
+        // Add host identified by DUID.
+        host = HostPtr(new Host(duids_[i]->toText(), "duid",
+                                SubnetID(10), SubnetID(1 + i % 2),
+                                IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:2::1"),
+                                                i)));
+        cfg.add(host);
+    }
+
+    for (int i = 0; i < 25; ++i) {
+        // Retrieve host by HW address. The DUID is non-null but there is no
+        // reservation made for the DUID so the reservation is returned for
+        // HW address.
+        HostPtr host = cfg.get6(SubnetID(1 + i % 2), duids_[i + 25],
+                                hwaddrs_[i]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+        IPv6ResrvRange reservations =
+            host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+        ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+        EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+                  reservations.first->second.getPrefix());
+
+        // Retrieve host by DUID. The HW address is non-null but there is no
+        // reservation made for the HW address so the reservation is returned
+        // for the DUID.
+        host = cfg.get6(SubnetID(1 + i % 2), duids_[i], hwaddrs_[i + 25]);
+        ASSERT_TRUE(host);
+        EXPECT_EQ(1 + i % 2, host->getIPv6SubnetID());
+        reservations = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+        ASSERT_EQ(1, std::distance(reservations.first, reservations.second));
+        EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+                  reservations.first->second.getPrefix());
+    }
+
+    // Also check that when the get6 finds multiple Host objects that fulfil
+    // search criteria, it will throw an exception. Note that the reservation
+    // exist both for hwaddrs_[0] and duids_[0].
+    EXPECT_THROW(cfg.get6(SubnetID(1), duids_[0], hwaddrs_[0]), DuplicateHost);
+}
+
+TEST_F(CfgHostsTest, zeroSubnetIDs) {
+    CfgHosts cfg;
+    ASSERT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                          "hw-address",
+                                          SubnetID(0), SubnetID(0),
+                                          IOAddress("10.0.0.1")))),
+                 isc::BadValue);
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4HWAddr) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(10), SubnetID(0),
+                                             IOAddress("10.0.0.1")))));
+
+    // Try to add the host with the same HW address to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                          "hw-address",
+                                          SubnetID(10), SubnetID(0),
+                                          IOAddress("10.0.0.10")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(11), SubnetID(0),
+                                             IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(10), SubnetID(0),
+                                             IOAddress("10.0.0.1")))));
+
+    // Try to add the host with the same DUID to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                          "duid",
+                                          SubnetID(10), SubnetID(0),
+                                          IOAddress("10.0.0.10")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(11), SubnetID(0),
+                                             IOAddress("10.0.0.10")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(0), SubnetID(1),
+                                             IOAddress("0.0.0.0")))));
+
+    // Try to add the host with the same HW address to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                          "hw-address",
+                                          SubnetID(0), SubnetID(1),
+                                          IOAddress("0.0.0.0")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                             "hw-address",
+                                             SubnetID(0), SubnetID(2),
+                                             IOAddress("0.0.0.0")))));
+}
+
+// This test verifies that it is not possible to add the same Host to the
+// same IPv4 subnet twice.
+TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
+    CfgHosts cfg;
+    // Add a host.
+    ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(0), SubnetID(1),
+                                             IOAddress("0.0.0.0")))));
+
+    // Try to add the host with the same DUID to the same subnet. The fact
+    // that the IP address is different here shouldn't really matter.
+    EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                          "duid",
+                                          SubnetID(0), SubnetID(1),
+                                          IOAddress("0.0.0.0")))),
+                 isc::dhcp::DuplicateHost);
+
+    // Now try to add it to a different subnet. It should go through.
+    EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
+                                             "duid",
+                                             SubnetID(0), SubnetID(2),
+                                             IOAddress("0.0.0.0")))));
+}
+
+
+} // end of anonymous namespace

+ 12 - 1
src/lib/dhcpsrv/tests/cfg_option_def_unittest.cc

@@ -122,7 +122,7 @@ TEST(CfgOptionDefTest, getAll) {
 }
 
 // This test verifies that single option definition is correctly
-// returned with getOptionDef function.
+// returned with get function.
 TEST(CfgOptionDefTest, get) {
     CfgOptionDef cfg;
     // Create a set of option definitions with codes between 100 and 109.
@@ -160,6 +160,12 @@ TEST(CfgOptionDefTest, get) {
         option_name << "option-" << code;
         EXPECT_EQ(option_name.str(), def->getName());
         EXPECT_EQ(code, def->getCode());
+
+        // Try to get the same option definition using an option name as
+        // a key.
+        def = cfg.get("isc", option_name.str());
+        ASSERT_TRUE(def);
+        EXPECT_EQ(code, def->getCode());
     }
 
     // Check that the option codes are valid.
@@ -172,7 +178,12 @@ TEST(CfgOptionDefTest, get) {
         std::ostringstream option_name;
         option_name << "option-other-" << code;
         EXPECT_EQ(option_name.str(), def->getName());
+        EXPECT_EQ(code, def->getCode());
 
+        // Try to get the same option definition using an option name as
+        // a key.
+        def = cfg.get("abcde", option_name.str());
+        ASSERT_TRUE(def);
         EXPECT_EQ(code, def->getCode());
     }
 

+ 11 - 10
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
 #include <gtest/gtest.h>
 
 using namespace isc;
@@ -29,7 +30,7 @@ namespace {
 
 // This test verifies that it is possible to retrieve a subnet using an
 // IP address.
-TEST(CfgSubnets4Test, getSubnetByCiaddr) {
+TEST(CfgSubnets4Test, selectSubnetByCiaddr) {
     CfgSubnets4 cfg;
 
     // Create 3 subnets.
@@ -38,7 +39,7 @@ TEST(CfgSubnets4Test, getSubnetByCiaddr) {
     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
 
     // Make sure that initially the subnets don't exist.
-    CfgSubnets4::Selector selector;
+    SubnetSelector selector;
     selector.ciaddr_ = IOAddress("192.0.2.0");
     // Set some unicast local address to simulate a Renew.
     selector.local_address_ = IOAddress("10.0.0.100");
@@ -70,7 +71,7 @@ TEST(CfgSubnets4Test, getSubnetByCiaddr) {
 
 // This test verifies that when the classification information is specified for
 // subnets, the proper subnets are returned by the subnet configuration.
-TEST(CfgSubnets4Test, getSubnetByClasses) {
+TEST(CfgSubnets4Test, selectSubnetByClasses) {
     CfgSubnets4 cfg;
 
     // Create 3 subnets.
@@ -83,7 +84,7 @@ TEST(CfgSubnets4Test, getSubnetByClasses) {
     cfg.add(subnet2);
     cfg.add(subnet3);
 
-    CfgSubnets4::Selector selector;
+    SubnetSelector selector;
 
     selector.local_address_ = IOAddress("10.0.0.10");
 
@@ -144,7 +145,7 @@ TEST(CfgSubnets4Test, getSubnetByClasses) {
 
 // This test verifies that the relay information can be used to retrieve the
 // subnet.
-TEST(CfgSubnetsTest, getSubnetByRelayAddress) {
+TEST(CfgSubnetsTest, selectSubnetByRelayAddress) {
     CfgSubnets4 cfg;
 
     // Create 3 subnets.
@@ -157,7 +158,7 @@ TEST(CfgSubnetsTest, getSubnetByRelayAddress) {
     cfg.add(subnet2);
     cfg.add(subnet3);
 
-    CfgSubnets4::Selector selector;
+    SubnetSelector selector;
 
     // Check that without relay-info specified, subnets are not selected
     selector.giaddr_ = IOAddress("10.0.0.1");
@@ -183,7 +184,7 @@ TEST(CfgSubnetsTest, getSubnetByRelayAddress) {
 
 // This test verifies that the subnet can be selected for the client
 // using a source address if the client hasn't set the ciaddr.
-TEST(CfgSubnetsTest, getSubnetNoCiaddr) {
+TEST(CfgSubnetsTest, selectSubnetNoCiaddr) {
     CfgSubnets4 cfg;
 
     // Create 3 subnets.
@@ -192,7 +193,7 @@ TEST(CfgSubnetsTest, getSubnetNoCiaddr) {
     Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
 
     // Make sure that initially the subnets don't exist.
-    CfgSubnets4::Selector selector;
+    SubnetSelector selector;
     selector.remote_address_ = IOAddress("192.0.2.0");
     // Set some unicast local address to simulate a Renew.
     selector.local_address_ = IOAddress("10.0.0.100");
@@ -223,7 +224,7 @@ TEST(CfgSubnetsTest, getSubnetNoCiaddr) {
 
 // This test verifies that the subnet can be selected using an address
 // set on the local interface.
-TEST(CfgSubnetsTest, getSubnetInterface) {
+TEST(CfgSubnetsTest, selectSubnetInterface) {
     // The IfaceMgrTestConfig object initializes fake interfaces:
     // eth0, eth1 and lo on the configuration manager. The CfgSubnets4
     // object uses addresses assigned to these fake interfaces to
@@ -231,7 +232,7 @@ TEST(CfgSubnetsTest, getSubnetInterface) {
     IfaceMgrTestConfig config(true);
 
     CfgSubnets4 cfg;
-    CfgSubnets4::Selector selector;
+    SubnetSelector selector;
 
     // Initially, there are no subnets configured, so none of the IPv4
     // addresses assigned to eth0 and eth1 can match with any subnet.

+ 353 - 0
src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc

@@ -0,0 +1,353 @@
+// Copyright (C) 2014 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 <config.h>
+#include <dhcp/classify.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcpsrv/cfg_subnets6.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/subnet_selector.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Generates interface id option.
+///
+/// @param text Interface id in a textual format.
+OptionPtr
+generateInterfaceId(const std::string& text) {
+    OptionBuffer buffer(text.begin(), text.end());
+    return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, buffer));
+}
+
+// This test checks that the subnet can be selected using a relay agent's
+// link address.
+TEST(CfgSubnets6Test, selectSubnetByRelayAddress) {
+    CfgSubnets6 cfg;
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Make sure that none of the subnets is selected when there is no relay
+    // information configured for them.
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Now specify relay information.
+    subnet1->setRelayInfo(IOAddress("2001:db8:ff::1"));
+    subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
+    subnet3->setRelayInfo(IOAddress("2001:db8:ff::3"));
+
+    // And try again. This time relay-info is there and should match.
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::1");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::2");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("2001:db8:ff::3");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+}
+
+// This test checks that the subnet can be selected using an interface
+// name associated with a asubnet.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceName) {
+    CfgSubnets6 cfg;
+
+    // Let's create 3 subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("bar");
+    subnet3->setIface("foobar");
+
+    // Until subnets are added to the configuration, there should be nothing
+    // returned.
+    SubnetSelector selector;
+    selector.iface_name_ = "foo";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add one of the subnets.
+    cfg.add(subnet1);
+
+    // The subnet should be now selected for the interface name "foo".
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+
+    // Check that the interface name is checked even when there is
+    // only one subnet defined: there should be nothing returned when
+    // other interface name is specified.
+    selector.iface_name_ = "bar";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add other subnets.
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // When we specify correct interface names, the subnets should be returned.
+    selector.iface_name_ = "foobar";
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+    selector.iface_name_ = "bar";
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+    // When specifying a non-existing interface the subnet should not be
+    // returned.
+    selector.iface_name_ = "xyzzy";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// This test checks that the subnet can be selected using an Interface ID
+// option inserted by a relay.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceId) {
+    CfgSubnets6 cfg;
+
+    // Create 3 subnets.
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    // Create Interface-id options used in subnets 1,2, and 3
+    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+    OptionPtr ifaceid2 = generateInterfaceId("VL32");
+    // That's a strange interface-id, but this is a real life example
+    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+    // Bogus interface-id.
+    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+    // Assign interface ids to the respective subnets.
+    subnet1->setInterfaceId(ifaceid1);
+    subnet2->setInterfaceId(ifaceid2);
+    subnet3->setInterfaceId(ifaceid3);
+
+    // There shouldn't be any subnet configured at this stage.
+    SubnetSelector selector;
+    selector.interface_id_ = ifaceid1;
+    // Note that some link address must be specified to indicate that it is
+    // a relayed message!
+    selector.first_relay_linkaddr_ = IOAddress("5000::1");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add one of the subnets.
+    cfg.add(subnet1);
+
+    // If only one subnet has been specified, it should be returned when the
+    // interface id matches. But, for a different interface id there should be
+    // no match.
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Add other subnets.
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Now that we have all subnets added. we should be able to retrieve them
+    // using appropriate interface ids.
+    selector.interface_id_ = ifaceid3;
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+
+    // For invalid interface id, there should be nothing returned.
+    selector.interface_id_ = ifaceid_bogus;
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that the client classes are considered when the subnet is selected by
+// the relay link address.
+TEST(CfgSubnets6Test, selectSubnetByRelayAddressAndClassify) {
+    CfgSubnets6 cfg;
+
+    // Let's configure 3 subnets
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Let's sanity check that we can use that configuration.
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    // Client now belongs to bar class.
+    selector.client_classes_.insert("bar");
+
+    // There are no class restrictions defined, so everything should work
+    // as before.
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    // Now let's add client class restrictions.
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    // The same check as above should result in client being served only in
+    // bar class, i.e. subnet2
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Now let's check that client with wrong class is not supported
+    selector.client_classes_.clear();
+    selector.client_classes_.insert("some_other_class");
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+
+    // Finally, let's check that client without any classes is not supported
+    selector.client_classes_.clear();
+    selector.first_relay_linkaddr_ = IOAddress("2000::123");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("3000::345");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.first_relay_linkaddr_ = IOAddress("4000::567");
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that client classes are considered when the subnet is selcted by the
+// interface name.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceNameAndClaassify) {
+    CfgSubnets6 cfg;
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+    subnet1->setIface("foo");
+    subnet2->setIface("bar");
+    subnet3->setIface("foobar");
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // Now we have only one subnet, any request will be served from it
+    SubnetSelector selector;
+    selector.client_classes_.insert("bar");
+    selector.iface_name_ = "foo";
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.iface_name_ = "bar";
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.iface_name_ = "foobar";
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    selector.iface_name_ = "foo";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.iface_name_ = "bar";
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.iface_name_ = "foobar";
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Test that client classes are considered when the interface is selected by
+// the interface id.
+TEST(CfgSubnets6Test, selectSubnetByInterfaceIdAndClassify) {
+    CfgSubnets6 cfg;
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
+
+    // interface-id options used in subnets 1,2, and 3
+    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
+    OptionPtr ifaceid2 = generateInterfaceId("VL32");
+    // That's a strange interface-id, but this is a real life example
+    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
+
+    // bogus interface-id
+    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
+
+    subnet1->setInterfaceId(ifaceid1);
+    subnet2->setInterfaceId(ifaceid2);
+    subnet3->setInterfaceId(ifaceid3);
+
+    cfg.add(subnet1);
+    cfg.add(subnet2);
+    cfg.add(subnet3);
+
+    // If we have only a single subnet and the request came from a local
+    // address, let's use that subnet
+    SubnetSelector selector;
+    selector.first_relay_linkaddr_ = IOAddress("5000::1");
+    selector.client_classes_.insert("bar");
+    selector.interface_id_ = ifaceid1;
+    EXPECT_EQ(subnet1, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid3;
+    EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
+
+    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
+    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
+    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
+
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid2;
+    EXPECT_EQ(subnet2, cfg.selectSubnet(selector));
+    selector.interface_id_ = ifaceid3;
+    EXPECT_FALSE(cfg.selectSubnet(selector));
+}
+
+// Checks that detection of duplicated subnet IDs works as expected. It should
+// not be possible to add two IPv6 subnets holding the same ID.
+TEST(CfgSubnets6, duplication) {
+    CfgSubnets6 cfg;
+
+    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4, 123));
+    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4, 124));
+    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4, 123));
+
+    ASSERT_NO_THROW(cfg.add(subnet1));
+    EXPECT_NO_THROW(cfg.add(subnet2));
+    // Subnet 3 has the same ID as subnet 1. It shouldn't be able to add it.
+    EXPECT_THROW(cfg.add(subnet3), isc::dhcp::DuplicateSubnetID);
+}
+
+} // end of anonymous namespace

+ 0 - 322
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -283,7 +283,6 @@ public:
 
     void clear() {
         CfgMgr::instance().setVerbose(false);
-        CfgMgr::instance().deleteSubnets6();
         CfgMgr::instance().clear();
     }
 
@@ -304,307 +303,6 @@ TEST_F(CfgMgrTest, configuration) {
     EXPECT_TRUE(configuration->getLoggingInfo().empty());
 }
 
-// This test verifies if the configuration manager is able to hold v6 subnets
-// with their relay address information and return proper subnets, based on
-// those addresses.
-TEST_F(CfgMgrTest, subnet6RelayOverride) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    // Let's configure 3 subnets
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
-
-    cfg_mgr.addSubnet6(subnet1);
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-    // Check that without relay-info specified, subnets are not selected
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::1"), classify_, true));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::2"), classify_, true));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::3"), classify_, true));
-
-    // Now specify relay info
-    subnet1->setRelayInfo(IOAddress("2001:db8:ff::1"));
-    subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
-    subnet3->setRelayInfo(IOAddress("2001:db8:ff::3"));
-
-    // And try again. This time relay-info is there and should match.
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::1"), classify_, true));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::2"), classify_, true));
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::3"), classify_, true));
-
-    // Finally, check that the relay works only if hint provided is relay address
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::1"), classify_, false));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::2"), classify_, false));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2001:db8:ff::3"), classify_, false));
-}
-
-
-// This test verifies if the configuration manager is able to hold and return
-// valid leases
-TEST_F(CfgMgrTest, classifySubnet6) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    // Let's configure 3 subnets
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
-
-    cfg_mgr.addSubnet6(subnet1);
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-    // Let's sanity check that we can use that configuration.
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
-
-    // Client now belongs to bar class.
-    classify_.insert("bar");
-
-    // There are no class restrictions defined, so everything should work
-    // as before
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
-
-    // Now let's add client class restrictions.
-    subnet1->allowClientClass("foo"); // Serve here only clients from foo class
-    subnet2->allowClientClass("bar"); // Serve here only clients from bar class
-    subnet3->allowClientClass("baz"); // Serve here only clients from baz class
-
-    // The same check as above should result in client being served only in
-    // bar class, i.e. subnet2
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
-
-    // Now let's check that client with wrong class is not supported
-    classify_.clear();
-    classify_.insert("some_other_class");
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
-
-    // Finally, let's check that client without any classes is not supported
-    classify_.clear();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::345"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::567"), classify_));
-}
-
-// This test verifies if the configuration manager is able to hold, select
-// and return valid subnets, based on interface names along with client
-// classification.
-TEST_F(CfgMgrTest, classifySubnet6Interface) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    // Let's have an odd configuration: 3 shared subnets available on the
-    // same direct link.
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
-    subnet1->setIface("foo");
-    subnet2->setIface("foo");
-    subnet3->setIface("foo");
-    cfg_mgr.addSubnet6(subnet1);
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-
-    // Regular client should get the first subnet, because it meets all
-    // criteria (matching interface name, no class restrictions.
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo", classify_));
-
-    // Now let's add class requirements for subnet1
-    subnet1->allowClientClass("alpha");
-
-    // Client should now get the subnet2, because he no longer meets
-    // requirements for subnet1 (belongs to wrong class)
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("foo", classify_));
-
-    // Now let's add (not matching) classes to the other two subnets
-    subnet2->allowClientClass("beta");
-    subnet3->allowClientClass("gamma");
-
-    // No subnets are suitable, so nothing will be selected
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
-
-    // Ok, let's add the client to gamme class, so he'll get a subnet
-    classify_.insert("gamma");
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foo", classify_));
-}
-
-// This test verifies if the configuration manager is able to hold, select
-// and return valid subnets, based on interface-id option inserted by relay,
-// along with client classification.
-TEST_F(CfgMgrTest, classifySubnet6InterfaceId) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    // Let's have an odd configuration: 3 shared subnets available via the
-    // same remote relay with the same interface-id.
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
-    OptionPtr ifaceid = generateInterfaceId("relay1.eth0");
-    subnet1->setInterfaceId(ifaceid);
-    subnet2->setInterfaceId(ifaceid);
-    subnet3->setInterfaceId(ifaceid);
-    cfg_mgr.addSubnet6(subnet1);
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-    // Regular client should get the first subnet, because it meets all
-    // criteria (matching interface name, no class restrictions.
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid, classify_));
-
-    // Now let's add class requirements for subnet1
-    subnet1->allowClientClass("alpha");
-
-    // Client should now get the subnet2, because he no longer meets
-    // requirements for subnet1 (belongs to wrong class)
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid, classify_));
-
-    // Now let's add (not matching) classes to the other two subnets
-    subnet2->allowClientClass("beta");
-    subnet3->allowClientClass("gamma");
-
-    // No subnets are suitable, so nothing will be selected
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid, classify_));
-
-    // Ok, let's add the client to gamme class, so he'll get a subnet
-    classify_.insert("gamma");
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid, classify_));
-}
-
-// This test verifies if the configuration manager is able to hold and return
-// valid leases
-TEST_F(CfgMgrTest, subnet6) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
-
-    // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
-
-    cfg_mgr.addSubnet6(subnet1);
-
-    // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1"), classify_));
-
-    // We used to allow getting a sole subnet if there was only one subnet
-    // configured. That is no longer true. The code should not return
-    // a subnet.
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"), classify_));
-
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123"), classify_));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef"),
-                  classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1"), classify_));
-
-    // Check that deletion of the subnets works.
-    cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::123"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123"), classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123"), classify_));
-}
-
-// This test verifies if the configuration manager is able to hold, select
-// and return valid subnets, based on interface names.
-TEST_F(CfgMgrTest, subnet6Interface) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
-    subnet1->setIface("foo");
-    subnet2->setIface("bar");
-    subnet3->setIface("foobar");
-
-    // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
-
-    cfg_mgr.addSubnet6(subnet1);
-
-    // Now we have only one subnet, any request will be served from it
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6("foo", classify_));
-
-    // Check that the interface name is checked even when there is
-    // only one subnet defined.
-    EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
-
-    // We used to allow getting a sole subnet if there was only one subnet
-    // configured. That is no longer true. The code should not return
-    // a subnet.
-    EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef"), classify_));
-
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6("foobar", classify_));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6("bar", classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("xyzzy", classify_)); // no such interface
-
-    // Check that deletion of the subnets works.
-    cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foo", classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("bar", classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6("foobar", classify_));
-}
-
-// This test verifies if the configuration manager is able to hold, select
-// and return valid leases, based on interface-id option values
-TEST_F(CfgMgrTest, subnet6InterfaceId) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
-
-    // interface-id options used in subnets 1,2, and 3
-    OptionPtr ifaceid1 = generateInterfaceId("relay1.eth0");
-    OptionPtr ifaceid2 = generateInterfaceId("VL32");
-    // That's a strange interface-id, but this is a real life example
-    OptionPtr ifaceid3 = generateInterfaceId("ISAM144|299|ipv6|nt:vp:1:110");
-
-    // bogus interface-id
-    OptionPtr ifaceid_bogus = generateInterfaceId("non-existent");
-
-    subnet1->setInterfaceId(ifaceid1);
-    subnet2->setInterfaceId(ifaceid2);
-    subnet3->setInterfaceId(ifaceid3);
-
-    // There shouldn't be any subnet configured at this stage
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1, classify_));
-
-    cfg_mgr.addSubnet6(subnet1);
-
-    // If we have only a single subnet and the request came from a local
-    // address, let's use that subnet
-    EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(ifaceid1, classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2, classify_));
-
-    cfg_mgr.addSubnet6(subnet2);
-    cfg_mgr.addSubnet6(subnet3);
-
-    EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(ifaceid3, classify_));
-    EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(ifaceid2, classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid_bogus, classify_));
-
-    // Check that deletion of the subnets works.
-    cfg_mgr.deleteSubnets6();
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid1, classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid2, classify_));
-    EXPECT_FALSE(cfg_mgr.getSubnet6(ifaceid3, classify_));
-}
-
-
 // This test verifies that new DHCPv4 option spaces can be added to
 // the configuration manager and that duplicated option space is
 // rejected.
@@ -731,26 +429,6 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     EXPECT_NE(*original_config, *updated_config);
 }
 
-// Checks that detection of duplicated subnet IDs works as expected. It should
-// not be possible to add two IPv6 subnets holding the same ID to the config
-// manager.
-TEST_F(CfgMgrTest, subnet6Duplication) {
-    CfgMgr& cfg_mgr = CfgMgr::instance();
-
-    Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3,
-                                   4, 123));
-    Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3,
-                                   4, 124));
-    Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 64, 1, 2, 3,
-                                   4, 123));
-
-    ASSERT_NO_THROW(cfg_mgr.addSubnet6(subnet1));
-    EXPECT_NO_THROW(cfg_mgr.addSubnet6(subnet2));
-    // Subnet 3 has the same ID as subnet 1. It shouldn't be able to add it.
-    EXPECT_THROW(cfg_mgr.addSubnet6(subnet3), isc::dhcp::DuplicateSubnetID);
-}
-
-
 // This test verifies that the configuration staging, commit and rollback works
 // as expected.
 TEST_F(CfgMgrTest, staging) {

+ 287 - 2
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
+#include <dhcp/option6_addrlst.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
@@ -361,9 +362,11 @@ public:
     /// @throw throws NotImplemented if element name isn't supported.
     ParserPtr createConfigParser(const std::string& config_id) {
         ParserPtr parser;
+        int family = parser_context_->universe_ == Option::V4 ?
+            AF_INET : AF_INET6;
         if (config_id.compare("option-data") == 0) {
             parser.reset(new OptionDataListParser(config_id, CfgOptionPtr(),
-                                                  AF_INET));
+                                                  family));
 
         } else if (config_id.compare("option-def") == 0) {
             parser.reset(new OptionDefListParser(config_id,
@@ -447,7 +450,6 @@ public:
     void reset_context(){
         // Note set context universe to V6 as it has to be something.
         CfgMgr::instance().clear();
-        CfgMgr::instance().deleteSubnets6();
         parser_context_.reset(new ParserContext(Option::V6));
 
         // Ensure no hooks libraries are loaded.
@@ -552,6 +554,289 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
     EXPECT_EQ(val, opt_ptr->toText());
 }
 
+// This test checks behavior of the configuration parser for option data
+// for different values of csv-format parameter and when there is an option
+// definition present.
+TEST_F(ParseConfigTest, optionDataCSVFormatWithOptionDef) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"swap-server\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 16,"
+        "    \"data\": \"192.0.2.0\""
+        " } ]"
+        "}";
+
+    // The default universe is V6. We need to change it to use dhcp4 option
+    // space.
+    parser_context_->universe_ = Option::V4;
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    ASSERT_EQ(0, rcode);
+
+    // Verify that the option data is correct.
+    OptionCustomPtr addr_opt = boost::dynamic_pointer_cast<
+        OptionCustom>(getOptionPtr("dhcp4", 16));
+    ASSERT_TRUE(addr_opt);
+    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+
+    // Explicitly enable csv-format.
+    CfgMgr::instance().clear();
+    config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"swap-server\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 16,"
+        "    \"csv-format\": True,"
+        "    \"data\": \"192.0.2.0\""
+        " } ]"
+        "}";
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    ASSERT_EQ(0, rcode);
+
+    // Verify that the option data is correct.
+    addr_opt = boost::dynamic_pointer_cast<
+        OptionCustom>(getOptionPtr("dhcp4", 16));
+    ASSERT_TRUE(addr_opt);
+    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+
+    // Explicitly disable csv-format and use hex instead.
+    CfgMgr::instance().clear();
+    config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"swap-server\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 16,"
+        "    \"csv-format\": False,"
+        "    \"data\": \"C0000200\""
+        " } ]"
+        "}";
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    ASSERT_EQ(0, rcode);
+
+    // Verify that the option data is correct.
+    addr_opt = boost::dynamic_pointer_cast<
+        OptionCustom>(getOptionPtr("dhcp4", 16));
+    ASSERT_TRUE(addr_opt);
+    EXPECT_EQ("192.0.2.0", addr_opt->readAddress().toText());
+}
+
+// This test checks behavior of the configuration parser for option data
+// for different values of csv-format parameter and when there is no
+// option definition.
+TEST_F(ParseConfigTest, optionDataCSVFormatNoOptionDef) {
+    // This option doesn't have any definition. It is ok to use such
+    // an option but the data should be specified in hex, not as CSV.
+    // Note that the parser will by default use the CSV format for the
+    // data but only in case there is a suitable option definition.
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"foo-name\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 25000,"
+        "    \"data\": \"1, 2, 5\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_NE(0, rcode);
+
+    CfgMgr::instance().clear();
+    // The data specified here will work both for CSV format and hex format.
+    // What we want to test here is that when the csv-format is enforced, the
+    // parser will fail because of lack of an option definition.
+    config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"foo-name\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 25000,"
+        "    \"csv-format\": True,"
+        "    \"data\": \"0\""
+        " } ]"
+        "}";
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_NE(0, rcode);
+
+    CfgMgr::instance().clear();
+    // The same test case as above, but for the data specified in hex should
+    // be successful.
+    config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"foo-name\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 25000,"
+        "    \"csv-format\": False,"
+        "    \"data\": \"0\""
+        " } ]"
+        "}";
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    ASSERT_EQ(0, rcode);
+    OptionPtr opt = getOptionPtr("dhcp6", 25000);
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getData().size());
+    EXPECT_EQ(0, opt->getData()[0]);
+
+    CfgMgr::instance().clear();
+    // When csv-format is not specified, the parser will check if the definition
+    // exists or not. Since there is no definition, the parser will accept the
+    // data in hex.
+    config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"foo-name\","
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 25000,"
+        "    \"data\": \"123456\""
+        " } ]"
+        "}";
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    opt = getOptionPtr("dhcp6", 25000);
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(3, opt->getData().size());
+    EXPECT_EQ(0x12, opt->getData()[0]);
+    EXPECT_EQ(0x34, opt->getData()[1]);
+    EXPECT_EQ(0x56, opt->getData()[2]);
+}
+
+// This test verifies that the option name is not mandatory, if the option
+// code has been specified.
+TEST_F(ParseConfigTest, optionDataNoName) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"space\": \"dhcp6\","
+        "    \"code\": 23,"
+        "    \"csv-format\": True,"
+        "    \"data\": \"2001:db8:1::1\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+}
+
+// This test verifies that the option code is not mandatory, if the option
+// name has been specified.
+TEST_F(ParseConfigTest, optionDataNoCode) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"space\": \"dhcp6\","
+        "    \"name\": \"dns-servers\","
+        "    \"csv-format\": True,"
+        "    \"data\": \"2001:db8:1::1\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::1", opt->getAddresses()[0].toText());
+}
+
+// This test verifies that the option data configuration with a minimal
+// set of parameters works as expected.
+TEST_F(ParseConfigTest, optionDataMinimal) {
+    std::string config =
+        "{ \"option-data\": [ {"
+        "    \"name\": \"dns-servers\","
+        "    \"data\": \"2001:db8:1::10\""
+        " } ]"
+        "}";
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::10", opt->getAddresses()[0].toText());
+
+    CfgMgr::instance().clear();
+    // This time using an option code.
+    config =
+        "{ \"option-data\": [ {"
+        "    \"code\": 23,"
+        "    \"data\": \"2001:db8:1::20\""
+        " } ]"
+        "}";
+    rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+                                                                   23));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(1, opt->getAddresses().size());
+    EXPECT_EQ( "2001:db8:1::20", opt->getAddresses()[0].toText());
+}
+
+// This test verifies that the option data configuration with a minimal
+// set of parameters works as expected when option definition is
+// created in the configruation file.
+TEST_F(ParseConfigTest, optionDataMinimalWithOptionDef) {
+    // Configuration string.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo-name\","
+        "      \"code\": 2345,"
+        "      \"type\": \"ipv6-address\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
+        "  } ],"
+        "  \"option-data\": [ {"
+        "    \"name\": \"foo-name\","
+        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
+        " } ]"
+        "}";
+
+    int rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    Option6AddrLstPtr opt = boost::dynamic_pointer_cast<
+        Option6AddrLst>(getOptionPtr("dhcp6", 2345));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(2, opt->getAddresses().size());
+    EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
+    EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
+
+    CfgMgr::instance().clear();
+    // Do the same test but now use an option code.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo-name\","
+        "      \"code\": 2345,"
+        "      \"type\": \"ipv6-address\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
+        "  } ],"
+        "  \"option-data\": [ {"
+        "    \"code\": 2345,"
+        "    \"data\": \"2001:db8:1::10, 2001:db8:1::123\""
+        " } ]"
+        "}";
+
+    rcode = 0;
+    ASSERT_NO_THROW(rcode = parseConfiguration(config));
+    EXPECT_EQ(0, rcode);
+    opt = boost::dynamic_pointer_cast<Option6AddrLst>(getOptionPtr("dhcp6",
+                                                                   2345));
+    ASSERT_TRUE(opt);
+    ASSERT_EQ(2, opt->getAddresses().size());
+    EXPECT_EQ("2001:db8:1::10", opt->getAddresses()[0].toText());
+    EXPECT_EQ("2001:db8:1::123", opt->getAddresses()[1].toText());
+
+}
+
 };  // Anonymous namespace
 
 /// These tests check basic operation of the HooksLibrariesParser.

+ 230 - 0
src/lib/dhcpsrv/tests/host_mgr_unittest.cc

@@ -0,0 +1,230 @@
+// Copyright (C) 2014 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 <config.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_mgr.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+namespace {
+
+/// @brief Test fixture class for @c HostMgr class.
+class HostMgrTest : public ::testing::Test {
+protected:
+
+    /// @brief Prepares the class for a test.
+    ///
+    /// This method crates a handful of unique HW address and DUID objects
+    /// for use in unit tests. These objects are held in the @c hwaddrs_ and
+    /// @c duids_ members respectively.
+    ///
+    /// This method also resets the @c CfgMgr configuration and re-creates
+    /// the @c HostMgr object.
+    virtual void SetUp();
+
+    /// @brief Convenience method returning a pointer to the @c CfgHosts object
+    /// in the @c CfgMgr.
+    CfgHostsPtr getCfgHosts() const;
+
+    /// @brief HW addresses to be used by the tests.
+    std::vector<HWAddrPtr> hwaddrs_;
+    /// @brief DUIDs to be used by the tests.
+    std::vector<DuidPtr> duids_;
+};
+
+void
+HostMgrTest::SetUp() {
+    // Remove all configuration which may be dangling from the previous test.
+    CfgMgr::instance().clear();
+    // Recreate HostMgr instance. It drops any previous state.
+    HostMgr::create();
+    // Create HW addresses from the template.
+    const uint8_t mac_template[] = {
+        0x01, 0x02, 0x0A, 0xBB, 0x03, 0x00
+    };
+    for (int i = 0; i < 10; ++i) {
+        std::vector<uint8_t> vec(mac_template,
+                                 mac_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        HWAddrPtr hwaddr(new HWAddr(vec, HTYPE_ETHER));
+        hwaddrs_.push_back(hwaddr);
+    }
+    // Create DUIDs from the template.
+    const uint8_t duid_template[] = {
+        0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x00
+    };
+    for (int i = 0; i < 10; ++i) {
+        std::vector<uint8_t> vec(duid_template,
+                                 duid_template + sizeof(mac_template));
+        vec[vec.size() - 1] = i;
+        DuidPtr duid(new DUID(vec));
+        duids_.push_back(duid);
+    }
+}
+
+CfgHostsPtr
+HostMgrTest::getCfgHosts() const {
+    return (CfgMgr::instance().getStagingCfg()->getCfgHosts());
+}
+
+/// This test verifies that HostMgr returns all reservations for the
+/// specified HW address. The reservations are defined in the server's
+/// configuration.
+TEST_F(HostMgrTest, getAll) {
+    // Initially, no reservations should be present.
+    ConstHostCollection hosts = HostMgr::instance().getAll(hwaddrs_[0]);
+    ASSERT_TRUE(hosts.empty());
+
+    // Add two reservations for the same HW address. They differ by the IP
+    // address reserved and the IPv4 subnet.
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address", SubnetID(1), SubnetID(0),
+                                        IOAddress("192.0.2.5"))));
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address", SubnetID(10), SubnetID(0),
+                                        IOAddress("192.0.3.10"))));
+    CfgMgr::instance().commit();
+
+    // If there non-matching HW address is specified, nothing should be
+    // returned.
+    ASSERT_TRUE(HostMgr::instance().getAll(hwaddrs_[1]).empty());
+    // For the correct HW address, there should be two reservations.
+    hosts = HostMgr::instance().getAll(hwaddrs_[0]);
+    ASSERT_EQ(2, hosts.size());
+
+    // We don't know the order in which the reservations are returned so
+    // we have to match with any of the two reservations returned.
+
+    // Look for the first reservation.
+    bool found = false;
+    for (int i = 0; i < 2; ++i) {
+        if (hosts[0]->getIPv4Reservation() == IOAddress("192.0.2.5")) {
+            ASSERT_EQ(1, hosts[0]->getIPv4SubnetID());
+            found = true;
+        }
+    }
+    if (!found) {
+        ADD_FAILURE() << "Reservation for the IPv4 address 192.0.2.5"
+            " not found using getAll method";
+    }
+
+    // Look for the second reservation.
+    found = false;
+    for (int i = 0; i < 2; ++i) {
+        if (hosts[1]->getIPv4Reservation() == IOAddress("192.0.3.10")) {
+            ASSERT_EQ(10, hosts[1]->getIPv4SubnetID());
+            found = true;
+        }
+    }
+    if (!found) {
+        ADD_FAILURE() << "Reservation for the IPv4 address 192.0.3.10"
+            " not found using getAll method";
+    }
+
+}
+
+// This test verifies that it is possible to gather all reservations for the
+// specified IPv4 address from the HostMgr. The reservations are specified in
+// the server's configuration. Note: this test is currently disabled because the
+// getAll4 method is not implemented in the CfgHosts object.
+TEST_F(HostMgrTest, DISABLED_getAll4) {
+    ConstHostCollection hosts =
+        HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(hosts.empty());
+
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address", SubnetID(1), SubnetID(0),
+                                        IOAddress("192.0.2.5"))));
+
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[1]->toText(false),
+                                        "hw-address", SubnetID(10), SubnetID(0),
+                                        IOAddress("192.0.2.5"))));
+    CfgMgr::instance().commit();
+
+    hosts = HostMgr::instance().getAll4(IOAddress("192.0.2.5"));
+    ASSERT_EQ(2, hosts.size());
+
+    /// @todo Extend this test to sanity check the hosts, once the test
+    /// is enabled.
+}
+
+// This test verifies that it is possible to retrieve a reservation for the
+// particular host using HostMgr. The reservation is specified in the server's
+// configuration.
+TEST_F(HostMgrTest, get4) {
+    ConstHostPtr host = HostMgr::instance().get4(SubnetID(1), hwaddrs_[0]);
+    ASSERT_FALSE(host);
+
+    getCfgHosts()->add(HostPtr(new Host(hwaddrs_[0]->toText(false),
+                                        "hw-address",
+                                        SubnetID(1), SubnetID(2),
+                                        IOAddress("192.0.2.5"))));
+    CfgMgr::instance().commit();
+
+    host = HostMgr::instance().get4(SubnetID(1), hwaddrs_[0]);
+    ASSERT_TRUE(host);
+    EXPECT_EQ(1, host->getIPv4SubnetID());
+    EXPECT_EQ("192.0.2.5", host->getIPv4Reservation().toText());
+}
+
+// This test verifies that it is possible to retrieve IPv6 reservations for
+// the particular host using HostMgr. The reservation is specified in the
+// server's configuration.
+TEST_F(HostMgrTest, get6) {
+    ConstHostPtr host = HostMgr::instance().get6(SubnetID(2), duids_[0]);
+    ASSERT_FALSE(host);
+
+    HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1),
+                              SubnetID(2), IOAddress("0.0.0.0")));
+    new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       IOAddress("2001:db8:1::1")));
+    getCfgHosts()->add(new_host);
+    CfgMgr::instance().commit();
+
+    host = HostMgr::instance().get6(SubnetID(2), duids_[0]);
+    ASSERT_TRUE(host);
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress("2001:db8:1::1"))));
+}
+
+// This test verifies that it is possible to retrieve the reservation of the
+// particular IPv6 prefix using HostMgr. Note: this test is currently disabled
+// because the get6(prefix, prefix_len) method is not implemented in the
+// CfgHosts class.
+TEST_F(HostMgrTest, DISABLED_get6ByPrefix) {
+    ConstHostPtr host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+    ASSERT_FALSE(host);
+
+    HostPtr new_host(new Host(duids_[0]->toText(), "duid", SubnetID(1),
+                              SubnetID(2), IOAddress("0.0.0.0")));
+    new_host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1::"), 64));
+    getCfgHosts()->add(new_host);
+    CfgMgr::instance().commit();
+
+    host = HostMgr::instance().get6(IOAddress("2001:db8:1::"), 64);
+    ASSERT_TRUE(host);
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+    IOAddress("2001:db8:1::"), 64)));
+}
+
+} // end of anonymous namespace

+ 354 - 0
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -0,0 +1,354 @@
+// Copyright (C) 2014 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 <config.h>
+
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/host_reservation_parser.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <gtest/gtest.h>
+#include <iterator>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c HostReservationParser.
+class HostReservationParserTest : public ::testing::Test {
+protected:
+
+    /// @brief Setup for each test.
+    ///
+    /// Clears the configuration in the @c CfgMgr.
+    virtual void SetUp();
+
+    /// @brief Cleans up after each test.
+    ///
+    /// Clears the configuration in the @c CfgMgr.
+    virtual void TearDown();
+
+    /// @brief Checks if the reservation is in the range of reservations.
+    ///
+    /// @param resrv Reservation to be searched for.
+    /// @param range Range of reservations returned by the @c Host object
+    /// in which the reservation will be searched.
+    bool
+    reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+        for (IPv6ResrvIterator it = range.first; it != range.second;
+             ++it) {
+            if (resrv == it->second) {
+                return (true);
+            }
+        }
+        return (false);
+    }
+
+    void
+    expectFailure(const HostReservationParser& parser,
+                  const std::string& config) const;
+
+    /// @brief HW Address object used by tests.
+    HWAddrPtr hwaddr_;
+
+    /// @brief DUID object used by tests.
+    DuidPtr duid_;
+
+};
+
+void
+HostReservationParserTest::SetUp() {
+    CfgMgr::instance().clear();
+    // Initialize HW address used by tests.
+    const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+    hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+                                   HTYPE_ETHER));
+
+    // Initialize DUID used by tests.
+    const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                                  0x08, 0x09, 0x0A };
+    duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
+}
+
+void
+HostReservationParserTest::TearDown() {
+    CfgMgr::instance().clear();
+}
+
+// This test verfies that the parser can parse the reservation entry for
+// which hw-address is a host identifier.
+TEST_F(HostReservationParserTest, dhcp4HWaddr) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"192.0.2.134\","
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(1));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.134", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+}
+
+// This test verfies that the parser can parse the reservation entry for
+// which DUID is a host identifier.
+TEST_F(HostReservationParserTest, dhcp4DUID) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-address\": \"192.0.2.112\","
+        "\"hostname\": \"\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_TRUE(hosts[0]->getHostname().empty());
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when IPv6 address is specified for IPv4 address
+// reservation.
+TEST_F(HostReservationParserTest, dhcp4IPv6Address) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"2001:db8:1::1\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when no HW address nor DUID is specified.
+TEST_F(HostReservationParserTest, noIdentifier) {
+    std::string config = "{ \"ip-address\": \"192.0.2.112\","
+        "\"hostname\": \"\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser for host reservations
+// throws an exception when invalid IP address is specified.
+TEST_F(HostReservationParserTest, malformedAddress) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-address\": \"192.0.2.bogus\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verfies that the parser can parse the IPv6 reservation entry for
+// which hw-address is a host identifier.
+TEST_F(HostReservationParserTest, dhcp6HWaddr) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::2\" ],"
+        "\"prefixes\": [ \"2001:db8:2000:0101::/64\", "
+        "\"2001:db8:2000:0102::/64\" ],"
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(10));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_, DuidPtr()));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(10, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+    IPv6ResrvRange addresses = hosts[0]->
+        getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::1")),
+                                  addresses));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::2")),
+                                  addresses));
+
+    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:2000:0101::"),
+                                            64),
+                                  prefixes));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:2000:0102::"),
+                                            64),
+                                  prefixes));
+
+}
+
+// This test verfies that the parser can parse the IPv6 reservation entry for
+// which DUID is a host identifier.
+TEST_F(HostReservationParserTest, dhcp6DUID) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+        "\"prefixes\": [ ],"
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+    IPv6ResrvRange addresses = hosts[0]->
+        getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::100")),
+                                  addresses));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::200")),
+                                  addresses));
+
+    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+}
+
+// This test verifies that the configuration parser throws an exception
+// when IPv4 address is specified for IPv6 reservation.
+TEST_F(HostReservationParserTest, dhcp6IPv4Address) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"192.0.2.3\", \"2001:db8:1::200\" ],"
+        "\"prefixes\": [ ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when empty address has been specified.
+TEST_F(HostReservationParserTest, dhcp6NullAddress) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"\" ],"
+        "\"prefixes\": [ ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when invalid prefix length is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8:1::/abc\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when empty prefix is specified.
+TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"/64\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when only slash is specified for the prefix..
+TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"/\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the same address is reserved twice.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedAddress) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"2001:db8:1::1\", \"2001:db8:1::1\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the same prefix is reserved twice.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8:0101::/64\", \"2001:db8:0101::/64\" ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+
+} // end of anonymous namespace

+ 83 - 37
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -27,7 +27,7 @@ namespace {
 // This test verifies that it is possible to create IPv6 address
 // reservation.
 TEST(IPv6ResrvTest, constructorAddress) {
-    IPv6Resrv resrv(IOAddress("2001:db8:1::cafe"));
+    IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"));
     EXPECT_EQ("2001:db8:1::cafe", resrv.getPrefix().toText());
     EXPECT_EQ(128, resrv.getPrefixLen());
     EXPECT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
@@ -36,74 +36,96 @@ TEST(IPv6ResrvTest, constructorAddress) {
 // This test verifies that it is possible to create IPv6 prefix
 // reservation.
 TEST(IPv6ResrvTest, constructorPrefix) {
-    IPv6Resrv resrv(IOAddress("2001:db8:1::"), 64);
+    IPv6Resrv resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
     EXPECT_EQ("2001:db8:1::", resrv.getPrefix().toText());
     EXPECT_EQ(64, resrv.getPrefixLen());
     EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
 }
 
+// This test verifies that the toText() function prints correctly.
+TEST(IPv6ResrvTest, toText) {
+    IPv6Resrv resrv_prefix(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 64);
+    EXPECT_EQ("2001:db8:1::/64", resrv_prefix.toText());
+
+    IPv6Resrv resrv_address(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:111::23"));
+    EXPECT_EQ("2001:db8:111::23", resrv_address.toText());
+}
+
 // This test verifies that invalid prefix is rejected.
 TEST(IPv6ResrvTest, constructorInvalidPrefix) {
     // IPv4 address is invalid for IPv6 reservation.
-    EXPECT_THROW(IPv6Resrv(IOAddress("10.0.0.1"), 128), isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+                 isc::BadValue);
     // Multicast address is invalid for IPv6 reservation.
-    EXPECT_THROW(IPv6Resrv(IOAddress("ff02:1::2"), 128), isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("ff02:1::2"), 128),
+                 isc::BadValue);
 }
 
 // This test verifies that invalid prefix length is rejected.
 TEST(IPv6ResrvTest, constructiorInvalidPrefixLength) {
-    ASSERT_NO_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 128));
-    EXPECT_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 129), isc::BadValue);
-    EXPECT_THROW(IPv6Resrv(IOAddress("2001:db8:1::"), 244), isc::BadValue);
+    ASSERT_NO_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"),
+                              128));
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 129),
+                 isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 244),
+                 isc::BadValue);
+    EXPECT_THROW(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::"), 64),
+                 isc::BadValue);
 }
 
 // This test verifies that it is possible to modify prefix and its
 // length in an existing reservation.
 TEST(IPv6ResrvTest, setPrefix) {
     // Create a reservation using an address and prefix length 128.
-    IPv6Resrv resrv(IOAddress("2001:db8:1::1"));
+    IPv6Resrv resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"));
     ASSERT_EQ("2001:db8:1::1", resrv.getPrefix().toText());
     ASSERT_EQ(128, resrv.getPrefixLen());
     ASSERT_EQ(IPv6Resrv::TYPE_NA, resrv.getType());
 
     // Modify the reservation to use a prefix having a length of 48.
-    ASSERT_NO_THROW(resrv.set(IOAddress("2001:db8::"), 48));
+    ASSERT_NO_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
     EXPECT_EQ("2001:db8::", resrv.getPrefix().toText());
     EXPECT_EQ(48, resrv.getPrefixLen());
     EXPECT_EQ(IPv6Resrv::TYPE_PD, resrv.getType());
 
     // IPv4 address is invalid for IPv6 reservation.
-    EXPECT_THROW(resrv.set(IOAddress("10.0.0.1"), 128), isc::BadValue);
+    EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("10.0.0.1"), 128),
+                 isc::BadValue);
     // IPv6 multicast address is invalid for IPv6 reservation.
-    EXPECT_THROW(resrv.set(IOAddress("ff02::1:2"), 128), isc::BadValue);
+    EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_NA, IOAddress("ff02::1:2"), 128),
+                 isc::BadValue);
     // Prefix length greater than 128 is invalid.
-    EXPECT_THROW(resrv.set(IOAddress("2001:db8:1::"), 129), isc::BadValue);
+    EXPECT_THROW(resrv.set(IPv6Resrv::TYPE_PD, IOAddress("2001:db8:1::"), 129),
+                 isc::BadValue);
 }
 
 // This test checks that the equality operators work fine.
 TEST(IPv6ResrvTest, equal) {
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::"), 64) ==
-                IPv6Resrv(IOAddress("2001:db8::"), 64));
-
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::"), 64) !=
-                IPv6Resrv(IOAddress("2001:db8::"), 64));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+                IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
 
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+                 IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64));
 
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::1")) ==
-                IPv6Resrv(IOAddress("2001:db8::1")));
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::1")) !=
-                 IPv6Resrv(IOAddress("2001:db8::1")));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+                IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+                 IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")));
 
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) ==
+                 IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1")) !=
+                 IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::2")));
 
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::1")) ==
-                 IPv6Resrv(IOAddress("2001:db8::2")));
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::1")) !=
-                 IPv6Resrv(IOAddress("2001:db8::2")));
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) ==
+                 IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 64) !=
+                IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::"), 48));
 
-    EXPECT_FALSE(IPv6Resrv(IOAddress("2001:db8::"), 64) ==
-                 IPv6Resrv(IOAddress("2001:db8::"), 48));
-    EXPECT_TRUE(IPv6Resrv(IOAddress("2001:db8::"), 64) !=
-                 IPv6Resrv(IOAddress("2001:db8::"), 48));
+    EXPECT_FALSE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) ==
+                 IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
+    EXPECT_TRUE(IPv6Resrv(IPv6Resrv::TYPE_NA, IOAddress("2001:db8::1"), 128) !=
+                IPv6Resrv(IPv6Resrv::TYPE_PD, IOAddress("2001:db8::1"), 128));
 
 }
 
@@ -333,27 +355,47 @@ TEST(HostTest, addReservations) {
 
     // Add 4 reservations: 2 for NAs, 2 for PDs.
     ASSERT_NO_THROW(
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1::cafe")));
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1:1::"), 64));
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1:2::"), 64));
-        host->addReservation(IPv6Resrv(IOAddress("2001:db8:1::1")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       IOAddress("2001:db8:1::cafe")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1:1::"), 64));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                       IOAddress("2001:db8:1:2::"), 64));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       IOAddress("2001:db8:1::1")));
     );
 
+    // Check that reservations exist.
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress("2001:db8:1::cafe"))));
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                               IOAddress("2001:db8:1:1::"),
+                                               64)));
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                               IOAddress("2001:db8:1:2::"),
+                                               64)));
+    EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress("2001:db8:1::1"))));
+
     // Get only NA reservations.
     IPv6ResrvRange addresses = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
     ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::cafe")),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::cafe")),
                                   addresses));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1::1")),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::1")),
                                   addresses));
 
 
     // Get only PD reservations.
     IPv6ResrvRange prefixes = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
     ASSERT_EQ(2, std::distance(prefixes.first, prefixes.second));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1:1::"), 64),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:1:1::"), 64),
                                   prefixes));
-    EXPECT_TRUE(reservationExists(IPv6Resrv(IOAddress("2001:db8:1:2::"), 64),
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:1:2::"), 64),
                                   prefixes));
 }
 
@@ -380,6 +422,10 @@ TEST(HostTest, setValues) {
     EXPECT_EQ(234, host->getIPv6SubnetID());
     EXPECT_EQ("10.0.0.1", host->getIPv4Reservation().toText());
     EXPECT_EQ("other-host.example.org", host->getHostname());
+
+    // An IPv6 address can't be used for IPv4 reservations.
+    EXPECT_THROW(host->setIPv4Reservation(IOAddress("2001:db8:1::1")),
+                 isc::BadValue);
 }
 
 // Test that Host constructors initialize client classes from string.

+ 1 - 17
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -41,8 +41,6 @@ public:
     /// is @c TEST_SUBNETS_NUM for IPv4 and IPv6 each.
     SrvConfigTest()
         : iface_mgr_test_config_(true) {
-        // Remove any subnets dangling from previous unit tests.
-        clearSubnets();
 
         // Disable DDNS.
         enableDDNS(false);
@@ -74,10 +72,7 @@ public:
     }
 
     /// @brief Destructor.
-    ///
-    /// Removes any dangling configuration.
     virtual ~SrvConfigTest() {
-        clearSubnets();
     }
 
     /// @brief Convenience function which adds IPv4 subnet to the configuration.
@@ -108,12 +103,6 @@ public:
     /// @c conf_ object.
     void addSubnet6(const unsigned int index);
 
-    /// @brief Removes all subnets from the configuration.
-    ///
-    /// @todo Modify this function once the subnet configuration is migrated
-    /// from @c CfgMgr to @c SrvConfig.
-    void clearSubnets();
-
     /// @brief Enable/disable DDNS.
     ///
     /// @param enable A boolean value indicating if the DDNS should be
@@ -146,12 +135,7 @@ SrvConfigTest::addSubnet6(const unsigned int index) {
         FAIL() << "Subnet index " << index << "out of range (0.."
                << TEST_SUBNETS_NUM << "): " << "unable to add IPv6 subnet";
     }
-    CfgMgr::instance().addSubnet6(test_subnets6_[index]);
-}
-
-void
-SrvConfigTest::clearSubnets() {
-    CfgMgr::instance().deleteSubnets6();
+    conf_.getCfgSubnets6()->add(test_subnets6_[index]);
 }
 
 void

+ 112 - 0
src/lib/dhcpsrv/writable_host_data_source.h

@@ -0,0 +1,112 @@
+// Copyright (C) 2014 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 WRITABLE_HOST_DATA_SOURCE_H
+#define WRITABLE_HOST_DATA_SOURCE_H
+
+#include <dhcpsrv/base_host_data_source.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Interface for retrieving writable host reservations.
+///
+/// This interface extends the @c BaseHostDataSource with methods which return
+/// pointers to the @c Host objects, which can be modified.
+class WritableHostDataSource : public BaseHostDataSource {
+public:
+
+    using BaseHostDataSource::getAll;
+    using BaseHostDataSource::getAll4;
+    using BaseHostDataSource::get4;
+    using BaseHostDataSource::get6;
+
+    /// @brief Non-const version of the @c getAll const method.
+    ///
+    /// Specifying both hardware address and DUID is allowed for this method
+    /// and results in returning all objects that are associated with hardware
+    /// address OR duid. For example: if one host is associated with the
+    /// specified hardware address and another host is associated with the
+    /// specified DUID, two hosts will be returned.
+    ///
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available, e.g. DHCPv4 client case.
+    ///
+    /// @return Collection of non-const @c Host objects.
+    virtual HostCollection
+    getAll(const HWAddrPtr& hwaddr, const DuidPtr& duid = DuidPtr()) = 0;
+
+    /// @brief Returns a collection of hosts using the specified IPv4 address.
+    ///
+    /// This method may return multiple @c Host objects if they are connected
+    /// to different subnets.
+    ///
+    /// @param address IPv4 address for which the @c Host object is searched.
+    ///
+    /// @return Collection of @c Host objects.
+    virtual HostCollection
+    getAll4(const asiolink::IOAddress& address) = 0;
+
+    /// @brief Returns a host connected to the IPv4 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid client id or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get4(const SubnetID& subnet_id, const HWAddrPtr& hwaddr,
+         const DuidPtr& duid = DuidPtr()) = 0;
+
+    /// @brief Returns a host connected to the IPv6 subnet.
+    ///
+    /// Implementations of this method should guard against the case when
+    /// mutliple instances of the @c Host are present, e.g. when two
+    /// @c Host objects are found, one for the DUID, another one for the
+    /// HW address. In such case, an implementation of this method
+    /// should throw an exception.
+    ///
+    /// @param subnet_id Subnet identifier.
+    /// @param hwaddr HW address of the client or NULL if no HW address
+    /// available.
+    /// @param duid DUID or NULL if not available.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get6(const SubnetID& subnet_id, const DuidPtr& duid,
+         const HWAddrPtr& hwaddr = HWAddrPtr()) = 0;
+
+    /// @brief Returns a host using the specified IPv6 prefix.
+    ///
+    /// @param prefix IPv6 prefix for which the @c Host object is searched.
+    /// @param prefix_len IPv6 prefix length.
+    ///
+    /// @return Non-const @c Host object using a specified HW address or DUID.
+    virtual HostPtr
+    get6(const asiolink::IOAddress& prefix, const uint8_t prefix_len) = 0;
+
+};
+
+}
+}
+
+#endif // WRITABLE_HOST_DATA_SOURCE_H

+ 0 - 0
src/lib/hooks/Makefile.am


Some files were not shown because too many files changed in this diff