Browse Source

Merge branch 'master' into trac2268

Mukund Sivaraman 12 years ago
parent
commit
83d00dc305
54 changed files with 4004 additions and 283 deletions
  1. 48 0
      ChangeLog
  2. 102 90
      configure.ac
  3. 0 23
      doc/devel/02-dhcp.dox
  4. 5 0
      doc/devel/mainpage.dox
  5. 89 10
      doc/guide/bind10-guide.xml
  6. 1 1
      src/bin/auth/auth_config.h
  7. 24 7
      src/bin/bind10/bind10_messages.mes
  8. 30 22
      src/bin/bind10/bind10_src.py.in
  9. 34 2
      src/bin/bind10/tests/bind10_test.py.in
  10. 2 0
      src/bin/dhcp6/Makefile.am
  11. 797 0
      src/bin/dhcp6/config_parser.cc
  12. 147 0
      src/bin/dhcp6/config_parser.h
  13. 25 5
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  14. 79 0
      src/bin/dhcp6/dhcp6.dox
  15. 90 2
      src/bin/dhcp6/dhcp6.spec
  16. 20 0
      src/bin/dhcp6/dhcp6_messages.mes
  17. 7 0
      src/bin/dhcp6/dhcp6_srv.cc
  18. 3 0
      src/bin/dhcp6/tests/Makefile.am
  19. 243 0
      src/bin/dhcp6/tests/config_parser_unittest.cc
  20. 8 8
      src/lib/cc/data.cc
  21. 22 22
      src/lib/cc/data.h
  22. 197 52
      src/lib/cc/tests/data_unittests.cc
  23. 1 1
      src/lib/datasrc/memory_datasrc_link.cc
  24. 2 0
      src/lib/dhcp/Makefile.am
  25. 95 10
      src/lib/dhcp/addr_utilities.cc
  26. 33 0
      src/lib/dhcp/cfgmgr.cc
  27. 43 4
      src/lib/dhcp/cfgmgr.h
  28. 90 0
      src/lib/dhcp/duid.cc
  29. 98 0
      src/lib/dhcp/duid.h
  30. 68 0
      src/lib/dhcp/lease_mgr.cc
  31. 480 0
      src/lib/dhcp/lease_mgr.h
  32. 32 1
      src/lib/dhcp/pool.cc
  33. 27 0
      src/lib/dhcp/pool.h
  34. 44 0
      src/lib/dhcp/subnet.cc
  35. 51 0
      src/lib/dhcp/subnet.h
  36. 2 0
      src/lib/dhcp/tests/Makefile.am
  37. 63 2
      src/lib/dhcp/tests/addr_utilities_unittest.cc
  38. 36 0
      src/lib/dhcp/tests/cfgmgr_unittest.cc
  39. 169 0
      src/lib/dhcp/tests/duid_unittest.cc
  40. 296 0
      src/lib/dhcp/tests/lease_mgr_unittest.cc
  41. 73 0
      src/lib/dhcp/tests/pool_unittest.cc
  42. 80 2
      src/lib/dhcp/tests/subnet_unittest.cc
  43. 5 0
      src/lib/dhcp/triplet.h
  44. 5 0
      src/lib/python/isc/bind10/sockcreator.py
  45. 4 2
      src/lib/util/Makefile.am
  46. 70 4
      src/lib/util/interprocess_sync_file.cc
  47. 9 3
      src/lib/util/interprocess_sync_file.h
  48. 6 2
      src/lib/util/locks.h
  49. 1 0
      src/lib/util/tests/Makefile.am
  50. 46 1
      src/lib/util/tests/interprocess_sync_file_unittest.cc
  51. 13 0
      src/lib/util/threads/lock.cc
  52. 50 5
      src/lib/util/threads/lock.h
  53. 38 0
      src/lib/util/threads/tests/lock_unittest.cc
  54. 1 2
      src/lib/util/unittests/Makefile.am

+ 48 - 0
ChangeLog

