BIND 10 trac3194_1, updated. 925a6f65ff995b19c877f064bdfdfb846d4c5b06 [3194] Cleanup in the OptionVendor class.

BIND 10 source code commits bind10-changes at lists.isc.org
Thu Oct 24 16:44:28 UTC 2013


The branch, trac3194_1 has been updated
       via  925a6f65ff995b19c877f064bdfdfb846d4c5b06 (commit)
       via  37f108c1fb43466d8ca55ee3175a938281eb99ce (commit)
       via  2b15e02a8b27e4a03ef6bdd2826acc44a50a5f0d (commit)
       via  faea98cbf4274229b207b20adbb97524a5d3e748 (commit)
       via  2e283ec70fa4c751835e1afcba65c4554fd78d15 (commit)
       via  748d5e585d6c4bcd5bb18498f768ad70f7e3bf0f (commit)
       via  7fe44b1d9400284a0bd22cac9d197bf0776cce1d (commit)
       via  ed672a898d28d6249ff0c96df12384b0aee403c8 (commit)
       via  3cbdec67c78ea16cf26742a3865046332774d2d7 (commit)
       via  f3f429b0e05eac677146e0419b50bc7704d1eb73 (commit)
       via  ea8ba75f55dceb13773d98a5836d075e231b2b6f (commit)
       via  4b0a0a96a7ac23db5d1c07c3e9511574e5d49c57 (commit)
       via  4ba8271b4050f3133dd6ca53721c0ce32ec715a3 (commit)
       via  f36aab92c85498f8511fbbe19fad5e3f787aef68 (commit)
       via  2c28d84f3657c10e6520c413705f532c0b42b1c8 (commit)
       via  99db31bc3494aece5861df6c64b821564cbb9348 (commit)
       via  541922b5300904a5de2eaeddc3666fc4b654ffba (commit)
       via  ea193899c17695b2a626cd5fe733506518eedcc0 (commit)
       via  3d320e51a5518a6bb3578ff8f804ac1479aee9b6 (commit)
       via  50d91e4c069c6de13680bfaaee3c56b68d6e4ab1 (commit)
       via  72e601f2a57ab70b25d50877c8e49242739d1c9f (commit)
       via  45eb1980e4238717e3aee354587eb857759d74e8 (commit)
       via  c936de6a262f1509d14c3ce457c5c0b27e86a4e2 (commit)
       via  e135eb799810f5671b622a2e5d5e4bedf545c049 (commit)
       via  d8d20c9781e6a644c85339b966c5c6a31ad24a59 (commit)
       via  1bbab8c3c1d12e1a7aaa2e2fc36ab4fc5653c92e (commit)
       via  4707a2dbce19333a1c6cb1bc9020ef2051eca1fe (commit)
       via  5baa1aeb9435170663bcce936e53fbac6d55eef8 (commit)
       via  9a9b9d8e466efb4a3a2e363a65c84ac0a37f2226 (commit)
       via  e9f4d7e510ae5392cfa25c40c5aebe1a49222c5b (commit)
       via  a1b91fbe2702b3fb60ac639f3ce7286c525c739a (commit)
       via  beac4c1be1f4063d1b8420d0241f5f384564bba3 (commit)
       via  28c612e504efd4f340fa1d9929ea17cd75565a67 (commit)
       via  7abead307c37c5d1f2a3ef3540e0648985997297 (commit)
       via  e76a6be3b3ffbc619ddb36b56e2a30d998ac9c36 (commit)
       via  358735b6bc9fe4caf2b12466e54a90eb69a4e673 (commit)
       via  07a72f62c2e93585de92d032b5af56c9a2ca562b (commit)
       via  ebeb291e7a086a17a6fcddb1abcab583dc59b46c (commit)
       via  a27c2a6a18ec14e5c79eae56beb238044ecb87d2 (commit)
       via  bd647c2a9820ecd3fc62852c42797265eca26ab1 (commit)
       via  e3683e45ff2288d6b1307a0c9d15271df9d0c722 (commit)
       via  4d71b071d1feafe88cc3756fd4e678301e5f0b70 (commit)
       via  770f4dfb5ac6abd620a01215ed85c1719b946f11 (commit)
       via  426203928242be40458c4e2ea861257e32424b14 (commit)
       via  f8487b5689486f3563f6b7546ecd867f473d74b1 (commit)
       via  11ec18030d858c037a862cc806a0329d3271d53a (commit)
       via  c95421cd2f4719b166700bac51361254483787ef (commit)
       via  6291862d54c72d64178fe3c5f5b489120e297e12 (commit)
       via  14918661a10e2f9c3367f668261656db1ee4bfed (commit)
       via  e98ccfe63db14be0d9a027797ffd75885b296969 (commit)
       via  f296e302ed073dadc5b03bc4edaff5a7da07376a (commit)
       via  f0edd52c244689cd370853d520bc6e97d23a3bb2 (commit)
       via  a9ec411b9522c9571f87afc39009bb7201be735f (commit)
       via  bf773a093fd8e7da0c93816fe3a9a744b689dd8c (commit)
       via  8e5d94583bc9da8374341509fbf0260d7a53edc2 (commit)
       via  33a9204a11501ee3289161543e5abcacf042a504 (commit)
       via  2115ed0befc5e58bc17960bfb89f5d209c2f9d71 (commit)
       via  dab092748d4083472373516cd4d576b0158aceeb (commit)
      from  7f26bb20177605c9826f85ebecda30cb1ceaa0e8 (commit)

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

- Log -----------------------------------------------------------------
commit 925a6f65ff995b19c877f064bdfdfb846d4c5b06
Author: Marcin Siodelski <marcin at isc.org>
Date:   Thu Oct 24 18:44:20 2013 +0200

    [3194] Cleanup in the OptionVendor class.
    
    Added consts where applicable and extended comments.

commit 37f108c1fb43466d8ca55ee3175a938281eb99ce
Author: Marcin Siodelski <marcin at isc.org>
Date:   Thu Oct 24 18:17:29 2013 +0200

    [3194] Do not duplicate options in responses from the server.

commit 2b15e02a8b27e4a03ef6bdd2826acc44a50a5f0d
Author: Marcin Siodelski <marcin at isc.org>
Date:   Thu Oct 24 18:10:07 2013 +0200

    [3194] Append requested vendor options only if the lease is assigned.
    
    Also, fix getting requested option codes from ORO.

commit faea98cbf4274229b207b20adbb97524a5d3e748
Merge: 7f26bb2 2e283ec
Author: Marcin Siodelski <marcin at isc.org>
Date:   Thu Oct 24 17:29:08 2013 +0200

    [3194] Merge branch 'master' into trac3194_1

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

Summary of changes:
 ChangeLog                                          |   45 ++++
 configure.ac                                       |    5 +
 doc/guide/bind10-guide.xml                         |   61 +++++
 src/Makefile.am                                    |    2 +
 src/bin/auth/query.cc                              |    2 +-
 src/bin/bind10/init.py.in                          |   19 +-
 src/bin/bind10/init_messages.mes                   |    7 +
 src/bin/dhcp4/config_parser.cc                     |   30 ++-
 src/bin/dhcp4/dhcp4.spec                           |   17 +-
 src/bin/dhcp4/dhcp4_messages.mes                   |    4 +-
 src/bin/dhcp4/dhcp4_srv.cc                         |   74 +++---
 src/bin/dhcp4/tests/config_parser_unittest.cc      |  145 ++++++++++++
 src/bin/dhcp4/tests/dhcp4_srv_unittest.cc          |  130 ++++++++++
 src/bin/dhcp4/tests/dhcp4_test_utils.cc            |  219 ++++++++++++++---
 src/bin/dhcp4/tests/dhcp4_test_utils.h             |   62 ++++-
 src/bin/dhcp6/ctrl_dhcp6_srv.cc                    |    2 +-
 src/bin/dhcp6/dhcp6_messages.mes                   |    3 +
 src/bin/dhcp6/dhcp6_srv.cc                         |   11 +-
 src/bin/msgq/msgq.py.in                            |    4 +-
 src/bin/msgq/msgq_messages.mes                     |    6 +
 src/hooks/Makefile.am                              |    1 +
 src/hooks/dhcp/Makefile.am                         |    1 +
 src/hooks/dhcp/user_chk/Makefile.am                |   65 +++++
 src/hooks/dhcp/user_chk/load_unload.cc             |  113 +++++++++
 src/hooks/dhcp/user_chk/subnet_select_co.cc        |  237 +++++++++++++++++++
 src/hooks/dhcp/user_chk/tests/Makefile.am          |   73 ++++++
 .../dhcp/user_chk}/tests/run_unittests.cc          |    2 +-
 .../user_chk/tests/test_data_files_config.h.in}    |    9 +-
 src/hooks/dhcp/user_chk/tests/test_users_1.txt     |    2 +
 src/hooks/dhcp/user_chk/tests/test_users_err.txt   |    2 +
 .../dhcp/user_chk/tests/user_file_unittests.cc     |  158 +++++++++++++
 .../dhcp/user_chk/tests/user_registry_unittests.cc |  216 +++++++++++++++++
 src/hooks/dhcp/user_chk/tests/user_unittests.cc    |   96 ++++++++
 src/hooks/dhcp/user_chk/tests/userid_unittests.cc  |  139 +++++++++++
 src/hooks/dhcp/user_chk/user.cc                    |  210 +++++++++++++++++
 src/hooks/dhcp/user_chk/user.h                     |  249 ++++++++++++++++++++
 .../dhcp/user_chk/user_chk_log.cc}                 |    7 +-
 .../dhcp/user_chk/user_chk_log.h}                  |   24 +-
 src/hooks/dhcp/user_chk/user_chk_messages.mes      |   43 ++++
 src/hooks/dhcp/user_chk/user_data_source.h         |   75 ++++++
 src/hooks/dhcp/user_chk/user_file.cc               |  159 +++++++++++++
 src/hooks/dhcp/user_chk/user_file.h                |  135 +++++++++++
 src/hooks/dhcp/user_chk/user_registry.cc           |  122 ++++++++++
 src/hooks/dhcp/user_chk/user_registry.h            |  128 ++++++++++
 .../dhcp/user_chk/version.cc}                      |   14 +-
 src/lib/datasrc/memory/zone_finder.cc              |    2 +-
 src/lib/dhcp/iface_mgr.cc                          |   90 ++++++-
 src/lib/dhcp/iface_mgr.h                           |   24 ++
 src/lib/dhcp/option_definition.cc                  |    6 +-
 src/lib/dhcp/option_vendor.cc                      |   12 +-
 src/lib/dhcp/option_vendor.h                       |   24 +-
 src/lib/dhcp/pkt4.cc                               |    4 +
 src/lib/dhcp/pkt4.h                                |    1 +
 src/lib/dhcp/pkt6.cc                               |   32 +--
 src/lib/dhcp/pkt6.h                                |    7 +-
 src/lib/dhcp/tests/iface_mgr_unittest.cc           |  134 +++++++++++
 src/lib/dhcp/tests/libdhcp++_unittest.cc           |    2 +-
 src/lib/dhcpsrv/cfgmgr.cc                          |   37 ++-
 src/lib/dhcpsrv/cfgmgr.h                           |   17 ++
 src/lib/dhcpsrv/dhcp_parsers.cc                    |    1 +
 src/lib/dhcpsrv/subnet.cc                          |   15 +-
 src/lib/dhcpsrv/subnet.h                           |   15 ++
 src/lib/dhcpsrv/tests/cfgmgr_unittest.cc           |   62 ++++-
 src/lib/dhcpsrv/tests/subnet_unittest.cc           |   18 ++
 tests/tools/perfdhcp/perf_pkt6.cc                  |    2 +-
 65 files changed, 3464 insertions(+), 169 deletions(-)
 create mode 100644 src/hooks/Makefile.am
 create mode 100644 src/hooks/dhcp/Makefile.am
 create mode 100644 src/hooks/dhcp/user_chk/Makefile.am
 create mode 100644 src/hooks/dhcp/user_chk/load_unload.cc
 create mode 100644 src/hooks/dhcp/user_chk/subnet_select_co.cc
 create mode 100644 src/hooks/dhcp/user_chk/tests/Makefile.am
 copy src/{lib/dhcp_ddns => hooks/dhcp/user_chk}/tests/run_unittests.cc (93%)
 copy src/{bin/resolver/common.cc => hooks/dhcp/user_chk/tests/test_data_files_config.h.in} (77%)
 create mode 100644 src/hooks/dhcp/user_chk/tests/test_users_1.txt
 create mode 100644 src/hooks/dhcp/user_chk/tests/test_users_err.txt
 create mode 100644 src/hooks/dhcp/user_chk/tests/user_file_unittests.cc
 create mode 100644 src/hooks/dhcp/user_chk/tests/user_registry_unittests.cc
 create mode 100644 src/hooks/dhcp/user_chk/tests/user_unittests.cc
 create mode 100644 src/hooks/dhcp/user_chk/tests/userid_unittests.cc
 create mode 100644 src/hooks/dhcp/user_chk/user.cc
 create mode 100644 src/hooks/dhcp/user_chk/user.h
 copy src/{bin/resolver/common.cc => hooks/dhcp/user_chk/user_chk_log.cc} (78%)
 copy src/{lib/dhcp_ddns/dhcp_ddns_log.h => hooks/dhcp/user_chk/user_chk_log.h} (68%)
 create mode 100644 src/hooks/dhcp/user_chk/user_chk_messages.mes
 create mode 100644 src/hooks/dhcp/user_chk/user_data_source.h
 create mode 100644 src/hooks/dhcp/user_chk/user_file.cc
 create mode 100644 src/hooks/dhcp/user_chk/user_file.h
 create mode 100644 src/hooks/dhcp/user_chk/user_registry.cc
 create mode 100644 src/hooks/dhcp/user_chk/user_registry.h
 copy src/{bin/dhcp6/tests/test_data_files_config.h.in => hooks/dhcp/user_chk/version.cc} (77%)

-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 1b45b97..4944bda 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,48 @@
+698.	[bug]		muks
+	A bug was fixed in the interaction between b10-init and b10-msgq
+	that caused BIND 10 failures after repeated start/stop of
+	components.
+	(Trac #3094, git ed672a898d28d6249ff0c96df12384b0aee403c8
+
+697.	[func]	tmark
+	Implements "user_check" hooks shared library which supports subnet
+	selection based upon the contents of a list of known DHCP lease users
+	(i.e. clients).  Adds the following subdirectories to the bind10 src
+	directory for maintaining hooks shared libraries:
+		-bind10/src/hooks  - base directory for hooks shared libraries
+		-bind10/src/hooks/dhcp - base directory for all hooks libs pertaining
+		to DHCP(Kea)
+		-bind10/src/hooks/dhcp/user_check - directory containing the user_check
+		 hooks library
+	(Trac #3186, git f36aab92c85498f8511fbbe19fad5e3f787aef68)
+
+696.	[func]		tomek
+	b10-dhcp4: It is now possible to specify value of siaddr field
+	in DHCPv4 responses. It is used to point out to the next
+	server in the boot process (that typically is TFTP server).
+	(Trac #3191, git 541922b5300904a5de2eaeddc3666fc4b654ffba)
+
+695.	[func]		tomek
+	b10-dhcp6 is now able to listen on global IPv6 unicast addresses.
+	(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
+
+694.	[bug]		tomek
+	b10-dhcp6 now handles exceptions better when processing initial
+	configuration. In particular, errors with socket binding do not
+	prevent b10-dhcp6 from establishing configuration session anymore.
+	(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
+
+693.	[bug]		tomek
+	b10-dhcp6 now handles IPv6 interface enabling correctly.
+	(Trac #3195, git 72e601f2a57ab70b25d50877c8e49242739d1c9f)
+
+692.	[bug]		marcin
+	b10-dhcp4: Fix a bug whereby the Parameter Request List was not parsed
+	by the server and requested DHCPv4 options were not returned to the
+	client. Options are not sent back to the client if server failed to
+	assign a lease.
+	(Trac #3200, git 50d91e4c069c6de13680bfaaee3c56b68d6e4ab1)
+
 691.	[bug]		marcin
 	libdhcp++: Created definitions for standard DHCPv4 options:
 	tftp-server-name (66) and boot-file-name (67). Also, fixed definition
diff --git a/configure.ac b/configure.ac
index 7786ab5..4bed0c6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1288,6 +1288,10 @@ AC_CONFIG_FILES([Makefile
                  src/bin/usermgr/Makefile
                  src/bin/usermgr/tests/Makefile
                  src/bin/tests/Makefile
+                 src/hooks/Makefile
+                 src/hooks/dhcp/Makefile
+                 src/hooks/dhcp/user_chk/Makefile
+                 src/hooks/dhcp/user_chk/tests/Makefile
                  src/lib/Makefile
                  src/lib/asiolink/Makefile
                  src/lib/asiolink/tests/Makefile
@@ -1468,6 +1472,7 @@ AC_OUTPUT([doc/version.ent
            src/bin/d2/spec_config.h.pre
            src/bin/d2/tests/test_data_files_config.h
            src/bin/tests/process_rename_test.py
+           src/hooks/dhcp/user_chk/tests/test_data_files_config.h
            src/lib/config/tests/data_def_unittests_config.h
            src/lib/dhcpsrv/tests/test_libraries.h
            src/lib/python/isc/config/tests/config_test
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index b2ee52d..bc7cc35 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -4390,6 +4390,30 @@ Dhcp4/subnet4	[]	list	(default)
       </para>
     </section>
 
+    <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
+      in the DHCPv4 packet for that purpose. That specific field can be configured
+      using next-server directive. It is possible to define it in global scope or
+      for a given subnet only. If both are defined, subnet value takes precedence.
+      The value in subnet can be set to 0.0.0.0, which means that next-server should
+      not be sent. It may also be set to empty string, which means the same as if
+      it was not defined at all - use global value.
+      </para>
+
+<screen>
+> <userinput>config add Dhcp4/next-server</userinput>
+> <userinput>config set Dhcp4/next-server "192.0.2.123"</userinput>
+> <userinput>config commit</userinput>
+<userinput></userinput>
+> <userinput>config add Dhcp4/subnet[0]/next-server</userinput>
+> <userinput>config set Dhcp4/subnet[0]/next-server "192.0.2.234"</userinput>
+> <userinput>config commit</userinput>
+</screen>
+
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
@@ -4669,6 +4693,43 @@ Dhcp6/subnet6/	list
       </para>
     </section>
 
+    <section id="dhcp6-unicast">
+      <title>Unicast traffic support</title>
+      <para>
+        When DHCPv6 server starts up, 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
+        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.
+      </para>
+      <para>
+        <screen>
+> <userinput>config set Dhcp6/interfaces[0] eth1/2001:db8::1</userinput>
+> <userinput>config commit</userinput></screen>
+        When configuration gets committed, the server will start to listen on
+        eth1 on link-local address, mutlicast group (ff02::1:2) and 2001:db8::1.
+      </para>
+      <para>
+        It is possible to mix interface names, wildcards and interface name/addresses
+        on the Dhcp6/interface list. It is not possible to specify more than one
+        unicast address on a given interface.
+      </para>
+      <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.
+      </para>
+    </section>
+
     <section>
       <title>Subnet and Address Pool</title>
       <para>
diff --git a/src/Makefile.am b/src/Makefile.am
index 395553c..0e0109a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,4 +1,6 @@
 SUBDIRS = lib bin
+# @todo hooks lib could be a configurable switch
+SUBDIRS += hooks
 
 EXTRA_DIST = \
 	cppcheck-suppress.lst		\
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index 65e5410..87f8e91 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -255,7 +255,7 @@ Query::addWildcardNXRRSETProof(ZoneFinder& finder, ConstRRsetPtr nsec) {
     if (nsec->getRdataCount() == 0) {
         isc_throw(BadNSEC, "NSEC for WILDCARD_NXRRSET is empty");
     }
-    
+
     ConstZoneFinderContextPtr fcontext =
         finder.find(*qname_, RRType::NSEC(),
                     dnssec_opt_ | ZoneFinder::NO_WILDCARD);
diff --git a/src/bin/bind10/init.py.in b/src/bin/bind10/init.py.in
index df69a34..cb1847d 100755
--- a/src/bin/bind10/init.py.in
+++ b/src/bin/bind10/init.py.in
@@ -337,6 +337,7 @@ class Init:
                 self.__propagate_component_config(new_config['components'])
             return isc.config.ccsession.create_answer(0)
         except Exception as e:
+            logger.error(BIND10_RECONFIGURE_ERROR, e)
             return isc.config.ccsession.create_answer(1, str(e))
 
     def get_processes(self):
@@ -597,6 +598,13 @@ class Init:
             process, the log_starting/log_started methods are not used.
         """
         logger.info(BIND10_STARTING_CC)
+
+        # Unsubscribe from the other CC session first, because we only
+        # monitor one and msgq expects all data sent to us to be read,
+        # or it will close its side of the socket.
+        if self.cc_session is not None:
+            self.cc_session.group_unsubscribe("Init")
+
         self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
                                       self.config_handler,
                                       self.command_handler,
@@ -764,9 +772,14 @@ class Init:
         it might want to choose if it is for this one).
         """
         logger.info(BIND10_STOP_PROCESS, process)
-        self.cc_session.group_sendmsg(isc.config.ccsession.
-                                      create_command('shutdown', {'pid': pid}),
-                                      recipient, recipient)
+        try:
+            self.cc_session.group_sendmsg(isc.config.ccsession.
+                                          create_command('shutdown',
+                                                         {'pid': pid}),
+                                          recipient, recipient)
+        except:
+            logger.error(BIND10_COMPONENT_SHUTDOWN_ERROR, process)
+            raise
 
     def component_shutdown(self, exitcode=0):
         """
diff --git a/src/bin/bind10/init_messages.mes b/src/bin/bind10/init_messages.mes
index 267790d..21cd142 100644
--- a/src/bin/bind10/init_messages.mes
+++ b/src/bin/bind10/init_messages.mes
@@ -325,3 +325,10 @@ the configuration manager to start up.  The total length of time Init
 will wait for the configuration manager before reporting an error is
 set with the command line --wait switch, which has a default value of
 ten seconds.
+
+% BIND10_RECONFIGURE_ERROR Error applying new config: %1
+A new configuration was received, but there was an error doing the
+re-configuration.
+
+% BIND10_COMPONENT_SHUTDOWN_ERROR An error occured stopping component %1
+An attempt to gracefully shutdown a component failed.
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 1ca8e10..a5acac0 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -199,7 +199,8 @@ protected:
             (config_id.compare("rebind-timer") == 0))  {
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
-                   (config_id.compare("interface") == 0)) {
+                   (config_id.compare("interface") == 0) ||
+                   (config_id.compare("next-server") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool4Parser(config_id, pools_);
@@ -264,14 +265,34 @@ protected:
         Triplet<uint32_t> t2 = getParam("rebind-timer");
         Triplet<uint32_t> valid = getParam("valid-lifetime");
 
-        /// @todo: Convert this to logger once the parser is working reliably
         stringstream tmp;
         tmp << addr.toText() << "/" << (int)len
             << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
 
         LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
 
-        subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
+        Subnet4Ptr subnet4(new Subnet4(addr, len, t1, t2, valid));
+        subnet_ = subnet4;
+
+        // Try global value first
+        try {
+            string next_server = globalContext()->string_values_->getParam("next-server");
+            if (!next_server.empty()) {
+                subnet4->setSiaddr(IOAddress(next_server));
+            }
+        } catch (const DhcpConfigError&) {
+            // Don't care. next_server is optional. We can live without it
+        }
+
+        // Try subnet specific value if it's available
+        try {
+            string next_server = string_values_->getParam("next-server");
+            if (!next_server.empty()) {
+                subnet4->setSiaddr(IOAddress(next_server));
+            }
+        } catch (const DhcpConfigError&) {
+            // Don't care. next_server is optional. We can live without it
+        }
     }
 };
 
@@ -366,7 +387,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     } else if (config_id.compare("option-def") == 0) {
         parser  = new OptionDefListParser(config_id,
                                           globalContext()->option_defs_);
-    } else if (config_id.compare("version") == 0) {
+    } else if ((config_id.compare("version") == 0) ||
+               (config_id.compare("next-server") == 0)) {
         parser  = new StringParser(config_id,
                                     globalContext()->string_values_);
     } else if (config_id.compare("lease-database") == 0) {
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index b979d45..b507159 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -16,7 +16,7 @@
           "item_default": ""
         }
       },
- 
+
       { "item_name": "interfaces",
         "item_type": "list",
         "item_optional": false,
@@ -48,6 +48,12 @@
         "item_default": 4000
       },
 
+      { "item_name": "next-server",
+        "item_type": "string",
+        "item_optional": true,
+        "item_default": ""
+      },
+
       { "item_name": "option-def",
         "item_type": "list",
         "item_optional": false,
@@ -218,6 +224,13 @@
                   "item_optional": false,
                   "item_default": 7200
                 },
+
+                { "item_name": "next-server",
+                  "item_type": "string",
+                  "item_optional": true,
+                  "item_default": "0.0.0.0"
+                },
+
                 { "item_name": "pool",
                   "item_type": "list",
                   "item_optional": false,
@@ -290,7 +303,7 @@
 
         {
             "command_name": "libreload",
-            "command_description": "Reloads the current hooks libraries.", 
+            "command_description": "Reloads the current hooks libraries.",
             "command_args": []
         }
 
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index fca75bf..1bfce66 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -118,7 +118,7 @@ a lease. It is up to the client to choose one server out of othe advertised
 and continue allocation with that server. This is a normal behavior and
 indicates successful operation.
 
-% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2
+% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2, client sent yiaddr %3
 This message indicates that the server has failed to offer a lease to
 the specified client after receiving a DISCOVER message from it. There are
 many possible reasons for such a failure.
@@ -128,7 +128,7 @@ This debug message indicates that the server successfully granted a lease
 in response to client's REQUEST message. This is a normal behavior and
 indicates successful operation.
 
-% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
+% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2, client sent yiaddr %3
 This message indicates that the server failed to grant a lease to the
 specified client after receiving a REQUEST message from it.  There are many
 possible reasons for such a failure. Additional messages will indicate the
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 4b80e97..4ac24dd 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -638,10 +638,12 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
     // to be returned to the client.
     for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
          opt != requested_opts.end(); ++opt) {
-        Subnet::OptionDescriptor desc =
-            subnet->getOptionDescriptor("dhcp4", *opt);
-        if (desc.option) {
-            msg->addOption(desc.option);
+        if (!msg->getOption(*opt)) {
+            Subnet::OptionDescriptor desc =
+                subnet->getOptionDescriptor("dhcp4", *opt);
+            if (desc.option && !msg->getOption(*opt)) {
+                msg->addOption(desc.option);
+            }
         }
     }
 }
@@ -683,19 +685,22 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
 
     // Get the list of options that client requested.
     bool added = false;
-    const std::vector<uint8_t>& requested_opts = oro->getData();
+    const std::vector<uint8_t>& requested_opts = oro->getValues();
 
     for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
          code != requested_opts.end(); ++code) {
-        Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, *code);
-        if (desc.option) {
-            vendor_rsp->addOption(desc.option);
-            added = true;
+        if  (!vendor_rsp->getOption(*code)) {
+            Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id,
+                                                                              *code);
+            if (desc.option) {
+                vendor_rsp->addOption(desc.option);
+                added = true;
+            }
         }
-    }
 
-    if (added) {
-        answer->addOption(vendor_rsp);
+        if (added) {
+            answer->addOption(vendor_rsp);
+        }
     }
 }
 
@@ -705,7 +710,6 @@ Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
     // Identify options that we always want to send to the
     // client (if they are configured).
     static const uint16_t required_options[] = {
-        DHO_SUBNET_MASK,
         DHO_ROUTERS,
         DHO_DOMAIN_NAME_SERVERS,
         DHO_DOMAIN_NAME };
@@ -758,6 +762,13 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         return;
     }
 
+    // Set up siaddr. Perhaps assignLease is not the best place to call this
+    // as siaddr has nothing to do with a lease, but otherwise we would have
+    // to select subnet twice (performance hit) or update too many functions
+    // at once.
+    // @todo: move subnet selection to a common code
+    answer->setSiaddr(subnet->getSiaddr());
+
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
         .arg(subnet->toText());
 
@@ -811,13 +822,6 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         opt->setUint32(lease->valid_lft_);
         answer->addOption(opt);
 
-        // Router (type 3)
-        Subnet::OptionDescriptor opt_routers =
-            subnet->getOptionDescriptor("dhcp4", DHO_ROUTERS);
-        if (opt_routers.option) {
-            answer->addOption(opt_routers.option);
-        }
-
         // Subnet mask (type 1)
         answer->addOption(getNetmaskOption(subnet));
 
@@ -914,15 +918,18 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
 
     copyDefaultFields(discover, offer);
     appendDefaultOptions(offer, DHCPOFFER);
-    appendRequestedOptions(discover, offer);
-    appendRequestedVendorOptions(discover, offer);
 
     assignLease(discover, offer);
 
-    // There are a few basic options that we always want to
-    // include in the response. If client did not request
-    // them we append them for him.
-    appendBasicOptions(discover, offer);
+    // Adding any other options makes sense only when we got the lease.
+    if (offer->getYiaddr() != IOAddress("0.0.0.0")) {
+        appendRequestedOptions(discover, offer);
+        appendRequestedVendorOptions(discover, offer);
+        // There are a few basic options that we always want to
+        // include in the response. If client did not request
+        // them we append them for him.
+        appendBasicOptions(discover, offer);
+    }
 
     return (offer);
 }
@@ -938,18 +945,21 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
 
     copyDefaultFields(request, ack);
     appendDefaultOptions(ack, DHCPACK);
-    appendRequestedOptions(request, ack);
-    appendRequestedVendorOptions(request, ack);
 
     // Note that we treat REQUEST message uniformly, regardless if this is a
     // first request (requesting for new address), renewing existing address
     // or even rebinding.
     assignLease(request, ack);
 
-    // There are a few basic options that we always want to
-    // include in the response. If client did not request
-    // them we append them for him.
-    appendBasicOptions(request, ack);
+    // Adding any other options makes sense only when we got the lease.
+    if (ack->getYiaddr() != IOAddress("0.0.0.0")) {
+        appendRequestedOptions(request, ack);
+        appendRequestedVendorOptions(request, ack);
+        // There are a few basic options that we always want to
+        // include in the response. If client did not request
+        // them we append them for him.
+        appendBasicOptions(request, ack);
+    }
 
     return (ack);
 }
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 9e43372..6e53d4d 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -412,6 +412,151 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
     EXPECT_EQ(4000, subnet->getValid());
 }
 
+// Checks if the next-server defined as global parameter is taken into
+// consideration.
+TEST_F(Dhcp4ParserTest, nextServerGlobal) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"next-server\": \"1.2.3.4\", "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
+}
+
+// Checks if the next-server defined as subnet parameter is taken into
+// consideration.
+TEST_F(Dhcp4ParserTest, nextServerSubnet) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"next-server\": \"1.2.3.4\", "
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
+}
+
+// Test checks several negative scenarios for next-server configuration: bogus
+// address, IPv6 adddress and empty string.
+TEST_F(Dhcp4ParserTest, nextServerNegative) {
+
+    ConstElementPtr status;
+
+    // Config with junk instead of next-server address
+    string config_bogus1 = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"next-server\": \"a.b.c.d\", "
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Config with IPv6 next server address
+    string config_bogus2 = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"next-server\": \"2001:db8::1\", "
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Config with empty next server address
+    string config_bogus3 = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"next-server\": \"\", "
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json1 = Element::fromJSON(config_bogus1);
+    ElementPtr json2 = Element::fromJSON(config_bogus2);
+    ElementPtr json3 = Element::fromJSON(config_bogus3);
+
+    // check if returned status is always a failure
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json1));
+    checkResult(status, 1);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json2));
+    checkResult(status, 1);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
+    checkResult(status, 0);
+}
+
+// Checks if the next-server defined as global value is overridden by subnet
+// specific value.
+TEST_F(Dhcp4ParserTest, nextServerOverride) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"next-server\": \"192.0.0.1\", "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"next-server\": \"1.2.3.4\", "
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
+}
+
 // This test checks if it is possible to override global values
 // on a per subnet basis.
 TEST_F(Dhcp4ParserTest, subnetLocal) {
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 8ac6ed3..1570ca9 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -1377,6 +1377,136 @@ TEST_F(Dhcpv4SrvTest, unpackOptions) {
     EXPECT_EQ(0x0, option_bar->getValue());
 }
 
+// Checks whether the server uses default (0.0.0.0) siaddr value, unless
+// explicitly specified
+TEST_F(Dhcpv4SrvTest, siaddrDefault) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+    IOAddress hint("192.0.2.107");
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+    dis->setYiaddr(hint);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+    ASSERT_TRUE(offer);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // Verify that it is 0.0.0.0
+    EXPECT_EQ("0.0.0.0", offer->getSiaddr().toText());
+}
+
+// Checks whether the server uses specified siaddr value
+TEST_F(Dhcpv4SrvTest, siaddr) {
+    boost::scoped_ptr<NakedDhcpv4Srv> srv;
+    ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+    subnet_->setSiaddr(IOAddress("192.0.2.123"));
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv->processDiscover(dis);
+    ASSERT_TRUE(offer);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+
+    // Verify that its value is proper
+    EXPECT_EQ("192.0.2.123", offer->getSiaddr().toText());
+}
+
+// Checks if the next-server defined as global value is overridden by subnet
+// specific value and returned in server messages. There's also similar test for
+// checking parser only configuration, see Dhcp4ParserTest.nextServerOverride in
+// config_parser_unittest.cc.
+TEST_F(Dhcpv4SrvTest, nextServerOverride) {
+
+    NakedDhcpv4Srv srv(0);
+
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"next-server\": \"192.0.0.1\", "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"next-server\": \"1.2.3.4\", "
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv.processDiscover(dis);
+    ASSERT_TRUE(offer);
+    EXPECT_EQ(DHCPOFFER, offer->getType());
+
+    EXPECT_EQ("1.2.3.4", offer->getSiaddr().toText());
+}
+
+// Checks if the next-server defined as global value is used in responses
+// when there is no specific value defined in subnet and returned to the client
+// properly. There's also similar test for checking parser only configuration,
+// see Dhcp4ParserTest.nextServerGlobal in config_parser_unittest.cc.
+TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
+
+    NakedDhcpv4Srv srv(0);
+
+    ConstElementPtr status;
+
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"next-server\": \"192.0.0.1\", "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv.processDiscover(dis);
+    ASSERT_TRUE(offer);
+    EXPECT_EQ(DHCPOFFER, offer->getType());
+
+    EXPECT_EQ("192.0.0.1", offer->getSiaddr().toText());
+}
+
+
 // a dummy MAC address
 const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
 
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
index d085316..56e96f1 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.cc
@@ -22,6 +22,7 @@
 #include <dhcp/option_custom.h>
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 
@@ -123,37 +124,101 @@ void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
     EXPECT_TRUE(q->getLocalHWAddr() == a->getLocalHWAddr());
     EXPECT_TRUE(q->getRemoteHWAddr() == a->getRemoteHWAddr());
 
-    // Check that bare minimum of required options are there.
-    // We don't check options requested by a client. Those
-    // are checked elsewhere.
-    EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
-    EXPECT_TRUE(a->getOption(DHO_ROUTERS));
+    // Check that the server identifier is present in the response.
+    // Presence (or absence) of other options is checked elsewhere.
     EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
-    EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
-    EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
-    EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
-    EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
 
     // Check that something is offered
     EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
 }
 
-void Dhcpv4SrvTest::optionsCheck(const Pkt4Ptr& pkt) {
-    // Check that the requested and configured options are returned
-    // in the ACK message.
-    EXPECT_TRUE(pkt->getOption(DHO_DOMAIN_NAME))
-        << "domain-name not present in the response";
-    EXPECT_TRUE(pkt->getOption(DHO_DOMAIN_NAME_SERVERS))
-        << "dns-servers not present in the response";
-    EXPECT_TRUE(pkt->getOption(DHO_LOG_SERVERS))
-        << "log-servers not present in the response";
-    EXPECT_TRUE(pkt->getOption(DHO_COOKIE_SERVERS))
-        << "cookie-servers not present in the response";
-    // Check that the requested but not configured options are not
-    // returned in the ACK message.
-    EXPECT_FALSE(pkt->getOption(DHO_LPR_SERVERS))
-        << "domain-name present in the response but it is"
-        << " expected not to be present";
+::testing::AssertionResult
+Dhcpv4SrvTest::basicOptionsPresent(const Pkt4Ptr& pkt) {
+    std::ostringstream errmsg;
+    errmsg << "option missing in the response";
+    if (!pkt->getOption(DHO_DOMAIN_NAME)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "domain-name " << errmsg));
+
+    } else if (!pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "dns-servers " << errmsg));
+
+    } else if (!pkt->getOption(DHO_SUBNET_MASK)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "subnet-mask " << errmsg));
+
+    } else if (!pkt->getOption(DHO_ROUTERS)) {
+        return (::testing::AssertionFailure(::testing::Message() << "routers "
+                                            << errmsg));
+
+    } else if (!pkt->getOption(DHO_DHCP_LEASE_TIME)) {
+        return (::testing::AssertionFailure(::testing::Message() <<
+                                            "dhcp-lease-time " << errmsg));
+
+    }
+    return (::testing::AssertionSuccess());
+
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::noBasicOptions(const Pkt4Ptr& pkt) {
+    std::ostringstream errmsg;
+    errmsg << "option present in the response";
+    if (pkt->getOption(DHO_DOMAIN_NAME)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "domain-name " << errmsg));
+
+    } else if (pkt->getOption(DHO_DOMAIN_NAME_SERVERS)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "dns-servers " << errmsg));
+
+    } else if (pkt->getOption(DHO_SUBNET_MASK)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "subnet-mask " << errmsg));
+
+    } else if (pkt->getOption(DHO_ROUTERS)) {
+        return (::testing::AssertionFailure(::testing::Message() << "routers "
+                                            << errmsg));
+
+    } else if (pkt->getOption(DHO_DHCP_LEASE_TIME)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "dhcp-lease-time " << errmsg));
+
+    }
+    return (::testing::AssertionSuccess());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::requestedOptionsPresent(const Pkt4Ptr& pkt) {
+    std::ostringstream errmsg;
+    errmsg << "option missing in the response";
+    if (!pkt->getOption(DHO_LOG_SERVERS)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "log-servers " << errmsg));
+
+    } else if (!pkt->getOption(DHO_COOKIE_SERVERS)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "cookie-servers " << errmsg));
+
+    }
+    return (::testing::AssertionSuccess());
+}
+
+::testing::AssertionResult
+Dhcpv4SrvTest::noRequestedOptions(const Pkt4Ptr& pkt) {
+    std::ostringstream errmsg;
+    errmsg << "option present in the response";
+    if (pkt->getOption(DHO_LOG_SERVERS)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "log-servers " << errmsg));
+
+    } else if (pkt->getOption(DHO_COOKIE_SERVERS)) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "cookie-servers " << errmsg));
+
+    }
+    return (::testing::AssertionSuccess());
 }
 
 OptionPtr Dhcpv4SrvTest::generateClientId(size_t size /*= 4*/) {
@@ -274,6 +339,48 @@ void Dhcpv4SrvTest::checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_
     EXPECT_TRUE(expected_clientid->getData() == opt->getData());
 }
 
+::testing::AssertionResult
+Dhcpv4SrvTest::createPacketFromBuffer(const Pkt4Ptr& src_pkt,
+                                      Pkt4Ptr& dst_pkt) {
+    // Create on-wire format of the packet. If pack() has been called
+    // on this instance of the packet already, the next call to pack()
+    // should remove all contents of the output buffer.
+    try {
+        src_pkt->pack();
+    } catch (const Exception& ex) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "Failed to parse source packet: "
+                                            << ex.what()));
+    }
+    // Get the output buffer from the source packet.
+    const util::OutputBuffer& buf = src_pkt->getBuffer();
+    // Create a copy of the packet using the output buffer from the source
+    // packet.
+    try {
+        dst_pkt.reset(new Pkt4(static_cast<const uint8_t*>(buf.getData()),
+                               buf.getLength()));
+    } catch (const Exception& ex) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "Failed to create a"
+                                            " destination packet from"
+                                            " the buffer: "
+                                            << ex.what()));
+    }
+
+    try {
+        // Parse the new packet and return to the caller.
+        dst_pkt->unpack();
+    } catch (const Exception& ex) {
+        return (::testing::AssertionFailure(::testing::Message()
+                                            << "Failed to parse a"
+                                            << " destination packet: "
+                                            << ex.what()));
+    }
+
+    return (::testing::AssertionSuccess());
+}
+
+
 void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
     // Create an instance of the tested class.
     boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
@@ -318,17 +425,22 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
     // are returned when requested.
     configureRequestedOptions();
 
+    // Create a copy of the original packet by parsing its wire format.
+    // This simulates the real life scenario when we process the packet
+    // which was parsed from its wire format.
+    Pkt4Ptr received;
+    ASSERT_TRUE(createPacketFromBuffer(req, received));
     if (msg_type == DHCPDISCOVER) {
         ASSERT_NO_THROW(
-            rsp = srv->processDiscover(req);
-            );
+            rsp = srv->processDiscover(received);
+        );
 
         // Should return OFFER
         ASSERT_TRUE(rsp);
         EXPECT_EQ(DHCPOFFER, rsp->getType());
 
     } else {
-        ASSERT_NO_THROW(rsp = srv->processRequest(req););
+        ASSERT_NO_THROW(rsp = srv->processRequest(received));
 
         // Should return ACK
         ASSERT_TRUE(rsp);
@@ -336,27 +448,30 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
 
     }
 
-    messageCheck(req, rsp);
+    messageCheck(received, rsp);
 
+    // Basic options should be present when we got the lease.
+    EXPECT_TRUE(basicOptionsPresent(rsp));
     // We did not request any options so these should not be present
     // in the RSP.
-    EXPECT_FALSE(rsp->getOption(DHO_LOG_SERVERS));
-    EXPECT_FALSE(rsp->getOption(DHO_COOKIE_SERVERS));
-    EXPECT_FALSE(rsp->getOption(DHO_LPR_SERVERS));
+    EXPECT_TRUE(noRequestedOptions(rsp));
 
     // Repeat the test but request some options.
     // Add 'Parameter Request List' option.
     addPrlOption(req);
 
+    ASSERT_TRUE(createPacketFromBuffer(req, received));
+    ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+
     if (msg_type == DHCPDISCOVER) {
-        ASSERT_NO_THROW(rsp = srv->processDiscover(req););
+        ASSERT_NO_THROW(rsp = srv->processDiscover(received));
 
         // Should return non-NULL packet.
         ASSERT_TRUE(rsp);
         EXPECT_EQ(DHCPOFFER, rsp->getType());
 
     } else {
-        ASSERT_NO_THROW(rsp = srv->processRequest(req););
+        ASSERT_NO_THROW(rsp = srv->processRequest(received));
 
         // Should return non-NULL packet.
         ASSERT_TRUE(rsp);
@@ -364,7 +479,41 @@ void Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
     }
 
     // Check that the requested options are returned.
-    optionsCheck(rsp);
+    EXPECT_TRUE(basicOptionsPresent(rsp));
+    EXPECT_TRUE(requestedOptionsPresent(rsp));
+
+    // The following part of the test will test that the NAK is sent when
+    // there is no address pool configured. In the same time, we expect
+    // that the requested options are not included in NAK message, but that
+    // they are only included when yiaddr is set to non-zero value.
+    ASSERT_NO_THROW(subnet_->delPools(Lease::TYPE_V4));
+
+    // There has been a lease allocated for the particular client. So,
+    // even though we deleted the subnet, the client would get the
+    // existing lease (not a NAK). Therefore, we have to change the chaddr
+    // in the packet so as the existing lease is not returned.
+    req->setHWAddr(1, 6, std::vector<uint8_t>(2, 6));
+    ASSERT_TRUE(createPacketFromBuffer(req, received));
+    ASSERT_TRUE(received->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+
+    if (msg_type == DHCPDISCOVER) {
+        ASSERT_NO_THROW(rsp = srv->processDiscover(received));
+        // Should return non-NULL packet.
+        ASSERT_TRUE(rsp);
+    } else {
+        ASSERT_NO_THROW(rsp = srv->processRequest(received));
+        // Should return non-NULL packet.
+        ASSERT_TRUE(rsp);
+    }
+    // We should get the NAK packet with yiaddr set to 0.
+    EXPECT_EQ(DHCPNAK, rsp->getType());
+    ASSERT_EQ("0.0.0.0", rsp->getYiaddr().toText());
+
+    // Make sure that none of the requested options is returned in NAK.
+    // Also options such as Routers or Subnet Mask should not be there,
+    // because lease hasn't been acquired.
+    EXPECT_TRUE(noRequestedOptions(rsp));
+    EXPECT_TRUE(noBasicOptions(rsp));
 }
 
 /// @brief This function cleans up after the test.
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index 2e39a8b..ee19728 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -109,10 +109,32 @@ public:
     /// @param a answer (server's message)
     void messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a);
 
-    /// @brief Check that requested options are present.
+    /// @brief Check that certain basic (always added when lease is acquired)
+    /// are present in a message.
     ///
-    /// @param pkt packet to be checked.
-    void optionsCheck(const Pkt4Ptr& pkt);
+    /// @param pkt A message to be checked.
+    /// @return Assertion result which indicates whether test passed or failed.
+    ::testing::AssertionResult basicOptionsPresent(const Pkt4Ptr& pkt);
+
+    /// @brief Check that certain basic (always added when lease is acquired)
+    /// are not present.
+    ///
+    /// @param pkt A packet to be checked.
+    /// @return Assertion result which indicates whether test passed or failed.
+    ::testing::AssertionResult noBasicOptions(const Pkt4Ptr& pkt);
+
+    /// @brief Check that certain requested options are present in the message.
+    ///
+    /// @param pkt A message to be checked.
+    /// @return Assertion result which indicates whether test passed or failed.
+    ::testing::AssertionResult requestedOptionsPresent(const Pkt4Ptr& pkt);
+
+    /// @brief Check that certain options (requested with PRL option)
+    /// are not present.
+    ///
+    /// @param pkt A packet to be checked.
+    /// @return Assertion result which indicates whether test passed or failed.
+    ::testing::AssertionResult noRequestedOptions(const Pkt4Ptr& pkt);
 
     /// @brief generates client-id option
     ///
@@ -183,6 +205,32 @@ public:
     /// @return relayed DISCOVER
     Pkt4Ptr captureRelayedDiscover();
 
+    /// @brief Create packet from output buffer of another packet.
+    ///
+    /// This function creates a packet using an output buffer from another
+    /// packet. This imitates reception of a packet from the wire. The
+    /// unpack function is then called to parse packet contents and to
+    /// create a collection of the options carried by this packet.
+    ///
+    /// This function is useful for unit tests which verify that the received
+    /// packet is parsed correctly. In those cases it is usually inappropriate
+    /// to create an instance of the packet, add options, set packet
+    /// fields and use such packet as an input to processDiscover or
+    /// processRequest function. This is because, such a packet has certain
+    /// options already initialized and there is no way to verify that they
+    /// have been initialized when packet instance was created or wire buffer
+    /// processing. By creating a packet from the buffer we guarantee that the
+    /// new packet is entirely initialized during wire data parsing.
+    ///
+    /// @param src_pkt A source packet, to be copied.
+    /// @param [out] dst_pkt A destination packet.
+    ///
+    /// @return assertion result indicating if a function completed with
+    /// success or failure.
+    static ::testing::AssertionResult
+    createPacketFromBuffer(const Pkt4Ptr& src_pkt,
+                           Pkt4Ptr& dst_pkt);
+
     /// @brief generates a DHCPv4 packet based on provided hex string
     ///
     /// @return created packet
@@ -190,6 +238,14 @@ public:
 
     /// @brief Tests if Discover or Request message is processed correctly
     ///
+    /// This test verifies that the Parameter Request List option is handled
+    /// correctly, i.e. it checks that certain options are present in the
+    /// server's response when they are requested and that they are not present
+    /// when they are not requested or NAK occurs.
+    ///
+    /// @todo We need an additional test for PRL option using real traffic
+    /// capture.
+    ///
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
     void testDiscoverRequest(const uint8_t msg_type);
 
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 7168b91..e42c83b 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -216,7 +216,7 @@ void ControlledDhcpv6Srv::establishSession() {
         // reopen sockets according to new configuration.
         openActiveSockets(getPort());
 
-    } catch (const DhcpConfigError& ex) {
+    } catch (const std::exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
 
     }
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index fb50e77..9678b4f 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -450,6 +450,9 @@ This debug message indicates that a shutdown of the IPv6 server has
 been requested via a call to the 'shutdown' method of the core Dhcpv6Srv
 object.
 
+% DHCP6_SOCKET_UNICAST server is about to open socket on address %1 on interface %2
+This is a debug message that inform that a unicast socket will be opened.
+
 % DHCP6_SRV_CONSTRUCT_ERROR error creating Dhcpv6Srv object, reason: %1
 This error message indicates that during startup, the construction of a
 core component within the IPv6 DHCP server (the Dhcpv6 server object)
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 9ebfd24..b9ecdcf 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -2284,7 +2284,7 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
                       << " trying to reopen sockets after reconfiguration");
         }
         if (CfgMgr::instance().isActiveIface(iface->getName())) {
-            iface_ptr->inactive4_ = false;
+            iface_ptr->inactive6_ = false;
             LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE)
                 .arg(iface->getFullName());
 
@@ -2297,6 +2297,15 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
             iface_ptr->inactive6_ = true;
 
         }
+
+        iface_ptr->clearUnicasts();
+
+        const IOAddress* unicast = CfgMgr::instance().getUnicast(iface->getName());
+        if (unicast) {
+            LOG_INFO(dhcp6_logger, DHCP6_SOCKET_UNICAST).arg(unicast->toText())
+                .arg(iface->getName());
+            iface_ptr->addUnicast(*unicast);
+        }
     }
     // Let's reopen active sockets. openSockets6 will check internally whether
     // sockets are marked active or inactive.
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 765d19f..07ea5ba 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -525,7 +525,9 @@ class MsgQ:
             # Append it to buffer (but check the data go away)
             if fileno in self.sendbuffs:
                 (last_sent, buff) = self.sendbuffs[fileno]
-                if now - last_sent > 0.1:
+                tdelta = now - last_sent
+                if tdelta > 0.1:
+                    logger.error(MSGQ_SOCKET_TIMEOUT_ERROR, fileno, tdelta)
                     self.kill_socket(fileno, sock)
                     return False
                 buff += msg
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
index 909d6a3..2bd2515 100644
--- a/src/bin/msgq/msgq_messages.mes
+++ b/src/bin/msgq/msgq_messages.mes
@@ -142,3 +142,9 @@ data structure.
 % MSGQ_SUBS_NEW_TARGET Creating new target for subscription to group '%1' for instance '%2'
 Debug message. Creating a new subscription. Also creating a new data structure
 to hold it.
+
+% MSGQ_SOCKET_TIMEOUT_ERROR Killing socket %1 because timeout exceeded (%2)
+Outgoing data was queued up on a socket connected to msgq, but the other
+side is not reading it. It could be deadlocked, or may not be monitoring
+it. Both cases are programming errors and should be corrected. The socket
+is closed on the msgq side.
diff --git a/src/hooks/Makefile.am b/src/hooks/Makefile.am
new file mode 100644
index 0000000..815beed
--- /dev/null
+++ b/src/hooks/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = dhcp
diff --git a/src/hooks/dhcp/Makefile.am b/src/hooks/dhcp/Makefile.am
new file mode 100644
index 0000000..6785617
--- /dev/null
+++ b/src/hooks/dhcp/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = user_chk
diff --git a/src/hooks/dhcp/user_chk/Makefile.am b/src/hooks/dhcp/user_chk/Makefile.am
new file mode 100644
index 0000000..874719b
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/Makefile.am
@@ -0,0 +1,65 @@
+SUBDIRS = . tests
+
+AM_CPPFLAGS  = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CXXFLAGS  = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+# Until logging in dynamically loaded libraries is fixed,
+# Define rule to build logging source files from message file
+# user_chk_messages.h user_chk_messages.cc: s-messages
+
+# Until logging in dynamically loaded libraries is fixed, exclude these.
+# s-messages: user_chk_messages.mes
+#	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/hooks/dhcp/user_chk/user_chk_messages.mes
+#	touch $@
+
+# Tell automake that the message files are built as part of the build process
+# (so that they are built before the main library is built).
+# BUILT_SOURCES = user_chk_messages.h user_chk_messages.cc
+BUILT_SOURCES =
+
+# Ensure that the message file is included in the distribution
+EXTRA_DIST =
+
+# Get rid of generated message files on a clean
+#CLEANFILES = *.gcno *.gcda user_chk_messages.h user_chk_messages.cc s-messages
+CLEANFILES = *.gcno *.gcda
+
+lib_LTLIBRARIES = libdhcp_user_chk.la
+libdhcp_user_chk_la_SOURCES  =
+libdhcp_user_chk_la_SOURCES += load_unload.cc
+libdhcp_user_chk_la_SOURCES += subnet_select_co.cc
+libdhcp_user_chk_la_SOURCES += user.cc user.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
+
+# Until logging in dynamically loaded libraries is fixed, exclude these.
+#nodist_libdhcp_user_chk_la_SOURCES = user_chk_messages.cc user_chk_messages.h
+
+libdhcp_user_chk_la_CXXFLAGS = $(AM_CXXFLAGS)
+libdhcp_user_chk_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libdhcp_user_chk_la_LDFLAGS  = $(AM_LDFLAGS)
+libdhcp_user_chk_la_LDFLAGS  += -avoid-version -export-dynamic -module
+libdhcp_user_chk_la_LIBADD  =
+libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/log/libb10-log.la
+libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/util/libb10-util.la
+libdhcp_user_chk_la_LIBADD  += $(top_builddir)/src/lib/util/threads/libb10-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
diff --git a/src/hooks/dhcp/user_chk/load_unload.cc b/src/hooks/dhcp/user_chk/load_unload.cc
new file mode 100644
index 0000000..79afb82
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/load_unload.cc
@@ -0,0 +1,113 @@
+// Copyright (C) 2013 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.
+
+/// @file This file defines the load and unload hooks library functions.
+
+#include <hooks/hooks.h>
+#include <user_registry.h>
+#include <user_file.h>
+
+#include <iostream>
+#include <fstream>
+#include <errno.h>
+
+using namespace isc::hooks;
+
+/// @brief Pointer to the registry instance.
+UserRegistryPtr user_registry;
+
+/// @brief Output filestream for recording user check outcomes.
+std::fstream user_chk_output;
+
+/// @brief User registry input file name.
+/// @todo Hard-coded for now, this should be configurable.
+const char* registry_fname = "/tmp/user_chk_registry.txt";
+
+/// @brief User check outcome file name.
+/// @todo Hard-coded for now, this should be configurable.
+const char* user_chk_output_fname = "/tmp/user_chk_outcome.txt";
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+/// @brief Called by the Hooks library manager when the library is loaded.
+///
+/// Instantiates the UserRegistry and opens the outcome file. Failure in
+/// either results in a failed return code.
+///
+/// @param unused library handle parameter required by Hooks API.
+///
+/// @return Returns 0 upon success, non-zero upon failure.
+int load(LibraryHandle&) {
+    // non-zero indicates an error.
+    int ret_val = 0;
+    try {
+        // Instantiate the registry.
+        user_registry.reset(new UserRegistry());
+
+        // Create the data source.
+        UserDataSourcePtr user_file(new UserFile(registry_fname));
+
+        // Set the registry's data source
+        user_registry->setSource(user_file);
+
+        // Do an initial load of the registry.
+        user_registry->refresh();
+
+        // Open up the output file for user_chk results.
+        user_chk_output.open(user_chk_output_fname,
+                     std::fstream::out | std::fstream::app);
+
+        if (!user_chk_output) {
+            // Grab the system error message.
+            const char* errmsg = strerror(errno);
+            isc_throw(isc::Unexpected, "Cannot open output file: "
+                                       << user_chk_output_fname
+                                       << " reason: " << errmsg);
+        }
+    }
+    catch (const std::exception& ex) {
+        // Log the error and return failure.
+        std::cout << "DHCP UserCheckHook could not be loaded: "
+                  << ex.what() << std::endl;
+        ret_val = 1;
+    }
+
+    return (ret_val);
+}
+
+/// @brief Called by the Hooks library manager when the library is unloaded.
+///
+/// Destroys the UserRegistry and closes the outcome file.
+///
+/// @return Always returns 0.
+int unload() {
+    try {
+        user_registry.reset();
+        if (user_chk_output.is_open()) {
+            user_chk_output.close();
+        }
+    } catch (const std::exception& ex) {
+        // On the off chance something goes awry, catch it and log it.
+        // @todo Not sure if we should return a non-zero result or not.
+        std::cout << "DHCP UserCheckHook could not be unloaded: "
+                  << ex.what() << std::endl;
+    }
+
+    return (0);
+}
+
+}
diff --git a/src/hooks/dhcp/user_chk/subnet_select_co.cc b/src/hooks/dhcp/user_chk/subnet_select_co.cc
new file mode 100644
index 0000000..31fbd6f
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/subnet_select_co.cc
@@ -0,0 +1,237 @@
+// Copyright (C) 2013 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.
+
+/// @file Defines the subnet4_select and subnet6_select callout functions.
+
+#include <hooks/hooks.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt6.h>
+#include <dhcpsrv/subnet.h>
+#include <user_registry.h>
+
+#include <fstream>
+#include <string>
+
+using namespace isc::dhcp;
+using namespace isc::hooks;
+using namespace std;
+
+extern UserRegistryPtr user_registry;
+extern std::fstream user_chk_output;
+extern const char* registry_fname;
+extern const char* user_chk_output_fname;
+
+// Functions accessed by the hooks framework use C linkage to avoid the name
+// mangling that accompanies use of the C++ compiler as well as to avoid
+// issues related to namespaces.
+extern "C" {
+
+/// @brief Adds an entry to the end of the user check outcome file.
+///
+/// Each user entry is written in an ini-like format, with one name-value pair
+/// per line as follows:
+///
+/// id_type=<id type>
+/// client=<id str>
+/// subnet=<subnet str>
+/// registered=<is registered>"
+///
+/// where:
+/// <id type> text label of the id type: "HW_ADDR" or "DUID"
+/// <id str> user's id formatted as either isc::dhcp::Hwaddr.toText() or
+/// isc::dhcp::DUID.toText()
+/// <subnet str> selected subnet formatted as isc::dhcp::Subnet4::toText() or
+/// isc::dhcp::Subnet6::toText() as appropriate.
+/// <is registered> "yes" or "no"
+///
+/// Sample IPv4 entry would like this:
+///
+/// @code
+/// id_type=DUID
+/// client=00:01:00:01:19:ef:e6:3b:00:0c:01:02:03:04
+/// subnet=2001:db8:2::/64
+/// registered=yes
+/// id_type=duid
+/// @endcode
+///
+/// Sample IPv4 entry would like this:
+///
+/// @code
+/// id_type=DUID
+/// id_type=HW_ADDR
+/// client=hwtype=1 00:0c:01:02:03:05
+/// subnet=152.77.5.0/24
+/// registered=no
+/// @endcode
+///
+/// @param id_type_str text label identify the id type
+/// @param id_val_str text representation of the user id
+/// @param subnet_str text representation  of the selected subnet
+/// @param registered boolean indicating if the user is registered or not
+void generate_output_record(const std::string& id_type_str,
+                            const std::string& id_val_str,
+                            const std::string& subnet_str,
+                            const bool& registered)
+{
+    user_chk_output << "id_type=" << id_type_str << std::endl
+                    << "client=" << id_val_str << std::endl
+                    << "subnet=" << subnet_str << std::endl
+                    << "registered=" << (registered ? "yes" : "no")
+                    << std::endl;
+
+    // @todo Flush is here to ensure output is immediate for demo purposes.
+    // Performance would generally dictate not using it.
+    flush(user_chk_output);
+}
+
+/// @brief  This callout is called at the "subnet4_select" hook.
+///
+/// This function searches the UserRegistry for the client indicated by the
+/// inbound IPv4 DHCP packet. If the client is found in the registry output
+/// the generate outcome record and return.
+///
+/// If the client is not found in the registry replace the selected subnet
+/// with the restricted access subnet, then generate the outcome record and
+/// return.  By convention, it is assumed that last subnet in the list of
+/// available subnets is the restricted access subnet.
+///
+/// @param handle CalloutHandle which provides access to context.
+///
+/// @return 0 upon success, non-zero otherwise.
+int subnet4_select(CalloutHandle& handle) {
+    if (!user_registry) {
+        std::cout << "DHCP UserCheckHook : subnet4_select UserRegistry is null"
+                  << std::endl;
+        return (1);
+    }
+
+    try {
+        // Get subnet collection. If it's empty just bail nothing to do.
+        const isc::dhcp::Subnet4Collection *subnets = NULL;
+        handle.getArgument("subnet4collection", subnets);
+        if (subnets->size() == 0) {
+            return 0;
+        }
+
+        // Refresh the registry.
+        user_registry->refresh();
+
+        // Get the HWAddress as the user identifier.
+        Pkt4Ptr query;
+        handle.getArgument("query4", query);
+        HWAddrPtr hwaddr = query->getHWAddr();
+
+        // Look for the user.
+        UserPtr registered_user = user_registry->findUser(*hwaddr);
+        if (registered_user) {
+            // User is in the registry, so leave the pre-selected subnet alone.
+            Subnet4Ptr subnet;
+            handle.getArgument("subnet4", subnet);
+            // Add the outcome entry to the output file.
+            generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
+                                   subnet->toText(), true);
+        } else {
+            // User is not in the registry, so assign them to the last subnet
+            // in the collection.  By convention we are assuming this is the
+            // restricted subnet.
+            Subnet4Ptr subnet = subnets->back();
+            handle.setArgument("subnet4", subnet);
+            // Add the outcome entry to the output file.
+            generate_output_record(UserId::HW_ADDRESS_STR, hwaddr->toText(),
+                                   subnet->toText(), false);
+        }
+    } catch (const std::exception& ex) {
+        std::cout << "DHCP UserCheckHook : subnet6_select unexpected error: "
+                  << ex.what() << std::endl;
+        return (1);
+    }
+
+    return (0);
+}
+
+/// @brief  This callout is called at the "subnet6_select" hook.
+///
+/// This function searches the UserRegistry for the client indicated by the
+/// inbound IPv6 DHCP packet. If the client is found in the registry generate
+/// the outcome record and return.
+///
+/// If the client is not found in the registry replace the selected subnet
+/// with the restricted access subnet, then generate the outcome record and
+/// return.  By convention, it is assumed that last subnet in the list of
+/// available subnets is the restricted access subnet.
+///
+/// @param handle CalloutHandle which provides access to context.
+///
+/// @return 0 upon success, non-zero otherwise.
+int subnet6_select(CalloutHandle& handle) {
+    if (!user_registry) {
+        std::cout << "DHCP UserCheckHook : subnet6_select UserRegistry is null"
+                  << std::endl;
+        return (1);
+    }
+
+    try {
+        // Get subnet collection. If it's empty just bail nothing to do.
+        const isc::dhcp::Subnet6Collection *subnets = NULL;
+        handle.getArgument("subnet6collection", subnets);
+        if (subnets->size() == 0) {
+            return 0;
+        }
+
+        // Refresh the registry.
+        user_registry->refresh();
+
+        // Get the HWAddress as the user identifier.
+        Pkt6Ptr query;
+        handle.getArgument("query6", query);
+
+        DuidPtr duid;
+        OptionPtr opt_duid = query->getOption(D6O_CLIENTID);
+        if (!opt_duid) {
+            std::cout << "DHCP6 query is missing DUID" << std::endl;
+            return (1);
+        }
+
+        duid = DuidPtr(new DUID(opt_duid->getData()));
+
+        // Look for the user.
+        UserPtr registered_user = user_registry->findUser(*duid);
+        if (registered_user) {
+            // User is in the registry, so leave the pre-selected subnet alone.
+            Subnet6Ptr subnet;
+            handle.getArgument("subnet6", subnet);
+            // Add the outcome entry to the output file.
+            generate_output_record(UserId::DUID_STR, duid->toText(),
+                                   subnet->toText(), true);
+        } else {
+            // User is not in the registry, so assign them to the last subnet
+            // in the collection.  By convention we are assuming this is the
+            // restricted subnet.
+            Subnet6Ptr subnet = subnets->back();
+            handle.setArgument("subnet6", subnet);
+            // Add the outcome entry to the output file.
+            generate_output_record(UserId::DUID_STR, duid->toText(),
+                                   subnet->toText(), false);
+        }
+    } catch (const std::exception& ex) {
+        std::cout << "DHCP UserCheckHook : subnet6_select unexpected error: "
+                  << ex.what() << std::endl;
+        return (1);
+    }
+
+    return (0);
+}
+
+}
diff --git a/src/hooks/dhcp/user_chk/tests/Makefile.am b/src/hooks/dhcp/user_chk/tests/Makefile.am
new file mode 100644
index 0000000..d76f215
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/Makefile.am
@@ -0,0 +1,73 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/user_chk -I$(top_srcdir)/src/hooks/dhcp/user_chk
+AM_CPPFLAGS += $(BOOST_INCLUDES) $(BOTAN_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_srcdir)/src/hooks/dhcp/user_chk/tests\"
+AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+# Some versions of GCC warn about some versions of Boost regarding
+# missing initializer for members in its posix_time.
+# https://svn.boost.org/trac/boost/ticket/3477
+# But older GCC compilers don't have the flag.
+AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+USER_CHK_LIB = $(top_builddir)/src/hooks/dhcp/user_chk/libdhcp_user_chk.la
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+# Unit test data files need to get installed.
+EXTRA_DIST = test_users_1.txt test_users_err.txt
+
+CLEANFILES = *.gcno *.gcda
+
+TESTS_ENVIRONMENT = \
+	$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
+
+TESTS =
+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 += ../subnet_select_co.cc
+libdhcp_user_chk_unittests_SOURCES += ../version.cc
+libdhcp_user_chk_unittests_SOURCES += ../user.cc ../user.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
+libdhcp_user_chk_unittests_SOURCES += user_registry_unittests.cc
+libdhcp_user_chk_unittests_SOURCES += user_file_unittests.cc
+
+libdhcp_user_chk_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
+
+libdhcp_user_chk_unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)
+
+libdhcp_user_chk_unittests_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# This is to workaround unused variables tcout and tcerr in
+# log4cplus's streams.h and unused parameters from some of the
+# Boost headers.
+libdhcp_user_chk_unittests_CXXFLAGS += -Wno-unused-parameter
+endif
+
+libdhcp_user_chk_unittests_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/hooks/libb10-hooks.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libdhcp_user_chk_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+libdhcp_user_chk_unittests_LDADD += ${BOTAN_LIBS} ${BOTAN_RPATH}
+libdhcp_user_chk_unittests_LDADD += $(GTEST_LDADD)
+endif
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/hooks/dhcp/user_chk/tests/run_unittests.cc b/src/hooks/dhcp/user_chk/tests/run_unittests.cc
new file mode 100644
index 0000000..185f888
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/run_unittests.cc
@@ -0,0 +1,27 @@
+// Copyright (C) 2013 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 <log/logger_support.h>
+
+#include <gtest/gtest.h>
+
+int
+main(int argc, char* argv[]) {
+    ::testing::InitGoogleTest(&argc, argv);
+    isc::log::initLogger();
+
+    int result = RUN_ALL_TESTS();
+
+    return (result);
+}
diff --git a/src/hooks/dhcp/user_chk/tests/test_data_files_config.h.in b/src/hooks/dhcp/user_chk/tests/test_data_files_config.h.in
new file mode 100644
index 0000000..9abdbc6
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/test_data_files_config.h.in
@@ -0,0 +1,16 @@
+// Copyright (C) 2013 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.
+//
+/// @brief Path to local dir so tests can locate test data files 
+#define USER_CHK_TEST_DIR "@abs_top_srcdir@/src/hooks/dhcp/user_chk/tests"
diff --git a/src/hooks/dhcp/user_chk/tests/test_users_1.txt b/src/hooks/dhcp/user_chk/tests/test_users_1.txt
new file mode 100644
index 0000000..5fa718f
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/test_users_1.txt
@@ -0,0 +1,2 @@
+{ "type" : "HW_ADDR", "id" : "01AC00F03344", "opt1" : "true" }
+{ "type" : "DUID", "id" : "225060de0a0b", "opt1" : "true" }
diff --git a/src/hooks/dhcp/user_chk/tests/test_users_err.txt b/src/hooks/dhcp/user_chk/tests/test_users_err.txt
new file mode 100644
index 0000000..3204006
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/test_users_err.txt
@@ -0,0 +1,2 @@
+{ "type" : "DUID", "id" : "777777777777", "opt1" : "true" }
+{ "type" : "BOGUS", "id" : "01AC00F03344", "opt1" : "true" }
diff --git a/src/hooks/dhcp/user_chk/tests/user_file_unittests.cc b/src/hooks/dhcp/user_chk/tests/user_file_unittests.cc
new file mode 100644
index 0000000..9507fab
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/user_file_unittests.cc
@@ -0,0 +1,158 @@
+// Copyright (C) 2013  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 <exceptions/exceptions.h>
+#include <test_data_files_config.h>
+#include <user_file.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+
+namespace {
+
+/// @brief Convenience method for reliably building test file path names.
+///
+/// Function prefixes the given file name with a path to unit tests directory
+/// so we can reliably find test data files.
+///
+/// @param name base file name of the test file
+std::string testFilePath(const std::string& name) {
+    return (std::string(USER_CHK_TEST_DIR) + "/" + name);
+}
+
+/// @brief Tests the UserFile constructor.
+TEST(UserFile, construction) {
+    // Verify that a UserFile with no file name is rejected.
+    ASSERT_THROW(UserFile(""), UserFileError);
+
+    // Verify that a UserFile with a non-blank file name is accepted.
+    ASSERT_NO_THROW(UserFile("someName"));
+}
+
+/// @brief Tests opening and closing UserFile
+TEST(UserFile, openFile) {
+    UserFilePtr user_file;
+
+    // Construct a user file that refers to a non existant file.
+    ASSERT_NO_THROW(user_file.reset(new UserFile("NoSuchFile")));
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Verify a non-existant file fails to open
+    ASSERT_THROW(user_file->open(), UserFileError);
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Construct a user file that should exist.
+    ASSERT_NO_THROW(user_file.reset(new UserFile
+                                   (testFilePath("test_users_1.txt"))));
+    // File should not be open.
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Verify that we can open it.
+    ASSERT_NO_THROW(user_file->open());
+    EXPECT_TRUE(user_file->isOpen());
+
+    // Verify that we cannot open an already open file.
+    ASSERT_THROW(user_file->open(), UserFileError);
+
+    // Verifyt we can close it.
+    ASSERT_NO_THROW(user_file->close());
+    EXPECT_FALSE(user_file->isOpen());
+
+    // Verify that we can reopen it.
+    ASSERT_NO_THROW(user_file->open());
+    EXPECT_TRUE(user_file->isOpen());
+}
+
+
+/// @brief Tests makeUser with invalid user strings
+TEST(UserFile, makeUser) {
+    const char* invalid_strs[]= {
+        // Missinge type element.
+        "{ \"id\" : \"01AC00F03344\" }",
+        // Invalid id type string value.
+        "{ \"type\" : \"BOGUS\", \"id\" : \"01AC00F03344\"}",
+        // Non-string id type
+        "{ \"type\" : 1, \"id\" : \"01AC00F03344\"}",
+        // Missing id element.
+        "{ \"type\" : \"HW_ADDR\" }",
+        // Odd number of digits in id value.
+        "{ \"type\" : \"HW_ADDR\", \"id\" : \"1AC00F03344\"}",
+        // Invalid characters in id value.
+        "{ \"type\" : \"HW_ADDR\", \"id\" : \"THIS IS BAD!\"}",
+        // Empty id value.
+        "{ \"type\" : \"HW_ADDR\", \"id\" : \"\"}",
+        // Non-string id.
+        "{ \"type\" : \"HW_ADDR\", \"id\" : 01AC00F03344 }",
+        // Option with non-string value
+        "{ \"type\" : \"HW_ADDR\", \"id\" : \"01AC00F03344\", \"opt\" : 4 }",
+        NULL
+        };
+
+    // Create a UseFile to work with.
+    UserFilePtr user_file;
+    ASSERT_NO_THROW(user_file.reset(new UserFile("noFile")));
+
+    // Iterate over the list of invalid user strings and verify
+    // each one fails.
+    const char** tmp = invalid_strs;;
+    while (*tmp) {
+        EXPECT_THROW(user_file->makeUser(*tmp), UserFileError)
+                     << "Invalid str not caught: ["
+                     <<  *tmp << "]" << std::endl;
+        ++tmp;
+    }
+}
+
+/// @brief Test reading from UserFile
+TEST(UserFile, readFile) {
+    UserFilePtr user_file;
+
+    // Construct and then open a known file.
+    ASSERT_NO_THROW(user_file.reset(new UserFile
+                                    (testFilePath("test_users_1.txt"))));
+    ASSERT_NO_THROW(user_file->open());
+    EXPECT_TRUE(user_file->isOpen());
+
+    // File should contain two valid users. Read and verify each.
+    UserPtr user;
+    int i = 0;
+    do {
+        ASSERT_NO_THROW(user = user_file->readNextUser());
+        switch (i++) {
+            case 0:
+                EXPECT_EQ(UserId::HW_ADDRESS, user->getUserId().getType());
+                EXPECT_EQ("01ac00f03344", user->getUserId().toText());
+                EXPECT_EQ("true", user->getProperty("opt1"));
+                break;
+            case 1:
+                EXPECT_EQ(UserId::DUID, user->getUserId().getType());
+                EXPECT_EQ("225060de0a0b", user->getUserId().toText());
+                EXPECT_EQ("true", user->getProperty("opt1"));
+                break;
+            default:
+                // Third time around, we are at EOF User should be null.
+                ASSERT_FALSE(user);
+                break;
+        }
+    } while (user);
+
+
+    ASSERT_NO_THROW(user_file->close());
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/user_chk/tests/user_registry_unittests.cc b/src/hooks/dhcp/user_chk/tests/user_registry_unittests.cc
new file mode 100644
index 0000000..1f250c8
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/user_registry_unittests.cc
@@ -0,0 +1,216 @@
+// Copyright (C) 2013  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 <dhcp/hwaddr.h>
+#include <exceptions/exceptions.h>
+#include <user_registry.h>
+#include <user_file.h>
+#include <test_data_files_config.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+
+namespace {
+
+/// @brief Convenience method for reliably building test file path names.
+///
+/// Function prefixes the given file name with a path to unit tests directory
+/// so we can reliably find test data files.
+///
+/// @param name base file name of the test file
+std::string testFilePath(const std::string& name) {
+    return (std::string(USER_CHK_TEST_DIR) + "/" + name);
+}
+
+/// @brief Tests UserRegistry construction.
+TEST(UserRegistry, constructor) {
+    // Currently there is only the default constructor which does not throw.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+}
+
+/// @brief Tests mechanics of adding, finding, removing Users.
+TEST(UserRegistry, userBasics) {
+    // Create an empty registry.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Verify that a blank user cannot be added.
+    UserPtr user;
+    ASSERT_THROW(reg->addUser(user), UserRegistryError);
+
+    // Make a new id and user.
+    UserIdPtr id;
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, "01010101")));
+    ASSERT_NO_THROW(user.reset(new User(*id)));
+
+    // Verify that we can add a user.
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    // Verify that the user can be found.
+    UserPtr found_user;
+    ASSERT_NO_THROW(found_user = reg->findUser(*id));
+    EXPECT_TRUE(found_user);
+    EXPECT_EQ(found_user->getUserId(), *id);
+
+    // Verify that searching for a non-existant user returns empty user pointer.
+    UserIdPtr id2;
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::HW_ADDRESS, "02020202")));
+    ASSERT_NO_THROW(found_user = reg->findUser(*id2));
+    EXPECT_FALSE(found_user);
+
+    // Verify that the user can be deleted.
+    ASSERT_NO_THROW(reg->removeUser(*id));
+    ASSERT_NO_THROW(found_user = reg->findUser(*id));
+    EXPECT_FALSE(found_user);
+}
+
+/// @brief Tests finding users by isc::dhcp::HWaddr instance.
+TEST(UserRegistry, findByHWAddr) {
+    // Create the registry.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Make a new user and add it.
+    UserPtr user;
+    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, "01010101")));
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    // Make a HWAddr instance using the same id value.
+    isc::dhcp::HWAddr hwaddr(user->getUserId().getId(), isc::dhcp::HTYPE_ETHER);
+
+    // Verify user can be found by HWAddr.
+    UserPtr found_user;
+    ASSERT_NO_THROW(found_user = reg->findUser(hwaddr));
+    EXPECT_TRUE(found_user);
+    EXPECT_EQ(found_user->getUserId(), user->getUserId());
+}
+
+/// @brief Tests finding users by isc::dhcp::DUID instance.
+TEST(UserRegistry, findByDUID) {
+    // Create the registry.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Make a new user and add it.
+    UserPtr user;
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, "01010101")));
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    // Make a DUID instance using the same id value.
+    isc::dhcp::DUID duid(user->getUserId().getId());
+
+    // Verify user can be found by DUID.
+    UserPtr found_user;
+    ASSERT_NO_THROW(found_user = reg->findUser(duid));
+    EXPECT_TRUE(found_user);
+    EXPECT_EQ(found_user->getUserId(), user->getUserId());
+}
+
+/// @brief Tests mixing users of different types.
+TEST(UserRegistry, oneOfEach) {
+    // Create the registry.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Make user ids.
+    UserIdPtr idh, idd;
+    ASSERT_NO_THROW(idh.reset(new UserId(UserId::HW_ADDRESS, "01010101")));
+    ASSERT_NO_THROW(idd.reset(new UserId(UserId::DUID, "03030303")));
+
+    // Make and add HW_ADDRESS user.
+    UserPtr user;
+    user.reset(new User(*idh));
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    // Make and add DUID user.
+    user.reset(new User(*idd));
+    ASSERT_NO_THROW(reg->addUser(user));
+
+    // Verify we can find both.
+    UserPtr found_user;
+    ASSERT_NO_THROW(found_user = reg->findUser(*idh));
+    ASSERT_NO_THROW(found_user = reg->findUser(*idd));
+}
+
+/// @brief Tests loading the registry from a file.
+TEST(UserRegistry, refreshFromFile) {
+    // Create the registry.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    UserDataSourcePtr user_file;
+
+    // Verify that data source cannot be set to null source.
+    ASSERT_THROW(reg->setSource(user_file), UserRegistryError);
+
+    // Create the data source.
+    ASSERT_NO_THROW(user_file.reset(new UserFile
+                                    (testFilePath("test_users_1.txt"))));
+
+    // Set the registry's data source and refresh the registry.
+    ASSERT_NO_THROW(reg->setSource(user_file));
+    ASSERT_NO_THROW(reg->refresh());
+
+    // Verify we can find all the expected users.
+    UserIdPtr id;
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, "01ac00f03344")));
+    EXPECT_TRUE(reg->findUser(*id));
+
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::DUID, "225060de0a0b")));
+    EXPECT_TRUE(reg->findUser(*id));
+}
+
+/// @brief Tests preservation of registry upon refresh failure.
+TEST(UserRegistry, refreshFail) {
+    // Create the registry.
+    UserRegistryPtr reg;
+    ASSERT_NO_THROW(reg.reset(new UserRegistry()));
+
+    // Create the data source.
+    UserDataSourcePtr user_file;
+    ASSERT_NO_THROW(user_file.reset(new UserFile
+                                    (testFilePath("test_users_1.txt"))));
+
+    // Set the registry's data source and refresh the registry.
+    ASSERT_NO_THROW(reg->setSource(user_file));
+    ASSERT_NO_THROW(reg->refresh());
+
+    // Make user ids of expected users.
+    UserIdPtr id1, id2;
+    ASSERT_NO_THROW(id1.reset(new UserId(UserId::HW_ADDRESS, "01ac00f03344")));
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::DUID, "225060de0a0b")));
+
+    // Verify we can find all the expected users.
+    EXPECT_TRUE(reg->findUser(*id1));
+    EXPECT_TRUE(reg->findUser(*id2));
+
+    // Replace original data source with a new one containing an invalid entry.
+    ASSERT_NO_THROW(user_file.reset(new UserFile
+                                    (testFilePath("test_users_err.txt"))));
+    ASSERT_NO_THROW(reg->setSource(user_file));
+
+    // Refresh should throw due to invalid data.
+    EXPECT_THROW(reg->refresh(), UserRegistryError);
+
+    // Verify we can still find all the original users.
+    EXPECT_TRUE(reg->findUser(*id1));
+    EXPECT_TRUE(reg->findUser(*id2));
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/user_chk/tests/user_unittests.cc b/src/hooks/dhcp/user_chk/tests/user_unittests.cc
new file mode 100644
index 0000000..2ca156c
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/user_unittests.cc
@@ -0,0 +1,96 @@
+// Copyright (C) 2013  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 <exceptions/exceptions.h>
+#include <user.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+
+namespace {
+
+/// @brief Tests User construction variants.
+/// Note, since all constructors accept or rely on UserId, invalid id
+/// variants are tested under UserId not here.
+TEST(UserTest, construction) {
+    std::string test_address("01FF02AC030B0709");
+
+    // Create a user id.
+    UserIdPtr id;
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, test_address)));
+
+    // Verify construction from a UserId.
+    UserPtr user;
+    ASSERT_NO_THROW(user.reset(new User(*id)));
+
+    // Verify construction from an id type and an address vector.
+    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, id->getId())));
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, id->getId())));
+
+    // Verify construction from an id type and an address string.
+    ASSERT_NO_THROW(user.reset(new User(UserId::HW_ADDRESS, test_address)));
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, test_address)));
+}
+
+/// @brief Tests property map fundamentals.
+TEST(UserTest, properties) {
+    // Create a user.
+    UserPtr user;
+    ASSERT_NO_THROW(user.reset(new User(UserId::DUID, "01020304050607")));
+
+    // Verify that we can add and retrieve a property.
+    std::string value = "";
+    EXPECT_NO_THROW(user->setProperty("one","1"));
+    EXPECT_NO_THROW(value = user->getProperty("one"));
+    EXPECT_EQ("1", value);
+
+    // Verify that we can update and then retrieve the property.
+    EXPECT_NO_THROW(user->setProperty("one","1.0"));
+    EXPECT_NO_THROW(value = user->getProperty("one"));
+    EXPECT_EQ("1.0", value);
+
+    // Verify that we can remove and then NOT find the property.
+    EXPECT_NO_THROW(user->delProperty("one"));
+    EXPECT_NO_THROW(value = user->getProperty("one"));
+    EXPECT_TRUE(value.empty());
+
+    // Verify that a blank property name is NOT permitted.
+    EXPECT_THROW(user->setProperty("", "blah"), isc::BadValue);
+
+    // Verify that a blank property value IS permitted.
+    EXPECT_NO_THROW(user->setProperty("one", ""));
+    EXPECT_NO_THROW(value = user->getProperty("one"));
+    EXPECT_TRUE(value.empty());
+
+    PropertyMap map;
+    map["one"]="1.0";
+    map["two"]="2.0";
+
+    ASSERT_NO_THROW(user->setProperties(map));
+
+    EXPECT_NO_THROW(value = user->getProperty("one"));
+    EXPECT_EQ("1.0", value);
+
+    EXPECT_NO_THROW(value = user->getProperty("two"));
+    EXPECT_EQ("2.0", value);
+
+    const PropertyMap& map2 = user->getProperties();
+    EXPECT_TRUE(map2 == map);
+}
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/user_chk/tests/userid_unittests.cc b/src/hooks/dhcp/user_chk/tests/userid_unittests.cc
new file mode 100644
index 0000000..c31c4ae
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/tests/userid_unittests.cc
@@ -0,0 +1,139 @@
+// Copyright (C) 2013  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 <exceptions/exceptions.h>
+#include <user.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+
+namespace {
+
+/// @brief Test invalid constructors.
+TEST(UserIdTest, invalidConstructors) {
+    // Verify that constructor does not allow empty id vector.
+    std::vector<uint8_t> empty_bytes;
+    ASSERT_THROW(UserId(UserId::HW_ADDRESS, empty_bytes), isc::BadValue);
+    ASSERT_THROW(UserId(UserId::DUID, empty_bytes), isc::BadValue);
+
+    // Verify that constructor does not allow empty id string.
+    ASSERT_THROW(UserId(UserId::HW_ADDRESS, ""), isc::BadValue);
+    ASSERT_THROW(UserId(UserId::DUID, ""), isc::BadValue);
+}
+
+/// @brief Test making and using HW_ADDRESS type UserIds
+TEST(UserIdTest, hwAddress_type) {
+    // Verify text label look up for HW_ADDRESS enum.
+    EXPECT_EQ(std::string(UserId::HW_ADDRESS_STR),
+              UserId::lookupTypeStr(UserId::HW_ADDRESS));
+
+    // Verify enum look up for HW_ADDRESS text label.
+    EXPECT_EQ(UserId::HW_ADDRESS,
+              UserId::lookupType(UserId::HW_ADDRESS_STR));
+
+    // Build a test address vector.
+    uint8_t tmp[] = { 0x01, 0xFF, 0x02, 0xAC, 0x03, 0x0B, 0x07, 0x08 };
+    std::vector<uint8_t> bytes(tmp, tmp + (sizeof(tmp)/sizeof(uint8_t)));
+
+    // Verify construction from an HW_ADDRESS id type and address vector.
+    UserIdPtr id;
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::HW_ADDRESS, bytes)));
+    // Verify that the id can be fetched.
+    EXPECT_EQ(id->getType(), UserId::HW_ADDRESS);
+    EXPECT_TRUE(bytes == id->getId());
+
+    // Check relational oeprators when a == b.
+    UserIdPtr id2;
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::HW_ADDRESS, id->toText())));
+    EXPECT_TRUE(*id == *id2);
+    EXPECT_FALSE(*id != *id2);
+    EXPECT_FALSE(*id < *id2);
+
+    // Check relational oeprators when a < b.
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::HW_ADDRESS,
+                                         "01FF02AC030B0709")));
+    EXPECT_FALSE(*id == *id2);
+    EXPECT_TRUE(*id != *id2);
+    EXPECT_TRUE(*id < *id2);
+
+    // Check relational oeprators when a > b.
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::HW_ADDRESS,
+                                         "01FF02AC030B0707")));
+    EXPECT_FALSE(*id == *id2);
+    EXPECT_TRUE(*id != *id2);
+    EXPECT_FALSE(*id < *id2);
+}
+
+/// @brief Test making and using DUID type UserIds
+TEST(UserIdTest, duid_type) {
+    // Verify text label look up for DUID enum.
+    EXPECT_EQ(std::string(UserId::DUID_STR),
+              UserId::lookupTypeStr(UserId::DUID));
+
+    // Verify enum look up for DUID text label.
+    EXPECT_EQ(UserId::DUID,
+              UserId::lookupType(UserId::DUID_STR));
+
+    // Build a test DUID vector.
+    uint8_t tmp[] = { 0x01, 0xFF, 0x02, 0xAC, 0x03, 0x0B, 0x07, 0x08 };
+    std::vector<uint8_t> bytes(tmp, tmp + (sizeof(tmp)/sizeof(uint8_t)));
+
+    // Verify construction from an DUID id type and address vector.
+    UserIdPtr id;
+    ASSERT_NO_THROW(id.reset(new UserId(UserId::DUID, bytes)));
+    // Verify that the id can be fetched.
+    EXPECT_EQ(id->getType(), UserId::DUID);
+    EXPECT_TRUE(bytes == id->getId());
+
+    // Check relational oeprators when a == b.
+    UserIdPtr id2;
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::DUID, id->toText())));
+    EXPECT_TRUE(*id == *id2);
+    EXPECT_FALSE(*id != *id2);
+    EXPECT_FALSE(*id < *id2);
+
+    // Check relational oeprators when a < b.
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::DUID, "01FF02AC030B0709")));
+    EXPECT_FALSE(*id == *id2);
+    EXPECT_TRUE(*id != *id2);
+    EXPECT_TRUE(*id < *id2);
+
+    // Check relational oeprators when a > b.
+    ASSERT_NO_THROW(id2.reset(new UserId(UserId::DUID, "01FF02AC030B0707")));
+    EXPECT_FALSE(*id == *id2);
+    EXPECT_TRUE(*id != *id2);
+    EXPECT_FALSE(*id < *id2);
+}
+
+/// @brief Tests that UserIds of different types compare correctly.
+TEST(UserIdTest, mixed_type_compare) {
+    UserIdPtr hw, duid;
+    // Create UserIds with different types, but same id data.
+    ASSERT_NO_THROW(hw.reset(new UserId(UserId::HW_ADDRESS,
+                                        "01FF02AC030B0709")));
+    ASSERT_NO_THROW(duid.reset(new UserId(UserId::DUID,
+                                          "01FF02AC030B0709")));
+
+    // Verify that UserIdType influences logical comparators.
+    EXPECT_FALSE(*hw == *duid);
+    EXPECT_TRUE(*hw != *duid);
+    EXPECT_TRUE(*hw < *duid);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/hooks/dhcp/user_chk/user.cc b/src/hooks/dhcp/user_chk/user.cc
new file mode 100644
index 0000000..c7c18fa
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user.cc
@@ -0,0 +1,210 @@
+// Copyright (C) 2013 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 <dhcp/hwaddr.h>
+#include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
+
+#include <user.h>
+
+#include <iomanip>
+#include <sstream>
+
+//********************************* UserId ******************************
+
+const char* UserId::HW_ADDRESS_STR = "HW_ADDR";
+const char* UserId::DUID_STR = "DUID";
+
+UserId::UserId(UserIdType id_type, const std::vector<uint8_t>& id)
+    : id_type_(id_type), id_(id) {
+    if (id.size() == 0) {
+        isc_throw(isc::BadValue, "UserId id may not be blank");
+    }
+}
+
+UserId::UserId(UserIdType id_type, const std::string & id_str) :
+    id_type_(id_type) {
+    if (id_str.empty()) {
+        isc_throw(isc::BadValue, "UserId id string may not be blank");
+    }
+
+    // Convert the id string to vector.
+    // Input is expected to be 2-digits per bytes, no delimiters.
+    std::vector<uint8_t> addr_bytes;
+    isc::util::encode::decodeHex(id_str, addr_bytes);
+
+    // Attempt to instantiate the appropriate id class to leverage validation.
+    switch (id_type) {
+        case HW_ADDRESS: {
+            isc::dhcp::HWAddr hwaddr(addr_bytes, isc::dhcp::HTYPE_ETHER);
+            break;
+            }
+        case DUID: {
+            isc::dhcp::DUID duid(addr_bytes);
+            break;
+            }
+        default:
+            isc_throw (isc::BadValue, "Invalid id_type: " << id_type);
+            break;
+    }
+
+    // It's a valid id.
+    id_ = addr_bytes;
+}
+
+UserId::~UserId() {
+}
+
+const std::vector<uint8_t>&
+UserId::getId() const {
+    return (id_);
+}
+
+UserId::UserIdType
+UserId::getType() const {
+    return (id_type_);
+}
+
+std::string
+UserId::toText(char delim_char) const {
+    std::stringstream tmp;
+    tmp << std::hex;
+    bool delim = false;
+    for (std::vector<uint8_t>::const_iterator it = id_.begin();
+         it != id_.end(); ++it) {
+        if (delim_char && delim) {
+            tmp << delim_char;
+        }
+
+        tmp << std::setw(2) << std::setfill('0')
+            << static_cast<unsigned int>(*it);
+        delim = true;
+    }
+
+    return (tmp.str());
+}
+
+bool
+UserId::operator ==(const UserId & other) const {
+    return ((this->id_type_ == other.id_type_) && (this->id_ == other.id_));
+}
+
+bool
+UserId::operator !=(const UserId & other) const {
+    return (!(*this == other));
+}
+
+bool
+UserId::operator <(const UserId & other) const {
+    return ((this->id_type_ < other.id_type_) ||
+            ((this->id_type_ == other.id_type_) && (this->id_ < other.id_)));
+}
+
+std::string
+UserId::lookupTypeStr(UserIdType type) {
+    const char* tmp = NULL;
+    switch (type) {
+        case HW_ADDRESS:
+            tmp = HW_ADDRESS_STR;
+            break;
+        case DUID:
+            tmp = DUID_STR;
+            break;
+        default:
+            isc_throw(isc::BadValue, "Invalid UserIdType:" << type);
+            break;
+    }
+
+    return (std::string(tmp));
+}
+
+UserId::UserIdType
+UserId::lookupType(const std::string& type_str) {
+    if (type_str.compare(HW_ADDRESS_STR) == 0) {
+        return (HW_ADDRESS);
+    } else if (type_str.compare(DUID_STR) == 0) {
+        return (DUID);
+    }
+
+    isc_throw(isc::BadValue, "Invalid UserIdType string:" << type_str);
+}
+
+std::ostream&
+operator<<(std::ostream& os, const UserId& user_id) {
+    std::string tmp = UserId::lookupTypeStr(user_id.getType());
+    os << tmp << "=" << user_id.toText();
+    return (os);
+}
+
+//********************************* User ******************************
+
+User::User(const UserId& user_id) : user_id_(user_id) {
+}
+
+User::User(UserId::UserIdType id_type, const std::vector<uint8_t>& id)
+    : user_id_(id_type, id) {
+}
+
+User::User(UserId::UserIdType id_type, const std::string& id_str)
+    : user_id_(id_type, id_str) {
+}
+
+User::~User() {
+}
+
+const PropertyMap&
+User::getProperties() const {
+    return (properties_);
+}
+
+void
+User::setProperties(const PropertyMap& properties) {
+    properties_ = properties;
+}
+
+void User::setProperty(const std::string& name, const std::string& value) {
+    if (name.empty()) {
+        isc_throw (isc::BadValue, "User property name cannot be blank");
+    }
+
+    // Note that if the property exists its value will be updated.
+    properties_[name]=value;
+}
+
+std::string
+User::getProperty(const std::string& name) const {
+    PropertyMap::const_iterator it = properties_.find(name);
+    if (it != properties_.end()) {
+        return ((*it).second);
+    }
+
+    // By returning an empty string rather than throwing, we allow the
+    // flexibility of defaulting to blank if not specified.  Let the caller
+    // decide if that is valid or not.
+    return ("");
+}
+
+void
+User::delProperty(const std::string & name) {
+    PropertyMap::iterator it = properties_.find(name);
+    if (it != properties_.end()) {
+        properties_.erase(it);
+    }
+}
+
+const UserId&
+User::getUserId() const {
+    return (user_id_);
+}
diff --git a/src/hooks/dhcp/user_chk/user.h b/src/hooks/dhcp/user_chk/user.h
new file mode 100644
index 0000000..20167b6
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user.h
@@ -0,0 +1,249 @@
+// Copyright (C) 2013 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 USER_H
+#define USER_H
+
+#include <boost/shared_ptr.hpp>
+
+#include <map>
+#include <stdint.h>
+#include <vector>
+
+/// @file user.h This file defines classes: UserId and User.
+/// @brief These classes are used to describe and recognize DHCP lease
+/// clients.
+
+/// @brief Encapsulates a unique identifier for a DHCP client.
+/// This class provides a generic wrapper around the information used to
+/// uniquely identify the requester in a DHCP request packet.  It provides
+/// the necessary operators such that it can be used as a key within STL
+/// containers such as maps.  It supports both IPv4 and IPv6 clients.
+class UserId {
+public:
+    /// @brief Defines the supported types of user ids.
+    // Use explicit values to ensure consistent numeric ordering for key
+    // comparisons.
+    enum UserIdType {
+        /// @brief Hardware addresses (MAC) are used for IPv4 clients.
+        HW_ADDRESS = 0,
+        /// @brief DUIDs are used for IPv6 clients.
+        DUID = 1
+    };
+
+    /// @brief Defines the text label hardware address id type.
+    static const char* HW_ADDRESS_STR;
+    /// @brief Define the text label DUID id type.
+    static const char* DUID_STR;
+
+    /// @brief Constructor
+    ///
+    /// Constructs a UserId from an id type and id vector.
+    ///
+    /// @param id_type The type of user id contained in vector
+    /// @param id a vector of unsigned bytes containing the id
+    ///
+    /// @throw isc::BadValue if the vector is empty.
+    UserId(UserIdType id_type, const std::vector<uint8_t>& id);
+
+    /// @brief Constructor
+    ///
+    /// Constructs a UserId from an id type and id string.
+    ///
+    /// @param id_type The type of user id contained in string.
+    /// The string is expected to contain an even number of hex digits
+    /// without delimiters.
+    ///
+    /// @param id a vector of unsigned bytes containing the id
+    ///
+    /// @throw isc::BadValue if the string is empty, contains non
+    /// valid hex digits, or an odd number of hex digits.
+    UserId(UserIdType id_type, const std::string& id_str);
+
+    /// @brief Destructor.
+    ~UserId();
+
+    /// @brief Returns a const reference to the actual id value
+    const std::vector<uint8_t>& getId() const;
+
+    /// @brief Returns the user id type
+    UserIdType getType() const;
+
+    /// @brief Returns textual representation of the id
+    ///
+    /// Outputs a string of hex digits representing the id with an
+    /// optional delimiter between digit pairs (i.e. bytes).
+    ///
+    /// Without a delimiter:
+    ///   "0c220F"
+    ///
+    /// with colon as a delimiter:
+    ///   "0c:22:0F"
+    ///
+    /// @param delim_char The delimiter to place in between
+    /// "bytes". It defaults to none.
+    ///  (e.g. 00010203ff)
+    std::string toText(char delim_char=0x0) const;
+
+    /// @brief Compares two UserIds for equality
+    bool operator ==(const UserId & other) const;
+
+    /// @brief Compares two UserIds for inequality
+    bool operator !=(const UserId & other) const;
+
+    /// @brief Performs less than comparison of two UserIds
+    bool operator <(const UserId & other) const;
+
+    /// @brief Returns the text label for a given id type
+    ///
+    /// @param type The id type value for which the label is desired
+    ///
+    /// @throw isc::BadValue if type is not valid.
+    static std::string lookupTypeStr(UserIdType type);
+
+    /// @brief Returns the id type for a given text label
+    ///
+    /// @param type_str The text label for which the id value is desired
+    ///
+    /// @throw isc::BadValue if type_str is not a valid text label.
+    static UserIdType lookupType(const std::string& type_str);
+
+private:
+    /// @brief The type of id value
+    UserIdType id_type_;
+
+    /// @brief The id value
+    std::vector<uint8_t> id_;
+
+};
+
+/// @brief Outputs the UserId contents in a string to the given stream.
+///
+/// The output string has the form "<type>=<id>" where:
+///
+/// <type> is the text label returned by UserId::lookupTypeStr()
+/// <id> is the output of UserId::toText() without a delimiter.
+///
+/// Examples:
+///       HW_ADDR=0c0e0a01ff06
+///       DUID=0001000119efe63b000c01020306
+///
+/// @param os output stream to which to write
+/// @param user_id source object to output
+std::ostream&
+operator<<(std::ostream& os, const UserId& user_id);
+
+/// @brief Defines a smart pointer to UserId
+typedef boost::shared_ptr<UserId> UserIdPtr;
+
+/// @brief Defines a map of string values keyed by string labels.
+typedef std::map<std::string, std::string> PropertyMap;
+
+/// @brief Represents a unique DHCP user
+/// This class is used to represent a specific DHCP user who is identified by a
+/// unique id and who possesses a set of properties.
+class User {
+public:
+    /// @brief Constructor
+    ///
+    /// Constructs a new User from a given id with an empty set of properties.
+    ///
+    /// @param user_id Id to assign to the user
+    ///
+    /// @throw isc::BadValue if user id is blank.
+    User(const UserId & user_id);
+
+    /// @brief Constructor
+    ///
+    /// Constructs a new User from a given id type and vector containing the
+    /// id data with an empty set of properties.
+    ///
+    /// @param user_id Type of id contained in the id vector
+    /// @param id Vector of data representing the user's id
+    ///
+    /// @throw isc::BadValue if user id vector is empty.
+    User(UserId::UserIdType id_type, const std::vector<uint8_t>& id);
+
+    /// @brief Constructor
+    ///
+    /// Constructs a new User from a given id type and string containing the
+    /// id data with an empty set of properties.
+    ///
+    /// @param user_id Type of id contained in the id vector
+    /// @param id string of hex digits representing the user's id
+    ///
+    /// @throw isc::BadValue if user id string is empty or invalid
+    User(UserId::UserIdType id_type, const std::string& id_str);
+
+    /// @brief Destructor
+    ~User();
+
+    /// @brief Returns a reference to the map of properties.
+    ///
+    /// Note that this reference can go out of scope and should not be
+    /// relied upon other than for momentary use.
+    const PropertyMap& getProperties() const;
+
+    /// @brief Sets the user's properties from a given property map
+    ///
+    /// Replaces the contents of the user's property map with the given
+    /// property map.
+    ///
+    /// @param properties property map to assign to the user
+    void setProperties(const PropertyMap& properties);
+
+    /// @brief Sets a given property to the given value
+    ///
+    /// Adds or updates the given property to the given value.
+    ///
+    /// @param name string by which the property is identified (keyed)
+    /// @param value string data to associate with the property
+    ///
+    /// @throw isc::BadValue if name is blank.
+    void setProperty(const std::string& name, const std::string& value);
+
+    /// @brief Fetches the string value for a given property name.
+    ///
+    /// @param name property name to fetch
+    ///
+    /// @return Returns the string value for the given name or an empty string
+    /// if the property is not found in the property map.
+    std::string getProperty(const std::string& name) const;
+
+    /// @brief Removes the given property from the property map.
+    ///
+    /// Removes the given property from the map if found. If not, no harm no
+    /// foul.
+    ///
+    /// @param name property name to remove
+    void delProperty(const std::string& name);
+
+    /// @brief Returns the user's id.
+    ///
+    /// Note that this reference can go out of scope and should not be
+    /// relied upon other than for momentary use.
+    const UserId& getUserId() const;
+
+private:
+    /// @brief The user's id.
+    UserId user_id_;
+
+    /// @brief The user's property map.
+    PropertyMap properties_;
+};
+
+/// @brief Defines a smart pointer to a User.
+typedef boost::shared_ptr<User> UserPtr;
+
+#endif
diff --git a/src/hooks/dhcp/user_chk/user_chk_log.cc b/src/hooks/dhcp/user_chk/user_chk_log.cc
new file mode 100644
index 0000000..f104efb
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_chk_log.cc
@@ -0,0 +1,18 @@
+// Copyright (C) 2013 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.
+
+/// Defines the logger used by the user check hooks library.
+#include <user_chk_log.h>
+
+isc::log::Logger user_chk_logger("user_chk");
diff --git a/src/hooks/dhcp/user_chk/user_chk_log.h b/src/hooks/dhcp/user_chk/user_chk_log.h
new file mode 100644
index 0000000..43fb364
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_chk_log.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2013  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 USER_CHK_LOG_H
+#define USER_CHK_LOG_H
+
+#include <log/message_initializer.h>
+#include <log/macros.h>
+#include <user_chk_messages.h>
+
+/// @brief User Check Logger
+///
+/// Define the logger used to log messages.  We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger user_chk_logger;
+
+#endif // USER_CHK_LOG_H
diff --git a/src/hooks/dhcp/user_chk/user_chk_messages.mes b/src/hooks/dhcp/user_chk/user_chk_messages.mes
new file mode 100644
index 0000000..23e3023
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_chk_messages.mes
@@ -0,0 +1,43 @@
+# Copyright (C) 2013  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.
+
+% USER_CHK_HOOK_LOAD_ERROR DHCP UserCheckHook could not be loaded: %1
+This is an error message issued when the DHCP UserCheckHook could not be loaded.
+The exact cause should be explained in the log message.  User subnet selection
+will revert to default processing.
+
+% USER_CHK_HOOK_UNLOAD_ERROR DHCP UserCheckHook an error occurred unloading the library: %1
+This is an error message issued when an error occurs while unloading the
+UserCheckHook library.  This is unlikely to occur and normal operations of the
+library will likely resume when it is next loaded.
+
+% USER_CHK_SUBNET4_SELECT_ERROR DHCP UserCheckHook an unexpected error occured in subnet4_select callout: %1
+This is an error message issued when the DHCP UserCheckHook subnet4_select hook
+encounters an unexpected error.  The message should contain a more detailed
+explanation.
+
+% USER_CHK_SUBNET4_SELECT_REGISTRY_NULL DHCP UserCheckHook UserRegistry has not been created.
+This is an error message issued when the DHCP UserCheckHook subnet4_select hook
+has been invoked but the UserRegistry has not been created.  This is a
+programmatic error and should not occur.
+
+% USER_CHK_SUBNET6_SELECT_ERROR DHCP UserCheckHook an unexpected error occured in subnet6_select callout: %1
+This is an error message issued when the DHCP UserCheckHook subnet6_select hook
+encounters an unexpected error.  The message should contain a more detailed
+explanation.
+
+% USER_CHK_SUBNET6_SELECT_REGISTRY_NULL DHCP UserCheckHook UserRegistry has not been created.
+This is an error message issued when the DHCP UserCheckHook subnet6_select hook
+has been invoked but the UserRegistry has not been created.  This is a
+programmatic error and should not occur.
diff --git a/src/hooks/dhcp/user_chk/user_data_source.h b/src/hooks/dhcp/user_chk/user_data_source.h
new file mode 100644
index 0000000..a1842cf
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_data_source.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2013 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 _USER_DATA_SOURCE_H
+#define _USER_DATA_SOURCE_H
+
+/// @file user_data_source.h Defines the base class, UserDataSource.
+#include <exceptions/exceptions.h>
+#include <user.h>
+
+/// @brief Thrown if UserDataSource encounters an error
+class UserDataSourceError : public isc::Exception {
+public:
+    UserDataSourceError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines an interface for reading user data into a registry.
+/// This is an abstract class which defines the interface for reading Users
+/// from an IO source such as a file.
+class UserDataSource {
+public:
+    /// @brief Constructor.
+    UserDataSource() {};
+
+    /// @brief Virtual Destructor.
+    virtual ~UserDataSource() {};
+
+    /// @brief Opens the data source.
+    ///
+    /// Prepares the data source for reading.  Upon successful completion the
+    /// data source is ready to read from the beginning of its content.
+    ///
+    /// @throw UserDataSourceError if the source fails to open.
+    virtual void open() = 0;
+
+    /// @brief Fetches the next user from the data source.
+    ///
+    /// Reads the next User from the data source and returns it.  If no more
+    /// data is available it should return an empty (null) user.
+    ///
+    /// @throw UserDataSourceError if an error occurs.
+    virtual UserPtr readNextUser() = 0;
+
+    /// @brief Closes that data source.
+    ///
+    /// Closes the data source.
+    ///
+    /// This method must not throw exceptions.
+    virtual void close() = 0;
+
+    /// @brief Returns true if the data source is open.
+    ///
+    /// This method should return true once the data source has been
+    /// successfully opened and until it has been closed.
+    ///
+    /// It is assumed to be exception safe.
+    virtual bool isOpen() const = 0;
+};
+
+/// @brief Defines a smart pointer to a UserDataSource.
+typedef boost::shared_ptr<UserDataSource> UserDataSourcePtr;
+
+#endif
diff --git a/src/hooks/dhcp/user_chk/user_file.cc b/src/hooks/dhcp/user_chk/user_file.cc
new file mode 100644
index 0000000..4181dc4
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_file.cc
@@ -0,0 +1,159 @@
+// Copyright (C) 2013 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 <cc/data.h>
+#include <user.h>
+#include <user_file.h>
+
+#include <boost/foreach.hpp>
+#include <errno.h>
+#include <iostream>
+
+UserFile::UserFile(const std::string& fname) : fname_(fname), file_() {
+    if (fname_.empty()) {
+        isc_throw(UserFileError, "file name cannot be blank");
+    }
+}
+
+UserFile::~UserFile(){
+    close();
+};
+
+void
+UserFile::open() {
+    if (isOpen()) {
+        isc_throw (UserFileError, "file is already open");
+    }
+
+    file_.open(fname_.c_str(), std::ifstream::in);
+    int sav_error = errno;
+    if (!file_.is_open()) {
+        isc_throw(UserFileError, "cannot open file:" << fname_
+                                 << " reason: " << strerror(sav_error));
+    }
+}
+
+UserPtr
+UserFile::readNextUser() {
+    if (!isOpen()) {
+        isc_throw (UserFileError, "cannot read, file is not open");
+    }
+
+    if (file_.good()) {
+        char buf[USER_ENTRY_MAX_LEN];
+
+        // Get the next line.
+        file_.getline(buf, sizeof(buf));
+
+        // We got something, try to make a user out of it.
+        if (file_.gcount() > 0) {
+            return(makeUser(buf));
+        }
+    }
+
+    // Returns an empty user on EOF.
+    return (UserPtr());
+}
+
+UserPtr
+UserFile::makeUser(const std::string& user_string) {
+    // This method leverages the existing JSON parsing provided by isc::data
+    // library.  Should this prove to be a performance issue, it may be that
+    // lighter weight solution would be appropriate.
+
+    // Turn the string of JSON text into an Element set.
+    isc::data::ElementPtr elements;
+    try {
+        elements = isc::data::Element::fromJSON(user_string);
+    } catch (isc::data::JSONError& ex) {
+        isc_throw(UserFileError,
+                  "UserFile entry is malformed JSON: " << ex.what());
+    }
+
+    // Get a map of the Elements, keyed by element name.
+    isc::data::ConstElementPtr element;
+    PropertyMap properties;
+    std::string id_type_str;
+    std::string id_str;
+
+    // Iterate over the elements, saving of "type" and "id" to their
+    // respective locals.  Anything else is assumed to be an option so
+    // add it to the local property map.
+    std::pair<std::string, isc::data::ConstElementPtr> element_pair;
+    BOOST_FOREACH (element_pair, elements->mapValue()) {
+        // Get the element's label.
+        std::string label = element_pair.first;
+
+        // Currently everything must be a string.
+        if (element_pair.second->getType() != isc::data::Element::string) {
+            isc_throw (UserFileError, "UserFile entry: " << user_string
+                       << "has non-string value for : " << label);
+        }
+
+        std::string value = element_pair.second->stringValue();
+
+        if (label == "type") {
+            id_type_str = value;
+        } else if (label == "id") {
+            id_str = value;
+        } else {
+            // JSON parsing reduces any duplicates to the last value parsed,
+            // so we will never see duplicates here.
+            properties[label]=value;
+        }
+    }
+
+    // First we attempt to translate the id type.
+    UserId::UserIdType id_type;
+    try {
+        id_type = UserId::lookupType(id_type_str);
+    } catch (const std::exception& ex) {
+        isc_throw (UserFileError, "UserFile entry has invalid type: "
+                                  << user_string << " " << ex.what());
+    }
+
+    // Id type is valid, so attempt to make the user based on that and
+    // the value we have for "id".
+    UserPtr user;
+    try {
+        user.reset(new User(id_type, id_str));
+    } catch (const std::exception& ex) {
+        isc_throw (UserFileError, "UserFile cannot create user form entry: "
+                                  << user_string << " " << ex.what());
+    }
+
+    // We have a new User, so add in the properties and return it.
+    user->setProperties(properties);
+    return (user);
+}
+
+bool
+UserFile::isOpen() const {
+    return (file_.is_open());
+}
+
+void
+UserFile::close() {
+    try {
+        if (file_.is_open()) {
+            file_.close();
+        }
+    } catch (const std::exception& ex) {
+        // Highly unlikely to occur but let's at least spit out an error.
+        // Beyond that we swallow it for tidiness.
+        std::cout << "UserFile unexpected error closing the file: "
+                  << fname_ << " : " << ex.what() << std::endl;
+    }
+}
+
diff --git a/src/hooks/dhcp/user_chk/user_file.h b/src/hooks/dhcp/user_chk/user_file.h
new file mode 100644
index 0000000..b65862e
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_file.h
@@ -0,0 +1,135 @@
+// Copyright (C) 2013 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 _USER_FILE_H
+#define _USER_FILE_H
+
+/// @file user_file.h Defines the class, UserFile, which implements the UserDataSource interface for text files.
+
+#include <user_data_source.h>
+#include <user.h>
+
+#include <boost/shared_ptr.hpp>
+#include <fstream>
+#include <string>
+
+using namespace std;
+
+/// @brief Thrown a UserFile encounters an error.
+/// Note that it derives from UserDataSourceError to comply with the interface.
+class UserFileError : public UserDataSourceError {
+public:
+    UserFileError(const char* file, size_t line,
+                               const char* what) :
+        UserDataSourceError(file, line, what) { };
+};
+
+/// @brief Provides a UserDataSource implementation for JSON text files.
+/// This class allows a text file of JSON entries to be treated as a source of
+/// User entries.  The format of the file is one user entry per line, where
+/// each line contains a JSON string as follows:
+///
+/// { "type" : "<user type>", "id" : "<user_id>" (options)  }
+///
+/// where:
+///
+/// <user_type>  text label of the id type: "HW_ADDR" or "DUID"
+/// <user_id>  the user's id as a string of hex digits without delimiters
+/// (options) zero or more string elements as name-value pairs, separated by
+/// commas: "opt1" : "val1",  "other_opt", "77" ...
+///
+/// Each entry must have a valid entry for "type" and a valid entry or "id".
+///
+/// If an entry contains duplicate option names, that option will be assigend
+/// the last value found. This is typical JSON behavior.
+/// Currently, only string option values (i.e. enclosed in quotes) are
+/// supported.
+///
+/// Example file entries might look like this:
+/// @code
+///
+/// { "type" : "HW_ADDR", "id" : "01AC00F03344", "opt1" : "true" }
+/// { "type" : "DUID", "id" : "225060de0a0b", "opt1" : "false" }
+///
+/// @endcode
+class UserFile : public UserDataSource {
+public:
+    /// @brief Maximum length of a single user entry.
+    /// This value is somewhat arbitrary. 4K seems reasonably large.  If it
+    /// goes beyond this, then a flat file is not likely the way to go.
+    static const size_t USER_ENTRY_MAX_LEN = 4096;
+
+    /// @brief Constructor
+    ///
+    /// Create a UserFile for the given file name without opening the file.
+    /// @param fname pathname to the input file.
+    ///
+    /// @throw UserFileError if given file name is empty.
+    UserFile(const std::string& fname);
+
+    /// @brief Destructor.
+    ////
+    /// The destructor does call the close method.
+    virtual ~UserFile();
+
+    /// @brief Opens the input file for reading.
+    ///
+    /// Upon successful completion, the file is opened and positioned to start
+    /// reading from the beginning of the file.
+    ///
+    /// @throw UserFileError if the file cannot be opened.
+    virtual void open();
+
+    /// @brief Fetches the next user from the file.
+    ///
+    /// Reads the next user entry from the file and attempts to create a
+    /// new User from the text therein.  If there is no more data to be read
+    /// it returns an empty UserPtr.
+    ///
+    /// @return A UserPtr pointing to the new User or an empty pointer on EOF.
+    ///
+    /// @throw UserFileError if an error occurs while reading.
+    virtual UserPtr readNextUser();
+
+    /// @brief Closes the underlying file.
+    ///
+    /// Method is exception safe.
+    virtual void close();
+
+    /// @brief Returns true if the file is open.
+    ///
+    /// @return True if the underlying file is open, false otherwise.
+    virtual bool isOpen() const;
+
+    /// @brief Creates a new User instance from JSON text.
+    ///
+    /// @param user_string string the JSON text for a user entry.
+    ///
+    /// @return A pointer to the newly created User instance.
+    ///
+    /// @throw UserFileError if the entry is invalid.
+    UserPtr makeUser(const std::string& user_string);
+
+private:
+    /// @brief Pathname of the input text file.
+    string fname_;
+
+    /// @brief Input file stream.
+    std::ifstream file_;
+
+};
+
+/// @brief Defines a smart pointer to a UserFile.
+typedef boost::shared_ptr<UserFile> UserFilePtr;
+
+#endif
diff --git a/src/hooks/dhcp/user_chk/user_registry.cc b/src/hooks/dhcp/user_chk/user_registry.cc
new file mode 100644
index 0000000..44b98a5
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_registry.cc
@@ -0,0 +1,122 @@
+// Copyright (C) 2013 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 <user_registry.h>
+#include <user.h>
+
+UserRegistry::UserRegistry() {
+}
+
+UserRegistry::~UserRegistry(){
+}
+
+void
+UserRegistry::addUser(UserPtr& user) {
+    if (!user) {
+        isc_throw (UserRegistryError, "UserRegistry cannot add blank user");
+    }
+
+    UserPtr found_user;
+    if ((found_user = findUser(user->getUserId()))) {
+        isc_throw (UserRegistryError, "UserRegistry duplicate user: "
+                   << user->getUserId());
+    }
+
+    users_[user->getUserId()] = user;
+}
+
+const UserPtr&
+UserRegistry::findUser(const UserId& id) const {
+    static UserPtr empty;
+    UserMap::const_iterator it = users_.find(id);
+    if (it != users_.end()) {
+        const UserPtr tmp = (*it).second;
+        return ((*it).second);
+    }
+
+    return empty;
+}
+
+void
+UserRegistry::removeUser(const UserId& id) {
+    static UserPtr empty;
+    UserMap::iterator it = users_.find(id);
+    if (it != users_.end()) {
+        users_.erase(it);
+    }
+}
+
+const UserPtr&
+UserRegistry::findUser(const isc::dhcp::HWAddr& hwaddr) const {
+    UserId id(UserId::HW_ADDRESS, hwaddr.hwaddr_);
+    return (findUser(id));
+}
+
+const UserPtr&
+UserRegistry::findUser(const isc::dhcp::DUID& duid) const {
+    UserId id(UserId::DUID, duid.getDuid());
+    return (findUser(id));
+}
+
+void UserRegistry::refresh() {
+    if (!source_) {
+        isc_throw(UserRegistryError,
+                  "UserRegistry: cannot refresh, no data source");
+    }
+
+    // If the source isn't open, open it.
+    if (!source_->isOpen()) {
+        source_->open();
+    }
+
+    // Make a copy in case something goes wrong midstream.
+    UserMap backup(users_);
+
+    // Empty the registry then read users from source until source is empty.
+    clearall();
+    try {
+        UserPtr user;
+        while ((user = source_->readNextUser())) {
+            addUser(user);
+        }
+    } catch (const std::exception& ex) {
+        // Source was compromsised so restore registry from backup.
+        users_ = backup;
+        // Close the source.
+        source_->close();
+        isc_throw (UserRegistryError, "UserRegistry: refresh failed during read"
+                   << ex.what());
+    }
+
+    // Close the source.
+    source_->close();
+}
+
+void UserRegistry::clearall() {
+    users_.clear();
+}
+
+void UserRegistry::setSource(UserDataSourcePtr& source) {
+    if (!source) {
+        isc_throw (UserRegistryError,
+                   "UserRegistry: data source cannot be set to null");
+    }
+
+    source_ = source;
+}
+
+const UserDataSourcePtr& UserRegistry::getSource() {
+    return (source_);
+}
+
diff --git a/src/hooks/dhcp/user_chk/user_registry.h b/src/hooks/dhcp/user_chk/user_registry.h
new file mode 100644
index 0000000..a0fb7dc
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/user_registry.h
@@ -0,0 +1,128 @@
+// Copyright (C) 2013 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 _USER_REGISTRY_H
+#define _USER_REGISTRY_H
+
+/// @file user_registry.h Defines the class, UserRegistry.
+
+#include <dhcp/hwaddr.h>
+#include <dhcp/duid.h>
+#include <exceptions/exceptions.h>
+#include <user.h>
+#include <user_data_source.h>
+
+#include <string>
+
+using namespace std;
+
+/// @brief Thrown UserRegistry encounters an error
+class UserRegistryError : public isc::Exception {
+public:
+    UserRegistryError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Defines a map of unique Users keyed by UserId.
+typedef std::map<UserId,UserPtr> UserMap;
+
+/// @brief Embodies an update-able, searchable list of unique users
+/// This class provides the means to create and maintain a searchable list
+/// of unique users. List entries are pointers to instances of User, keyed
+/// by their UserIds.
+/// Users may be added and removed from the list individually or the list
+/// may be updated by loading it from a data source, such as a file.
+class UserRegistry {
+public:
+    /// @brief Constructor
+    ///
+    /// Creates a new registry with an empty list of users and no data source.
+    UserRegistry();
+
+    /// @brief Destructor
+    ~UserRegistry();
+
+    /// @brief Adds a given user to the registry.
+    ///
+    /// @param user A pointer to the user to add
+    ///
+    /// @throw UserRegistryError if the user is null or if the user already
+    /// exists in the registry.
+    void addUser(UserPtr& user);
+
+    /// @brief Finds a user in the registry by user id
+    ///
+    /// @param id The user id for which to search
+    ///
+    /// @return A pointer to the user if found or an null pointer if not.
+    const UserPtr& findUser(const UserId& id) const;
+
+    /// @brief Removes a user from the registry by user id
+    ///
+    /// Removes the user entry if found, if not simply return.
+    ///
+    /// @param id The user id of the user to remove
+    void removeUser(const UserId&  id);
+
+    /// @brief Finds a user in the registry by hardware address
+    ///
+    /// @param hwaddr The hardware address for which to search
+    ///
+    /// @return A pointer to the user if found or an null pointer if not.
+    const UserPtr& findUser(const isc::dhcp::HWAddr& hwaddr) const;
+
+    /// @brief Finds a user in the registry by DUID
+    ///
+    /// @param duid The DUID for which to search
+    ///
+    /// @return A pointer to the user if found or an null pointer if not.
+    const UserPtr& findUser(const isc::dhcp::DUID& duid) const;
+
+    /// @brief Updates the registry from its data source.
+    ///
+    /// This method will replace the contents of the registry with new content
+    /// read from its data source.  It will attempt to open the source and
+    /// then add users from the source to the registry until the source is
+    /// exhausted.  If an error occurs accessing the source the registry
+    /// contents will be restored to that of before the call to refresh.
+    ///
+    /// @throw UserRegistryError if the data source has not been set (is null)
+    /// or if an error occurs accessing the data source.
+    void refresh();
+
+    /// @brief Removes all entries from the registry.
+    void clearall();
+
+    /// @brief Returns a reference to the data source.
+    const UserDataSourcePtr& getSource();
+
+    /// @brief Sets the data source to the given value.
+    ///
+    /// @param source reference to the data source to use.
+    ///
+    /// @throw UserRegistryError if new source value is null.
+    void setSource(UserDataSourcePtr& source);
+
+private:
+    /// @brief The registry of users.
+    UserMap users_;
+
+    /// @brief The current data source of users.
+    UserDataSourcePtr source_;
+};
+
+/// @brief Define a smart pointer to a UserRegistry.
+typedef boost::shared_ptr<UserRegistry> UserRegistryPtr;
+
+#endif
diff --git a/src/hooks/dhcp/user_chk/version.cc b/src/hooks/dhcp/user_chk/version.cc
new file mode 100644
index 0000000..eaa9d50
--- /dev/null
+++ b/src/hooks/dhcp/user_chk/version.cc
@@ -0,0 +1,25 @@
+// Copyright (C) 2013 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.
+// version.cc
+
+#include <hooks/hooks.h>
+
+extern "C" {
+
+/// @brief Version function required by Hooks API for compatibility checks.
+int version() {
+    return (BIND10_HOOKS_VERSION);
+}
+
+}
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index 3f61b89..a6c601b 100644
--- a/src/lib/datasrc/memory/zone_finder.cc
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -772,7 +772,7 @@ InMemoryZoneFinder::findAll(const isc::dns::Name& name,
 // the case of CNAME can be eliminated (these should be guaranteed at the load
 // or update time, but even if they miss a corner case and allows a CNAME to
 // be added at origin, the zone is broken anyway, so we'd just let this
-// method return garbage, too).  As a result, there can be only too cases
+// method return garbage, too).  As a result, there can be only two cases
 // for the result codes: SUCCESS if the requested type of RR exists; NXRRSET
 // otherwise.  Due to its simplicity we implement it separately, rather than
 // sharing the code with findInternal.
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 128aafe..0ad35a5 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -177,6 +177,17 @@ IfaceMgr::IfaceMgr()
     }
 }
 
+void Iface::addUnicast(const isc::asiolink::IOAddress& addr) {
+    for (Iface::AddressCollection::const_iterator i = unicasts_.begin();
+         i != unicasts_.end(); ++i) {
+        if (*i == addr) {
+            isc_throw(BadValue, "Address " << addr.toText()
+                      << " already defined on the " << name_ << " interface.");
+        }
+    }
+    unicasts_.push_back(addr);
+}
+
 void IfaceMgr::closeSockets() {
     for (IfaceCollection::iterator iface = ifaces_.begin();
          iface != ifaces_.end(); ++iface) {
@@ -343,8 +354,10 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
 
             }
             if (sock < 0) {
+                const char* errstr = strerror(errno);
                 isc_throw(SocketConfigError, "failed to open IPv4 socket"
-                          << " supporting broadcast traffic");
+                          << " supporting broadcast traffic, reason:"
+                          << errstr);
             }
 
             count++;
@@ -368,6 +381,23 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
             continue;
         }
 
+        // Open unicast sockets if there are any unicast addresses defined
+        Iface::AddressCollection unicasts = iface->getUnicasts();
+        for (Iface::AddressCollection::iterator addr = unicasts.begin();
+             addr != unicasts.end(); ++addr) {
+
+            sock = openSocket(iface->getName(), *addr, port);
+            if (sock < 0) {
+                const char* errstr = strerror(errno);
+                isc_throw(SocketConfigError, "failed to open unicast socket on "
+                          << addr->toText() << " on interface " << iface->getName()
+                          << ", reason: " << errstr);
+            }
+
+            count++;
+
+        }
+
         Iface::AddressCollection addrs = iface->getAddresses();
         for (Iface::AddressCollection::iterator addr = addrs.begin();
              addr != addrs.end();
@@ -389,7 +419,10 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
 
             sock = openSocket(iface->getName(), *addr, port);
             if (sock < 0) {
-                isc_throw(SocketConfigError, "failed to open unicast socket");
+                const char* errstr = strerror(errno);
+                isc_throw(SocketConfigError, "failed to open link-local socket on "
+                          << addr->toText() << " on interface "
+                          << iface->getName() << ", reason: " << errstr);
             }
 
             // Binding socket to unicast address and then joining multicast group
@@ -414,8 +447,10 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
                                    IOAddress(ALL_DHCP_RELAY_AGENTS_AND_SERVERS),
                                    port);
             if (sock2 < 0) {
+                const char* errstr = strerror(errno);
                 isc_throw(SocketConfigError, "Failed to open multicast socket on "
-                          << " interface " << iface->getFullName());
+                          << " interface " << iface->getFullName() << ", reason:"
+                          << errstr);
                 iface->delSocket(sock); // delete previously opened socket
             }
 #endif
@@ -608,7 +643,9 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
         // interface.
         sock.open(asio::ip::udp::v4(), err_code);
         if (err_code) {
-            isc_throw(Unexpected, "failed to open UDPv4 socket");
+            const char* errstr = strerror(errno);
+            isc_throw(Unexpected, "failed to open UDPv4 socket, reason:"
+                      << errstr);
         }
         sock.set_option(asio::socket_base::broadcast(true), err_code);
         if (err_code) {
@@ -1137,16 +1174,50 @@ uint16_t IfaceMgr::getSocket(const isc::dhcp::Pkt6& pkt) {
                   << pkt.getIface());
     }
 
+
     const Iface::SocketCollection& socket_collection = iface->getSockets();
+
+    Iface::SocketCollection::const_iterator candidate = socket_collection.end();
+
     Iface::SocketCollection::const_iterator s;
     for (s = socket_collection.begin(); s != socket_collection.end(); ++s) {
-        if ((s->family_ == AF_INET6) &&
-            (!s->addr_.getAddress().to_v6().is_multicast())) {
+
+        // We should not merge those conditions for debugging reasons.
+
+        // V4 sockets are useless for sending v6 packets.
+        if (s->family_ != AF_INET6) {
+            continue;
+        }
+
+        // Sockets bound to multicast address are useless for sending anything.
+        if (s->addr_.getAddress().to_v6().is_multicast()) {
+            continue;
+        }
+
+        if (s->addr_ == pkt.getLocalAddr()) {
+            // This socket is bound to the source address. This is perfect
+            // match, no need to look any further.
             return (s->sockfd_);
         }
-        /// @todo: Add more checks here later. If remote address is
-        /// not link-local, we can't use link local bound socket
-        /// to send data.
+
+        // If we don't have any other candidate, this one will do
+        if (candidate == socket_collection.end()) {
+            candidate = s;
+        } else {
+            // If we want to send something to link-local and the socket is
+            // bound to link-local or we want to send to global and the socket
+            // is bound to global, then use it as candidate
+            if ( (pkt.getRemoteAddr().getAddress().to_v6().is_link_local() &&
+                s->addr_.getAddress().to_v6().is_link_local()) ||
+                 (!pkt.getRemoteAddr().getAddress().to_v6().is_link_local() &&
+                  !s->addr_.getAddress().to_v6().is_link_local()) ) {
+                candidate = s;
+            }
+        }
+    }
+
+    if (candidate != socket_collection.end()) {
+        return (candidate->sockfd_);
     }
 
     isc_throw(Unexpected, "Interface " << iface->getFullName()
@@ -1175,5 +1246,6 @@ uint16_t IfaceMgr::getSocket(isc::dhcp::Pkt4 const& pkt) {
               << " does not have any suitable IPv4 sockets open.");
 }
 
+
 } // end of namespace isc::dhcp
 } // end of namespace isc
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index 217524d..2f48813 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -264,6 +264,27 @@ public:
     /// @return collection of sockets added to interface
     const SocketCollection& getSockets() const { return sockets_; }
 
+    /// @brief Removes any unicast addresses
+    ///
+    /// Removes any unicast addresses that the server was configured to
+    /// listen on
+    void clearUnicasts() {
+        unicasts_.clear();
+    }
+
+    /// @brief Adds unicast the server should listen on
+    ///
+    /// @throw BadValue if specified address is already defined on interface
+    /// @param addr unicast address to listen on
+    void addUnicast(const isc::asiolink::IOAddress& addr);
+
+    /// @brief Returns a container of addresses the server should listen on
+    ///
+    /// @return address collection (may be empty)
+    const AddressCollection& getUnicasts() const {
+        return unicasts_;
+    }
+
 protected:
     /// Socket used to send data.
     SocketCollection sockets_;
@@ -277,6 +298,9 @@ protected:
     /// List of assigned addresses.
     AddressCollection addrs_;
 
+    /// List of unicast addresses the server should listen on
+    AddressCollection unicasts_;
+
     /// Link-layer address.
     uint8_t mac_[MAX_MAC_LEN];
 
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index ab74dfa..e94097c 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -137,12 +137,14 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
             return (factoryGeneric(u, type, begin, end));
 
         case OPT_UINT8_TYPE:
-            return (array_type_ ? factoryGeneric(u, type, begin, end) :
+            return (array_type_ ?
+                    factoryIntegerArray<uint8_t>(u, type, begin, end) :
                     factoryInteger<uint8_t>(u, type, getEncapsulatedSpace(),
                                             begin, end, callback));
 
         case OPT_INT8_TYPE:
-            return (array_type_ ? factoryGeneric(u, type, begin, end) :
+            return (array_type_ ?
+                    factoryIntegerArray<int8_t>(u, type, begin, end) :
                     factoryInteger<int8_t>(u, type, getEncapsulatedSpace(),
                                            begin, end, callback));
 
diff --git a/src/lib/dhcp/option_vendor.cc b/src/lib/dhcp/option_vendor.cc
index 0e7da46..878b534 100644
--- a/src/lib/dhcp/option_vendor.cc
+++ b/src/lib/dhcp/option_vendor.cc
@@ -18,7 +18,7 @@
 
 using namespace isc::dhcp;
 
-OptionVendor::OptionVendor(Option::Universe u, uint32_t vendor_id)
+OptionVendor::OptionVendor(Option::Universe u, const uint32_t vendor_id)
     :Option(u, u==Option::V4?DHO_VIVSO_SUBOPTIONS:D6O_VENDOR_OPTS), vendor_id_(vendor_id) {
 }
 
@@ -37,14 +37,18 @@ void OptionVendor::pack(isc::util::OutputBuffer& buf) {
 
     // The format is slightly different for v4
     if (universe_ == Option::V4) {
-        // Store data-len1 (it's a length of following suboptions
-        buf.writeUint8(len() - getHeaderLen() - sizeof(uint32_t) - sizeof(uint8_t));
+        // Calculate and store data-len as follows:
+        // data-len = total option length - header length
+        //            - enterprise id field length - data-len field size
+        buf.writeUint8(len() - getHeaderLen() -
+                       sizeof(uint32_t) - sizeof(uint8_t));
     }
 
     packOptions(buf);
 }
 
-void OptionVendor::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+void OptionVendor::unpack(OptionBufferConstIter begin,
+                          OptionBufferConstIter end) {
     if (distance(begin, end) < sizeof(uint32_t)) {
         isc_throw(OutOfRange, "Truncated vendor-specific information option"
                   << ", length=" << distance(begin, end));
diff --git a/src/lib/dhcp/option_vendor.h b/src/lib/dhcp/option_vendor.h
index c5268fd..5b43508 100644
--- a/src/lib/dhcp/option_vendor.h
+++ b/src/lib/dhcp/option_vendor.h
@@ -25,16 +25,18 @@
 namespace isc {
 namespace dhcp {
 
-/// This class represents vendor-specific information option.
-/// As defined in RFC3925. The option formatting is slightly
+/// @brief This class represents vendor-specific information option.
+///
+/// As specified in RFC3925, the option formatting is slightly different
+/// for DHCPv4 than DHCPv6. The DHCPv4 Option includes additional field
+/// holding vendor data length.
 class OptionVendor: public Option {
-
 public:
     /// @brief Constructor.
     ///
     /// @param u universe (V4 or V6)
     /// @param vendor_id vendor enterprise-id (unique 32 bit integer)
-    OptionVendor(Option::Universe u, uint32_t vendor_id);
+    OptionVendor(Option::Universe u, const uint32_t vendor_id);
 
     /// @brief Constructor.
     ///
@@ -47,17 +49,15 @@ public:
     /// @param end iterator to end of option data (first byte after option end).
     ///
     /// @throw isc::OutOfRange if provided buffer is shorter than data size.
-    /// @throw isc::dhcp::InvalidDataType if data field type provided
-    /// as template parameter is not a supported integer type.
     /// @todo Extend constructor to set encapsulated option space name.
     OptionVendor(Option::Universe u, OptionBufferConstIter begin,
                  OptionBufferConstIter end);
 
-    /// Writes option in wire-format to buf, returns pointer to first unused
-    /// byte after stored option.
+    /// @brief Writes option in wire-format to buf, returns pointer to first
+    /// unused byte after stored option.
     ///
     /// @param [out] buf buffer (option will be stored here)
-    void pack(isc::util::OutputBuffer& buf);
+    virtual void pack(isc::util::OutputBuffer& buf);
 
     /// @brief Parses received buffer
     ///
@@ -73,12 +73,12 @@ public:
     /// @brief Sets enterprise identifier
     ///
     /// @param value vendor identifier
-    void setVendorId(uint32_t vendor_id) { vendor_id_ = vendor_id; }
+    void setVendorId(const uint32_t vendor_id) { vendor_id_ = vendor_id; }
 
     /// @brief Returns enterprise identifier
     ///
     /// @return enterprise identifier
-    uint32_t getVendorId() const { return vendor_id_; }
+    uint32_t getVendorId() const { return (vendor_id_); }
 
     /// @brief returns complete length of option
     ///
@@ -89,7 +89,7 @@ public:
 
 private:
 
-    uint32_t vendor_id_;  ///< Enterprise-id 
+    uint32_t vendor_id_;  ///< Enterprise-id
 };
 
 /// Pointer to a vendor option
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 4fc1c42..468037a 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -108,6 +108,10 @@ Pkt4::pack() {
         isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
     }
 
+    // Clear the output buffer to make sure that consecutive calls to pack()
+    // will not result in concatenation of multiple packet copies.
+    buffer_out_.clear();
+
     try {
         size_t hw_len = hwaddr_->hwaddr_.size();
 
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index be82e13..c8015cd 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -72,6 +72,7 @@ public:
     /// Prepares on-wire format of message and all its options.
     /// Options must be stored in options_ field.
     /// Output buffer will be stored in buffer_out_.
+    /// The buffer_out_ is cleared before writting to the buffer.
     ///
     /// @throw InvalidOperation if packing fails
     void
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 9d194a5..57daee0 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -43,7 +43,7 @@ Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */)
     remote_addr_("::"),
     local_port_(0),
     remote_port_(0),
-    bufferOut_(0) {
+    buffer_out_(0) {
     data_.resize(buf_len);
     memcpy(&data_[0], buf, buf_len);
 }
@@ -58,7 +58,7 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
     remote_addr_("::"),
     local_port_(0),
     remote_port_(0),
-    bufferOut_(0) {
+    buffer_out_(0) {
 }
 
 uint16_t Pkt6::len() {
@@ -199,6 +199,8 @@ Pkt6::pack() {
 void
 Pkt6::packUDP() {
     try {
+        // Make sure that the buffer is empty before we start writting to it.
+        buffer_out_.clear();
 
         // is this a relayed packet?
         if (!relay_info_.empty()) {
@@ -215,11 +217,11 @@ Pkt6::packUDP() {
                  relay != relay_info_.end(); ++relay) {
 
                 // build relay-forw/relay-repl header (see RFC3315, section 7)
-                bufferOut_.writeUint8(relay->msg_type_);
-                bufferOut_.writeUint8(relay->hop_count_);
-                bufferOut_.writeData(&(relay->linkaddr_.toBytes()[0]),
+                buffer_out_.writeUint8(relay->msg_type_);
+                buffer_out_.writeUint8(relay->hop_count_);
+                buffer_out_.writeData(&(relay->linkaddr_.toBytes()[0]),
                                      isc::asiolink::V6ADDRESS_LEN);
-                bufferOut_.writeData(&relay->peeraddr_.toBytes()[0],
+                buffer_out_.writeData(&relay->peeraddr_.toBytes()[0],
                                      isc::asiolink::V6ADDRESS_LEN);
 
                 // store every option in this relay scope. Usually that will be
@@ -230,28 +232,28 @@ Pkt6::packUDP() {
                 for (OptionCollection::const_iterator opt =
                          relay->options_.begin();
                      opt != relay->options_.end(); ++opt) {
-                    (opt->second)->pack(bufferOut_);
+                    (opt->second)->pack(buffer_out_);
                 }
 
                 // and include header relay-msg option. Its payload will be
                 // generated in the next iteration (if there are more relays)
                 // or outside the loop (if there are no more relays and the
                 // payload is a direct message)
-                bufferOut_.writeUint16(D6O_RELAY_MSG);
-                bufferOut_.writeUint16(relay->relay_msg_len_);
+                buffer_out_.writeUint16(D6O_RELAY_MSG);
+                buffer_out_.writeUint16(relay->relay_msg_len_);
             }
 
         }
 
         // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
-        bufferOut_.writeUint8(msg_type_);
+        buffer_out_.writeUint8(msg_type_);
         // store 3-octet transaction-id
-        bufferOut_.writeUint8( (transid_ >> 16) & 0xff );
-        bufferOut_.writeUint8( (transid_ >> 8) & 0xff );
-        bufferOut_.writeUint8( (transid_) & 0xff );
+        buffer_out_.writeUint8( (transid_ >> 16) & 0xff );
+        buffer_out_.writeUint8( (transid_ >> 8) & 0xff );
+        buffer_out_.writeUint8( (transid_) & 0xff );
 
         // the rest are options
-        LibDHCP::packOptions(bufferOut_, options_);
+        LibDHCP::packOptions(buffer_out_, options_);
     }
     catch (const Exception& e) {
        // An exception is thrown and message will be written to Logger
@@ -502,7 +504,7 @@ Pkt6::delOption(uint16_t type) {
 }
 
 void Pkt6::repack() {
-    bufferOut_.writeData(&data_[0], data_.size());
+    buffer_out_.writeData(&data_[0], data_.size());
 }
 
 void
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 207f576..702c424 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -115,6 +115,7 @@ public:
     /// Options must be stored in options_ field.
     /// Output buffer will be stored in data_. Length
     /// will be set in data_len_.
+    /// The output buffer is cleared before new data is written to it.
     ///
     /// @throw BadValue if packet protocol is invalid, InvalidOperation
     /// if packing fails, or NotImplemented if protocol is TCP (IPv6 over TCP is
@@ -139,7 +140,7 @@ public:
     /// zero length
     ///
     /// @return reference to output buffer
-    const isc::util::OutputBuffer& getBuffer() const { return (bufferOut_); };
+    const isc::util::OutputBuffer& getBuffer() const { return (buffer_out_); };
 
     /// @brief Returns protocol of this packet (UDP or TCP).
     ///
@@ -530,7 +531,7 @@ protected:
     /// remote TCP or UDP port
     uint16_t remote_port_;
 
-    /// output buffer (used during message transmission)
+    /// Output buffer (used during message transmission)
     ///
     /// @warning This protected member is accessed by derived
     /// classes directly. One of such derived classes is
@@ -538,7 +539,7 @@ protected:
     /// behavior must be taken into consideration before making
     /// changes to this member such as access scope restriction or
     /// data format change etc.
-    isc::util::OutputBuffer bufferOut_;
+    isc::util::OutputBuffer buffer_out_;
 
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 488ecb3..b936b5c 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -145,6 +145,27 @@ public:
         return (sockets_count);
     }
 
+    /// @brief returns socket bound to a specific address (or NULL)
+    ///
+    /// A helper function, used to pick a socketinfo that is bound to a given
+    /// address.
+    ///
+    /// @param sockets sockets collection
+    /// @param addr address the socket is bound to
+    ///
+    /// @return socket info structure (or NULL)
+    const isc::dhcp::SocketInfo*
+    getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets,
+                    const IOAddress& addr) {
+        for (isc::dhcp::Iface::SocketCollection::const_iterator s =
+                 sockets.begin(); s != sockets.end(); ++s) {
+            if (s->addr_ == addr) {
+                return (&(*s));
+            }
+        }
+        return (NULL);
+    }
+
 };
 
 // We need some known interface to work reliably. Loopback interface is named
@@ -781,6 +802,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
     // try to send/receive data over the closed socket. Closed socket's descriptor is
     // still being hold by IfaceMgr which will try to use it to receive data.
     close(socket1);
+    close(socket2);
     EXPECT_THROW(ifacemgr->receive6(10), SocketReadError);
     EXPECT_THROW(ifacemgr->send(sendPkt), SocketWriteError);
 }
@@ -1520,4 +1542,116 @@ TEST_F(IfaceMgrTest, controlSession) {
     close(pipefd[0]);
 }
 
+// Test checks if the unicast sockets can be opened.
+// This test is now disabled, because there is no reliable way to test it. We
+// can't even use loopback, beacuse openSockets() skips loopback interface
+// (as it should be, because DHCP server is not supposed to listen on loopback).
+TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) {
+    /// @todo Need to implement a test that is able to check whether we can open
+    /// unicast sockets. There are 2 problems with it:
+    /// 1. We need to have a non-link-local address on an interface that is
+    ///    up, running, IPv6 and multicast capable
+    /// 2. We need that information on every OS that we run tests on. So far
+    ///    we are only supporting interface detection in Linux.
+    ///
+    /// To achieve this, we will probably need a pre-test setup, similar to what
+    /// BIND9 is doing (i.e. configuring well known addresses on loopback).
+
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    // Get the interface (todo: which interface)
+    Iface* iface = ifacemgr->getIface("eth0");
+    ASSERT_TRUE(iface);
+    iface->inactive6_ = false;
+
+    // Tell the interface that it should bind to this global interface
+    EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+    // Tell IfaceMgr to open sockets. This should trigger at least 2 sockets
+    // to open on eth0: link-local and global. On some systems (Linux), an
+    // additional socket for multicast may be opened.
+    EXPECT_TRUE(ifacemgr->openSockets6(PORT1));
+
+    const Iface::SocketCollection& sockets = iface->getSockets();
+    ASSERT_GE(2, sockets.size());
+
+    // Global unicast should be first
+    EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("2001:db8::1")));
+    EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("figure-out-link-local-addr")));
+}
+
+// Checks if there is a protection against unicast duplicates.
+TEST_F(IfaceMgrTest, unicastDuplicates) {
+    NakedIfaceMgr ifacemgr;
+
+    Iface* iface = ifacemgr.getIface(LOOPBACK);
+    if (iface == NULL) {
+        cout << "Local loopback interface not found. Skipping test. " << endl;
+        return;
+    }
+
+    // Tell the interface that it should bind to this global interface
+    EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+    // Tell the interface that it should bind to this global interface
+    EXPECT_THROW(iface->addUnicast(IOAddress("2001:db8::1")), BadValue);
+}
+
+// This test requires addresses 2001:db8:15c::1/128 and fe80::1/64 to be
+// configured on loopback interface
+//
+// Useful commands:
+// ip a a 2001:db8:15c::1/128 dev lo
+// ip a a fe80::1/64 dev lo
+//
+// If you do not issue those commands before running this test, it will fail.
+TEST_F(IfaceMgrTest, DISABLED_getSocket) {
+    // Testing socket operation in a portable way is tricky
+    // without interface detection implemented.
+
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+    IOAddress lo_addr("::1");
+    IOAddress link_local("fe80::1");
+    IOAddress global("2001:db8:15c::1");
+
+    IOAddress dst_link_local("fe80::dead:beef");
+    IOAddress dst_global("2001:db8:15c::dead:beef");
+
+    // Bind loopback address
+    int socket1 = ifacemgr->openSocket(LOOPBACK, lo_addr, 10547);
+    EXPECT_GE(socket1, 0); // socket >= 0
+
+    // Bind link-local address
+    int socket2 = ifacemgr->openSocket(LOOPBACK, link_local, 10547);
+    EXPECT_GE(socket2, 0);
+
+    int socket3 = ifacemgr->openSocket(LOOPBACK, global, 10547);
+    EXPECT_GE(socket3, 0);
+
+    // Let's make sure those sockets are unique
+    EXPECT_NE(socket1, socket2);
+    EXPECT_NE(socket2, socket3);
+    EXPECT_NE(socket3, socket1);
+
+    // Create a packet
+    Pkt6 pkt6(DHCPV6_SOLICIT, 123);
+    pkt6.setIface(LOOPBACK);
+
+    // Check that packets sent to link-local will get socket bound to link local
+    pkt6.setLocalAddr(global);
+    pkt6.setRemoteAddr(dst_global);
+    EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6));
+
+    // Check that packets sent to link-local will get socket bound to link local
+    pkt6.setLocalAddr(link_local);
+    pkt6.setRemoteAddr(dst_link_local);
+    EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6));
+
+    // Close sockets here because the following tests will want to
+    // open sockets on the same ports.
+    ifacemgr->closeSockets();
+}
+
+
 }
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index ad564e7..a55a7ea 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -707,7 +707,7 @@ TEST_F(LibDhcpTest, stdOptionDefs4) {
                                     typeid(OptionCustom));
 
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_PARAMETER_REQUEST_LIST, begin, end,
-                                    typeid(Option));
+                                    typeid(OptionUint8Array));
 
     LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end,
                                     typeid(OptionString));
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 56e4c8e..b4fb11d 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -16,6 +16,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <string>
 
 using namespace isc::asiolink;
 using namespace isc::util;
@@ -269,14 +270,31 @@ std::string CfgMgr::getDataDir() {
 
 void
 CfgMgr::addActiveIface(const std::string& iface) {
-    if (isIfaceListedActive(iface)) {
+
+    size_t pos = iface.find("/");
+    std::string iface_copy = iface;
+
+    if (pos != std::string::npos) {
+        std::string addr_string = iface.substr(pos + 1);
+        try {
+            IOAddress addr(addr_string);
+            iface_copy = iface.substr(0,pos);
+            unicast_addrs_.insert(make_pair(iface_copy, addr));
+        } catch (...) {
+            isc_throw(BadValue, "Can't convert '" << addr_string
+                      << "' into address in interface defition ('"
+                      << iface << "')");
+        }
+    }
+
+    if (isIfaceListedActive(iface_copy)) {
         isc_throw(DuplicateListeningIface,
-                  "attempt to add duplicate interface '" << iface << "'"
+                  "attempt to add duplicate interface '" << iface_copy << "'"
                   " to the set of interfaces on which server listens");
     }
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
-        .arg(iface);
-    active_ifaces_.push_back(iface);
+        .arg(iface_copy);
+    active_ifaces_.push_back(iface_copy);
 }
 
 void
@@ -292,6 +310,8 @@ CfgMgr::deleteActiveIfaces() {
               DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
     active_ifaces_.clear();
     all_ifaces_active_ = false;
+
+    unicast_addrs_.clear();
 }
 
 bool
@@ -319,6 +339,15 @@ CfgMgr::isIfaceListedActive(const std::string& iface) const {
     return (false);
 }
 
+const isc::asiolink::IOAddress*
+CfgMgr::getUnicast(const std::string& iface) const {
+    UnicastIfacesCollection::const_iterator addr = unicast_addrs_.find(iface);
+    if (addr == unicast_addrs_.end()) {
+        return (NULL);
+    }
+    return (&(*addr).second);
+}
+
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
       all_ifaces_active_(false) {
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index a589284..7b5ead3 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -305,6 +305,17 @@ public:
     /// interfaces on which server is configured to listen.
     bool isActiveIface(const std::string& iface) const;
 
+    /// @brief returns unicast a given interface should listen on (or NULL)
+    ///
+    /// This method will return an address for a specified interface, if the
+    /// server is supposed to listen on unicast address. This address is
+    /// intended to be used immediately. This pointer is valid only until
+    /// the next configuration change.
+    ///
+    /// @return IOAddress pointer (or NULL if none)
+    const isc::asiolink::IOAddress*
+    getUnicast(const std::string& iface) const;
+
 protected:
 
     /// @brief Protected constructor.
@@ -372,6 +383,12 @@ private:
     std::list<std::string> active_ifaces_;
     //@}
 
+    /// @name a collection of unicast addresses and the interfaces names the
+    //        server is supposed to listen on
+    //@{
+    typedef std::map<std::string, isc::asiolink::IOAddress> UnicastIfacesCollection;
+    UnicastIfacesCollection unicast_addrs_;
+
     /// A flag which indicates that server should listen on all available
     /// interfaces.
     bool all_ifaces_active_;
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index c41a883..ffd49d3 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -231,6 +231,7 @@ InterfaceListConfigParser::commit() {
 
 bool
 InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
+
     for (IfaceListStorage::const_iterator it = interfaces_.begin();
          it != interfaces_.end(); ++it) {
         if (iface == *it) {
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index d01fb3d..d861afe 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -176,13 +176,26 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t2,
                  const Triplet<uint32_t>& valid_lifetime)
-    :Subnet(prefix, length, t1, t2, valid_lifetime) {
+    :Subnet(prefix, length, t1, t2, valid_lifetime),
+    siaddr_(IOAddress("0.0.0.0")) {
     if (!prefix.isV4()) {
         isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
                   << " specified in subnet4");
     }
 }
 
+void Subnet4::setSiaddr(const isc::asiolink::IOAddress& siaddr) {
+    if (!siaddr.isV4()) {
+        isc_throw(BadValue, "Can't set siaddr to non-IPv4 address "
+                  << siaddr.toText());
+    }
+    siaddr_ = siaddr;
+}
+
+isc::asiolink::IOAddress Subnet4::getSiaddr() const {
+    return (siaddr_);
+}
+
 const PoolCollection& Subnet::getPools(Lease::Type type) const {
     // check if the type is valid (and throw if it isn't)
     checkType(type);
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index cad888f..31dc947 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -505,6 +505,18 @@ public:
             const Triplet<uint32_t>& t2,
             const Triplet<uint32_t>& valid_lifetime);
 
+    /// @brief Sets siaddr for the Subnet4
+    ///
+    /// Will be used for siaddr field (the next server) that typically is used
+    /// as TFTP server. If not specified, the default value of 0.0.0.0 is
+    /// used.
+    void setSiaddr(const isc::asiolink::IOAddress& siaddr);
+
+    /// @brief Returns siaddr for this subnet
+    ///
+    /// @return siaddr value
+    isc::asiolink::IOAddress getSiaddr() const;
+
 protected:
 
     /// @brief Check if option is valid and can be added to a subnet.
@@ -527,6 +539,9 @@ protected:
     /// @param type type to be checked
     /// @throw BadValue if invalid value is used
     virtual void checkType(Lease::Type type) const;
+
+    /// @brief siaddr value for this subnet
+    isc::asiolink::IOAddress siaddr_;
 };
 
 /// @brief A pointer to a Subnet4 object
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 38d2f0a..94f78c3 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -579,20 +579,55 @@ TEST_F(CfgMgrTest, optionSpace6) {
 TEST_F(CfgMgrTest, addActiveIface) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
-    cfg_mgr.addActiveIface("eth0");
-    cfg_mgr.addActiveIface("eth1");
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth0"));
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth1"));
 
     EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
     EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 
-    cfg_mgr.deleteActiveIfaces();
+    EXPECT_NO_THROW(cfg_mgr.deleteActiveIfaces());
 
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 }
 
+
+// This test verifies that it is possible to specify interfaces that server
+// should listen on.
+TEST_F(CfgMgrTest, addUnicastAddresses) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth1/2001:db8::1"));
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth2/2001:db8::2"));
+    EXPECT_NO_THROW(cfg_mgr.addActiveIface("eth3"));
+
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
+    EXPECT_TRUE(cfg_mgr.isActiveIface("eth3"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth4"));
+
+    ASSERT_TRUE(cfg_mgr.getUnicast("eth1"));
+    EXPECT_EQ("2001:db8::1", cfg_mgr.getUnicast("eth1")->toText());
+    EXPECT_EQ("2001:db8::2", cfg_mgr.getUnicast("eth2")->toText());
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth3"));
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth4"));
+
+    EXPECT_NO_THROW(cfg_mgr.deleteActiveIfaces());
+
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth3"));
+    EXPECT_FALSE(cfg_mgr.isActiveIface("eth4"));
+
+    ASSERT_FALSE(cfg_mgr.getUnicast("eth1"));
+    ASSERT_FALSE(cfg_mgr.getUnicast("eth2"));
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth3"));
+    EXPECT_FALSE(cfg_mgr.getUnicast("eth4"));
+}
+
+
 // This test verifies that it is possible to set the flag which configures the
 // server to listen on all interfaces.
 TEST_F(CfgMgrTest, activateAllIfaces) {
@@ -618,6 +653,27 @@ TEST_F(CfgMgrTest, activateAllIfaces) {
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 }
 
+/// @todo Add unit-tests for testing:
+/// - addActiveIface() with invalid interface name
+/// - addActiveIface() with the same interface twice
+/// - addActiveIface() with a bogus address
+///
+/// This is somewhat tricky. Care should be taken here, because it is rather
+/// difficult to decide if interface name is valid or not. Some servers, e.g.
+/// dibbler, allow to specify interface names that are not currently present in
+/// the system. The server accepts them, but upon discovering that they are
+/// yet available (for different definitions of not being available), adds
+/// the to to-be-activated list.
+///
+/// Cases covered by dibbler are:
+/// - missing interface (e.g. PPP connection that is not established yet)
+/// - downed interface (no link local address, no way to open sockets)
+/// - up, but not running interface (wifi up, but not associated)
+/// - tentative addresses (interface up and running, but DAD procedure is
+///   still in progress)
+/// - weird interfaces without link-local addresses (don't ask, 6rd tunnels
+///   look weird to me as well)
+
 // No specific tests for getSubnet6. That method (2 overloaded versions) is tested
 // in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
 // (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index f872ed9..d0dd57a 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -58,6 +58,24 @@ TEST(Subnet4Test, in_range) {
     EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
 }
 
+// Checks whether siaddr field can be set and retrieved correctly.
+TEST(Subnet4Test, siaddr) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    // Check if the default is 0.0.0.0
+    EXPECT_EQ("0.0.0.0", subnet.getSiaddr().toText());
+
+    // Check that we can set it up
+    EXPECT_NO_THROW(subnet.setSiaddr(IOAddress("1.2.3.4")));
+
+    // Check that we can get it back
+    EXPECT_EQ("1.2.3.4", subnet.getSiaddr().toText());
+
+    // Check that only v4 addresses are supported
+    EXPECT_THROW(subnet.setSiaddr(IOAddress("2001:db8::1")),
+        BadValue);
+}
+
 TEST(Subnet4Test, Pool4InSubnet4) {
 
     Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
diff --git a/tests/tools/perfdhcp/perf_pkt6.cc b/tests/tools/perfdhcp/perf_pkt6.cc
index 56fe9df..0ede077 100644
--- a/tests/tools/perfdhcp/perf_pkt6.cc
+++ b/tests/tools/perfdhcp/perf_pkt6.cc
@@ -43,7 +43,7 @@ PerfPkt6::rawPack() {
                                options_,
                                getTransidOffset(),
                                getTransid(),
-                               bufferOut_));
+                               buffer_out_));
 }
 
 bool



More information about the bind10-changes mailing list