@@ -1,3 +1,51 @@
+494.	[bug]		jinmei
+	Fixed a problem that shutting down BIND 10 kept some of the
+	processes alive.  It was two-fold: when the main bind10 process
+	started as a root, started b10-sockcreator with the privilege, and
+	then dropped the privilege, the bind10 process cannot kill the
+	sockcreator via signal any more (when it has to), but it kept
+	sending the signal and didn't stop.  Also, when running on Python
+	3.1 (or older), the sockcreator had some additional file
+	descriptor open, which prevented it from exiting even after the
+	bind10 process terminated.  Now the bind10 process simply gives up
+	killing a subprocess if it fails due to lack of permission, and it
+	makes sure the socket creator is spawned without any unnecessary
+	FDs open.
+	(Trac #1858, git 405d85c8a0042ba807a3a123611ff383c4081ee1)
+
+493.	[build]		jinmei
+	Fixed build failure with newer versions of clang++.  These
+	versions are stricter regarding "unused variable" and "unused
+	(driver) arguments" warnings, and cause fatal build error
+	with -Werror.  The affected versions of clang++ include Apple's
+	customized version 4.1 included in Xcode 4.5.1.  So this fix
+	will solve build errors for Mac OS X that uses newer versions of
+	Xcode.
+	(Trac #2340, git 55be177fc4f7537143ab6ef5a728bd44bdf9d783,
+	3e2a372012e633d017a97029d13894e743199741 and commits before it
+	with [2340] in the commit log)
+
+492.	[func]		tomek
+	libdhcpsrv: The DHCP Configuration Manager is now able to store
+	information about IPv4 subnets and pools. It is still not possible
+	to configure that information. Such capability will be implemented
+	in a near future.
+	(Trac #2237, git a78e560343b41f0f692c7903c938b2b2b24bf56b)
+
+491.	[func]		tomek
+	b10-dhcp6: Configuration for DHCPv6 has been implemented.
+	Currently it is possible to configure IPv6 subnets and pools
+	within those subnets, global and per subnet values of renew,
+	rebind, preferred and valid lifetimes. Configured parameters
+	are accepted, but are not used yet by the allocation engine yet.
+	(Trac #2269, git 028bed9014b15facf1a29d3d4a822c9d14fc6411)
+
+490.	[func]		tomek
+	libdhcpsrv: An abstract API for lease database has been
+	implemented. It offers a common interface to all concrete
+	database backends.
+	(Trac #2140, git df196f7609757253c4f2f918cd91012bb3af1163)
+
 489.	[func]		muks
 	The isc::dns::RRsetList class has been removed. It was now unused
 	inside the BIND 10 codebase, and the interface was considered

+ 102 - 90
configure.ac

@@ -70,6 +70,106 @@ AC_TRY_LINK([],[],
     ])
 LDFLAGS=$LDFLAGS_SAVED
 
+# Compiler dependent settings: define some mandatory CXXFLAGS here.
+# We also use a separate variable B10_CXXFLAGS.  This will (and should) be
+# used as the default value for each specific AM_CXXFLAGS:
+# AM_CXXFLAGS = $(B10_CXXFLAGS)
+# AM_CXXFLAGS += ... # add module specific flags
+# We need this so that we can disable some specific compiler warnings per
+# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since
+# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot
+# specify the default warning flags in CXXFLAGS and let specific modules
+# "override" the default.
+
+# This may be used to try linker flags.
+AC_DEFUN([BIND10_CXX_TRY_FLAG], [
+  AC_MSG_CHECKING([whether $CXX supports $1])
+
+  bind10_save_CXXFLAGS="$CXXFLAGS"
+  CXXFLAGS="$CXXFLAGS $1"
+
+  AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
+                 [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
+  CXXFLAGS="$bind10_save_CXXFLAGS"
+
+  if test "x$bind10_cxx_flag" = "xyes"; then
+    ifelse([$2], , :, [$2])
+  else
+    ifelse([$3], , :, [$3])
+  fi
+
+  AC_MSG_RESULT([$bind10_cxx_flag])
+])
+
+# SunStudio compiler requires special compiler options for boost
+# (http://blogs.sun.com/sga/entry/boost_mini_howto)
+if test "$SUNCXX" = "yes"; then
+CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
+MULTITHREADING_FLAG="-mt"
+fi
+
+# Newer versions of clang++ promotes "unused driver arguments" warnings to
+# a fatal error with -Werror, causing build failure.  Since we use multiple
+# compilers on multiple systems, this can easily happen due to settings for
+# non clang++ environments that could be just ignored otherwise.  It can also
+# happen if clang++ is used via ccache.  So, although probably suboptimal,
+# we suppress this particular warning.  Note that it doesn't weaken checks
+# on the source code.
+if test "$CLANGPP" = "yes"; then
+	B10_CXXFLAGS="$B10_CXXFLAGS -Qunused-arguments"
+fi
+
+BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
+	[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
+AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
+
+# gcc specific settings:
+if test "X$GXX" = "Xyes"; then
+B10_CXXFLAGS="$B10_CXXFLAGS -Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
+case "$host" in
+*-solaris*)
+	MULTITHREADING_FLAG=-pthreads
+	# In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces
+	B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces"
+	;;
+*)
+	MULTITHREADING_FLAG=-pthread
+	;;
+esac
+
+# Don't use -Werror if configured not to
+AC_ARG_WITH(werror,
+    AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
+    [
+     case "${withval}" in
+         yes) with_werror=1 ;;
+         no)  with_werror=0 ;;
+         *)   AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
+     esac],
+     [with_werror=1])
+
+werror_ok=0
+
+# Certain versions of gcc (g++) have a bug that incorrectly warns about
+# the use of anonymous name spaces even if they're closed in a single
+# translation unit.  For these versions we have to disable -Werror.
+if test $with_werror = 1; then
+   CXXFLAGS_SAVED="$CXXFLAGS"
+   CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
+   AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
+   AC_TRY_COMPILE([namespace { class Foo {}; }
+   namespace isc {class Bar {Foo foo_;};} ],,
+	[AC_MSG_RESULT(no)
+	 werror_ok=1
+	 B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
+	[AC_MSG_RESULT(yes)])
+   CXXFLAGS="$CXXFLAGS_SAVED"
+fi
+
+fi				dnl GXX = yes
+
+AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
+
 # allow building programs with static link.  we need to make it selective
 # because loadable modules cannot be statically linked.
 AC_ARG_ENABLE([static-link],
@@ -256,95 +356,11 @@ fi
 
 # TODO: check for _sqlite3.py module
 
-# Compiler dependent settings: define some mandatory CXXFLAGS here.
-# We also use a separate variable B10_CXXFLAGS.  This will (and should) be
-# used as the default value for each specific AM_CXXFLAGS:
-# AM_CXXFLAGS = $(B10_CXXFLAGS)
-# AM_CXXFLAGS += ... # add module specific flags
-# We need this so that we can disable some specific compiler warnings per
-# module basis; since AM_CXXFLAGS are placed before CXXFLAGS, and since
-# gcc's -Wno-XXX option must be specified after -Wall or -Wextra, we cannot
-# specify the default warning flags in CXXFLAGS and let specific modules
-# "override" the default.
-
-# This may be used to try linker flags.
-AC_DEFUN([BIND10_CXX_TRY_FLAG], [
-  AC_MSG_CHECKING([whether $CXX supports $1])
-
-  bind10_save_CXXFLAGS="$CXXFLAGS"
-  CXXFLAGS="$CXXFLAGS $1"
-
-  AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void){ return 0;}])],
-                 [bind10_cxx_flag=yes], [bind10_cxx_flag=no])
-  CXXFLAGS="$bind10_save_CXXFLAGS"
-
-  if test "x$bind10_cxx_flag" = "xyes"; then
-    ifelse([$2], , :, [$2])
-  else
-    ifelse([$3], , :, [$3])
-  fi
-
-  AC_MSG_RESULT([$bind10_cxx_flag])
-])
-
-# SunStudio compiler requires special compiler options for boost
-# (http://blogs.sun.com/sga/entry/boost_mini_howto)
-if test "$SUNCXX" = "yes"; then
-CXXFLAGS="$CXXFLAGS -library=stlport4 -features=tmplife -features=tmplrefstatic"
-MULTITHREADING_FLAG="-mt"
-fi
-
-BIND10_CXX_TRY_FLAG([-Wno-missing-field-initializers],
-	[WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG="-Wno-missing-field-initializers"])
-AC_SUBST(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-
-# gcc specific settings:
-if test "X$GXX" = "Xyes"; then
-B10_CXXFLAGS="-Wall -Wextra -Wwrite-strings -Woverloaded-virtual -Wno-sign-compare"
-case "$host" in
-*-solaris*)
-	MULTITHREADING_FLAG=-pthreads
-	# In Solaris, IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT need -Wno-missing-braces
-	B10_CXXFLAGS="$B10_CXXFLAGS -Wno-missing-braces"
-	;;
-*)
-	MULTITHREADING_FLAG=-pthread
-	;;
-esac
-
-# Don't use -Werror if configured not to
-AC_ARG_WITH(werror,
-    AC_HELP_STRING([--with-werror], [Compile using -Werror (default=yes)]),
-    [
-     case "${withval}" in
-         yes) with_werror=1 ;;
-         no)  with_werror=0 ;;
-         *)   AC_MSG_ERROR(bad value ${withval} for --with-werror) ;;
-     esac],
-     [with_werror=1])
-
-werror_ok=0
-
-# Certain versions of gcc (g++) have a bug that incorrectly warns about
-# the use of anonymous name spaces even if they're closed in a single
-# translation unit.  For these versions we have to disable -Werror.
-if test $with_werror = 1; then
-   CXXFLAGS_SAVED="$CXXFLAGS"
-   CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
-   AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
-   AC_TRY_COMPILE([namespace { class Foo {}; }
-   namespace isc {class Bar {Foo foo_;};} ],,
-	[AC_MSG_RESULT(no)
-	 werror_ok=1
-	 B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
-	[AC_MSG_RESULT(yes)])
-   CXXFLAGS="$CXXFLAGS_SAVED"
-fi
-
+# (g++ only check)
 # Python 3.2 has an unused parameter in one of its headers. This
 # has been reported, but not fixed as of yet, so we check if we need
 # to set -Wno-unused-parameter.
-if test $werror_ok = 1; then
+if test "X$GXX" = "Xyes" -a $werror_ok = 1; then
 	CPPFLAGS_SAVED="$CPPFLAGS"
 	CPPFLAGS=${PYTHON_INCLUDES}
 	CXXFLAGS_SAVED="$CXXFLAGS"
@@ -370,10 +386,6 @@ if test $werror_ok = 1; then
 	CPPFLAGS="$CPPFLAGS_SAVED"
 fi
 
-fi				dnl GXX = yes
-
-AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-
 # produce PIC unless we disable shared libraries. need this for python bindings.
 if test $enable_shared != "no" -a "X$GXX" = "Xyes"; then
    B10_CXXFLAGS="$B10_CXXFLAGS -fPIC"

+ 0 - 23
doc/devel/02-dhcp.dox

@@ -57,29 +57,6 @@
  * that does not support msgq. That is useful for embedded environments.
  * It may also be useful in validation.
  *
- * @page dhcpv6 DHCPv6 Server Component
- *
- * BIND10 offers DHCPv6 server implementation. It is implemented as
- * b10-dhcp6 component. Its primary code is located in
- * isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
- * especially lib::dhcp::Pkt6, isc::dhcp::Option and
- * isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
- * functionality, i.e. it is able to receive and process incoming
- * requests and trasmit responses. However, it does not have database
- * management, so it returns only one, hardcoded lease to whoever asks
- * for it.
- *
- * DHCPv6 server component does not support relayed traffic yet, as
- * support for relay decapsulation is not implemented yet.
- *
- * DHCPv6 server component does not use BIND10 logging yet.
- *
- * @section dhcpv6Session BIND10 message queue integration
- *
- * DHCPv4 server component is now integrated with BIND10 message queue.
- * It follows the same principle as DHCPv4. See \ref dhcpv4Session for
- * details.
- *
  * @page libdhcp libdhcp++
  *
  * @section libdhcpIntro Libdhcp++ Library Introduction

+ 5 - 0
doc/devel/mainpage.dox

@@ -15,12 +15,17 @@
  * <a href="http://bind10.isc.org/">BIND10 webpage (http://bind10.isc.org)</a>
  *
  * @section DNS
+ * - Authoritative DNS (todo)
+ * - Recursive resolver (todo)
  * - @subpage DataScrubbing
  *
  * @section DHCP
  * - @subpage dhcpv4
  *   - @subpage dhcpv4Session
  * - @subpage dhcpv6
+ *   - @subpage dhcpv6-session
+ *   - @subpage dhcpv6-config-parser
+ *   - @subpage dhcpv6-config-inherit
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
  *   - @subpage libdhcpIfaceMgr

+ 89 - 10
doc/guide/bind10-guide.xml

@@ -2751,13 +2751,13 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
       <title>DHCPv4 Server Configuration</title>
       <para>
         The DHCPv4 server does not have a lease database implemented yet
-        nor any support for configuration, so every time the same set
+        nor any support for configuration, so the same set
         of configuration options (including the same fixed address)
         will be assigned every time.
       </para>
       <para>
         At this stage of development, the only way to alter the server
-        configuration is to tweak its source code. To do so, please
+        configuration is to modify its source code. To do so, please
         edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
         parameters and recompile:
         <screen>
@@ -2944,16 +2944,95 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
     <section id="dhcp6-config">
       <title>DHCPv6 Server Configuration</title>
       <para>
-        The DHCPv6 server does not have lease database implemented yet
-        or any support for configuration, so every time the same set
-        of configuration options (including the same fixed address)
-        will be assigned every time.
+        Once the server is started, it can be configured. To view the
+        current configuration, use the following command in <command>bindctl</command>:
+        <screen>
+          &gt; <userinput>config show Dhcp6</userinput></screen>
+        When starting Dhcp6 daemon for the first time, the default configuration
+        will be available. It will look similar to this:
+        <screen>
+&gt; <userinput>config show Dhcp6</userinput>
+Dhcp6/interface	         "eth0" string	(default)
+Dhcp6/renew-timer        1000   integer	(default)
+Dhcp6/rebind-timer       2000   integer	(default)
+Dhcp6/preferred-lifetime 3000   integer	(default)
+Dhcp6/valid-lifetime	 4000   integer	(default)
+Dhcp6/subnet6	         []     list    (default)</screen>
       </para>
+
       <para>
-        At this stage of development, the only way to alter server
-        configuration is to tweak its source code. To do so, please
-        edit src/bin/dhcp6/dhcp6_srv.cc file, modify the following
-        parameters and recompile:
+        To change one of the parameters, simply follow
+        the usual <command>bindctl</command> procedure. For example, to make the
+        leases longer, change their valid-lifetime parameter:
+        <screen>
+&gt; <userinput>config set Dhcp6/valid-lifetime 7200</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        Please note that most Dhcp6 parameters are of global scope
+        and apply to all defined subnets, unless they are overridden on a
+        per-subnet basis.
+      </para>
+
+      <para>
+        The essential role of DHCPv6 server is address assignment. The server
+        has to be configured with at least one subnet and one pool of dynamic
+        addresses to be managed. For example, assume that the server
+        is connected to a network segment that uses the 2001:db8:1::/64
+        prefix. The Administrator of that network has decided that addresses from range
+        2001:db8:1::1 to 2001:db8:1::ffff are going to be managed by the Dhcp6
+        server. Such a configuration can be achieved in the following way:
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::0 - 2001:db8:1::ffff" ]</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        Note that subnet is defined as a simple string, but the pool parameter
+        is actually a list of pools: for this reason, the pool definition is
+        enclosed in square brackets, even though only one range of addresses
+        is specified.</para>
+        <para>It is possible to define more than one pool in a
+        subnet: continuing the previous example, further assume that 
+        2001:db8:1:0:5::/80 should be also be managed by the server. It could be written as
+        2001:db8:1:0:5:: to 2001:db8:1::5:ffff:ffff:ffff, but typing so many 'f's
+        is cumbersome. It can be expressed more simply as 2001:db8:1:0:5::/80. Both
+        formats are supported by Dhcp6 and can be mixed in the pool list.
+        For example, one could define the following pools:
+        <screen>
+&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::1 - 2001:db8:1::ffff", "2001:db8:1:0:5::/80" ]</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        The number of pools is not limited, but for performance reasons it is recommended to
+        use as few as possible.
+      </para>
+      <para>
+         The server may be configured to serve more than one subnet. To add a second subnet,
+         use a command similar to the following:
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/subnet "2001:db8:beef::/48"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[1]/pool [ "2001:db8:beef::/48" ]</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        Arrays are counted from 0. subnet[0] refers to the subnet defined in the
+        previous example.  The <command>config add Dhcp6/subnet6</command> adds
+        another (second) subnet. It can be referred to as
+        <command>Dhcp6/subnet6[1]</command>. In this example, we allow server to
+        dynamically assign all addresses available in the whole subnet. Although
+        very wasteful, it is certainly a valid configuration to dedicate the
+        whole /48 subnet for that purpose.
+      </para>
+      <para>
+        When configuring a DHCPv6 server using prefix/length notation, please pay
+        attention to the boundary values. When specifying that the server should use
+        a given pool, it will be able to allocate also first (typically network
+        address) address from that pool. For example for pool 2001:db8::/64 the
+        2001:db8:: address may be assigned as well. If you want to avoid this,
+        please use min-max notation.
+      </para>
+
+      <para>
+        Note: Although configuration is now accepted, it is not internally used
+        by they server yet.  At this stage of development, the only way to alter
+        server configuration is to modify its source code. To do so, please edit
+        src/bin/dhcp6/dhcp6_srv.cc file, modify the following parameters and
+        recompile:
         <screen>
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const uint32_t HARDCODED_T1 = 1500; // in seconds

+ 1 - 1
src/bin/auth/auth_config.h

@@ -93,7 +93,7 @@ public:
     /// that corresponds to this derived class and prepares a new value to
     /// apply to the server.
     /// In the above example, the derived class for the identifier "param1"
-    /// would be passed an data \c Element storing an integer whose value
+    /// would be passed a data \c Element storing an integer whose value
     /// is 10, and would record that value internally;
     /// the derived class for the identifier "param2" would be passed a
     /// map element and (after parsing) convert it into some internal

+ 24 - 7
src/bin/bind10/bind10_messages.mes

@@ -167,6 +167,30 @@ so BIND 10 will now shut down. The specific error is printed.
 % BIND10_SEND_SIGKILL sending SIGKILL to %1 (PID %2)
 The boss module is sending a SIGKILL signal to the given process.
 
+% BIND10_SEND_SIGNAL_FAIL sending %1 to %2 (PID %3) failed: %4
+The boss module sent a single (either SIGTERM or SIGKILL) to a process,
+but it failed due to some system level error.  There are two major cases:
+the target process has already terminated but the boss module had sent
+the signal before it noticed the termination.  In this case an error
+message should indicate something like "no such process".  This can be
+safely ignored.  The other case is that the boss module doesn't have
+the privilege to send a signal to the process.  It can typically
+happen when the boss module started as a privileged process, spawned a
+subprocess, and then dropped the privilege.  It includes the case for
+the socket creator when the boss process runs with the -u command line
+option.  In this case, the boss module simply gives up to terminate
+the process explicitly because it's unlikely to succeed by keeping
+sending the signal.  Although the socket creator is implemented so
+that it will terminate automatically when the boss process exits
+(and that should be the case for any other future process running with
+a higher privilege), but it's recommended to check if there's any
+remaining BIND 10 process if this message is logged.  For all other
+cases, the boss module will keep sending the signal until it confirms
+all child processes terminate.  Although unlikely, this could prevent
+the boss module from exiting, just keeping sending the signals.  So,
+again, it's advisable to check if it really terminates when this
+message is logged.
+
 % BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
 The boss module is sending a SIGTERM signal to the given process.
 
@@ -274,13 +298,6 @@ During the startup process, a number of messages are exchanged between the
 Boss process and the processes it starts.  This error is output when a
 message received by the Boss process is not recognised.
 
-% BIND10_START_AS_NON_ROOT_AUTH starting b10-auth as a user, not root. This might fail.
-The authoritative server is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
 % BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
 The resolver is being started or restarted without root privileges.
 If the module needs these privileges, it may have problems starting.

+ 30 - 22
src/bin/bind10/bind10_src.py.in

@@ -546,8 +546,6 @@ class BoB:
         """
             Start the Authoritative server
         """
-        if self.uid is not None and self.__started:
-            logger.warn(BIND10_START_AS_NON_ROOT_AUTH)
         authargs = ['b10-auth']
         if self.verbose:
             authargs += ['-v']
@@ -693,32 +691,42 @@ class BoB:
         # from doing so
         if not self.nokill:
             # next try sending a SIGTERM
-            components_to_stop = list(self.components.values())
-            for component in components_to_stop:
-                logger.info(BIND10_SEND_SIGTERM, component.name(), component.pid())
-                try:
-                    component.kill()
-                except OSError:
-                    # ignore these (usually ESRCH because the child
-                    # finally exited)
-                    pass
-            # finally, send SIGKILL (unmaskable termination) until everybody dies
+            self.__kill_children(False)
+            # finally, send SIGKILL (unmaskable termination) until everybody
+            # dies
             while self.components:
                 # XXX: some delay probably useful... how much is uncertain
                 time.sleep(0.1)
                 self.reap_children()
-                components_to_stop = list(self.components.values())
-                for component in components_to_stop:
-                    logger.info(BIND10_SEND_SIGKILL, component.name(),
-                                component.pid())
-                    try:
-                        component.kill(True)
-                    except OSError:
-                        # ignore these (usually ESRCH because the child
-                        # finally exited)
-                        pass
+                self.__kill_children(True)
             logger.info(BIND10_SHUTDOWN_COMPLETE)
 
+    def __kill_children(self, forceful):
+        '''Terminate remaining subprocesses by sending a signal.
+
+        The forceful paramter will be passed Component.kill().
+        This is a dedicated subroutine of shutdown(), just to unify two
+        similar cases.
+
+        '''
+        logmsg = BIND10_SEND_SIGKILL if forceful else BIND10_SEND_SIGTERM
+        # We need to make a copy of values as the components may be modified
+        # in the loop.
+        for component in list(self.components.values()):
+            logger.info(logmsg, component.name(), component.pid())
+            try:
+                component.kill(forceful)
+            except OSError as ex:
+                # If kill() failed due to EPERM, it doesn't make sense to
+                # keep trying, so we just log the fact and forget that
+                # component.  Ignore other OSErrors (usually ESRCH because
+                # the child finally exited)
+                signame = "SIGKILL" if forceful else "SIGTERM"
+                logger.info(BIND10_SEND_SIGNAL_FAIL, signame,
+                            component.name(), component.pid(), ex)
+                if ex.errno == errno.EPERM:
+                    del self.components[component.pid()]
+
     def _get_process_exit_status(self):
         return os.waitpid(-1, os.WNOHANG)
 

+ 34 - 2
src/bin/bind10/tests/bind10_test.py.in

@@ -1181,7 +1181,7 @@ class TestBossComponents(unittest.TestCase):
         # We check somewhere else that the shutdown is actually called
         # from there (the test_kills).
 
-    def __real_test_kill(self, nokill = False):
+    def __real_test_kill(self, nokill=False, ex_on_kill=None):
         """
         Helper function that does the actual kill functionality testing.
         """
@@ -1195,8 +1195,23 @@ class TestBossComponents(unittest.TestCase):
             (anyway it is not told so). It does not die if it is killed
             the first time. It dies only when killed forcefully.
             """
+            def __init__(self):
+                # number of kill() calls, preventing infinite loop.
+                self.__call_count = 0
+
             def kill(self, forceful=False):
+                self.__call_count += 1
+                if self.__call_count > 2:
+                    raise Exception('Too many calls to ImmortalComponent.kill')
+
                 killed.append(forceful)
+                if ex_on_kill is not None:
+                    # If exception is given by the test, raise it here.
+                    # In the case of ESRCH, the process should have gone
+                    # somehow, so we clear the components.
+                    if ex_on_kill.errno == errno.ESRCH:
+                        bob.components = {}
+                    raise ex_on_kill
                 if forceful:
                     bob.components = {}
             def pid(self):
@@ -1224,7 +1239,10 @@ class TestBossComponents(unittest.TestCase):
         if nokill:
             self.assertEqual([], killed)
         else:
-            self.assertEqual([False, True], killed)
+            if ex_on_kill is not None:
+                self.assertEqual([False], killed)
+            else:
+                self.assertEqual([False, True], killed)
 
         self.assertTrue(self.__called)
 
@@ -1236,6 +1254,20 @@ class TestBossComponents(unittest.TestCase):
         """
         self.__real_test_kill()
 
+    def test_kill_fail(self):
+        """Test cases where kill() results in an exception due to OS error.
+
+        The behavior should be different for EPERM, so we test two cases.
+
+        """
+
+        ex = OSError()
+        ex.errno, ex.strerror = errno.ESRCH, 'No such process'
+        self.__real_test_kill(ex_on_kill=ex)
+
+        ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+        self.__real_test_kill(ex_on_kill=ex)
+
     def test_nokill(self):
         """
         Test that the boss *doesn't* kill components which don't want to

+ 2 - 0
src/bin/dhcp6/Makefile.am

@@ -46,6 +46,7 @@ pkglibexec_PROGRAMS = b10-dhcp6
 
 b10_dhcp6_SOURCES  = main.cc
 b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
+b10_dhcp6_SOURCES += config_parser.cc config_parser.h
 b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
 b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 
@@ -62,6 +63,7 @@ b10_dhcp6_LDADD  = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 

+ 797 - 0
src/bin/dhcp6/config_parser.cc

@@ -0,0 +1,797 @@
+// Copyright (C) 2010  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 <stdint.h>
+#include <iostream>
+#include <vector>
+#include <map>
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <asiolink/io_address.h>
+#include <cc/data.h>
+#include <config/ccsession.h>
+#include <log/logger_support.h>
+#include <dhcp/triplet.h>
+#include <dhcp/pool.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
+#include <dhcp6/config_parser.h>
+#include <dhcp6/dhcp6_log.h>
+
+using namespace std;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+/// @brief auxiliary type used for storing element name and its parser
+typedef pair<string, ConstElementPtr> ConfigPair;
+
+/// @brief a factory method that will create a parser for a given element name
+typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+
+/// @brief a collection of factories that creates parsers for specified element names
+typedef std::map<std::string, ParserFactory*> FactoryMap;
+
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<string, string> StringStorage;
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<Pool6Ptr> PoolStorage;
+
+/// @brief Global uint32 parameters that will be used as defaults.
+Uint32Storage uint32_defaults;
+
+/// @brief global string parameters that will be used as defaults.
+StringStorage string_defaults;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public DhcpConfigParser {
+public:
+
+    /// @brief Constructor
+    ///
+    /// See \ref DhcpConfigParser class for details.
+    ///
+    /// @param param_name name of the parsed parameter
+    DebugParser(const std::string& param_name)
+        :param_name_(param_name) {
+    }
+
+    /// @brief builds parameter value
+    ///
+    /// See \ref DhcpConfigParser class for details.
+    ///
+    /// @param new_config pointer to the new configuration
+    virtual void build(ConstElementPtr new_config) {
+        std::cout << "Build for token: [" << param_name_ << "] = ["
+                  << value_->str() << "]" << std::endl;
+        value_ = new_config;
+    }
+
+    /// @brief pretends to apply the configuration
+    ///
+    /// This is a method required by base class. It pretends to apply the
+    /// configuration, but in fact it only prints the parameter out.
+    ///
+    /// See \ref DhcpConfigParser class for details.
+    virtual void commit() {
+        // Debug message. The whole DebugParser class is used only for parser
+        // debugging, and is not used in production code. It is very convenient
+        // to keep it around. Please do not turn this cout into logger calls.
+        std::cout << "Commit for token: [" << param_name_ << "] = ["
+                  << value_->str() << "]" << std::endl;
+    }
+
+    /// @brief factory that constructs DebugParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new DebugParser(param_name));
+    }
+
+protected:
+    /// name of the parsed parameter
+    std::string param_name_;
+
+    /// pointer to the actual value of the parameter
+    ConstElementPtr value_;
+};
+
+/// @brief Configuration parser for uint32 parameters
+///
+/// This class is a generic parser that is able to handle any uint32 integer
+/// type. By default it stores the value in external global container
+/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref DhcpConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv6-config-inherit page.
+class Uint32Parser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor for Uint32Parser
+    /// @param param_name name of the configuration parameter being parsed
+    Uint32Parser(const std::string& param_name)
+        :storage_(&uint32_defaults), param_name_(param_name) {
+    }
+
+    /// @brief builds parameter value
+    ///
+    /// Parses configuration entry and stores it in a storage. See
+    /// \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(ConstElementPtr value) {
+        try {
+            value_ = boost::lexical_cast<uint32_t>(value->str());
+        } catch (const boost::bad_lexical_cast &) {
+            isc_throw(BadValue, "Failed to parse value " << value->str()
+                      << " as unsigned 32-bit integer.");
+        }
+        storage_->insert(pair<string, uint32_t>(param_name_, value_));
+    }
+
+    /// @brief does nothing
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers are expected to
+    /// use values stored in the storage, e.g. renew-timer for a given
+    /// subnet is stored in subnet-specific storage. It is not commited
+    /// here, but is rather used by \ref Subnet6Parser when constructing
+    /// the subnet.
+    virtual void commit() {
+    }
+
+    /// @brief factory that constructs Uint32Parser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new Uint32Parser(param_name));
+    }
+
+    /// @brief sets storage for value of this parameter
+    ///
+    /// See \ref dhcpv6-config-inherit for details.
+    ///
+    /// @param storage pointer to the storage container
+    void setStorage(Uint32Storage* storage) {
+        storage_ = storage;
+    }
+
+protected:
+    /// pointer to the storage, where parsed value will be stored
+    Uint32Storage* storage_;
+
+    /// name of the parameter to be parsed
+    std::string param_name_;
+
+    /// the actual parsed value
+    uint32_t value_;
+};
+
+/// @brief Configuration parser for string parameters
+///
+/// This class is a generic parser that is able to handle any string
+/// parameter. By default it stores the value in external global container
+/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, \ref DhcpConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv6-config-inherit page.
+class StringParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor for StringParser
+    /// @param param_name name of the configuration parameter being parsed
+    StringParser(const std::string& param_name)
+        :storage_(&string_defaults), param_name_(param_name) {
+    }
+
+    /// @brief parses parameter value
+    ///
+    /// Parses configuration entry and stored it in storage. See
+    /// \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(ConstElementPtr value) {
+        value_ = value->str();
+        boost::erase_all(value_, "\"");
+        storage_->insert(pair<string, string>(param_name_, value_));
+    }
+
+    /// @brief does nothing
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers are expected to
+    /// use values stored in the storage, e.g. renew-timer for a given
+    /// subnet is stored in subnet-specific storage. It is not commited
+    /// here, but is rather used by its parent parser when constructing
+    /// an object, e.g. the subnet.
+    virtual void commit() {
+    }
+
+    /// @brief factory that constructs StringParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new StringParser(param_name));
+    }
+
+    /// @brief sets storage for value of this parameter
+    ///
+    /// See \ref dhcpv6-config-inherit for details.
+    ///
+    /// @param storage pointer to the storage container
+    void setStorage(StringStorage* storage) {
+        storage_ = storage;
+    }
+
+protected:
+    /// pointer to the storage, where parsed value will be stored
+    StringStorage* storage_;
+
+    /// name of the parameter to be parsed
+    std::string param_name_;
+
+    /// the actual parsed value
+    std::string value_;
+};
+
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp6/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp6/interface parameter.
+class InterfaceListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    /// As this is a dedicated parser, it must be used to parse
+    /// "interface" parameter only. All other types will throw exception.
+    ///
+    /// @param param_name name of the configuration parameter being parsed
+    InterfaceListConfigParser(const std::string& param_name) {
+        if (param_name != "interface") {
+            isc_throw(NotImplemented, "Internal error. Interface configuration "
+                      "parser called for the wrong parameter: " << param_name);
+        }
+    }
+
+    /// @brief parses parameters value
+    ///
+    /// Parses configuration entry (list of parameters) and stores it in
+    /// storage. See \ref setStorage() for details.
+    ///
+    /// @param value pointer to the content of parsed values
+    virtual void build(ConstElementPtr value) {
+        BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+            interfaces_.push_back(iface->str());
+        }
+    }
+
+    /// @brief commits interfaces list configuration
+    virtual void commit() {
+        /// @todo: Implement per interface listening. Currently always listening
+        /// on all interfaces.
+    }
+
+    /// @brief factory that constructs InterfaceListConfigParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new InterfaceListConfigParser(param_name));
+    }
+
+protected:
+    /// contains list of network interfaces
+    vector<string> interfaces_;
+};
+
+/// @brief parser for pool definition
+///
+/// This parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool6 objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// As there are no default values for pool, setStorage() must be called
+/// before build(). Otherwise exception will be thrown.
+///
+/// It is useful for parsing Dhcp6/subnet6[X]/pool parameters.
+class PoolParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor.
+    PoolParser(const std::string& /*param_name*/)
+        :pools_(NULL) {
+        // ignore parameter name, it is always Dhcp6/subnet6[X]/pool
+    }
+
+    /// @brief parses the actual list
+    ///
+    /// This method parses the actual list of interfaces.
+    /// No validation is done at this stage, everything is interpreted as
+    /// interface name.
+    void build(ConstElementPtr pools_list) {
+        // setStorage() should have been called before build
+        if (!pools_) {
+            isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+                      " but pool parser asked to parse pools");
+        }
+
+        BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+
+            // That should be a single pool representation. It should contain
+            // text is form prefix/len or first - last. Note that spaces
+            // are allowed
+            string txt = text_pool->stringValue();
+
+            // first let's remove any whitespaces
+            boost::erase_all(txt, " "); // space
+            boost::erase_all(txt, "\t"); // tabulation
+
+            // Is this prefix/len notation?
+            size_t pos = txt.find("/");
+            if (pos != string::npos) {
+                IOAddress addr("::");
+                uint8_t len = 0;
+                try {
+                    addr = IOAddress(txt.substr(0, pos));
+
+                    // start with the first character after /
+                    string prefix_len = txt.substr(pos + 1);
+
+                    // It is lexical cast to int and then downcast to uint8_t.
+                    // Direct cast to uint8_t (which is really an unsigned char)
+                    // will result in interpreting the first digit as output
+                    // value and throwing exception if length is written on two
+                    // digits (because there are extra characters left over).
+
+                    // No checks for values over 128. Range correctness will
+                    // be checked in Pool6 constructor.
+                    len = boost::lexical_cast<int>(prefix_len);
+                } catch (...)  {
+                    isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+                              "definition: " << text_pool->stringValue());
+                }
+
+                Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
+                pools_->push_back(pool);
+                continue;
+            }
+
+            // Is this min-max notation?
+            pos = txt.find("-");
+            if (pos != string::npos) {
+                // using min-max notation
+                IOAddress min(txt.substr(0,pos - 1));
+                IOAddress max(txt.substr(pos + 1));
+
+                Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
+
+                pools_->push_back(pool);
+                continue;
+            }
+
+            isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+                      << text_pool->stringValue() <<
+                      ". Does not contain - (for min-max) nor / (prefix/len)");
+        }
+    }
+
+    /// @brief sets storage for value of this parameter
+    ///
+    /// See \ref dhcpv6-config-inherit for details.
+    ///
+    /// @param storage pointer to the storage container
+    void setStorage(PoolStorage* storage) {
+        pools_ = storage;
+    }
+
+    /// @brief does nothing.
+    ///
+    /// This method is required for all parser. The value itself
+    /// is not commited anywhere. Higher level parsers (for subnet) are expected
+    /// to use values stored in the storage.
+    virtual void commit() {}
+
+    /// @brief factory that constructs PoolParser objects
+    ///
+    /// @param param_name name of the parameter to be parsed
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new PoolParser(param_name));
+    }
+
+protected:
+    /// @brief pointer to the actual Pools storage
+    ///
+    /// That is typically a storage somewhere in Subnet parser
+    /// (an upper level parser).
+    PoolStorage* pools_;
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class Subnet6ConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    Subnet6ConfigParser(const std::string& ) {
+        // The parameter should always be "subnet", but we don't check here
+        // against it in case some wants to reuse this parser somewhere.
+    }
+
+    /// @brief parses parameter value
+    ///
+    /// @param subnet pointer to the content of subnet definition
+    void build(ConstElementPtr subnet) {
+
+        BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+
+            ParserPtr parser(createSubnet6ConfigParser(param.first));
+
+            // if this is an Uint32 parser, tell it to store the values
+            // in values_, rather than in global storage
+            boost::shared_ptr<Uint32Parser> uintParser =
+                boost::dynamic_pointer_cast<Uint32Parser>(parser);
+            if (uintParser) {
+                uintParser->setStorage(&uint32_values_);
+            } else {
+
+                boost::shared_ptr<StringParser> stringParser =
+                    boost::dynamic_pointer_cast<StringParser>(parser);
+                if (stringParser) {
+                    stringParser->setStorage(&string_values_);
+                } else {
+
+                    boost::shared_ptr<PoolParser> poolParser =
+                        boost::dynamic_pointer_cast<PoolParser>(parser);
+                    if (poolParser) {
+                        poolParser->setStorage(&pools_);
+                    }
+                }
+            }
+
+            parser->build(param.second);
+            parsers_.push_back(parser);
+        }
+
+        // Ok, we now have subnet parsed
+    }
+
+    /// @brief commits received configuration.
+    ///
+    /// This method does most of the configuration. Many other parsers are just
+    /// storing the values that are actually consumed here. Pool definitions
+    /// created in other parsers are used here and added to newly created Subnet6
+    /// objects. Subnet6 are then added to DHCP CfgMgr.
+    void commit() {
+
+        StringStorage::const_iterator it = string_values_.find("subnet");
+        if (it == string_values_.end()) {
+            isc_throw(Dhcp6ConfigError,
+                      "Mandatory subnet definition in subnet missing");
+        }
+        string subnet_txt = it->second;
+        boost::erase_all(subnet_txt, " ");
+        boost::erase_all(subnet_txt, "\t");
+
+        size_t pos = subnet_txt.find("/");
+        if (pos == string::npos) {
+            isc_throw(Dhcp6ConfigError,
+                      "Invalid subnet syntax (prefix/len expected):" << it->second);
+        }
+        IOAddress addr(subnet_txt.substr(0, pos));
+        uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+        Triplet<uint32_t> t1 = getParam("renew-timer");
+        Triplet<uint32_t> t2 = getParam("rebind-timer");
+        Triplet<uint32_t> pref = getParam("preferred-lifetime");
+        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 << ", pref="
+            << pref << ", valid=" << valid;
+
+        LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
+
+        Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+
+        for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
+            subnet->addPool6(*it);
+        }
+
+        CfgMgr::instance().addSubnet6(subnet);
+    }
+
+protected:
+
+    /// @brief creates parsers for entries in subnet definition
+    ///
+    /// @todo Add subnet-specific things here (e.g. subnet-specific options)
+    ///
+    /// @param config_id name od the entry
+    /// @return parser object for specified entry name
+    DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
+        FactoryMap factories;
+
+        factories.insert(pair<string, ParserFactory*>(
+                             "preferred-lifetime", Uint32Parser::Factory));
+        factories.insert(pair<string, ParserFactory*>(
+                             "valid-lifetime", Uint32Parser::Factory));
+        factories.insert(pair<string, ParserFactory*>(
+                             "renew-timer", Uint32Parser::Factory));
+        factories.insert(pair<string, ParserFactory*>(
+                             "rebind-timer", Uint32Parser::Factory));
+
+        factories.insert(pair<string, ParserFactory*>(
+                             "subnet", StringParser::Factory));
+
+        factories.insert(pair<string, ParserFactory*>(
+                             "pool", PoolParser::Factory));
+
+        FactoryMap::iterator f = factories.find(config_id);
+        if (f == factories.end()) {
+            // Used for debugging only.
+            // return new DebugParser(config_id);
+
+            isc_throw(NotImplemented,
+                      "Parser error: Subnet6 parameter not supported: "
+                      << config_id);
+        }
+        return (f->second(config_id));
+    }
+
+    /// @brief returns value for a given parameter (after using inheritance)
+    ///
+    /// This method implements inheritance. For a given parameter name, it first
+    /// checks if there is a global value for it and overwrites it with specific
+    /// value if such value was defined in subnet.
+    ///
+    /// @param name name of the parameter
+    /// @return triplet with the parameter name
+    Triplet<uint32_t> getParam(const std::string& name) {
+        uint32_t value = 0;
+        bool found = false;
+        Uint32Storage::iterator global = uint32_defaults.find(name);
+        if (global != uint32_defaults.end()) {
+            value = global->second;
+            found = true;
+        }
+
+        Uint32Storage::iterator local = uint32_values_.find(name);
+        if (local != uint32_values_.end()) {
+            value = local->second;
+            found = true;
+        }
+
+        if (found) {
+            return (Triplet<uint32_t>(value));
+        } else {
+            isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+                      << " missing (no global default and no subnet-"
+                      << "specific value)");
+        }
+    }
+
+    /// storage for subnet-specific uint32 values
+    Uint32Storage uint32_values_;
+
+    /// storage for subnet-specific integer values
+    StringStorage string_values_;
+
+    /// storage for pools belonging to this subnet
+    PoolStorage pools_;
+
+    /// parsers are stored here
+    ParserCollection parsers_;
+};
+
+/// @brief this class parses list of subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet6
+/// definitions. It iterates over all entries and creates Subnet6ConfigParser
+/// for each entry.
+class Subnets6ListConfigParser : public DhcpConfigParser {
+public:
+
+    /// @brief constructor
+    ///
+    Subnets6ListConfigParser(const std::string&) {
+        /// parameter name is ignored
+    }
+
+    /// @brief parses contents of the list
+    ///
+    /// Iterates over all entries on the list and creates Subnet6ConfigParser
+    /// for each entry.
+    ///
+    /// @param subnets_list pointer to a list of IPv6 subnets
+    void build(ConstElementPtr subnets_list) {
+
+        // No need to define FactoryMap here. There's only one type
+        // used: Subnet6ConfigParser
+
+        BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
+
+            ParserPtr parser(new Subnet6ConfigParser("subnet"));
+            parser->build(subnet);
+            subnets_.push_back(parser);
+        }
+
+    }
+
+    /// @brief commits subnets definitions.
+    ///
+    /// Iterates over all Subnet6 parsers. Each parser contains definitions
+    /// of a single subnet and its parameters and commits each subnet separately.
+    void commit() {
+        // @todo: Implement more subtle reconfiguration than toss
+        // the old one and replace with the new one.
+
+        // remove old subnets
+        CfgMgr::instance().deleteSubnets6();
+
+        BOOST_FOREACH(ParserPtr subnet, subnets_) {
+            subnet->commit();
+        }
+
+    }
+
+    /// @brief Returns Subnet6ListConfigParser object
+    /// @param param_name name of the parameter
+    /// @return Subnets6ListConfigParser object
+    static DhcpConfigParser* Factory(const std::string& param_name) {
+        return (new Subnets6ListConfigParser(param_name));
+    }
+
+    /// @brief collection of subnet parsers.
+    ParserCollection subnets_;
+};
+
+/// @brief creates global parsers
+///
+/// This method creates global parsers that parse global parameters, i.e.
+/// those that take format of Dhcp6/param1, Dhcp6/param2 and so forth.
+///
+/// @param config_id pointer to received global configuration entry
+/// @return parser for specified global DHCPv6 parameter
+DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
+    FactoryMap factories;
+
+    //
+    factories.insert(pair<string, ParserFactory*>(
+                         "preferred-lifetime", Uint32Parser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "valid-lifetime", Uint32Parser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "renew-timer", Uint32Parser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "rebind-timer", Uint32Parser::Factory));
+
+    factories.insert(pair<string, ParserFactory*>(
+                         "interface", InterfaceListConfigParser::Factory));
+    factories.insert(pair<string, ParserFactory*>(
+                         "subnet6", Subnets6ListConfigParser::Factory));
+
+    factories.insert(pair<string, ParserFactory*>(
+                         "version", StringParser::Factory));
+
+    FactoryMap::iterator f = factories.find(config_id);
+    if (f == factories.end()) {
+        // Used for debugging only.
+        // return new DebugParser(config_id);
+
+        isc_throw(NotImplemented,
+                  "Parser error: Global configuration parameter not supported: "
+                  << config_id);
+    }
+    return (f->second(config_id));
+}
+
+/// @brief configures DHCPv6 server
+///
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
+///
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - logical error (parsing was successful, but the values are invalid)
+///
+/// @param config_set a new configuration for DHCPv6 server
+/// @return answer that contains result of reconfiguration
+ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+    if (!config_set) {
+        isc_throw(Dhcp6ConfigError,
+                  "Null pointer is passed to configuration parser");
+    }
+
+    /// @todo: append most essential info here (like "2 new subnets configured")
+    string config_details;
+
+    LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
+
+    ParserCollection parsers;
+    try {
+        BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+
+            ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
+            parser->build(config_pair.second);
+            parsers.push_back(parser);
+        }
+    } catch (const isc::Exception& ex) {
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 string("Configuration parsing failed:") + ex.what());
+        return (answer);
+    } catch (...) {
+        // for things like bad_cast in boost::lexical_cast
+        ConstElementPtr answer = isc::config::createAnswer(1,
+                                 string("Configuration parsing failed"));
+    }
+
+    try {
+        BOOST_FOREACH(ParserPtr parser, parsers) {
+            parser->commit();
+        }
+    }
+    catch (const isc::Exception& ex) {
+        ConstElementPtr answer = isc::config::createAnswer(2,
+                                 string("Configuration commit failed:") + ex.what());
+        return (answer);
+    } catch (...) {
+        // for things like bad_cast in boost::lexical_cast
+        ConstElementPtr answer = isc::config::createAnswer(2,
+                                 string("Configuration commit failed"));
+    }
+
+    LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
+
+    ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+    return (answer);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 147 - 0
src/bin/dhcp6/config_parser.h

@@ -0,0 +1,147 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+
+#ifndef DHCP6_CONFIG_PARSER_H
+#define DHCP6_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+class Dhcpv6Srv;
+
+/// An exception that is thrown if an error occurs while configuring an
+/// \c Dhcpv6Srv object.
+class Dhcp6ConfigError : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+Dhcp6ConfigError(const char* file, size_t line, const char* what) :
+    isc::Exception(file, line, what) {}
+};
+
+class DhcpConfigParser {
+    ///
+    /// \name Constructors and Destructor
+    ///
+    /// Note: The copy constructor and the assignment operator are
+    /// intentionally defined as private to make it explicit that this is a
+    /// pure base class.
+    //@{
+private:
+    DhcpConfigParser(const DhcpConfigParser& source);
+    DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+    /// \brief The default constructor.
+    ///
+    /// This is intentionally defined as \c protected as this base class should
+    /// never be instantiated (except as part of a derived class).
+    DhcpConfigParser() {}
+public:
+    /// The destructor.
+    virtual ~DhcpConfigParser() {}
+    //@}
+
+    /// \brief Prepare configuration value.
+    ///
+    /// This method parses the "value part" of the configuration identifier
+    /// that corresponds to this derived class and prepares a new value to
+    /// apply to the server.
+    ///
+    /// This method must validate the given value both in terms of syntax
+    /// and semantics of the configuration, so that the server will be
+    /// validly configured at the time of \c commit().  Note: the given
+    /// configuration value is normally syntactically validated, but the
+    /// \c build() implementation must also expect invalid input.  If it
+    /// detects an error it may throw an exception of a derived class
+    /// of \c isc::Exception.
+    ///
+    /// Preparing a configuration value will often require resource
+    /// allocation.  If it fails, it may throw a corresponding standard
+    /// exception.
+    ///
+    /// This method is not expected to be called more than once in the
+    /// life of the object. Although multiple calls are not prohibited
+    /// by the interface, the behavior is undefined.
+    ///
+    /// \param config_value The configuration value for the identifier
+    /// corresponding to the derived class.
+    virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+    /// \brief Apply the prepared configuration value to the server.
+    ///
+    /// This method is expected to be exception free, and, as a consequence,
+    /// it should normally not involve resource allocation.
+    /// Typically it would simply perform exception free assignment or swap
+    /// operation on the value prepared in \c build().
+    /// In some cases, however, it may be very difficult to meet this
+    /// condition in a realistic way, while the failure case should really
+    /// be very rare.  In such a case it may throw, and, if the parser is
+    /// called via \c configureDhcp6Server(), the caller will convert the
+    /// exception as a fatal error.
+    ///
+    /// This method is expected to be called after \c build(), and only once.
+    /// The result is undefined otherwise.
+    virtual void commit() = 0;
+};
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief a collection of parsers
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+
+/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
+///
+/// This function parses configuration information stored in \c config_set
+/// and configures the \c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of \c DhcpConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function throws an exception of class \c Dhcp6ConfigError.
+/// If the given configuration requires resource allocation and it fails,
+/// a corresponding standard exception will be thrown.
+/// Other exceptions may also be thrown, depending on the implementation of
+/// the underlying derived class of \c Dhcp6ConfigError.
+/// In any case the strong guarantee is provided as described above except
+/// in the very rare cases where the \c commit() method of a parser throws
+/// an exception.  If that happens this function converts the exception
+/// into a \c FatalError exception and rethrows it.  This exception is
+/// expected to be caught at the highest level of the application to terminate
+/// the program gracefully.
+///
+/// \param server The \c Dhcpv6Srv object to be configured.
+/// \param config_set A JSON style configuration to apply to \c server.
+isc::data::ConstElementPtr
+configureDhcp6Server(Dhcpv6Srv& server,
+                     isc::data::ConstElementPtr config_set);
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP6_CONFIG_PARSER_H

+ 25 - 5
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -25,6 +25,7 @@
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>
 #include <dhcp6/spec_config.h>
+#include <dhcp6/config_parser.h>
 #include <dhcp/iface_mgr.h>
 #include <exceptions/exceptions.h>
 #include <util/buffer.h>
@@ -47,8 +48,15 @@ ConstElementPtr
 ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
               .arg(new_config->str());
-    ConstElementPtr answer = isc::config::createAnswer(0,
-                             "Thank you for sending config.");
+
+    if (server_) {
+        return (configureDhcp6Server(*server_, new_config));
+    }
+
+    // That should never happen as we install config_handler after we instantiate
+    // the server.
+    ConstElementPtr answer = isc::config::createAnswer(1,
+           "Configuration rejected, server is during startup/shutdown phase.");
     return (answer);
 }
 
@@ -86,7 +94,7 @@ void ControlledDhcpv6Srv::sessionReader(void) {
 }
 
 void ControlledDhcpv6Srv::establishSession() {
-    
+
     string specfile;
     if (getenv("B10_FROM_BUILD")) {
         specfile = string(getenv("B10_FROM_BUILD")) +
@@ -96,15 +104,27 @@ void ControlledDhcpv6Srv::establishSession() {
     }
 
     /// @todo: Check if session is not established already. Throw, if it is.
-    
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTING)
               .arg(specfile);
     cc_session_ = new Session(io_service_.get_io_service());
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
-                                          dhcp6ConfigHandler,
+                                          NULL,
                                           dhcp6CommandHandler, false);
     config_session_->start();
 
+    // We initially create ModuleCCSession() without configHandler, as
+    // the session module is too eager to send partial configuration.
+    // We want to get the full configuration, so we explicitly call
+    // getFullConfig() and then pass it to our configHandler.
+    config_session_->setConfigHandler(dhcp6ConfigHandler);
+
+    try {
+        configureDhcp6Server(*this, config_session_->getFullConfig());
+    } catch (const Dhcp6ConfigError& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
+    }
+
     /// Integrate the asynchronous I/O model of BIND 10 configuration
     /// control with the "select" model of the DHCP server.  This is
     /// fully explained in \ref dhcpv6Session.

+ 79 - 0
src/bin/dhcp6/dhcp6.dox

@@ -0,0 +1,79 @@
+/**
+ @page dhcpv6 DHCPv6 Server Component
+
+ BIND10 offers DHCPv6 server implementation. It is implemented as
+ b10-dhcp6 component. Its primary code is located in
+ isc::dhcp::Dhcpv6Srv class. It uses \ref libdhcp extensively,
+ especially lib::dhcp::Pkt6, isc::dhcp::Option and
+ isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+ functionality, i.e. it is able to receive and process incoming
+ requests and trasmit responses. However, it does not have database
+ management, so it returns only one, hardcoded lease to whoever asks
+ for it.
+
+ DHCPv6 server component does not support relayed traffic yet, as
+ support for relay decapsulation is not implemented yet.
+
+ DHCPv6 server component does not use BIND10 logging yet.
+
+ @section dhcpv6-session BIND10 message queue integration
+
+ DHCPv4 server component is now integrated with BIND10 message queue.
+ It follows the same principle as DHCPv4. See \ref dhcpv4Session for
+ details.
+
+ @section dhcpv6-config-parser Configuration Parser in DHCPv6
+
+ b10-dhcp6 component uses BIND10 cfgmgr for commands and configuration. During
+ initial configuration (See \ref
+ isc::dhcp::ControlledDhcpv6Srv::establishSession()), the configuration handler
+ callback is installed (see isc::dhcp::ControlledDhcpv6Srv::dhcp6ConfigHandler().
+ It is called every time there is a new configuration. In particular, it is
+ called every time during daemon start process. It contains a
+ isc::data::ConstElementPtr to a new configuration.  This simple handler calls
+ \ref isc::dhcp::configureDhcp6Server() method that processes received configuration.
+
+ This method iterates over list of received configuration elements and creates a
+ list of parsers for each received entry. Parser is an object that is derived
+ from a \ref isc::dhcp::Dhcp6ConfigParser class. Once a parser is created
+ (constructor), its value is set (using build() method). Once all parsers are
+ build, the configuration is then applied ("commited") and commit() method is
+ called.
+
+ All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
+ are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g. \ref
+ isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
+ some cases, e.g. subnet6 definitions, the configuration entry is not a simple
+ value, but a map or a list itself. In such case, the parser iterates over all
+ elements and creates parsers for a given scope. This process may be repeated
+ (sort of) recursively.
+
+ @section dhcpv6-config-inherit DHCPv6 Configuration Inheritance
+
+ One notable useful features of DHCP configuration is its parameter inheritance.
+ For example, renew-timer value may be specified at a global scope and it then
+ applies to all subnets. However, some subnets may have it overwritten with more
+ specific values that takes precedence over global values that are considered
+ defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
+ isc::dhcp::StringParser) implement that inheritance. By default, they store
+ values in global uint32_defaults and string_defaults storages. However, it is
+ possible to instruct them to store parsed values in more specific
+ storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
+ that has its own storage that is unique for each subnet. Finally, during commit
+ phase (commit() method), appropriate parsers can use apply parameter inheritance.
+
+ Debugging configuration parser may be confusing. Therefore there is a special
+ class called \ref isc::dhcp::DummyParser. It does not configure anything, but just
+ accepts any parameter of any type. If requested to commit configuration, it will
+ print out received parameter name and its value. This class is not currently used,
+ but it is convenient to have it every time a new parameter is added to DHCP
+ configuration. For that purpose it should be left in the code.
+
+ Parameter inheritance is done during reconfiguration phase, as reconfigurations
+ are rare, so extra logic here is not a problem. On the other hand, values of
+ those parameters may be used thousands times per second, so its use must be as
+ simple as possible. In fact, currently the code has to call Subnet6->getT1() and
+ do not implement any fancy inheritance logic.
+
+ */

+ 90 - 2
src/bin/dhcp6/dhcp6.spec

@@ -4,9 +4,97 @@
     "module_description": "DHCPv6 server daemon",
     "config_data": [
       { "item_name": "interface",
-        "item_type": "string",
+        "item_type": "list",
         "item_optional": false,
-        "item_default": "eth0"
+        "item_default": [ "all" ],
+        "list_item_spec":
+        {
+          "item_name": "interface_name",
+          "item_type": "string",
+          "item_optional": false,
+          "item_default": "all"
+        }
+      } ,
+
+      { "item_name": "renew-timer",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 1000
+      },
+
+      { "item_name": "rebind-timer",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 2000
+      },
+
+      { "item_name": "preferred-lifetime",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 3000
+      },
+
+      { "item_name": "valid-lifetime",
+        "item_type": "integer",
+        "item_optional": false,
+        "item_default": 4000
+      },
+
+      { "item_name": "subnet6",
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [],
+        "list_item_spec":
+        {
+            "item_name": "single-subnet6",
+            "item_type": "map",
+            "item_optional": false,
+            "item_default": {},
+            "map_item_spec": [
+
+                { "item_name": "subnet",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": ""
+                },
+
+                { "item_name": "renew-timer",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 1000
+                },
+
+                { "item_name": "rebind-timer",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 2000
+                },
+
+                { "item_name": "preferred-lifetime",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 3000
+                },
+
+                { "item_name": "valid-lifetime",
+                  "item_type": "integer",
+                  "item_optional": false,
+                  "item_default": 7200
+                },
+                { "item_name": "pool",
+                  "item_type": "list",
+                  "item_optional": false,
+                  "item_default": [],
+                    "list_item_spec":
+                    {
+                        "item_name": "type",
+                        "item_type": "string",
+                        "item_optional": false,
+                        "item_default": ""
+                    }
+                }
+            ]
+        }
       }
     ],
     "commands": [

+ 20 - 0
src/bin/dhcp6/dhcp6_messages.mes

@@ -109,3 +109,23 @@ processed any command-line switches and is starting.
 This is a debug message issued during the IPv6 DHCP server startup.
 It lists some information about the parameters with which the server
 is running.
+
+% DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
+This critical error message indicates that the initial DHCPv6
+configuration has failed. The server will start, but nothing will be
+served until the configuration has been corrected.
+
+% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
+This is a debug message that is issued every time the server receives a
+configuration. That happens start up and also when a server configuration
+change is committed by the administrator.
+
+% DHCP6_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified subnet.
+
+% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. it is output during server startup, and when an updated
+configuration is committed by the administrator.  Additional information
+may be provided.

+ 7 - 0
src/bin/dhcp6/dhcp6_srv.cc

@@ -42,6 +42,13 @@ const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
 const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
 
 Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
+    if (port == 0) {
+        // used for testing purposes. Some tests, e.g. configuration parser,
+        // require Dhcpv6Srv object, but they don't really need it to do
+        // anything. This speed up and simplifies the tests.
+        return;
+    }
+
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
 
     // First call to instance() will create IfaceMgr (it's a singleton)

+ 3 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -46,9 +46,11 @@ TESTS += dhcp6_unittests
 dhcp6_unittests_SOURCES  = dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += config_parser_unittest.cc
 dhcp6_unittests_SOURCES += ../dhcp6_srv.h ../dhcp6_srv.cc
 dhcp6_unittests_SOURCES += ../dhcp6_log.h ../dhcp6_log.cc
 dhcp6_unittests_SOURCES += ../ctrl_dhcp6_srv.cc
+dhcp6_unittests_SOURCES += ../config_parser.cc ../config_parser.h
 nodist_dhcp6_unittests_SOURCES = ../dhcp6_messages.h ../dhcp6_messages.cc
 
 if USE_CLANGPP
@@ -62,6 +64,7 @@ dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp6_unittests_LDADD = $(GTEST_LDADD)
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la

+ 243 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -0,0 +1,243 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <dhcp6/dhcp6_srv.h>
+#include <dhcp6/config_parser.h>
+#include <config/ccsession.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+class Dhcp6ParserTest : public ::testing::Test {
+public:
+    Dhcp6ParserTest()
+    :rcode_(-1) {
+        // Open port 0 means to not do anything at all. We don't want to
+        // deal with sockets here, just check if configuration handling
+        // is sane.
+        srv_ = new Dhcpv6Srv(0);
+    }
+
+    ~Dhcp6ParserTest() {
+        delete srv_;
+    };
+
+    Dhcpv6Srv* srv_;
+
+    int rcode_;
+    ConstElementPtr comment_;
+};
+
+// Goal of this test is a verification if a very simple config update
+// with just a bumped version number. That's the simplest possible
+// config update.
+TEST_F(Dhcp6ParserTest, version) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{\"version\": 0}")));
+
+    // returned value must be 0 (configuration accepted)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+}
+
+/// The goal of this test is to verify that the code accepts only
+/// valid commands and malformed or unsupported parameters are rejected.
+TEST_F(Dhcp6ParserTest, bogus_command) {
+
+    ConstElementPtr x;
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{\"bogus\": 5}")));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(1, rcode_);
+}
+
+/// The goal of this test is to verify if wrongly defined subnet will
+/// be rejected. Properly defined subnet must include at least one
+/// pool definition.
+TEST_F(Dhcp6ParserTest, empty_subnet) {
+
+    ConstElementPtr status;
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_,
+                    Element::fromJSON("{ \"interface\": [ \"all\" ],"
+                                      "\"preferred-lifetime\": 3000,"
+                                      "\"rebind-timer\": 2000, "
+                                      "\"renew-timer\": 1000, "
+                                      "\"subnet6\": [  ], "
+                                      "\"valid-lifetime\": 4000 }")));
+
+    // returned value should be 0 (success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+}
+
+/// The goal of this test is to verify if defined subnet uses global
+/// parameter timer definitions.
+TEST_F(Dhcp6ParserTest, subnet_global_defaults) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    // Now check if the configuration was indeed handled and we have
+    // expected pool configured.
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(3000, subnet->getPreferred());
+    EXPECT_EQ(4000, subnet->getValid());
+}
+
+// This test checks if it is possible to override global values
+// on a per subnet basis.
+TEST_F(Dhcp6ParserTest, subnet_local) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"renew-timer\": 1, "
+        "    \"rebind-timer\": 2, "
+        "    \"preferred-lifetime\": 3,"
+        "    \"valid-lifetime\": 4,"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+
+    // returned value should be 0 (configuration success)
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1, subnet->getT1());
+    EXPECT_EQ(2, subnet->getT2());
+    EXPECT_EQ(3, subnet->getPreferred());
+    EXPECT_EQ(4, subnet->getValid());
+}
+
+// Test verifies that a subnet with pool values that do not belong to that
+// pool are rejected.
+TEST_F(Dhcp6ParserTest, pool_out_of_subnet) {
+
+    ConstElementPtr status;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"4001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 2 (values error)
+    // as the pool does not belong to that subnet
+    ASSERT_TRUE(status);
+    comment_ = parseAnswer(rcode_, status);
+    EXPECT_EQ(2, rcode_);
+}
+
+// Goal of this test is to verify if pools can be defined
+// using prefix/length notation. There is no separate test for min-max
+// notation as it was tested in several previous tests.
+TEST_F(Dhcp6ParserTest, pool_prefix_len) {
+
+    ConstElementPtr x;
+
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"valid-lifetime\": 4000 }";
+    cout << config << endl;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+
+    // returned value must be 1 (configuration parse error)
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    EXPECT_EQ(0, rcode_);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ(1000, subnet->getT1());
+    EXPECT_EQ(2000, subnet->getT2());
+    EXPECT_EQ(3000, subnet->getPreferred());
+    EXPECT_EQ(4000, subnet->getValid());
+}
+
+};

+ 8 - 8
src/lib/cc/data.cc

@@ -57,32 +57,32 @@ Element::toWire(std::ostream& ss) const {
 }
 
 bool
-Element::getValue(long int&) {
+Element::getValue(long int&) const {
     return (false);
 }
 
 bool
-Element::getValue(double&) {
+Element::getValue(double&) const {
     return (false);
 }
 
 bool
-Element::getValue(bool&) {
+Element::getValue(bool&) const {
     return (false);
 }
 
 bool
-Element::getValue(std::string&) {
+Element::getValue(std::string&) const {
     return (false);
 }
 
 bool
-Element::getValue(std::vector<ConstElementPtr>&) {
+Element::getValue(std::vector<ConstElementPtr>&) const {
     return (false);
 }
 
 bool
-Element::getValue(std::map<std::string, ConstElementPtr>&) {
+Element::getValue(std::map<std::string, ConstElementPtr>&) const {
     return (false);
 }
 
@@ -167,7 +167,7 @@ Element::find(const std::string&) const {
 }
 
 bool
-Element::find(const std::string&, ConstElementPtr) const {
+Element::find(const std::string&, ConstElementPtr&) const {
     return (false);
 }
 
@@ -812,7 +812,7 @@ MapElement::set(const std::string& key, ConstElementPtr value) {
 }
 
 bool
-MapElement::find(const std::string& id, ConstElementPtr t) const {
+MapElement::find(const std::string& id, ConstElementPtr& t) const {
     try {
         ConstElementPtr p = find(id);
         if (p) {

+ 22 - 22
src/lib/cc/data.h

@@ -71,7 +71,7 @@ public:
 /// the type in question.
 ///
 class Element {
-    
+
 private:
     // technically the type could be omitted; is it useful?
     // should we remove it or replace it with a pure virtual
@@ -112,7 +112,7 @@ public:
     /// \returns true if the other ElementPtr has the same type and
     ///          value
     virtual bool equals(const Element& other) const = 0;
-    
+
     /// Converts the Element to JSON format and appends it to
     /// the given stringstream.
     virtual void toJSON(std::ostream& ss) const = 0;
@@ -152,12 +152,12 @@ public:
     /// data to the given reference and returning true
     ///
     //@{
-    virtual bool getValue(long int& t);
-    virtual bool getValue(double& t);
-    virtual bool getValue(bool& t);
-    virtual bool getValue(std::string& t);
-    virtual bool getValue(std::vector<ConstElementPtr>& t);
-    virtual bool getValue(std::map<std::string, ConstElementPtr>& t);
+    virtual bool getValue(long int& t) const;
+    virtual bool getValue(double& t) const;
+    virtual bool getValue(bool& t) const;
+    virtual bool getValue(std::string& t) const;
+    virtual bool getValue(std::vector<ConstElementPtr>& t) const;
+    virtual bool getValue(std::map<std::string, ConstElementPtr>& t) const;
     //@}
 
     ///
@@ -209,7 +209,7 @@ public:
     virtual size_t size() const;
     //@}
 
-    
+
     /// \name MapElement functions
     ///
     /// \brief If the Element on which these functions are called are not
@@ -253,12 +253,12 @@ public:
     /// \param identifier The identifier of the element to find
     /// \param t Reference to store the resulting ElementPtr, if found.
     /// \return true if the element was found, false if not.
-    virtual bool find(const std::string& identifier, ConstElementPtr t) const;
+    virtual bool find(const std::string& identifier, ConstElementPtr& t) const;
     //@}
 
 
     /// \name Factory functions
-    
+
     // TODO: should we move all factory functions to a different class
     // so as not to burden the Element base with too many functions?
     // and/or perhaps even to a separate header?
@@ -349,7 +349,7 @@ public:
     /// These function pparse the wireformat at the given stringstream
     /// (of the given length). If there is a parse error an exception
     /// of the type isc::cc::DecodeError is raised.
-    
+
     //@{
     /// Creates an Element from the wire format in the given
     /// stringstream of the given length.
@@ -378,7 +378,7 @@ public:
     IntElement(long int v) : Element(integer), i(v) { }
     long int intValue() const { return (i); }
     using Element::getValue;
-    bool getValue(long int& t) { t = i; return (true); }
+    bool getValue(long int& t) const { t = i; return (true); }
     using Element::setValue;
     bool setValue(const long int v) { i = v; return (true); }
     void toJSON(std::ostream& ss) const;
@@ -392,7 +392,7 @@ public:
     DoubleElement(double v) : Element(real), d(v) {};
     double doubleValue() const { return (d); }
     using Element::getValue;
-    bool getValue(double& t) { t = d; return (true); }
+    bool getValue(double& t) const { t = d; return (true); }
     using Element::setValue;
     bool setValue(const double v) { d = v; return (true); }
     void toJSON(std::ostream& ss) const;
@@ -406,7 +406,7 @@ public:
     BoolElement(const bool v) : Element(boolean), b(v) {};
     bool boolValue() const { return (b); }
     using Element::getValue;
-    bool getValue(bool& t) { t = b; return (true); }
+    bool getValue(bool& t) const { t = b; return (true); }
     using Element::setValue;
     bool setValue(const bool v) { b = v; return (true); }
     void toJSON(std::ostream& ss) const;
@@ -427,7 +427,7 @@ public:
     StringElement(std::string v) : Element(string), s(v) {};
     std::string stringValue() const { return (s); }
     using Element::getValue;
-    bool getValue(std::string& t) { t = s; return (true); }
+    bool getValue(std::string& t) const { t = s; return (true); }
     using Element::setValue;
     bool setValue(const std::string& v) { s = v; return (true); }
     void toJSON(std::ostream& ss) const;
@@ -441,7 +441,7 @@ public:
     ListElement() : Element(list) {}
     const std::vector<ConstElementPtr>& listValue() const { return (l); }
     using Element::getValue;
-    bool getValue(std::vector<ConstElementPtr>& t) {
+    bool getValue(std::vector<ConstElementPtr>& t) const {
         t = l;
         return (true);
     }
@@ -474,7 +474,7 @@ public:
         return (m);
     }
     using Element::getValue;
-    bool getValue(std::map<std::string, ConstElementPtr>& t) {
+    bool getValue(std::map<std::string, ConstElementPtr>& t) const {
         t = m;
         return (true);
     }
@@ -495,7 +495,7 @@ public:
         return (m.find(s) != m.end());
     }
     void toJSON(std::ostream& ss) const;
-    
+
     // we should name the two finds better...
     // find the element at id; raises TypeError if one of the
     // elements at path except the one we're looking for is not a
@@ -507,7 +507,7 @@ public:
     // returns true if found, or false if not found (either because
     // it doesnt exist or one of the elements in the path is not
     // a MapElement)
-    bool find(const std::string& id, ConstElementPtr t) const;
+    bool find(const std::string& id, ConstElementPtr& t) const;
 
     bool equals(const Element& other) const;
 };
@@ -569,6 +569,6 @@ bool operator!=(const Element& a, const Element& b);
 } }
 #endif // _ISC_DATA_H
 
-// Local Variables: 
+// Local Variables:
 // mode: c++
-// End: 
+// End:

+ 197 - 52
src/lib/cc/tests/data_unittests.cc

@@ -112,7 +112,7 @@ TEST(Element, from_and_to_json) {
         std::string s = std::string(pe.what());
         EXPECT_EQ("String expected in <string>:1:3", s);
     }
-    
+
     sv.clear();
     sv.push_back("{1}");
     //ElementPtr ep = Element::fromJSON("\"aaa\nbbb\"err");
@@ -172,14 +172,14 @@ TEST(Element, from_and_to_json) {
 
 }
 
-TEST(Element, create_and_value_throws) {
-    // this test checks whether elements throw exceptions if the
-    // incorrect type is requested
-    ElementPtr el;
+template <typename T>
+void
+testGetValueInt() {
+    T el;
     long int i;
     double d;
     bool b;
-    std::string s("asdf");
+    std::string s;
     std::vector<ConstElementPtr> v;
     std::map<std::string, ConstElementPtr> m;
 
@@ -196,27 +196,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(i, 1);
-    i = 2;
-    EXPECT_TRUE(el->setValue(i));
-    EXPECT_EQ(2, el->intValue());
-    EXPECT_FALSE(el->setValue(d));
-    EXPECT_FALSE(el->setValue(b));
-    EXPECT_FALSE(el->setValue(s));
-    EXPECT_FALSE(el->setValue(v));
-    EXPECT_FALSE(el->setValue(m));
-    EXPECT_THROW(el->get(1), TypeError);
-    EXPECT_THROW(el->set(1, el), TypeError);
-    EXPECT_THROW(el->add(el), TypeError);
-    EXPECT_THROW(el->remove(1), TypeError);
-    EXPECT_THROW(el->size(), TypeError);
-    EXPECT_THROW(el->get("foo"), TypeError);
-    EXPECT_THROW(el->set("foo", el), TypeError);
-    EXPECT_THROW(el->remove("foo"), TypeError);
-    EXPECT_THROW(el->contains("foo"), TypeError);
-    ConstElementPtr tmp;
-    EXPECT_FALSE(el->find("foo", tmp));
-    
+    EXPECT_EQ(1, i);
+}
+
+template <typename T>
+void
+testGetValueDouble() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
     el = Element::create(1.1);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -231,15 +223,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(d, 1.1);
-    d = 2.2;
-    EXPECT_TRUE(el->setValue(d));
-    EXPECT_EQ(2.2, el->doubleValue());
-    EXPECT_FALSE(el->setValue(i));
-    EXPECT_FALSE(el->setValue(b));
-    EXPECT_FALSE(el->setValue(s));
-    EXPECT_FALSE(el->setValue(v));
-    EXPECT_FALSE(el->setValue(m));
+    EXPECT_EQ(1.1, d);
+}
+
+template <typename T>
+void
+testGetValueBool() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
     el = Element::create(true);
     EXPECT_THROW(el->intValue(), TypeError);
@@ -254,10 +250,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(b, true);
-    b = false;
-    EXPECT_TRUE(el->setValue(b));
-    EXPECT_FALSE(el->boolValue());
+    EXPECT_EQ(true, b);
+}
+
+template <typename T>
+void
+testGetValueString() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
     el = Element::create("foo");
     EXPECT_THROW(el->intValue(), TypeError);
@@ -272,10 +277,19 @@ TEST(Element, create_and_value_throws) {
     EXPECT_TRUE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
-    EXPECT_EQ(s, "foo");
-    s = "bar";
-    EXPECT_TRUE(el->setValue(s));
-    EXPECT_EQ("bar", el->stringValue());
+    EXPECT_EQ("foo", s);
+}
+
+template <typename T>
+void
+testGetValueList() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
     el = Element::createList();
     EXPECT_THROW(el->intValue(), TypeError);
@@ -291,9 +305,18 @@ TEST(Element, create_and_value_throws) {
     EXPECT_TRUE(el->getValue(v));
     EXPECT_FALSE(el->getValue(m));
     EXPECT_EQ("[  ]", el->str());
-    v.push_back(Element::create(1));
-    EXPECT_TRUE(el->setValue(v));
-    EXPECT_EQ("[ 1 ]", el->str());
+}
+
+template <typename T>
+void
+testGetValueMap() {
+    T el;
+    long int i;
+    double d;
+    bool b;
+    std::string s;
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
 
     el = Element::createMap();
     EXPECT_THROW(el->intValue(), TypeError);
@@ -308,7 +331,128 @@ TEST(Element, create_and_value_throws) {
     EXPECT_FALSE(el->getValue(s));
     EXPECT_FALSE(el->getValue(v));
     EXPECT_TRUE(el->getValue(m));
+    EXPECT_EQ("{  }", el->str());
+}
+
+TEST(Element, create_and_value_throws) {
+    // this test checks whether elements throw exceptions if the
+    // incorrect type is requested
+    ElementPtr el;
+    ConstElementPtr cel;
+    long int i = 0;
+    double d = 0.0;
+    bool b = false;
+    std::string s("asdf");
+    std::vector<ConstElementPtr> v;
+    std::map<std::string, ConstElementPtr> m;
+    ConstElementPtr tmp;
+
+    testGetValueInt<ElementPtr>();
+    testGetValueInt<ConstElementPtr>();
+
+    el = Element::create(1);
+    i = 2;
+    EXPECT_TRUE(el->setValue(i));
+    EXPECT_EQ(2, el->intValue());
+    EXPECT_FALSE(el->setValue(d));
+    EXPECT_FALSE(el->setValue(b));
+    EXPECT_FALSE(el->setValue(s));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueDouble<ElementPtr>();
+    testGetValueDouble<ConstElementPtr>();
+
+    el = Element::create(1.1);
+    d = 2.2;
+    EXPECT_TRUE(el->setValue(d));
+    EXPECT_EQ(2.2, el->doubleValue());
+    EXPECT_FALSE(el->setValue(i));
+    EXPECT_FALSE(el->setValue(b));
+    EXPECT_FALSE(el->setValue(s));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueBool<ElementPtr>();
+    testGetValueBool<ConstElementPtr>();
 
+    el = Element::create(true);
+    b = false;
+    EXPECT_TRUE(el->setValue(b));
+    EXPECT_FALSE(el->boolValue());
+    EXPECT_FALSE(el->setValue(i));
+    EXPECT_FALSE(el->setValue(d));
+    EXPECT_FALSE(el->setValue(s));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueString<ElementPtr>();
+    testGetValueString<ConstElementPtr>();
+
+    el = Element::create("foo");
+    s = "bar";
+    EXPECT_TRUE(el->setValue(s));
+    EXPECT_EQ("bar", el->stringValue());
+    EXPECT_FALSE(el->setValue(i));
+    EXPECT_FALSE(el->setValue(b));
+    EXPECT_FALSE(el->setValue(d));
+    EXPECT_FALSE(el->setValue(v));
+    EXPECT_FALSE(el->setValue(m));
+    EXPECT_THROW(el->get(1), TypeError);
+    EXPECT_THROW(el->set(1, el), TypeError);
+    EXPECT_THROW(el->add(el), TypeError);
+    EXPECT_THROW(el->remove(1), TypeError);
+    EXPECT_THROW(el->size(), TypeError);
+    EXPECT_THROW(el->get("foo"), TypeError);
+    EXPECT_THROW(el->set("foo", el), TypeError);
+    EXPECT_THROW(el->remove("foo"), TypeError);
+    EXPECT_THROW(el->contains("foo"), TypeError);
+    EXPECT_FALSE(el->find("foo", tmp));
+
+    testGetValueList<ElementPtr>();
+    testGetValueList<ConstElementPtr>();
+
+    el = Element::createList();
+    v.push_back(Element::create(1));
+    EXPECT_TRUE(el->setValue(v));
+    EXPECT_EQ("[ 1 ]", el->str());
+
+    testGetValueMap<ElementPtr>();
+    testGetValueMap<ConstElementPtr>();
+
+    el = Element::createMap();
+    EXPECT_NO_THROW(el->set("foo", Element::create("bar")));
+    EXPECT_EQ("{ \"foo\": \"bar\" }", el->str());
 }
 
 // Helper for escape check; it puts the given string in a StringElement,
@@ -382,7 +526,7 @@ TEST(Element, MapElement) {
     // this function checks the specific functions for ListElements
     ElementPtr el = Element::fromJSON("{ \"name\": \"foo\", \"value1\": \"bar\", \"value2\": { \"number\": 42 } }");
     ConstElementPtr el2;
-    
+
     EXPECT_EQ(el->get("name")->stringValue(), "foo");
     EXPECT_EQ(el->get("value2")->getType(), Element::map);
 
@@ -396,11 +540,12 @@ TEST(Element, MapElement) {
 
     EXPECT_EQ(el->find("value2/number")->intValue(), 42);
     EXPECT_TRUE(isNull(el->find("value2/nothing/")));
-   
+
     EXPECT_EQ(el->find("value1")->stringValue(), "bar");
     EXPECT_EQ(el->find("value1/")->stringValue(), "bar");
-    
+
     EXPECT_TRUE(el->find("value1", el2));
+    EXPECT_EQ("bar", el2->stringValue());
     EXPECT_FALSE(el->find("name/error", el2));
 
     // A map element whose (only) element has the maximum length of tag.
@@ -410,7 +555,7 @@ TEST(Element, MapElement) {
                        "9123456789abcdefa123456789abcdefb123456789abcdef"
                        "c123456789abcdefd123456789abcdefe123456789abcdef"
                        "f123456789abcde");
-    
+
     EXPECT_EQ(255, long_maptag.length()); // check prerequisite
     el = Element::fromJSON("{ \"" + long_maptag + "\": \"bar\"}");
     EXPECT_EQ("bar", el->find(long_maptag)->stringValue());
@@ -689,7 +834,7 @@ TEST(Element, merge) {
     c = Element::fromJSON("{ \"a\": { \"b\": \"c\" } }");
     merge(b, a);
     EXPECT_EQ(*b, *c);
-    
+
     // And some tests with multiple values
     a = Element::fromJSON("{ \"a\": 1, \"b\": true, \"c\": null }");
     b = Element::fromJSON("{ \"a\": 1, \"b\": null, \"c\": \"a string\" }");

+ 1 - 1
src/lib/datasrc/memory_datasrc_link.cc

@@ -129,7 +129,7 @@ checkConfig(ConstElementPtr config, ElementPtr errors) {
                 result = false;
             } else {
                 try {
-                    RRClass rrc(config->get("class")->stringValue());
+                    RRClass(config->get("class")->stringValue());
                 } catch (const isc::Exception& rrce) {
                     addError(errors,
                              "Error parsing class config for memory backend: " +

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

@@ -16,6 +16,7 @@ CLEANFILES = *.gcno *.gcda
 lib_LTLIBRARIES = libb10-dhcp++.la libb10-dhcpsrv.la
 libb10_dhcp___la_SOURCES  =
 libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
+libb10_dhcp___la_SOURCES += lease_mgr.cc lease_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
 libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
 libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
@@ -28,6 +29,7 @@ libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
 libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
 libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
 libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += duid.cc duid.h
 
 libb10_dhcpsrv_la_SOURCES  = cfgmgr.cc cfgmgr.h
 libb10_dhcpsrv_la_SOURCES += pool.cc pool.h

+ 95 - 10
src/lib/dhcp/addr_utilities.cc

@@ -13,17 +13,39 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <string.h>
+#include <exceptions/exceptions.h>
 #include <dhcp/addr_utilities.h>
 
 using namespace isc::asiolink;
 
-namespace isc {
-namespace dhcp {
-
-isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+namespace {
+
+/// @brief mask used for first/last address calculation in a IPv4 prefix
+///
+/// Using a static mask is faster than calculating it dynamically every time.
+const uint32_t bitMask4[] = { 0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
+                              0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
+                              0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
+                              0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
+                              0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
+                              0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
+                              0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
+                              0x0000000f, 0x00000007, 0x00000003, 0x00000001,
+                              0x00000000 };
+
+/// @brief mask used for first/last address calculation in a IPv6 prefix
+const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
+
+/// @brief calculates the first IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix
+/// @param len prefix length
+isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
                                             uint8_t len) {
 
-    const static uint8_t bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
     uint8_t packed[V6ADDRESS_LEN];
 
     // First we copy the whole address as 16 bytes.
@@ -36,7 +58,7 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
 
         // Get the appropriate mask. It has relevant bits (those that should
         // stay) set and irrelevant (those that should be wiped) cleared.
-        uint8_t mask = bitMask[len % 8];
+        uint8_t mask = bitMask6[len % 8];
 
         // Let's leave only whatever the mask says should not be cleared.
         packed[len / 8] = packed[len / 8] & mask;
@@ -55,10 +77,50 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 
-isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+/// @brief calculates the first IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix
+/// @param len netmask length (0-32)
+isc::asiolink::IOAddress firstAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
+                                            uint8_t len) {
+    uint32_t addr = prefix;
+    if (len > 32) {
+        isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+    }
+
+    return (IOAddress(addr & (~bitMask4[len])));
+}
+
+/// @brief calculates the last IPv4 address in a IPv4 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use firstAddrInPrefix() instead.
+///
+/// @param prefix IPv4 prefix that we calculate first address for
+/// @param len netmask length (0-32)
+isc::asiolink::IOAddress lastAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
+                                           uint8_t len) {
+    uint32_t addr = prefix;
+    if (len>32) {
+        isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
+    }
+
+    return (IOAddress(addr | bitMask4[len]));
+}
+
+/// @brief calculates the last IPv6 address in a IPv6 prefix
+///
+/// Note: This is a private function. Do not use it directly.
+/// Please use lastAddrInPrefix() instead.
+///
+/// @param prefix IPv6 prefix that we calculate first address for
+/// @param len netmask length (0-128)
+isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
                                            uint8_t len) {
 
-    const static uint8_t bitMask[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
     uint8_t packed[V6ADDRESS_LEN];
 
     // First we copy the whole address as 16 bytes.
@@ -70,10 +132,10 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
     if (len % 8 != 0) {
         // Get the appropriate mask. It has relevant bits (those that should
         // stay) set and irrelevant (those that should be set to 1) cleared.
-        uint8_t mask = bitMask[len % 8];
+        uint8_t mask = bitMask6[len % 8];
 
         // Let's set those irrelevant bits with 1. It would be perhaps
-        // easier to not use negation here and invert bitMask content. However,
+        // easier to not use negation here and invert bitMask6 content. However,
         // with this approach, we can use the same mask in first and last
         // address calculations.
         packed[len / 8] = packed[len / 8] | ~mask;
@@ -92,5 +154,28 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
     return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
 }
 
+}; // end of anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+                                            uint8_t len) {
+    if (prefix.getFamily() == AF_INET) {
+        return firstAddrInPrefix4(prefix, len);
+    } else {
+        return firstAddrInPrefix6(prefix, len);
+    }
+}
+
+isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
+                                           uint8_t len) {
+    if (prefix.getFamily() == AF_INET) {
+        return lastAddrInPrefix4(prefix, len);
+    } else {
+        return lastAddrInPrefix6(prefix, len);
+    }
+}
+
 };
 };

+ 33 - 0
src/lib/dhcp/cfgmgr.cc

@@ -68,6 +68,39 @@ void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
     subnets6_.push_back(subnet);
 }
 
+Subnet4Ptr
+CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
+
+    // If there's only one subnet configured, let's just use it
+    // The idea is to keep small deployments easy. In a small network - one
+    // router that also runs DHCPv6 server. Users specifies a single pool and
+    // expects it to just work. Without this, the server would complain that it
+    // doesn't have IP address on its interfaces that matches that
+    // configuration. Such requirement makes sense in IPv4, but not in IPv6.
+    // The server does not need to have a global address (using just link-local
+    // is ok for DHCPv6 server) from the pool it serves.
+    if (subnets4_.size() == 1) {
+        return (subnets4_[0]);
+    }
+
+    // If there is more than one, we need to choose the proper one
+    for (Subnet4Collection::iterator subnet = subnets4_.begin();
+         subnet != subnets4_.end(); ++subnet) {
+        if ((*subnet)->inRange(hint)) {
+            return (*subnet);
+        }
+    }
+
+    // sorry, we don't support that subnet
+    return (Subnet4Ptr());
+}
+
+void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
+    /// @todo: Check that this new subnet does not cross boundaries of any
+    /// other already defined subnet.
+    subnets4_.push_back(subnet);
+}
+
 CfgMgr::CfgMgr() {
 }
 

+ 43 - 4
src/lib/dhcp/cfgmgr.h

@@ -72,7 +72,7 @@ public:
     /// accessing it.
     static CfgMgr& instance();
 
-    /// @brief get subnet by address
+    /// @brief get IPv6 subnet by address
     ///
     /// Finds a matching subnet, based on an address. This can be used
     /// in two cases: when trying to find an appropriate lease based on
@@ -83,7 +83,7 @@ public:
     /// @param hint an address that belongs to a searched subnet
     Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
 
-    /// @brief get subnet by interface-id
+    /// @brief get IPv6 subnet by interface-id
     ///
     /// Another possibility to find a subnet is based on interface-id.
     ///
@@ -91,13 +91,44 @@ public:
     /// @todo This method is not currently supported.
     Subnet6Ptr getSubnet6(OptionPtr interface_id);
 
-    /// @brief adds a subnet6
+    /// @brief adds an IPv6 subnet
     void addSubnet6(const Subnet6Ptr& subnet);
 
     /// @todo: Add subnet6 removal routines. Currently it is not possible
     /// to remove subnets. The only case where subnet6 removal would be
     /// needed is a dynamic server reconfiguration - a use case that is not
     /// planned to be supported any time soon.
+
+    /// @brief removes all subnets
+    ///
+    /// This method removes all existing subnets. It is used during
+    /// reconfiguration - old configuration is wiped and new definitions
+    /// are used to recreate subnets.
+    ///
+    /// @todo Implement more intelligent approach. Note that comparison
+    /// between old and new configuration is tricky. For example: is
+    /// 2000::/64 and 2000::/48 the same subnet or is it something
+    /// completely new?
+    void deleteSubnets6() {
+        subnets6_.clear();
+    }
+
+    /// @brief get IPv4 subnet by address
+    ///
+    /// Finds a matching subnet, based on an address. This can be used
+    /// in two cases: when trying to find an appropriate lease based on
+    /// a) relay link address (that must be the address that is on link)
+    /// b) our global address on the interface the message was received on
+    ///    (for directly connected clients)
+    ///
+    /// @param hint an address that belongs to a searched subnet
+    Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
+
+    /// @brief adds a subnet4
+    void addSubnet4(const Subnet4Ptr& subnet);
+
+    /// @brief removes all IPv4 subnets
+    void removeSubnets4();
 protected:
 
     /// @brief Protected constructor.
@@ -111,13 +142,21 @@ protected:
     /// @brief virtual desctructor
     virtual ~CfgMgr();
 
-    /// @brief a container for Subnet6
+    /// @brief a container for IPv6 subnets.
     ///
     /// That is a simple vector of pointers. It does not make much sense to
     /// optimize access time (e.g. using a map), because typical search
     /// pattern will use calling inRange() method on each subnet until
     /// a match is found.
     Subnet6Collection subnets6_;
+
+    /// @brief a container for IPv4 subnets.
+    ///
+    /// That is a simple vector of pointers. It does not make much sense to
+    /// optimize access time (e.g. using a map), because typical search
+    /// pattern will use calling inRange() method on each subnet until
+    /// a match is found.
+    Subnet4Collection subnets4_;
 };
 
 } // namespace isc::dhcp

+ 90 - 0
src/lib/dhcp/duid.cc

@@ -0,0 +1,90 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <vector>
+#include <exceptions/exceptions.h>
+#include <stdint.h>
+#include <util/io_utilities.h>
+#include <dhcp/duid.h>
+
+namespace isc {
+namespace dhcp {
+
+DUID::DUID(const std::vector<uint8_t>& duid) {
+    if (duid.size() > MAX_DUID_LEN) {
+        isc_throw(OutOfRange, "DUID too large");
+    } else {
+        duid_ = duid;
+    }
+}
+
+DUID::DUID(const uint8_t * data, size_t len) {
+    if (len > MAX_DUID_LEN) {
+        isc_throw(OutOfRange, "DUID too large");
+    }
+
+    duid_ = std::vector<uint8_t>(data, data + len);
+}
+
+const std::vector<uint8_t> DUID::getDuid() const {
+    return (duid_);
+}
+
+DUID::DUIDType DUID::getType() const {
+    if (duid_.size() < 2) {
+        return (DUID_UNKNOWN);
+    }
+    uint16_t type = (duid_[0] << 8) + duid_[1];
+    if (type < DUID_MAX) {
+        return (static_cast<DUID::DUIDType>(type));
+    } else {
+        return (DUID_UNKNOWN);
+    }
+}
+
+bool DUID::operator == (const DUID& other) const {
+    return (this->duid_ == other.duid_);
+}
+
+bool DUID::operator != (const DUID& other) const {
+    return (this->duid_ != other.duid_);
+}
+
+/// constructor based on vector<uint8_t>
+ClientId::ClientId(const std::vector<uint8_t>& clientid)
+    :DUID(clientid) {
+}
+
+/// constructor based on C-style data
+ClientId::ClientId(const uint8_t *clientid, size_t len)
+    :DUID(clientid, len) {
+}
+
+/// @brief returns a copy of client-id data
+const std::vector<uint8_t> ClientId::getClientId() const {
+    return (duid_);
+}
+
+// compares two client-ids
+bool ClientId::operator == (const ClientId& other) const {
+    return (this->duid_ == other.duid_);
+}
+
+// compares two client-ids
+bool ClientId::operator != (const ClientId& other) const {
+    return (this->duid_ != other.duid_);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 98 - 0
src/lib/dhcp/duid.h

@@ -0,0 +1,98 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <stdint.h>
+#include <unistd.h>
+#include <vector>
+#include <asiolink/io_address.h>
+
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Holds DUID (DHCPv6 Unique Identifier)
+///
+/// This class holds DUID, that is used in client-id, server-id and
+/// several other options. It is used to identify DHCPv6 entity.
+class DUID {
+ public:
+    /// @brief maximum duid size
+    /// As defined in RFC3315, section 9.1
+    static const size_t MAX_DUID_LEN = 128;
+
+    /// @brief specifies DUID type
+    typedef enum {
+        DUID_UNKNOWN = 0, ///< invalid/unknown type
+        DUID_LLT = 1,     ///< link-layer + time, see RFC3315, section 9.2
+        DUID_EN = 2,      ///< enterprise-id, see RFC3315, section 9.3
+        DUID_LL = 3,      ///< link-layer, see RFC3315, section 9.4
+        DUID_UUID = 4,    ///< UUID, see RFC6355
+        DUID_MAX          ///< not a real type, just maximum defined value + 1
+    } DUIDType;
+
+    /// @brief creates a DUID
+    DUID(const std::vector<uint8_t>& duid);
+
+    /// @brief creates a DUID
+    DUID(const uint8_t *duid, size_t len);
+
+    /// @brief returns a const reference to the actual DUID value
+    ///
+    /// Note: For safety reasons, this method returns a copy of data as
+    /// otherwise the reference would be only valid as long as the object that
+    /// returned it. In any case, this method should be used only sporadically.
+    /// If there are frequent uses, we must implement some other method
+    /// (e.g. storeSelf()) that will avoid data copying.
+    const std::vector<uint8_t> getDuid() const;
+
+    /// @brief returns DUID type
+    DUIDType getType() const;
+
+    // compares two DUIDs
+    bool operator == (const DUID& other) const;
+
+    // compares two DUIDs
+    bool operator != (const DUID& other) const;
+
+ protected:
+    /// the actual content of the DUID
+    std::vector<uint8_t> duid_;
+};
+
+/// @brief Holds Client identifier or client IPv4 address
+///
+/// This class is intended to be a generic IPv4 client identifier. It can hold
+/// a client-id
+class ClientId : DUID {
+ public:
+
+    /// constructor based on vector<uint8_t>
+    ClientId(const std::vector<uint8_t>& clientid);
+
+    /// constructor based on C-style data
+    ClientId(const uint8_t *clientid, size_t len);
+
+    /// @brief returns reference to the client-id data
+    ///
+    const std::vector<uint8_t> getClientId() const;
+
+    // compares two client-ids
+    bool operator == (const ClientId& other) const;
+
+    // compares two client-ids
+    bool operator != (const ClientId& other) const;
+};
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace

+ 68 - 0
src/lib/dhcp/lease_mgr.cc

@@ -0,0 +1,68 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <sstream>
+#include <iostream>
+#include <map>
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <algorithm>
+#include <iterator>
+#include <exceptions/exceptions.h>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string.hpp>
+#include "lease_mgr.h"
+
+using namespace std;
+
+using namespace isc::dhcp;
+
+LeaseMgr::LeaseMgr(const std::string& dbconfig) {
+
+    if (dbconfig.length() == 0) {
+        return;
+    }
+
+    vector<string> tokens;
+
+    // we need to pass a string to is_any_of, not just char *. Otherwise there
+    // are cryptic warnings on Debian6 running g++ 4.4 in /usr/include/c++/4.4
+    // /bits/stl_algo.h:2178 "array subscript is above array bounds"
+    boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+    BOOST_FOREACH(std::string token, tokens) {
+        size_t pos = token.find("=");
+        if (pos != string::npos) {
+            string name = token.substr(0, pos);
+            string value = token.substr(pos + 1);
+            parameters_.insert(pair<string,string>(name, value));
+        } else {
+            isc_throw(InvalidParameter, "Cannot parse " << token
+                      << ", expected format is name=value");
+        }
+
+    }
+}
+
+std::string LeaseMgr::getParameter(const std::string& name) const {
+    std::map<std::string, std::string>::const_iterator param
+        = parameters_.find(name);
+    if (param == parameters_.end()) {
+        isc_throw(BadValue, "Parameter not found");
+    }
+    return (param->second);
+}
+
+LeaseMgr::~LeaseMgr() {
+}

+ 480 - 0
src/lib/dhcp/lease_mgr.h

@@ -0,0 +1,480 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <string>
+#include <fstream>
+#include <vector>
+#include <map>
+#include <asiolink/io_address.h>
+#include <boost/shared_ptr.hpp>
+#include <dhcp/option.h>
+#include <dhcp/duid.h>
+
+/// @file dhcp/lease_mgr.h
+/// @brief An abstract API for lease database
+///
+/// This file contains declarations of Lease4, Lease6 and LeaseMgr classes.
+/// They are essential components of the interface to any database backend.
+/// Each concrete database backend (e.g. MySQL) will define a class derived
+/// from LeaseMgr class.
+///
+/// Failover considerations:
+/// There are no intermediate plans to implement DHCPv4 failover
+/// (draft-ietf-dhc-failover-12.txt). Currently (Oct. 2012) the DHCPv6 failover
+/// is being defined in DHC WG in IETF (draft-ietf-dhcpv6-failover-requirements,
+/// draft-ietf-dhcpv6-dailover-design), but the work is not advanced enough
+/// for implementation plans yet. v4 failover requires additional parameters
+/// to be kept with a lease. It is likely that v6 failover will require similar
+/// fields. Such implementation will require database schema extension.
+/// We have designed a way to expand/upgrade schemas during upgrades: a database
+/// schema is versioned and sanity checks about required version will be done
+/// upon start and/or upgrade. With this mechanism in place, we can add new
+/// fields to the database. In particular we can use that capability to
+/// introduce failover related fields.
+///
+/// However, there is another approach that can be reliably used to provide
+/// failover, even without the actual failover protocol implemented. As the
+/// first backend will use MySQL, we will be able to use Multi-Master capability
+/// offered by MySQL and use two separatate Kea instances connecting to the
+/// same database.
+///
+/// Nevertheless, we hope to have failover protocol eventually implemented in
+/// the Kea.
+
+namespace isc {
+namespace dhcp {
+
+/// @brief specifies unique subnet identifier
+/// @todo: Move this to subnet.h once ticket #2237 is merged
+typedef uint32_t SubnetID;
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 {
+    /// IPv4 address
+    isc::asiolink::IOAddress addr_;
+
+    /// @brief Address extension
+    ///
+    /// It is envisaged that in some cases IPv4 address will be accompanied with some
+    /// additional data. One example of such use are Address + Port solutions (or
+    /// Port-restricted Addresses), where several clients may get the same address, but
+    /// different port ranges. This feature is not expected to be widely used.
+    /// Under normal circumstances, the value should be 0.
+    uint32_t ext_;
+
+    /// @brief hardware address
+    std::vector<uint8_t> hwaddr_;
+
+    /// @brief client identifier
+    boost::shared_ptr<ClientId> client_id_;
+
+    /// @brief renewal timer
+    ///
+    /// Specifies renewal time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t1_;
+
+    /// @brief rebinding timer
+    ///
+    /// Specifies rebinding time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t2_;
+
+    /// @brief valid lifetime
+    ///
+    /// Expressed as number of seconds since cltt
+    uint32_t valid_lft_;
+
+    /// @brief client last transmission time
+    ///
+    /// Specifies a timestamp, when last transmission from a client was received.
+    time_t cltt_;
+
+    /// @brief Subnet identifier
+    ///
+    /// Specifies subnet-id of the subnet that the lease belongs to
+    SubnetID subnet_id_;
+
+    /// @brief Is this a fixed lease?
+    ///
+    /// Fixed leases are kept after they are released/expired.
+    bool fixed_;
+
+    /// @brief client hostname
+    ///
+    /// This field may be empty
+    std::string hostname_;
+
+    /// @brief did we update AAAA record for this lease?
+    bool fqdn_fwd_;
+
+    /// @brief did we update PTR record for this lease?
+    bool fqdn_rev_;
+
+    /// @brief Lease comments.
+    ///
+    /// Currently not used. It may be used for keeping comments made by the
+    /// system administrator.
+    std::string comments_;
+
+    /// @todo: Add DHCPv4 failover related fields here
+};
+
+/// @brief Pointer to a Lease4 structure.
+typedef boost::shared_ptr<Lease4> Lease4Ptr;
+
+/// @brief A collection of IPv4 leases.
+typedef std::vector< boost::shared_ptr<Lease4Ptr> > Lease4Collection;
+
+/// @brief Structure that holds a lease for IPv6 address and/or prefix
+///
+/// For performance reasons it is a simple structure, not a class. Had we chose to
+/// make it a class, all fields would have to be made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access rather than through getters/setters is warranted.
+struct Lease6 {
+    typedef enum {
+        LEASE_IA_NA, /// the lease contains non-temporary IPv6 address
+        LEASE_IA_TA, /// the lease contains temporary IPv6 address
+        LEASE_IA_PD  /// the lease contains IPv6 prefix (for prefix delegation)
+    } LeaseType;
+
+    /// @brief specifies lease type (normal addr, temporary addr, prefix)
+    LeaseType type_;
+
+    /// IPv6 address
+    isc::asiolink::IOAddress addr_;
+
+    /// IPv6 prefix length (used only for PD)
+    uint8_t prefixlen_;
+
+    /// @brief IAID
+    ///
+    /// Identity Association IDentifier. DHCPv6 stores all addresses and prefixes
+    /// in IA containers (IA_NA, IA_TA, IA_PD). Most containers may appear more
+    /// than once in a message. To differentiate between them, IAID field is present
+    uint32_t iaid_;
+
+    /// @brief hardware address
+    ///
+    /// This field is not really used and is optional at best. The concept of identifying
+    /// clients by their hardware address was replaced in DHCPv6 by DUID concept. Each
+    /// client has its own unique DUID (DHCP Unique IDentifier). Furthermore, client's
+    /// HW address is not always available, because client may be behind a relay (relay
+    /// stores only link-local address).
+    std::vector<uint8_t> hwaddr_;
+
+    /// @brief client identifier
+    boost::shared_ptr<DUID> duid_;
+
+    /// @brief preferred lifetime
+    ///
+    /// This parameter specifies preferred lifetime since the lease was assigned/renewed
+    /// (cltt), expressed in seconds.
+    uint32_t preferred_lft_;
+
+    /// @brief valid lifetime
+    ///
+    /// This parameter specified valid lifetime since the lease was assigned/renewed
+    /// (cltt), expressed in seconds.
+    uint32_t valid_lft_;
+
+    /// @brief T1 timer
+    ///
+    /// Specifies renewal time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t1_;
+
+    /// @brief T2 timer
+    ///
+    /// Specifies rebinding time. Although technically it is a property of IA container,
+    /// not the address itself, since our data model does not define separate IA
+    /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
+    /// for the same IA, each must have consistent T1 and T2 values. Specified in
+    /// seconds since cltt.
+    uint32_t t2_;
+
+    /// @brief client last transmission time
+    ///
+    /// Specifies a timestamp, when last transmission from a client was received.
+    time_t cltt_;
+
+    /// @brief Subnet identifier
+    ///
+    /// Specifies subnet-id of the subnet that the lease belongs to
+    SubnetID subnet_id_;
+
+    /// @brief Is this a fixed lease?
+    ///
+    /// Fixed leases are kept after they are released/expired.
+    bool fixed_;
+
+    /// @brief client hostname
+    ///
+    /// This field may be empty
+    std::string hostname_;
+
+    /// @brief did we update AAAA record for this lease?
+    bool fqdn_fwd_;
+
+    /// @brief did we update PTR record for this lease?
+    bool fqdn_rev_;
+
+    /// @brief Lease comments
+    ///
+    /// This field is currently not used.
+    std::string comments_;
+
+    /// @todo: Add DHCPv6 failover related fields here
+};
+
+/// @brief Pointer to a Lease6 structure.
+typedef boost::shared_ptr<Lease6> Lease6Ptr;
+
+/// @brief Const pointer to a Lease6 structure.
+typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
+
+/// @brief A collection of IPv6 leases.
+typedef std::vector< boost::shared_ptr<Lease6Ptr> > Lease6Collection;
+
+/// @brief Abstract Lease Manager
+///
+/// This is an abstract API for lease database backends. It provides unified
+/// interface to all backends. As this is an abstract class, it should not
+/// be used directly, but rather specialized derived class should be used
+/// instead.
+class LeaseMgr {
+public:
+
+    /// Client Hardware address
+    typedef std::vector<uint8_t> HWAddr;
+
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters
+    /// are passed in the "name=value" format, separated by spaces.
+    /// Values may be enclosed in double quotes, if needed.
+    ///
+    /// @param dbconfig database configuration
+    LeaseMgr(const std::string& dbconfig);
+
+    /// @brief Destructor (closes file)
+    virtual ~LeaseMgr();
+
+    /// @brief Adds an IPv4 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease4Ptr lease) = 0;
+
+    /// @brief Adds an IPv6 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease6Ptr lease) = 0;
+
+    /// @brief Returns existing IPv4 lease for specified IPv4 address and subnet_id
+    ///
+    /// This method is used to get a lease for specific subnet_id. There can be
+    /// at most one lease for any given subnet, so this method returns a single
+    /// pointer.
+    ///
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Returns an IPv4 lease for specified IPv4 address
+    ///
+    /// This method return a lease that is associated with a given address.
+    /// For other query types (by hardware addr, by client-id) there can be
+    /// several leases in different subnets (e.g. for mobile clients that
+    /// got address in different subnets). However, for a single address
+    /// there can be only one lease, so this method returns a pointer to
+    /// a single lease, not a container of leases.
+    ///
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const = 0;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address.
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param hwaddr hardware address of the client
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address
+    ///        and a subnet
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param hwaddr hardware address of the client
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param clientid client identifier
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const ClientId& clientid) const = 0;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param clientid client identifier
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const ClientId& clientid,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Returns existing IPv6 lease for a given IPv6 address.
+    ///
+    /// For a given address, we assume that there will be only one lease.
+    /// The assumtion here is that there will not be site or link-local
+    /// addresses used, so there is no way of having address duplication.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const = 0;
+
+    /// @brief Returns existing IPv6 leases for a given DUID+IA combination
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Collection getLease6(const DUID& duid,
+                                       uint32_t iaid) const = 0;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    /// @param subnet_id subnet id of the subnet the lease belongs to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
+                                SubnetID subnet_id) const = 0;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    virtual void updateLease4(Lease4Ptr lease4) = 0;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    virtual void updateLease6(Lease6Ptr lease6) = 0;
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    virtual bool deleteLease4(uint32_t addr) = 0;
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    virtual bool deleteLease6(isc::asiolink::IOAddress addr) = 0;
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name, e.g. "mysql" or "sqlite".
+    virtual std::string getName() const = 0;
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    virtual std::string getDescription() const = 0;
+
+    /// @brief Returns backend version.
+    ///
+    /// @todo: We will need to implement 3 version functions eventually:
+    /// A. abstract API version
+    /// B. backend version
+    /// C. database version (stored in the database scheme)
+    ///
+    /// and then check that:
+    /// B>=A and B=C (it is ok to have newer backend, as it should be backward
+    /// compatible)
+    /// Also if B>C, some database upgrade procedure may be triggered
+    virtual std::string getVersion() const = 0;
+
+    /// @todo: Add host management here
+    /// As host reservation is outside of scope for 2012, support for hosts
+    /// is currently postponed.
+
+protected:
+    /// @brief returns value of the parameter
+    std::string getParameter(const std::string& name) const;
+
+    /// @brief list of parameters passed in dbconfig
+    ///
+    /// That will be mostly used for storing database name, username,
+    /// password and other parameters required for DB access. It is not
+    /// intended to keep any DHCP-related parameters.
+    std::map<std::string, std::string> parameters_;
+};
+
+}; // end of isc::dhcp namespace
+
+}; // end of isc namespace

+ 32 - 1
src/lib/dhcp/pool.cc

@@ -30,6 +30,38 @@ bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
     return (first_.smallerEqual(addr) && addr.smallerEqual(last_));
 }
 
+Pool4::Pool4(const isc::asiolink::IOAddress& first,
+             const isc::asiolink::IOAddress& last)
+    :Pool(first, last) {
+    // check if specified address boundaries are sane
+    if (first.getFamily() != AF_INET || last.getFamily() != AF_INET) {
+        isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
+    }
+
+    if (last < first) {
+        isc_throw(BadValue, "Upper boundary is smaller than lower boundary.");
+    }
+}
+
+Pool4::Pool4(const isc::asiolink::IOAddress& prefix,
+             uint8_t prefix_len)
+    :Pool(prefix, IOAddress("0.0.0.0")) {
+
+    // check if the prefix is sane
+    if (prefix.getFamily() != AF_INET) {
+        isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
+    }
+
+    // check if the prefix length is sane
+    if (prefix_len == 0 || prefix_len > 32) {
+        isc_throw(BadValue, "Invalid prefix length");
+    }
+
+    // Let's now calculate the last address in defined pool
+    last_ = lastAddrInPrefix(prefix, prefix_len);
+}
+
+
 Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
              const isc::asiolink::IOAddress& last)
     :Pool(first, last), type_(type), prefix_len_(0) {
@@ -52,7 +84,6 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
         // last_ = first;
     }
 
-
     // TYPE_PD is not supported by this constructor. first-last style
     // parameters are for IA and TA only. There is another dedicated
     // constructor for that (it uses prefix/length)

+ 27 - 0
src/lib/dhcp/pool.h

@@ -91,6 +91,33 @@ protected:
     std::string comments_;
 };
 
+/// @brief Pool information for IPv4 addresses
+///
+/// It holds information about pool4, i.e. a range of IPv4 address space that
+/// is configured for DHCP allocation.
+class Pool4 : public Pool {
+public:
+    /// @brief the constructor for Pool4 "min-max" style definition
+    ///
+    /// @param first the first address in a pool
+    /// @param last the last address in a pool
+    Pool4(const isc::asiolink::IOAddress& first,
+          const isc::asiolink::IOAddress& last);
+
+    /// @brief the constructor for Pool4 "prefix/len" style definition
+    ///
+    /// @param prefix specifies prefix of the pool
+    /// @param prefix_len specifies length of the prefix of the pool
+    Pool4(const isc::asiolink::IOAddress& prefix,
+          uint8_t prefix_len);
+};
+
+/// @brief a pointer an IPv4 Pool
+typedef boost::shared_ptr<Pool4> Pool4Ptr;
+
+/// @brief a container for IPv4 Pools
+typedef std::vector<Pool4Ptr> Pool4Collection;
+
 /// @brief Pool information for IPv6 addresses and prefixes
 ///
 /// It holds information about pool6, i.e. a range of IPv6 address space that

+ 44 - 0
src/lib/dhcp/subnet.cc

@@ -41,6 +41,50 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
     return ((first <= addr) && (addr <= last));
 }
 
+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) {
+    if (prefix.getFamily() != AF_INET) {
+        isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
+                  << " specified in subnet4");
+    }
+}
+
+void Subnet4::addPool4(const Pool4Ptr& pool) {
+    IOAddress first_addr = pool->getFirstAddress();
+    IOAddress last_addr = pool->getLastAddress();
+
+    if (!inRange(first_addr) || !inRange(last_addr)) {
+        isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
+                  << " does not belong in this (" << prefix_ << "/" << prefix_len_
+                  << ") subnet4");
+    }
+
+    /// @todo: Check that pools do not overlap
+
+    pools_.push_back(pool);
+}
+
+Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
+    Pool4Ptr candidate;
+    for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+
+        // if we won't find anything better, then let's just use the first pool
+        if (!candidate) {
+            candidate = *pool;
+        }
+
+        // if the client provided a pool and there's a pool that hint is valid in,
+        // then let's use that pool
+        if ((*pool)->inRange(hint)) {
+            return (*pool);
+        }
+    }
+    return (candidate);
+}
+
 Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
                  const Triplet<uint32_t>& t1,
                  const Triplet<uint32_t>& t2,

+ 51 - 0
src/lib/dhcp/subnet.h

@@ -93,6 +93,57 @@ protected:
     Triplet<uint32_t> valid_;
 };
 
+/// @brief A configuration holder for IPv4 subnet.
+///
+/// This class represents an IPv4 subnet.
+class Subnet4 : public Subnet {
+public:
+
+    /// @brief Constructor with all parameters
+    ///
+    /// @param prefix Subnet4 prefix
+    /// @param length prefix length
+    /// @param t1 renewal timer (in seconds)
+    /// @param t2 rebind timer (in seconds)
+    /// @param valid_lifetime preferred lifetime of leases (in seconds)
+    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);
+
+    /// @brief Returns a pool that specified address belongs to
+    ///
+    /// @param hint address that the returned pool should cover (optional)
+    /// @return Pointer to found pool4 (or NULL)
+    Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
+                      isc::asiolink::IOAddress("0.0.0.0"));
+
+    /// @brief Adds a new pool.
+    /// @param pool pool to be added
+    void addPool4(const Pool4Ptr& pool);
+
+    /// @brief returns all pools
+    ///
+    /// The reference is only valid as long as the object that
+    /// returned it.
+    ///
+    /// @return a collection of all pools
+    const Pool4Collection& getPools() const {
+        return pools_;
+    }
+
+protected:
+    /// @brief collection of pools in that list
+    Pool4Collection pools_;
+};
+
+/// @brief A pointer to a Subnet4 object
+typedef boost::shared_ptr<Subnet4> Subnet4Ptr;
+
+/// @brief A collection of Subnet6 objects
+typedef std::vector<Subnet4Ptr> Subnet4Collection;
+
+
 /// @brief A configuration holder for IPv6 subnet.
 ///
 /// This class represents an IPv6 subnet.

+ 2 - 0
src/lib/dhcp/tests/Makefile.am

@@ -28,6 +28,7 @@ TESTS += libdhcp++_unittests libdhcpsrv_unittests
 libdhcp___unittests_SOURCES  = run_unittests.cc
 libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
 libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
+libdhcp___unittests_SOURCES += lease_mgr_unittest.cc
 libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
 libdhcp___unittests_SOURCES += option6_ia_unittest.cc
 libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
@@ -35,6 +36,7 @@ libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
 libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
+libdhcp___unittests_SOURCES += duid_unittest.cc
 
 libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
 libdhcp___unittests_LDFLAGS  = $(AM_LDFLAGS)  $(GTEST_LDFLAGS)

+ 63 - 2
src/lib/dhcp/tests/addr_utilities_unittest.cc

@@ -26,7 +26,67 @@ using namespace std;
 using namespace isc::dhcp;
 using namespace isc::asiolink;
 
-TEST(Pool6Test, lastAddrInPrefix) {
+// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
+    IOAddress addr1("192.0.2.1");
+
+    // Prefixes rounded to addresses are easy...
+    EXPECT_EQ("192.255.255.255", lastAddrInPrefix(addr1, 8).toText());
+    EXPECT_EQ("192.0.255.255",   lastAddrInPrefix(addr1, 16).toText());
+    EXPECT_EQ("192.0.2.255",     lastAddrInPrefix(addr1, 24).toText());
+
+    // these are trickier
+    EXPECT_EQ("192.0.2.127", lastAddrInPrefix(addr1, 25).toText());
+    EXPECT_EQ("192.0.2.63",  lastAddrInPrefix(addr1, 26).toText());
+    EXPECT_EQ("192.0.2.31",  lastAddrInPrefix(addr1, 27).toText());
+    EXPECT_EQ("192.0.2.15",  lastAddrInPrefix(addr1, 28).toText());
+    EXPECT_EQ("192.0.2.7",   lastAddrInPrefix(addr1, 29).toText());
+    EXPECT_EQ("192.0.2.3",   lastAddrInPrefix(addr1, 30).toText());
+
+    // that doesn't make much sense as /31 subnet consists of network address
+    // and a broadcast address, with 0 usable addresses.
+    EXPECT_EQ("192.0.2.1",   lastAddrInPrefix(addr1, 31).toText());
+    EXPECT_EQ("192.0.2.1",   lastAddrInPrefix(addr1, 32).toText());
+
+    // Let's check extreme cases
+    IOAddress anyAddr("0.0.0.0");
+    EXPECT_EQ("127.255.255.255", lastAddrInPrefix(anyAddr, 1).toText());
+    EXPECT_EQ("255.255.255.255", lastAddrInPrefix(anyAddr, 0).toText());
+    EXPECT_EQ("0.0.0.0", lastAddrInPrefix(anyAddr, 32).toText());
+}
+
+// This test checks if firstAddrInPrefix is able to handle IPv4 operations.
+TEST(AddrUtilitiesTest, firstAddrInPrefix4) {
+    IOAddress addr1("192.223.2.255");
+
+    // Prefixes rounded to addresses are easy...
+    EXPECT_EQ("192.0.0.0",   firstAddrInPrefix(addr1, 8).toText());
+    EXPECT_EQ("192.223.0.0", firstAddrInPrefix(addr1, 16).toText());
+    EXPECT_EQ("192.223.2.0", firstAddrInPrefix(addr1, 24).toText());
+
+    // these are trickier
+    EXPECT_EQ("192.223.2.128", firstAddrInPrefix(addr1, 25).toText());
+    EXPECT_EQ("192.223.2.192", firstAddrInPrefix(addr1, 26).toText());
+    EXPECT_EQ("192.223.2.224", firstAddrInPrefix(addr1, 27).toText());
+    EXPECT_EQ("192.223.2.240", firstAddrInPrefix(addr1, 28).toText());
+    EXPECT_EQ("192.223.2.248", firstAddrInPrefix(addr1, 29).toText());
+    EXPECT_EQ("192.223.2.252", firstAddrInPrefix(addr1, 30).toText());
+
+    // that doesn't make much sense as /31 subnet consists of network address
+    // and a broadcast address, with 0 usable addresses.
+    EXPECT_EQ("192.223.2.254", firstAddrInPrefix(addr1, 31).toText());
+    EXPECT_EQ("192.223.2.255", firstAddrInPrefix(addr1, 32).toText());
+
+    // Let's check extreme cases.
+    IOAddress bcast("255.255.255.255");
+    EXPECT_EQ("128.0.0.0", firstAddrInPrefix(bcast, 1).toText());
+    EXPECT_EQ("0.0.0.0", firstAddrInPrefix(bcast, 0).toText());
+    EXPECT_EQ("255.255.255.255", firstAddrInPrefix(bcast, 32).toText());
+
+}
+
+/// This test checks if lastAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, lastAddrInPrefix6) {
     IOAddress addr1("2001:db8:1:1234:5678:abcd:1234:beef");
 
     // Prefixes rounded to nibbles are easy...
@@ -63,7 +123,8 @@ TEST(Pool6Test, lastAddrInPrefix) {
     EXPECT_EQ("::", lastAddrInPrefix(anyAddr, 128).toText());
 }
 
-TEST(Pool6Test, firstAddrInPrefix) {
+/// This test checks if firstAddrInPrefix properly supports IPv6 operations
+TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
     IOAddress addr1("2001:db8:1:1234:5678:1234:abcd:beef");
 
     // Prefixes rounded to nibbles are easy...

+ 36 - 0
src/lib/dhcp/tests/cfgmgr_unittest.cc

@@ -34,6 +34,38 @@ namespace {
 
 // This test verifies if the configuration manager is able to hold and return
 // valid leases
+TEST(CfgMgrTest, subnet4) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    ASSERT_TRUE(&cfg_mgr != 0);
+
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
+    Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
+
+    // there shouldn't be any subnet configured at this stage
+    EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+
+    cfg_mgr.addSubnet4(subnet1);
+
+    // Now we have only one subnet, any request will be served from it
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.63")));
+
+    // Now we add more subnets and check that both old and new subnets
+    // are accessible.
+    cfg_mgr.addSubnet4(subnet2);
+    cfg_mgr.addSubnet4(subnet3);
+
+    EXPECT_EQ(subnet3, cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+    EXPECT_EQ(subnet1, cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+    EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
+
+    // Try to find an address that does not belong to any subnet
+    EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+}
+
+// This test verifies if the configuration manager is able to hold and return
+// valid leases
 TEST(CfgMgrTest, subnet6) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
 
@@ -58,6 +90,10 @@ TEST(CfgMgrTest, subnet6) {
     EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
     EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
 
+    cfg_mgr.deleteSubnets6();
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
+    EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
 }
 
 } // end of anonymous namespace

+ 169 - 0
src/lib/dhcp/tests/duid_unittest.cc

@@ -0,0 +1,169 @@
+// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+#include <exceptions/exceptions.h>
+#include <boost/scoped_ptr.hpp>
+#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+// don't import the entire boost namespace.  It will unexpectedly hide uint8_t
+// for some systems.
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(DuidTest, constructor) {
+
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+
+    vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+    scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
+    scoped_ptr<DUID> duid2(new DUID(data2));
+
+    vector<uint8_t> vecdata = duid1->getDuid();
+    EXPECT_TRUE(data2 == vecdata);
+    EXPECT_EQ(DUID::DUID_LLT, duid1->getType());
+
+    vecdata = duid2->getDuid();
+    EXPECT_TRUE(data2 == vecdata);
+
+    EXPECT_EQ(DUID::DUID_LLT, duid2->getType());
+}
+
+// This test verifies if DUID size restrictions are implemented
+// properly.
+TEST(DuidTest, size) {
+    const int MAX_DUID_SIZE = 128;
+    uint8_t data[MAX_DUID_SIZE + 1];
+    vector<uint8_t> data2;
+    for (uint8_t i = 0; i < MAX_DUID_SIZE + 1; ++i) {
+        data[i] = i;
+        if (i < MAX_DUID_SIZE)
+            data2.push_back(i);
+    }
+    ASSERT_EQ(data2.size(), MAX_DUID_SIZE);
+
+    scoped_ptr<DUID> duidmaxsize1(new DUID(data, MAX_DUID_SIZE));
+    scoped_ptr<DUID> duidmaxsize2(new DUID(data2));
+
+    EXPECT_THROW(
+        scoped_ptr<DUID> toolarge1(new DUID(data, MAX_DUID_SIZE + 1)),
+        OutOfRange);
+
+    // that's one too much
+    data2.push_back(128);
+
+    EXPECT_THROW(
+        scoped_ptr<DUID> toolarge2(new DUID(data2)),
+        OutOfRange);
+}
+
+// This test verifies if the implementation supports all defined
+// DUID types.
+TEST(DuidTest, getType) {
+    uint8_t llt[] =     {0, 1, 2, 3, 4, 5, 6};
+    uint8_t en[] =      {0, 2, 2, 3, 4, 5, 6};
+    uint8_t ll[] =      {0, 3, 2, 3, 4, 5, 6};
+    uint8_t uuid[] =    {0, 4, 2, 3, 4, 5, 6};
+    uint8_t invalid[] = {0,55, 2, 3, 4, 5, 6};
+
+    scoped_ptr<DUID> duid_llt(new DUID(llt, sizeof(llt)));
+    scoped_ptr<DUID> duid_en(new DUID(en, sizeof(en)));
+    scoped_ptr<DUID> duid_ll(new DUID(ll, sizeof(ll)));
+    scoped_ptr<DUID> duid_uuid(new DUID(uuid, sizeof(uuid)));
+    scoped_ptr<DUID> duid_invalid(new DUID(invalid, sizeof(invalid)));
+
+    EXPECT_EQ(DUID::DUID_LLT,     duid_llt->getType());
+    EXPECT_EQ(DUID::DUID_EN,      duid_en->getType());
+    EXPECT_EQ(DUID::DUID_LL,      duid_ll->getType());
+    EXPECT_EQ(DUID::DUID_UUID,    duid_uuid->getType());
+    EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
+}
+
+// This test checks if the comparison operators are sane.
+TEST(DuidTest, operators) {
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    uint8_t data2[] = {0, 1, 2, 3, 4};
+    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+    scoped_ptr<DUID> duid1(new DUID(data1, sizeof(data1)));
+    scoped_ptr<DUID> duid2(new DUID(data2, sizeof(data2)));
+    scoped_ptr<DUID> duid3(new DUID(data3, sizeof(data3)));
+    scoped_ptr<DUID> duid4(new DUID(data4, sizeof(data4)));
+
+    EXPECT_TRUE(*duid1 == *duid4);
+    EXPECT_FALSE(*duid1 == *duid2);
+    EXPECT_FALSE(*duid1 == *duid3);
+
+    EXPECT_FALSE(*duid1 != *duid4);
+    EXPECT_TRUE(*duid1 != *duid2);
+    EXPECT_TRUE(*duid1 != *duid3);
+}
+
+// This test verifies if the ClientId constructors are working properly
+// and passed parameters are used
+TEST(ClientIdTest, constructor) {
+    IOAddress addr2("192.0.2.1");
+    IOAddress addr3("2001:db8:1::1");
+
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+    // checks for C-style construtor (uint8_t * + len)
+    scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
+    vector<uint8_t> vecdata = id1->getClientId();
+    EXPECT_TRUE(data2 == vecdata);
+
+    // checks for vector-based constructor
+    scoped_ptr<ClientId> id2(new ClientId(data2));
+    vecdata = id2->getClientId();
+    EXPECT_TRUE(data2 == vecdata);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(ClientIdTest, operators) {
+    uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+    uint8_t data2[] = {0, 1, 2, 3, 4};
+    uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+    uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+    scoped_ptr<ClientId> id1(new ClientId(data1, sizeof(data1)));
+    scoped_ptr<ClientId> id2(new ClientId(data2, sizeof(data2)));
+    scoped_ptr<ClientId> id3(new ClientId(data3, sizeof(data3)));
+    scoped_ptr<ClientId> id4(new ClientId(data4, sizeof(data4)));
+
+    EXPECT_TRUE(*id1 == *id4);
+    EXPECT_FALSE(*id1 == *id2);
+    EXPECT_FALSE(*id1 == *id3);
+
+    EXPECT_FALSE(*id1 != *id4);
+    EXPECT_TRUE(*id1 != *id2);
+    EXPECT_TRUE(*id1 != *id3);
+}
+
+} // end of anonymous namespace

+ 296 - 0
src/lib/dhcp/tests/lease_mgr_unittest.cc

@@ -0,0 +1,296 @@
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <iostream>
+#include <sstream>
+#include <gtest/gtest.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/lease_mgr.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+// This is a concrete implementation of a Lease database.
+// It does not do anything useful now, and is used for abstract LeaseMgr
+// class testing. It may later evolve into more useful backend if the
+// need arises. We can reuse code from memfile benchmark. See code in
+// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
+class Memfile_LeaseMgr : public LeaseMgr {
+public:
+
+    /// @brief The sole lease manager constructor
+    ///
+    /// dbconfig is a generic way of passing parameters. Parameters
+    /// are passed in the "name=value" format, separated by spaces.
+    /// Values may be enclosed in double quotes, if needed.
+    ///
+    /// @param dbconfig database configuration
+    Memfile_LeaseMgr(const std::string& dbconfig);
+
+    /// @brief Destructor (closes file)
+    virtual ~Memfile_LeaseMgr();
+
+    /// @brief Adds an IPv4 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease4Ptr lease);
+
+    /// @brief Adds an IPv6 lease.
+    ///
+    /// @param lease lease to be added
+    virtual bool addLease(Lease6Ptr lease);
+
+    /// @brief Returns existing IPv4 lease for specified IPv4 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return a collection of leases
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
+
+    /// @brief Returns existing IPv4 lease for specific address and subnet
+    /// @param addr address of the searched lease
+    /// @param subnet_id ID of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address.
+    ///
+    /// Although in the usual case there will be only one lease, for mobile
+    /// clients or clients with multiple static/fixed/reserved leases there
+    /// can be more than one. Thus return type is a container, not a single
+    /// pointer.
+    ///
+    /// @param hwaddr hardware address of the client
+    ///
+    /// @return lease collection
+    virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+
+    /// @brief Returns existing IPv4 leases for specified hardware address
+    ///        and a subnet
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param hwaddr hardware address of the client
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// @param clientid client identifier
+    virtual Lease4Collection getLease4(const ClientId& clientid) const;
+
+    /// @brief Returns existing IPv4 lease for specified client-id
+    ///
+    /// There can be at most one lease for a given HW address in a single
+    /// pool, so this method with either return a single lease or NULL.
+    ///
+    /// @param clientid client identifier
+    /// @param subnet_id identifier of the subnet that lease must belong to
+    ///
+    /// @return a pointer to the lease (or NULL if a lease is not found)
+    virtual Lease4Ptr getLease4(const ClientId& clientid,
+                                SubnetID subnet_id) const;
+
+    /// @brief Returns existing IPv6 lease for a given IPv6 address.
+    ///
+    /// @param addr address of the searched lease
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    Lease6Ptr getLease6(isc::asiolink::IOAddress addr) const;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    ///
+    /// @return collection of IPv6 leases
+    Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
+
+    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+    ///
+    /// @param duid client DUID
+    /// @param iaid IA identifier
+    /// @param subnet_id identifier of the subnet the lease must belong to
+    ///
+    /// @return smart pointer to the lease (or NULL if a lease is not found)
+    Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    void updateLease4(Lease4Ptr lease4);
+
+    /// @brief Updates IPv4 lease.
+    ///
+    /// @param lease4 The lease to be updated.
+    ///
+    /// If no such lease is present, an exception will be thrown.
+    void updateLease6(Lease6Ptr lease6);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease4(uint32_t addr);
+
+    /// @brief Deletes a lease.
+    ///
+    /// @param addr IPv4 address of the lease to be deleted.
+    ///
+    /// @return true if deletion was successful, false if no such lease exists
+    bool deleteLease6(isc::asiolink::IOAddress addr);
+
+    /// @brief Returns backend name.
+    ///
+    /// Each backend have specific name, e.g. "mysql" or "sqlite".
+    std::string getName() const { return "memfile"; }
+
+    /// @brief Returns description of the backend.
+    ///
+    /// This description may be multiline text that describes the backend.
+    std::string getDescription() const;
+
+    /// @brief Returns backend version.
+    std::string getVersion() const { return "test-version"; }
+
+    using LeaseMgr::getParameter;
+
+protected:
+
+
+};
+
+Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
+    : LeaseMgr(dbconfig) {
+}
+
+Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+}
+
+bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease4>) {
+    return (false);
+}
+
+bool Memfile_LeaseMgr::addLease(boost::shared_ptr<isc::dhcp::Lease6>) {
+    return (false);
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
+    return (Lease4Collection());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
+                                      SubnetID) const {
+    return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
+    return (Lease4Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(isc::asiolink::IOAddress) const {
+    return (Lease6Ptr());
+}
+
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+    return (Lease6Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
+                                      SubnetID) const {
+    return (Lease6Ptr());
+}
+
+void Memfile_LeaseMgr::updateLease4(Lease4Ptr ) {
+}
+
+void Memfile_LeaseMgr::updateLease6(Lease6Ptr ) {
+
+}
+
+bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
+    return (false);
+}
+
+bool Memfile_LeaseMgr::deleteLease6(isc::asiolink::IOAddress ) {
+    return (false);
+}
+
+std::string Memfile_LeaseMgr::getDescription() const {
+    return (string("This is a dummy memfile backend implementation.\n"
+                   "It does not offer any useful lease management and its only\n"
+                   "purpose is to test abstract lease manager API."));
+}
+
+namespace {
+// empty class for now, but may be extended once Addr6 becomes bigger
+class LeaseMgrTest : public ::testing::Test {
+public:
+    LeaseMgrTest() {
+    }
+};
+
+// This test checks if the LeaseMgr can be instantiated and that it
+// parses parameters string properly.
+TEST_F(LeaseMgrTest, constructor) {
+
+    // should not throw any exceptions here
+    Memfile_LeaseMgr * leaseMgr = new Memfile_LeaseMgr("");
+    delete leaseMgr;
+
+    leaseMgr = new Memfile_LeaseMgr("param1=value1 param2=value2");
+
+    EXPECT_EQ("value1", leaseMgr->getParameter("param1"));
+    EXPECT_EQ("value2", leaseMgr->getParameter("param2"));
+    EXPECT_THROW(leaseMgr->getParameter("param3"), BadValue);
+
+    delete leaseMgr;
+}
+
+// There's no point in calling any other methods in LeaseMgr, as they
+// are purely virtual, so we would only call Memfile_LeaseMgr methods.
+// Those methods are just stubs that does not return anything.
+// It seems likely that we will need to extend the memfile code for
+// allocation engine tests, so we may implement tests that call
+// Memfile_LeaseMgr methods then.
+
+}; // end of anonymous namespace

+ 73 - 0
src/lib/dhcp/tests/pool_unittest.cc

@@ -27,6 +27,79 @@ using namespace isc::asiolink;
 
 namespace {
 
+TEST(Pool4Test, constructor_first_last) {
+
+    // let's construct 192.0.2.1-192.0.2.255 pool
+    Pool4 pool1(IOAddress("192.0.2.1"), IOAddress("192.0.2.255"));
+
+    EXPECT_EQ(IOAddress("192.0.2.1"), pool1.getFirstAddress());
+    EXPECT_EQ(IOAddress("192.0.2.255"), pool1.getLastAddress());
+
+    // This is Pool4, IPv6 addresses do not belong here
+    EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::1"),
+                       IOAddress("192.168.0.5")), BadValue);
+    EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("192.168.0.2"),
+                       IOAddress("2001:db8::1")), BadValue);
+
+    // Should throw. Range should be 192.0.2.1-192.0.2.2, not
+    // the other way around.
+    EXPECT_THROW(Pool6(Pool6::TYPE_IA, IOAddress("192.0.2.2"),
+                       IOAddress("192.0.2.1")), BadValue);
+}
+
+TEST(Pool4Test, constructor_prefix_len) {
+
+    // let's construct 2001:db8:1::/96 pool
+    Pool4 pool1(IOAddress("192.0.2.0"), 25);
+
+    EXPECT_EQ("192.0.2.0", pool1.getFirstAddress().toText());
+    EXPECT_EQ("192.0.2.127", pool1.getLastAddress().toText());
+
+    // No such thing as /33 prefix
+    EXPECT_THROW(Pool4(IOAddress("192.0.2.1"), 33), BadValue);
+
+    // /0 prefix does not make sense
+    EXPECT_THROW(Pool4(IOAddress("192.0.2.0"), 0), BadValue);
+
+    // This is Pool6, IPv4 addresses do not belong here
+    EXPECT_THROW(Pool4(IOAddress("2001:db8::1"), 20), BadValue);
+}
+
+TEST(Pool4Test, in_range) {
+   Pool4 pool1(IOAddress("192.0.2.10"), IOAddress("192.0.2.20"));
+
+   EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.0")));
+   EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.10")));
+   EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.17")));
+   EXPECT_TRUE(pool1.inRange(IOAddress("192.0.2.20")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.21")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("192.0.2.255")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("255.255.255.255")));
+   EXPECT_FALSE(pool1.inRange(IOAddress("0.0.0.0")));
+}
+
+// This test creates 100 pools and verifies that their IDs are unique.
+TEST(Pool4Test, unique_id) {
+
+    const int num_pools = 100;
+    std::vector<Pool4Ptr> pools;
+
+    for (int i = 0; i < num_pools; ++i) {
+        pools.push_back(Pool4Ptr(new Pool4(IOAddress("192.0.2.0"),
+                                           IOAddress("192.0.2.255"))));
+    }
+
+    for (int i = 0; i < num_pools; ++i) {
+        for (int j = i + 1; j < num_pools; ++j) {
+            if (pools[i]->getId() == pools[j]->getId()) {
+                FAIL() << "Pool-ids must be unique";
+            }
+        }
+    }
+
+}
+
+
 TEST(Pool6Test, constructor_first_last) {
 
     // let's construct 2001:db8:1:: - 2001:db8:1::ffff:ffff:ffff:ffff pool

+ 80 - 2
src/lib/dhcp/tests/subnet_unittest.cc

@@ -29,6 +29,83 @@ using namespace isc::asiolink;
 
 namespace {
 
+TEST(Subnet4Test, constructor) {
+    EXPECT_NO_THROW(Subnet4 subnet1(IOAddress("192.0.2.2"), 16,
+                                    1, 2, 3));
+
+    EXPECT_THROW(Subnet4 subnet2(IOAddress("192.0.2.0"), 33, 1, 2, 3),
+                BadValue); // invalid prefix length
+    EXPECT_THROW(Subnet4 subnet3(IOAddress("2001:db8::1"), 24, 1, 2, 3),
+                BadValue); // IPv6 addresses are not allowed in Subnet4
+}
+
+TEST(Subnet4Test, in_range) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    EXPECT_EQ(1000, subnet.getT1());
+    EXPECT_EQ(2000, subnet.getT2());
+    EXPECT_EQ(3000, subnet.getValid());
+
+    EXPECT_FALSE(subnet.inRange(IOAddress("192.0.0.0")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.0")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.1")));
+    EXPECT_TRUE(subnet.inRange(IOAddress("192.0.2.255")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("192.0.3.0")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("0.0.0.0")));
+    EXPECT_FALSE(subnet.inRange(IOAddress("255.255.255.255")));
+}
+
+TEST(Subnet4Test, Pool4InSubnet4) {
+
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
+
+    Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+    Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+    Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+
+    subnet->addPool4(pool1);
+
+    // If there's only one pool, get that pool
+    Pool4Ptr mypool = subnet->getPool4();
+    EXPECT_EQ(mypool, pool1);
+
+
+    subnet->addPool4(pool2);
+    subnet->addPool4(pool3);
+
+    // If there are more than one pool and we didn't provide hint, we
+    // should get the first pool
+    mypool = subnet->getPool4();
+
+    EXPECT_EQ(mypool, pool1);
+
+    // If we provide a hint, we should get a pool that this hint belongs to
+    mypool = subnet->getPool4(IOAddress("192.1.2.195"));
+
+    EXPECT_EQ(mypool, pool3);
+
+}
+
+TEST(Subnet4Test, Subnet4_Pool4_checks) {
+
+    Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
+
+    // this one is in subnet
+    Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
+    subnet->addPool4(pool1);
+
+    // this one is larger than the subnet!
+    Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
+
+    EXPECT_THROW(subnet->addPool4(pool2), BadValue);
+
+    // this one is totally out of blue
+    Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
+    EXPECT_THROW(subnet->addPool4(pool3), BadValue);
+}
+
+// Tests for Subnet6
+
 TEST(Subnet6Test, constructor) {
 
     EXPECT_NO_THROW(Subnet6 subnet1(IOAddress("2001:db8:1::"), 64,
@@ -48,7 +125,6 @@ TEST(Subnet6Test, in_range) {
     EXPECT_EQ(3000, subnet.getPreferred());
     EXPECT_EQ(4000, subnet.getValid());
 
-
     EXPECT_FALSE(subnet.inRange(IOAddress("2001:db8:0:ffff:ffff:ffff:ffff:ffff")));
     EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::0")));
     EXPECT_TRUE(subnet.inRange(IOAddress("2001:db8:1::1")));
@@ -106,7 +182,9 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
     Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
     EXPECT_THROW(subnet->addPool6(pool3), BadValue);
 
-}
 
+    Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
+    EXPECT_THROW(subnet->addPool6(pool4), BadValue);
+}
 
 };

+ 5 - 0
src/lib/dhcp/triplet.h

@@ -12,6 +12,9 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#ifndef TRIPLET_H
+#define TRIPLET_H
+
 #include <exceptions/exceptions.h>
 
 namespace isc {
@@ -108,3 +111,5 @@ protected:
 
 } // namespace isc::dhcp
 } // namespace isc
+
+#endif // ifdef TRIPLET_H

+ 5 - 0
src/lib/python/isc/bind10/sockcreator.py

@@ -214,9 +214,14 @@ class Creator(Parser):
                                 socket.SOCK_STREAM)
         env = copy.deepcopy(os.environ)
         env['PATH'] = path
+        # We explicitly set close_fs to True; it's False by default before
+        # Python 3.2.  If we don't close the remaining FDs, the copy of
+        # 'local' will prevent the child process from terminating when
+        # the parent process died abruptly.
         self.__process = subprocess.Popen(['b10-sockcreator'], env=env,
                                           stdin=remote.fileno(),
                                           stdout=remote2.fileno(),
+                                          close_fds=True,
                                           preexec_fn=self.__preexec_work)
         remote.close()
         remote2.close()

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

@@ -1,4 +1,5 @@
-SUBDIRS = . io unittests tests pyunittests python threads
+# InterprocessSyncFile has a dependency on isc::util::thread::Mutex
+SUBDIRS = io unittests threads . tests pyunittests python
 
 AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
@@ -31,7 +32,8 @@ libb10_util_la_SOURCES += random/random_number_generator.h
 
 EXTRA_DIST = python/pycppwrapper_util.h
 
-libb10_util_la_LIBADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_util_la_LIBADD =  $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+libb10_util_la_LIBADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 CLEANFILES = *.gcno *.gcda
 
 libb10_util_includedir = $(includedir)/$(PACKAGE_NAME)/util

+ 70 - 4
src/lib/util/interprocess_sync_file.cc

@@ -14,6 +14,9 @@
 
 #include "interprocess_sync_file.h"
 
+#include <boost/weak_ptr.hpp>
+
+#include <map>
 #include <string>
 
 #include <stdlib.h>
@@ -23,9 +26,38 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 
+using namespace isc::util::thread;
+
 namespace isc {
 namespace util {
 
+namespace { // unnamed namespace
+
+typedef std::map<std::string, boost::weak_ptr<Mutex> > SyncMap;
+
+Mutex sync_map_mutex;
+SyncMap sync_map;
+
+} // end of unnamed namespace
+
+InterprocessSyncFile::InterprocessSyncFile(const std::string& task_name) :
+    InterprocessSync(task_name),
+    fd_(-1)
+{
+    Mutex::Locker locker(sync_map_mutex);
+
+    SyncMap::iterator it = sync_map.find(task_name);
+    if (it != sync_map.end()) {
+        mutex_ = it->second.lock();
+    } else {
+        mutex_.reset(new Mutex());
+        sync_map[task_name] = mutex_;
+    }
+
+    // Lock on sync_map_mutex is automatically unlocked during
+    // destruction when basic block is exited.
+}
+
 InterprocessSyncFile::~InterprocessSyncFile() {
     if (fd_ != -1) {
         // This will also release any applied locks.
@@ -33,6 +65,23 @@ InterprocessSyncFile::~InterprocessSyncFile() {
         // The lockfile will continue to exist, and we must not delete
         // it.
     }
+
+    Mutex::Locker locker(sync_map_mutex);
+
+    // Unref the shared mutex.
+    locker_.reset();
+    mutex_.reset();
+
+    // Remove name from the map if it is unused anymore.
+    SyncMap::iterator it = sync_map.find(task_name_);
+    assert(it != sync_map.end());
+
+    if (it->second.expired()) {
+        sync_map.erase(it);
+    }
+
+    // Lock on sync_map_mutex is automatically unlocked during
+    // destruction when basic block is exited.
 }
 
 bool
@@ -90,8 +139,13 @@ InterprocessSyncFile::lock() {
         return (true);
     }
 
+    // First grab the thread lock...
+    LockerPtr locker(new Mutex::Locker(*mutex_));
+
+    // ... then the file lock.
     if (do_lock(F_SETLKW, F_WRLCK)) {
         is_locked_ = true;
+        locker_ = locker;
         return (true);
     }
 
@@ -104,8 +158,18 @@ InterprocessSyncFile::tryLock() {
         return (true);
     }
 
+    // First grab the thread lock...
+    LockerPtr locker;
+    try {
+        locker.reset(new Mutex::Locker(*mutex_, false));
+    } catch (const Mutex::Locker::AlreadyLocked&) {
+        return (false);
+    }
+
+    // ... then the file lock.
     if (do_lock(F_SETLK, F_WRLCK)) {
         is_locked_ = true;
+        locker_ = locker;
         return (true);
     }
 
@@ -118,12 +182,14 @@ InterprocessSyncFile::unlock() {
         return (true);
     }
 
-    if (do_lock(F_SETLKW, F_UNLCK)) {
-        is_locked_ = false;
-        return (true);
+    // First release the file lock...
+    if (do_lock(F_SETLKW, F_UNLCK) == 0) {
+        return (false);
     }
 
-    return (false);
+    locker_.reset();
+    is_locked_ = false;
+    return (true);
 }
 
 } // namespace util

+ 9 - 3
src/lib/util/interprocess_sync_file.h

@@ -16,8 +16,11 @@
 #define __INTERPROCESS_SYNC_FILE_H__
 
 #include <util/interprocess_sync.h>
+#include <util/threads/lock.h>
 #include <exceptions/exceptions.h>
 
+#include <boost/shared_ptr.hpp>
+
 namespace isc {
 namespace util {
 
@@ -55,9 +58,7 @@ public:
     /// \param name Name of the synchronization task. This has to be
     /// identical among the various processes that need to be
     /// synchronized for the same task.
-    InterprocessSyncFile(const std::string& task_name) :
-        InterprocessSync(task_name), fd_(-1)
-    {}
+    InterprocessSyncFile(const std::string& task_name);
 
     /// \brief Destructor
     virtual ~InterprocessSyncFile();
@@ -83,6 +84,11 @@ private:
     bool do_lock(int cmd, short l_type);
 
     int fd_; ///< The descriptor for the open file
+
+    typedef boost::shared_ptr<isc::util::thread::Mutex> MutexPtr;
+    typedef boost::shared_ptr<isc::util::thread::Mutex::Locker> LockerPtr;
+    MutexPtr mutex_;   ///< A mutex for mutual exclusion among threads
+    LockerPtr locker_; ///< A locker on mutex_
 };
 
 } // namespace util

+ 6 - 2
src/lib/util/locks.h

@@ -42,13 +42,17 @@ class upgradable_mutex {
 template <typename T>
 class sharable_lock {
 public:
-    sharable_lock(T) { }
+    sharable_lock(T) {}
 };
 
 template <typename T>
 class scoped_lock {
 public:
-    scoped_lock(T) { }
+    scoped_lock(T) {}
+
+    // We need to define this explicitly.  Some versions of clang++ would
+    // complain about this otherwise.  See Trac ticket #2340
+    ~scoped_lock() {}
 
     void lock() {}
     void unlock() {}

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

@@ -46,6 +46,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 
 run_unittests_LDADD = $(top_builddir)/src/lib/util/libb10-util.la
+run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libb10-util-io.la
 run_unittests_LDADD += \
 	$(top_builddir)/src/lib/util/unittests/libutil_unittests.la

+ 46 - 1
src/lib/util/tests/interprocess_sync_file_unittest.cc

@@ -12,10 +12,15 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
-#include "util/interprocess_sync_file.h"
+#include <util/interprocess_sync_file.h>
+#include <util/threads/thread.h>
+
 #include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
 #include <unistd.h>
 
+using namespace isc::util::thread;
 using namespace std;
 
 namespace isc {
@@ -108,6 +113,46 @@ TEST(InterprocessSyncFileTest, TestLock) {
   EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
 }
 
+void
+threadTest(bool* locked)
+{
+    InterprocessSyncFile sync2("test");
+    InterprocessSyncLocker locker2(sync2);
+
+    *locked = !locker2.tryLock();
+}
+
+TEST(InterprocessSyncFileTest, TestLockThreaded) {
+    InterprocessSyncFile sync("test");
+    InterprocessSyncLocker locker(sync);
+
+    EXPECT_FALSE(locker.isLocked());
+    EXPECT_TRUE(locker.lock());
+    EXPECT_TRUE(locker.isLocked());
+
+    bool locked_in_other_thread = false;
+
+    // Here, we check that a lock has been taken by creating a new
+    // thread and checking from the new thread that a lock exists. The
+    // lock attempt must fail to pass our check.
+    Thread thread(boost::bind(&threadTest, &locked_in_other_thread));
+    thread.wait();
+
+    EXPECT_TRUE(locked_in_other_thread);
+
+    // Release the lock and try again. This time, the attempt must pass.
+
+    EXPECT_TRUE(locker.unlock());
+    EXPECT_FALSE(locker.isLocked());
+
+    Thread thread2(boost::bind(&threadTest, &locked_in_other_thread));
+    thread2.wait();
+
+    EXPECT_FALSE(locked_in_other_thread);
+
+    EXPECT_EQ (0, unlink(TEST_DATA_TOPBUILDDIR "/test_lockfile"));
+}
+
 TEST(InterprocessSyncFileTest, TestMultipleFilesDirect) {
   InterprocessSyncFile sync("test1");
   InterprocessSyncLocker locker(sync);

+ 13 - 0
src/lib/util/threads/lock.cc

@@ -119,6 +119,19 @@ Mutex::lock() {
     ++impl_->locked_count; // Only in debug mode
 }
 
+bool
+Mutex::tryLock() {
+    assert(impl_ != NULL);
+    const int result = pthread_mutex_trylock(&impl_->mutex);
+    if (result == EBUSY) {
+        return (false);
+    } else if (result != 0) {
+        isc_throw(isc::InvalidOperation, std::strerror(result));
+    }
+    ++impl_->locked_count; // Only in debug mode
+    return (true);
+}
+
 void
 Mutex::unlock() {
     assert(impl_ != NULL);

+ 50 - 5
src/lib/util/threads/lock.h

@@ -15,6 +15,7 @@
 #ifndef B10_THREAD_LOCK_H
 #define B10_THREAD_LOCK_H
 
+#include <exceptions/exceptions.h>
 #include <boost/noncopyable.hpp>
 
 #include <cstdlib> // for NULL.
@@ -76,21 +77,39 @@ public:
     /// of function no matter by what means.
     class Locker : public boost::noncopyable {
     public:
+        /// \brief Exception thrown when the mutex is already locked and
+        ///     a non-blocking locker is attempted around it.
+        struct AlreadyLocked : public isc::InvalidParameter {
+            AlreadyLocked(const char* file, size_t line, const char* what) :
+                isc::InvalidParameter(file, line, what)
+            {}
+        };
+
         /// \brief Constructor.
         ///
-        /// Locks the mutex. May block for extended period of time.
+        /// Locks the mutex. May block for extended period of time if
+        /// \c block is true.
         ///
         /// \throw isc::InvalidOperation when OS reports error. This usually
         ///     means an attempt to use the mutex in a wrong way (locking
         ///     a mutex second time from the same thread, for example).
-        Locker(Mutex& mutex) :
+        /// \throw AlreadyLocked if \c block is false and the mutex is
+        ///     already locked.
+        Locker(Mutex& mutex, bool block = true) :
             mutex_(NULL)
         {
             // Set the mutex_ after we acquire the lock. This is because of
             // exception safety. If lock() throws, it didn't work, so we must
             // not unlock when we are destroyed. In such case, mutex_ is
             // NULL and checked in the destructor.
-            mutex.lock();
+            if (block) {
+                mutex.lock();
+            } else {
+                if (!mutex.tryLock()) {
+                    isc_throw(AlreadyLocked, "The mutex is already locked");
+                }
+            }
+
             mutex_ = &mutex;
         }
 
@@ -105,6 +124,7 @@ public:
     private:
         Mutex* mutex_;
     };
+
     /// \brief If the mutex is currently locked
     ///
     /// This is debug aiding method only. And it might be unavailable in
@@ -113,11 +133,36 @@ public:
     ///
     /// \todo Disable in non-debug build
     bool locked() const;
+
 private:
-    class Impl;
-    Impl* impl_;
+    /// \brief Lock the mutex
+    ///
+    /// This method blocks until the mutex can be locked.
+    ///
+    /// Please consider not using this method directly and instead using
+    /// a Mutex::Locker object instead.
     void lock();
+
+    /// \brief Try to lock the mutex
+    ///
+    /// This method doesn't block and returns immediately with a status
+    /// on whether the lock operation was successful.
+    ///
+    /// Please consider not using this method directly and instead using
+    /// a Mutex::Locker object instead.
+    ///
+    /// \return true if the lock was successful, false otherwise.
+    bool tryLock();
+
+    /// \brief Unlock the mutex
+    ///
+    /// Please consider not using this method directly and instead using
+    /// a Mutex::Locker object instead.
     void unlock();
+
+private:
+    class Impl;
+    Impl* impl_;
 };
 
 

+ 38 - 0
src/lib/util/threads/tests/lock_unittest.cc

@@ -37,6 +37,44 @@ TEST(MutexTest, lockMultiple) {
         Mutex::Locker l2(mutex); // Attempt to lock again.
     }, isc::InvalidOperation);
     EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+    // block=true explicitly.
+    Mutex mutex2;
+    EXPECT_FALSE(mutex2.locked()); // Debug-only build
+    Mutex::Locker l12(mutex2, true);
+    EXPECT_TRUE(mutex2.locked()); // Debug-only build
+}
+
+void
+testThread(Mutex* mutex)
+{
+    // block=false (tryLock).  This should not block indefinitely, but
+    // throw AlreadyLocked. If block were true, this would block
+    // indefinitely here.
+    EXPECT_THROW({
+        Mutex::Locker l3(*mutex, false);
+    }, Mutex::Locker::AlreadyLocked);
+
+    EXPECT_TRUE(mutex->locked()); // Debug-only build
+}
+
+// Test the non-blocking variant using a second thread.
+TEST(MutexTest, lockNonBlocking) {
+    // block=false (tryLock).
+    Mutex mutex;
+    Mutex::Locker l1(mutex, false);
+    EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+    // First, try another locker from the same thread.
+    EXPECT_THROW({
+        Mutex::Locker l2(mutex, false);
+    }, Mutex::Locker::AlreadyLocked);
+
+    EXPECT_TRUE(mutex.locked()); // Debug-only build
+
+    // Now try another locker from a different thread.
+    Thread thread(boost::bind(&testThread, &mutex));
+    thread.wait();
 }
 
 // Destroying a locked mutex is a bad idea as well

+ 1 - 2
src/lib/util/unittests/Makefile.am

@@ -20,8 +20,7 @@ if HAVE_GTEST
 libutil_unittests_la_CPPFLAGS += $(GTEST_INCLUDES)
 endif
 
-libutil_unittests_la_LIBADD  = $(top_builddir)/src/lib/util/libb10-util.la
-libutil_unittests_la_LIBADD += $(top_builddir)/src/lib/util/io/libb10-util-io.la
+libutil_unittests_la_LIBADD  = $(top_builddir)/src/lib/util/io/libb10-util-io.la
 libutil_unittests_la_LIBADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 
 CLEANFILES = *.gcno *.gcda