Browse Source

[3400] Merge remote-tracking branch 'origin/trac3449' into trac3400

Conflicts:
	ChangeLog
Tomek Mrugalski 11 years ago
parent
commit
80ef9a69a2
58 changed files with 2188 additions and 1040 deletions
  1. 12 0
      ChangeLog
  2. 3 2
      configure.ac
  3. 3 20
      doc/Doxyfile
  4. 0 60
      doc/devel/02-dhcp.dox
  5. 156 0
      doc/devel/config-backend.dox
  6. 5 0
      doc/devel/mainpage.dox
  7. 151 86
      doc/guide/bind10-guide.xml
  8. 6 2
      examples/m4/ax_isc_rpath.m4
  9. 96 90
      src/bin/d2/d2_config.cc
  10. 44 37
      src/bin/d2/d2_config.h
  11. 23 40
      src/bin/d2/tests/d2_cfg_mgr_unittests.cc
  12. 0 1
      src/bin/d2/tests/nc_test_utils.cc
  13. 48 19
      src/bin/dhcp4/config_parser.cc
  14. 21 0
      src/bin/dhcp4/dhcp4.spec
  15. 1 0
      src/bin/dhcp4/tests/Makefile.am
  16. 40 45
      src/bin/dhcp4/tests/config_parser_unittest.cc
  17. 17 8
      src/bin/dhcp4/tests/d2_unittest.cc
  18. 9 3
      src/bin/dhcp4/tests/d2_unittest.h
  19. 0 3
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  20. 1 0
      src/bin/dhcp4/tests/fqdn_unittest.cc
  21. 21 0
      src/bin/dhcp6/dhcp6.spec
  22. 59 35
      src/bin/dhcp6/json_config_parser.cc
  23. 1 0
      src/bin/dhcp6/tests/Makefile.am
  24. 60 102
      src/bin/dhcp6/tests/config_parser_unittest.cc
  25. 18 9
      src/bin/dhcp6/tests/d2_unittest.cc
  26. 9 4
      src/bin/dhcp6/tests/d2_unittest.h
  27. 2 2
      src/bin/dhcp6/tests/dhcp6_test_utils.cc
  28. 3 1
      src/bin/dhcp6/tests/fqdn_unittest.cc
  29. 1 1
      src/lib/asiodns/tests/io_fetch_unittest.cc
  30. 24 8
      src/lib/cc/data.cc
  31. 34 8
      src/lib/cc/data.h
  32. 34 13
      src/lib/cc/tests/data_unittests.cc
  33. 0 30
      src/lib/dhcp/option_custom.cc
  34. 29 0
      src/lib/dhcp/option_custom.h
  35. 1 1
      src/lib/dhcp/option_definition.cc
  36. 1 1
      src/lib/dhcpsrv/Makefile.am
  37. 57 24
      src/lib/dhcpsrv/d2_client_cfg.cc
  38. 34 0
      src/lib/dhcpsrv/d2_client_cfg.h
  39. 4 10
      src/lib/dhcpsrv/d2_client_mgr.cc
  40. 17 10
      src/lib/dhcpsrv/dbaccess_parser.cc
  41. 323 181
      src/lib/dhcpsrv/dhcp_parsers.cc
  42. 158 82
      src/lib/dhcpsrv/dhcp_parsers.h
  43. 3 3
      src/lib/dhcpsrv/dhcpsrv_messages.mes
  44. 36 22
      src/lib/dhcpsrv/memfile_lease_mgr.cc
  45. 10 11
      src/lib/dhcpsrv/memfile_lease_mgr.h
  46. 1 0
      src/lib/dhcpsrv/tests/Makefile.am
  47. 128 28
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  48. 81 11
      src/lib/dhcpsrv/tests/d2_client_unittest.cc
  49. 44 3
      src/lib/dhcpsrv/tests/d2_udp_unittest.cc
  50. 160 14
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  51. 3 3
      src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
  52. 25 0
      src/lib/dhcpsrv/testutils/Makefile.am
  53. 94 0
      src/lib/dhcpsrv/testutils/config_result_check.cc
  54. 56 0
      src/lib/dhcpsrv/testutils/config_result_check.h
  55. 7 0
      src/lib/dns/python/Makefile.am
  56. 0 7
      src/lib/dns/tests/labelsequence_unittest.cc
  57. 7 0
      src/lib/python/isc/log/Makefile.am
  58. 7 0
      src/lib/python/isc/util/cio/Makefile.am

+ 12 - 0
ChangeLog

@@ -5,6 +5,18 @@
 	JSON, which reads configuration from a JSON file.
 	(Trac #3400, git TBD)
 
+782.	[func]		tmark
+	Added sender-ip, sender-port, and max-queue-size parameters to
+	the dhcp-ddns configuration section of both b10-dhcp4 and b10-dhcp6.
+	(Trac #3328,    git 8d8d0b5eedaab20bf1008dfb3a6913eb006a6e73)
+
+781.	[func]		marcin
+	libkea-dhcpsrv: the Memfile lease storage backend returns leases
+	of a specified type. Previously, it ignored the lease type parameter
+	and returned all leases for a particular client. Thanks to David
+	Carlier for helping to implement this ticket.
+	(Trac #3148, git d2f0edf473716cd747a21d6917e89ba55c148d8e)
+
 780.	[func]		marcin
 	libkea-cc: JSON parser stores information about the position
 	of the data element values in the JSON string. The position

+ 3 - 2
configure.ac

@@ -292,7 +292,7 @@ case "$host" in
 	;;
 esac
 
-m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.3 python3.2 python3.1 python3])
+m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.4 python3.3 python3.2 python3.1 python3])
 AC_ARG_WITH([pythonpath],
 AC_HELP_STRING([--with-pythonpath=PATH],
   [specify an absolute path to python executable when automatic version check (incorrectly) fails]),
@@ -916,7 +916,7 @@ if test "$MYSQL_CONFIG" != "" ; then
 fi
 
 # Solaris puts FIONREAD in filio.h
-AC_CHECK_HEADER(sys/filio.h)
+AC_CHECK_HEADERS(sys/filio.h,,,)
 
 # ... and at the shell level, so Makefile.am can take action depending on this.
 AM_CONDITIONAL(HAVE_MYSQL, test "$MYSQL_CONFIG" != "")
@@ -1495,6 +1495,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/lib/dhcp_ddns/tests/Makefile
                  src/lib/dhcp/Makefile
                  src/lib/dhcpsrv/Makefile
+                 src/lib/dhcpsrv/testutils/Makefile
                  src/lib/dhcpsrv/tests/Makefile
                  src/lib/dhcpsrv/tests/test_libraries.h
                  src/lib/dhcp/tests/Makefile

+ 3 - 20
doc/Doxyfile

@@ -324,22 +324,6 @@ INLINE_SIMPLE_STRUCTS  = NO
 
 TYPEDEF_HIDES_STRUCT   = NO
 
-# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
-# determine which symbols to keep in memory and which to flush to disk.
-# When the cache is full, less often used symbols will be written to disk.
-# For small to medium size projects (<1000 input files) the default value is
-# probably good enough. For larger projects a too small cache size can cause
-# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penalty.
-# If the system has enough physical memory increasing the cache will improve the
-# performance by keeping more symbols in memory. Note that the value works on
-# a logarithmic scale so increasing the size by one will roughly double the
-# memory usage. The cache size is given by this formula:
-# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols.
-
-SYMBOL_CACHE_SIZE      = 0
-
 # Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
 # set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
 # their name and scope. Since this can be an expensive process and often the
@@ -678,7 +662,6 @@ INPUT                  = ../src/bin/d2 \
                          ../src/lib/hooks \
                          ../src/lib/log \
                          ../src/lib/log/compiler \
-                         ../src/lib/resolve \
                          ../src/lib/testutils \
                          ../src/lib/util \
                          ../src/lib/util/io \
@@ -1660,18 +1643,18 @@ DOT_NUM_THREADS        = 0
 # the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
 # directory containing the font.
 
-DOT_FONTNAME           = FreeSans
+#DOT_FONTNAME           = FreeSans
 
 # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
 # The default size is 10pt.
 
-DOT_FONTSIZE           = 10
+#DOT_FONTSIZE           = 10
 
 # By default doxygen will tell dot to use the Helvetica font.
 # If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
 # set the path where dot can find it.
 
-DOT_FONTPATH           =
+#DOT_FONTPATH           =
 
 # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
 # will generate a graph for each documented class showing the direct and

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

@@ -1,60 +0,0 @@
-/**
- * @page dhcpv4 DHCPv4 Server Component
- *
- * BIND10 offers DHCPv4 server implementation. It is implemented as
- * b10-dhcp4 component.  Its primary code is located in
- * isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively,
- * especially isc::dhcp::Pkt4, 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.
- *
- * DHCPv4 server component does not support direct traffic (relayed
- * only), as support for transmission to hosts without IPv4 address
- * assigned is not implemented in IfaceMgr yet.
- *
- * DHCPv4 server component does not use BIND10 logging yet.
- *
- * @section dhcpv4Session BIND10 message queue integration
- *
- * DHCPv4 server component is now integrated with the BIND10 message queue.
- * The integration is performed by establishSession() and disconnectSession()
- * functions in isc::dhcp::ControlledDhcpv4Srv class. main() method defined
- * in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
- * class that establishes connection with msgq and install necessary handlers
- * for receiving commands and configuration updates. It is derived from
- * a base isc::dhcp::Dhcpv4Srv class that implements DHCPv4 server functionality,
- * without any controlling mechanisms.
- *
- * ControlledDhcpv4Srv instantiates several components to make management
- * session possible. In particular, isc::cc::Session cc_session
- * object uses ASIO for establishing connection. It registers its socket
- * in isc::asiolink::IOService io_service object. Typically, other components
- * (e.g. auth or resolver) that use ASIO for their communication, register their
- * other sockets in the
- * same io_service and then just call io_service.run() method that does
- * not return, until one of the callback decides that it is time to shut down
- * the whole component cal calls io_service.stop(). DHCPv4 works in a
- * different way. It does receive messages using select()
- * (see isc::dhcp::IfaceMgr::receive4()), which is incompatible with ASIO.
- * To solve this problem, socket descriptor is extracted from cc_session
- * object and is passed to IfaceMgr by using isc::dhcp::IfaceMgr::set_session_socket().
- * IfaceMgr then uses this socket in its select() call. If there is some
- * data to be read, it calls registered callback that is supposed to
- * read and process incoming data.
- *
- * This somewhat complicated approach is needed for a simple reason. In
- * embedded deployments there will be no message queue. Not referring directly
- * to anything related to message queue in isc::dhcp::Dhcpv4Srv and
- * isc::dhcp::IfaceMgr classes brings in two benefits. First, the can
- * be used with and without message queue. Second benefit is related to the
- * first one: \ref libdhcp is supposed to be simple and robust and not require
- * many dependencies. One notable example of a use case that benefits from
- * this approach is a perfdhcp tool. Finally, the idea is that it should be
- * possible to instantiate Dhcpv4Srv object directly, thus getting a server
- * that does not support msgq. That is useful for embedded environments.
- * It may also be useful in validation.
- *
- */

+ 156 - 0
doc/devel/config-backend.dox

@@ -0,0 +1,156 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/**
+
+ @page configBackend Kea Configuration Backends
+
+@section configBackendIntro Introduction
+
+Kea is a flexible DHCP protocol engine. It offers a selection of lease database
+backends, extensibility via the hooks API and the definition of custom options.
+Depending on the environment, one lease database backend may be better than
+other. Similarly, because the best way of configuring the server can depend on
+the environment, Kea provides different ways of obtaining configuration
+information, through the Configuration Backend. Since the means by which
+configuration information is received cannot be part of the configuration itself, it
+has to be chosen at the compilation time (when configuring the sources).
+
+This page explains the background to the Configuration Backend and how
+it is implemented. It is aimed at people who want to develop and
+maintain their own backends.
+
+@section configBackendMotivation Motivation for Different Backends
+
+BIND10 (the project under which the first stages of Keas were developed)
+used to maintain an extensive framework that was responsible for the
+configuration of components.  After BIND10 was cancelled, two projects
+were created: <a href="http://kea.isc.org">Kea</a> (focused on DHCP)
+and <a href="http://www.bundy-dns.de">Bundy</a> (aimed at DNS). The
+Kea team decided to remove the BIND10 framework, while the Bundy team
+decided to keep it. However, even though the Kea team is focused on a
+backend that reads a JSON configuration file from disk, it decided to
+make it easy for others to use different backends.
+
+While ISC currently (May 2014) plans to maintain only one configuration backend
+(a JSON file read from disk), there may be other organizations (e.g.
+the Bundy project community) that will maintain other backends. It is quite
+possible that additional backends (e.g. using LDAP or XML) will be
+developed and maintained by other organizations.
+
+@section configBackendAdding How to Add a New Configuration Backend
+
+@todo Will be covered in ticket #3400.
+
+@section configBackendJSONDesign The JSON Configuration Backend
+
+The following are some considerations that shaped the design of the configuration
+backend framework.
+
+-# A new parameter called --with-kea-config will be implemented in the
+   configure script. It will allow the selection at compilation time of how the
+   servers will be configured. For the next 2-3 months (until around June 2014),
+   there will be two values: JSON (read from file) and BIND10 (use the BIND10 framework).
+   Once the file based configuration is implemented and the Kea team is ready to switch
+   (i.e. enough confidence, Forge tests updated for new configuration
+   mechanism), the BIND10 backend will be removed from the Kea repository. Other projects
+   (e.g. Bundy) who want to maintain it, are advised to just revert the single
+   commit that will bring the BIND10 framework back to their repositories.<br/><br/>
+   This switchable backend concept is quite simple. There are just different
+   implementations of ControlledXSrv class, so it is a matter of compiling/linking
+   one file or another. Hence it is easy to remove the old backend (and for
+   Bundy to keep it if they desire so). It is also easy for other
+   organizations to add and maintain their own backends (e.g. LDAP).<br/><br/>
+-# Each backend must use the common code
+   for configuration and command processing callbacks. They all assume that
+   JSON formatted parameters are sent and they are expected to return well
+   formatted JSON responses. The exact format of configuration and commands is
+   module specific.<br/><br/>
+-# After Kea 0.9 is released, a form of secure socket will be implemented through
+   which commands can be sent. Whatever the design, it
+   will allow the sending of configurations and commands in JSON format and
+   the receiving of responses.<br/><br/>
+   Once that is done, Kea will have the same capability the BIND10
+   framework to send additional parameters. One obvious use case will be
+   to send a new configuration file name as the parameter for "reload".<br/><br/>
+-# A command handler needs to be added for reading the configuration from a file. Its main
+   responsibility is to load the configuration and process it. The JSON backend
+   must call that handler when starting up the server.<br/><br/>
+-# Extend the existing JSON parser. The current JSON parser in
+   @ref isc::data::Element::fromJSON() needs to be extended to allow optional preprocessing.
+   For now that capability will simply remove whole-line comments staring with the hash
+   character, but it is expected
+   to grow over time (in-line comments and file inclusions are the obvious
+   envisaged additions).<br/><br/>
+-# Implement a common base class for the Kea4, Kea6, and D2 servers. Some operations will be
+   common for all three components: logger initialization, handling and, at some future point,
+   control socket. This calls for a small base class that @ref isc::dhcp::Dhcpv4Srv "Dhcpv4Srv",
+   @ref isc::dhcp::Dhcpv6Srv "Dhcpv6Srv" and the @ref isc::d2::D2Controller "D2Controller" classes can use.
+   It is expected that the base class
+   (@ref isc::dhcp::Daemon) will be a small one but will grow over time as the code is unified.<br/><br/>
+-# A way is needed to initialize stand-alone logging (i.e. each
+   Kea component will initialize it on its own).<br/><br/>
+-# The current format of the BIND10 configuration file, b10-config.db will be
+   retained as the configuration file format.  This is slight change
+   from the BIND10 days, as then a subset of the configuration was received by
+   the daemon processes.<br/><br/>
+   To take a specific example, the following is how b10-config.db
+   looks today:<br/><br/>
+   @code
+   {
+     "Init": { ... }
+     "Dhcp4": {
+       "subnet4" { subnet definitions here },
+       "option-data" { option data here },
+       "interfaces": [ "eth0" ],
+       ...
+    },
+     "Dhcp6": {
+       "subnet6" { subnet definitions here },
+       "option-data" { option data here },
+       "interfaces": [ "eth0" ],
+       ...
+     },
+     "Logging": {
+       "Loggers": [{"name": *, "severity": "DEBUG" }]
+      }
+   }
+   @endcode
+   <br/>
+   The Kea components used to receive only relevant parts of it (e.g. Kea4
+   received config that contained content of the Dhcp4 element). Now they
+   will receive all of it. The modification in the code to handle this
+   is really minor: just iterate over the top level elements and pick the appropriate
+   tree (or get the element by name). Also, that approach makes the logging
+   initialization code very easy to share among Kea4, Kea6 and D2.<br/><br/>
+-# The .spec files used in BIND 10 by the control program to validate commands
+   will be retained. They will be kept and maintained even though no use of
+   them is planned. At some future time syntax validation may be implemented,
+   although it is out of scope for Kea 0.9 (and probably
+   for 1.0 as it is pretty big task).<br/><br/>
+-# Addition of a shell script to start/stop Kea4,Kea6 and D2. There will be a script that will
+   start, stop and reconfigure the daemons. Its only
+   job will be to pass the configuration file to each daemon and remember its PID file, so
+   that sending signals will be be possible (for configuration reload or shutdown). Optionally,
+   it could also print out a status based on PID, but that may be tricky to
+   implement in a portable way. The minimum set of commands will be:
+   -# Start the processes
+      - eventually based on configuration, initially start them all
+      - it could launch a nanny script which restarts them on a crash (past 0.9)
+   -# Prompt the processes to reload configuration
+      - for now it will be a matter of sending singal to the right process
+      - this could also decide if D2 should still be running or not, and react accordingly (post 0.9)
+   -# Stop the processes in an orderly fashion
+   -# Perhaps return status of each process
+*/

+ 5 - 0
doc/devel/mainpage.dox

@@ -87,6 +87,10 @@
  *   - @subpage cfgmgr
  *   - @subpage allocengine
  * - @subpage dhcpDatabaseBackends
+ * - @subpage configBackend
+ *   - @subpage configBackendMotivation
+ *   - @subpage configBackendJSONDesign
+ *   - @subpage configBackendAdding
  * - @subpage perfdhcpInternals
  * - @subpage libdhcp_ddns
  *
@@ -95,6 +99,7 @@
  *   - @subpage logBasicIdeas
  *   - @subpage logDeveloperUse
  *   - @subpage logNotes
+ * - @subpage LoggingApi
  * - @subpage SocketSessionUtility
  * - <a href="./doxygen-error.log">Documentation warnings and errors</a>
  *

+ 151 - 86
doc/guide/bind10-guide.xml

@@ -2167,6 +2167,9 @@ Dhcp4/subnet4/	list
 Dhcp4/dhcp-ddns/enable-updates	true	boolean
 Dhcp4/dhcp-ddns/server-ip	"127.0.0.1"	string
 Dhcp4/dhcp-ddns/server-port	53001	integer
+Dhcp4/dhcp-ddns/sender-ip	""	string
+Dhcp4/dhcp-ddns/sender-port	0	integer
+Dhcp4/dhcp-ddns/max-queue-size	1024	integer
 Dhcp4/dhcp-ddns/ncr-protocol	"UDP"	string
 Dhcp4/dhcp-ddns/ncr-format	"JSON"	string
 Dhcp4/dhcp-ddns/override-no-update	false	boolean
@@ -3052,7 +3055,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
     <section id="dhcp4-ddns-config">
       <title>Configuring DHCPv4 for DDNS</title>
       <para>
-      As mentioned earlier, DHCPv4 can be configured to generate requests to the
+      As mentioned earlier, b10-dhcp4 can be configured to generate requests to the
       DHCP-DDNS server to update DNS entries.  These requests are known as
       NameChangeRequests or NCRs.  Each NCR contains the following information:
       <orderedlist>
@@ -3068,13 +3071,16 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       </para></listitem>
       </orderedlist>
       The parameters for controlling the generation of NCRs for submission to D2
-      are contained in the "dhcp-ddns" section of the DHCPv4 server
+      are contained in the "dhcp-ddns" section of the b10-dhcp4 server
       configuration. The default values for this section appears as follows:
 <screen>
 &gt; <userinput>config show Dhcp4/dhcp-ddns</userinput>
 Dhcp4/dhcp-ddns/enable-updates	true	boolean
 Dhcp4/dhcp-ddns/server-ip	"127.0.0.1"	string
 Dhcp4/dhcp-ddns/server-port	53001	integer
+Dhcp4/dhcp-ddns/sender-ip	""	string
+Dhcp4/dhcp-ddns/sender-port	0	integer
+Dhcp4/dhcp-ddns/max-queue-size	1024	integer
 Dhcp4/dhcp-ddns/ncr-protocol	"UDP"	string
 Dhcp4/dhcp-ddns/ncr-format	"JSON"	string
 Dhcp4/dhcp-ddns/override-no-update	false	boolean
@@ -3085,7 +3091,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
 </screen>
       </para>
       <para>
-      The "enable-updates" parameter determines whether or not DHCPv4 will
+      The "enable-updates" parameter determines whether or not b10-dhcp4 will
       generate NCRs.  By default, this value is false hence DDNS updates are
       disabled.  To enable DDNS updates set this value to true:
       </para>
@@ -3096,47 +3102,74 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       <section id="dhcpv4-d2-io-config">
       <title>DHCP-DDNS Server Connectivity</title>
       <para>
-      In order for NCRs to reach the D2 server, DHCPv4 must be able
-      to communicate with it and so the relevant parameters must be set
-      appropriately.  The parameters, "server-ip" and "server-port", specify
-      the address of the D2 server.  By default, D2 is assumed to running
-      on the same machine as DHCPv4, and the default values for these two
-      parameters should be sufficient. However, if D2 has been configured
-      to listen on a different address or port, these values must altered
-      accordingly.  For example, if D2 has been configured to listen on
-      198.162.1.10 port 900, the following commands would be required:
+      In order for NCRs to reach the D2 server, b10-dhcp4 must be able
+      to communicate with it.  b10-dhcp4 uses the following configuration
+      parameters to control how it communications with D2:
+      <orderedlist>
+      <listitem><para>
+      server-ip - IP address on which D2 listens for requests. The default is
+      the local loopback interface at address 127.0.0.1. You may specify
+      either an IPv4 or IPv6 address.
+      </para></listitem>
+      <listitem><para>
+      server-port - port on which D2 listens for requests.  The default value
+      is 53001.
+      </para></listitem>
+      <listitem><para>
+      sender-ip - IP address which b10-dhcp4 should use to send requests to D2.
+      The default value is blank which instructs b10-dhcp4 to select a suitable
+      address.
+      </para></listitem>
+      <listitem><para>
+      sender-port - port which b10-dhcp4 should use to send requests to D2. The
+      default value of 0 instructs b10-dhcp4 to select suitable port.
+      </para></listitem>
+      <listitem><para>
+      ncr-format - Socket protocol use when sending requests to D2.  Currently
+      only UDP is supported.  TCP may be available in an upcoming release.
+      </para></listitem>
+      <listitem><para>
+      ncr-protocol - Packet format to use when sending requests to D2.
+      Currently only JSON format is supported.  Other formats may be available
+      in future releases.
+      </para></listitem>
+      <listitem><para>
+      max-queue-size - maximum number of requests allowed to queue waiting to
+      be sent to D2. This value guards against requests accumulating
+      uncontrollably if they are being generated faster than they can be
+      delivered.  If the number of requests queued for transmission reaches
+      this value, DDNS updating will be turned off until the queue backlog has
+      been sufficiently reduced.  The intent is allow the b10-dhcp4 server to
+      continue lease operations.  The default value is 1024.
+      </para></listitem>
+      </orderedlist>
+      By default, D2 is assumed to running on the same machine as b10-dhcp4, and
+      all of the default values mentioned above should be sufficient.
+      If, however, D2 has been configured to listen on a different address or
+      port, these values must altered accordingly. For example, if D2 has been
+      configured to listen on 198.162.1.10 port 900, the following commands
+      would be required:
 <screen>
 &gt; <userinput>config set Dhcp4/dhcp-ddns/server-ip "198.162.1.10"</userinput>
 &gt; <userinput>config set Dhcp4/dhcp-ddns/server-port 900</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
-      D2 can be configured to listen over IPv4 or IPv6, therefore server-ip
-      may be either an IPv4 or IPv6 address.
-      </para>
-      <para>
-      The socket protocol that DHCPv4 should use to communicate with D2 is
-      specified with the "ncr-protocol" parameter.  Currently only UDP is
-      supported.
-      </para>
-      <para>
-      The internal format for DDNS update requests sent by DHCPv4 is specified
-      with the "ncr-format" parameter. Currently only JSON is supported.
       </para>
       </section>
       <section id="dhcpv4-d2-rules-config">
-      <title>When does the DHCPv4 server generate DDNS requests?</title>
-      DHCPv4 follows the behavior prescribed for DHCP servers in RFC 4702.
-      It is important to keep in mind that DHCPv4 provides the initial decision
+      <title>When does the b10-dhcp4 server generate DDNS requests?</title>
+      b10-dhcp4 follows the behavior prescribed for DHCP servers in RFC 4702.
+      It is important to keep in mind that b10-dhcp4 provides the initial decision
       making of when and what to update and forwards that information to D2 in
       the form of NCRs. Carrying out the actual DNS updates and dealing with
       such things as conflict resolution are the purview of D2 (<xref linkend="dhcp-ddns-server"/>).
       <para>
-      This section describes when DHCPv4 will generate NCRs and the
+      This section describes when b10-dhcp4 will generate NCRs and the
       configuration parameters that can be used to influence this decision.
       It assumes that the "enable-updates" parameter is true.
       </para>
       <para>
-      In general, DHCPv4 will generate DDNS update requests when:
+      In general, b10-dhcp4 will generate DDNS update requests when:
       <orderedlist>
       <listitem><para>
       A new lease is granted in response to a DHCP REQUEST
@@ -3157,10 +3190,10 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       involved and is discussed next.
       </para>
       <para>
-      When a new lease is granted, the DHCPv4 server will generate a DDNS
+      When a new lease is granted, b10-dhcp4 will generate a DDNS
       update request if the DHCP REQUEST contains either the FQDN option
       (code 81) or the Host Name option (code 12). If both are present,
-      the server will use the FQDN option. By default the DHCPv4 server
+      the server will use the FQDN option. By default b10-dhcp4
       will respect the FQDN N and S flags specified by the client as shown
       in the following table:
       </para>
@@ -3206,11 +3239,11 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       <para>
       The first row in the table above represents "client delegation". Here
       the DHCP client states that it intends to do the forward DNS updates and
-      the server should do the reverse updates.  By default, DHCPv4 will honor
+      the server should do the reverse updates.  By default, b10-dhcp4 will honor
       the client's wishes and generate a DDNS request to D2 to update only
       reverse DNS data.  The parameter, "override-client-update", can be used
       to instruct the server to override client delegation requests.  When
-      this parameter is true, DHCPv4 will disregard requests for client
+      this parameter is true, b10-dhcp4 will disregard requests for client
       delegation and generate a DDNS request to update both forward and
       reverse DNS data.  In this case, the N-S-O flags in the server's
       response to the client will be 0-1-1 respectively.
@@ -3218,7 +3251,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       <para>
       (Note that the flag combination N=1, S=1 is prohibited according to
       RFC 4702. If such a combination is received from the client, the packet
-      will be dropped by the DHCPv4 server.)
+      will be dropped by the b10-dhcp4.)
       </para>
       <para>
       To override client delegation, issue the following commands:
@@ -3231,7 +3264,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       The third row in the table above describes the case in which the client
       requests that no DNS updates be done. The parameter, "override-no-update",
       can be used to instruct the server to disregard the client's wishes. When
-      this parameter is true, DHCPv4 will generate DDNS update request to D2
+      this parameter is true, b10-dhcp4 will generate DDNS update request to D2
       even if the client requests no updates be done.  The N-S-O flags in the
       server's response to the client will be 0-1-1.
       </para>
@@ -3243,7 +3276,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
 &gt; <userinput>config commit</userinput>
 </screen>
       <para>
-      DHCPv4 will always generate DDNS update requests if the client request
+      b10-dhcp4 will always generate DDNS update requests if the client request
       only contains the Host Name option. In addition it will include an FQDN
       option in the response to the client with the FQDN N-S-O flags set to
       0-1-0 respectively. The domain name portion of the FQDN option will be
@@ -3251,9 +3284,9 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       </para>
       </section>
       <section id="dhcpv4-fqdn-name-generation">
-      <title>DHCPv4 name generation for DDNS update requests</title>
+      <title>b10-dhcp4 name generation for DDNS update requests</title>
       Each NameChangeRequest must of course include the fully qualified domain
-      name whose DNS entries are to be affected.  DHCPv4 can be configured to
+      name whose DNS entries are to be affected.  b10-dhcp4 can be configured to
       supply a portion or all of that name based upon what it receives from
       the client in the DHCP REQUEST.
       <para>
@@ -3282,7 +3315,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       </orderedlist>
       </para></listitem>
       </orderedlist>
-      To instruct DHCPv4 to always generate the FQDN for a client, set the
+      To instruct b10-dhcp4 to always generate the FQDN for a client, set the
       parameter "replace-client-name" to true as follows:
       </para>
 <screen>
@@ -3310,7 +3343,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
 </screen>
       </section>
       <para>
-      When generating a name, DHCPv4 will construct name of the format:
+      When generating a name, b10-dhcp4 will construct name of the format:
       </para>
       <para>
         [generated-prefix]-[address-text].[qualifying-suffix].
@@ -3326,7 +3359,7 @@ Dhcp4/dhcp-ddns/qualifying-suffix	"example.com"	string
       </para>
     </section>
 
-  </section> <!-- end of configuring DHCPv4 server section with many subsections -->
+  </section> <!-- end of configuring b10-dhcp4 server section with many subsections -->
 
     <section id="dhcp4-serverid">
       <title>Server Identifier in DHCPv4</title>
@@ -3671,6 +3704,9 @@ Dhcp6/subnet6/  list
 Dhcp6/dhcp-ddns/enable-updates  true    boolean
 Dhcp6/dhcp-ddns/server-ip   "127.0.0.1" string
 Dhcp6/dhcp-ddns/server-port 53001   integer
+Dhcp6/dhcp-ddns/sender-ip	""	string
+Dhcp6/dhcp-ddns/sender-port	0	integer
+Dhcp6/dhcp-ddns/max-queue-size	1024  integer
 Dhcp6/dhcp-ddns/ncr-protocol    "UDP"   string
 Dhcp6/dhcp-ddns/ncr-format  "JSON"  string
 Dhcp6/dhcp-ddns/always-include-fqdn false   boolean
@@ -4604,7 +4640,7 @@ should include options from the isc option space:
     <section id="dhcp6-ddns-config">
       <title>Configuring DHCPv6 for DDNS</title>
       <para>
-      As mentioned earlier, DHCPv6 can be configured to generate requests to
+      As mentioned earlier, b10-dhcp6 can be configured to generate requests to
       the DHCP-DDNS server (referred to here as the "D2" server) to update
       DNS entries.  These requests are known as NameChangeRequests or NCRs.
       Each NCR contains the following information:
@@ -4621,13 +4657,16 @@ should include options from the isc option space:
       </para></listitem>
       </orderedlist>
       The parameters controlling the generation of NCRs for submission to D2
-      are contained in the "dhcp-ddns" section of the DHCPv6 server
+      are contained in the "dhcp-ddns" section of b10-dhcp6
       configuration. The default values for this section appears as follows:
 <screen>
 &gt; <userinput>config show Dhcp6/dhcp-ddns</userinput>
 Dhcp6/dhcp-ddns/enable-updates	true	boolean
 Dhcp6/dhcp-ddns/server-ip	"127.0.0.1"	string
 Dhcp6/dhcp-ddns/server-port	53001	integer
+Dhcp6/dhcp-ddns/sender-ip	""	string
+Dhcp6/dhcp-ddns/sender-port	0	integer
+Dhcp6/dhcp-ddns/max-queue-size	1024 integer
 Dhcp6/dhcp-ddns/ncr-protocol	"UDP"	string
 Dhcp6/dhcp-ddns/ncr-format	"JSON"	string
 Dhcp6/dhcp-ddns/override-no-update	false	boolean
@@ -4638,7 +4677,7 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
 </screen>
       </para>
       <para>
-      The "enable-updates" parameter determines whether or not DHCPv6 will
+      The "enable-updates" parameter determines whether or not b10-dhcp6 will
       generate NCRs.  By default, this value is false hence DDNS updates are
       disabled.  To enable DDNS updates set this value to true as follows:
       </para>
@@ -4648,58 +4687,84 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
 </screen>
       <section id="dhcpv6-d2-io-config">
       <title>DHCP-DDNS Server Connectivity</title>
-      In order for NCRs to reach D2, DHCPv6 must be able to communicate with it.
-      The following parameters are used to establish connectivty between DHCPv6
-      and D2.
-      <para>
-      The parameters, "server-ip" and "server-port", specify the address of the
-      D2 server.  By default, D2 is assumed to running on the same machine as
-      DHCPv6, and the default values for these two parameters should be
-      sufficient. However, if D2 has been configured to listen on a different
-      address or port, these values must altered accordingly.  For example, if
-      D2 has been configured to listen on 198.162.1.10 port 900, the following
-      commands would be required:
-      </para>
+      <para>
+      In order for NCRs to reach the D2 server, b10-dhcp6 must be able
+      to communicate with it.  b10-dhcp6 uses the following configuration
+      parameters to control how it communications with D2:
+      <orderedlist>
+      <listitem><para>
+      server-ip - IP address on which D2 listens for requests. The default is
+      the local loopback interface at address 127.0.0.1. You may specify
+      either an IPv4 or IPv6 address.
+      </para></listitem>
+      <listitem><para>
+      server-port - port on which D2 listens for requests.  The default value
+      is 53001.
+      </para></listitem>
+      <listitem><para>
+      sender-ip - IP address which b10-dhcp6 should use to send requests to D2.
+      The default value is blank which instructs b10-dhcp6 to select a suitable
+      address.
+      </para></listitem>
+      <listitem><para>
+      sender-port - port which b10-dhcp6 should use to send requests to D2. The
+      default value of 0 instructs b10-dhcp6 to select suitable port.
+      </para></listitem>
+      <listitem><para>
+      ncr-format - Socket protocol use when sending requests to D2.  Currently
+      only UDP is supported.  TCP may be available in an upcoming release.
+      </para></listitem>
+      <listitem><para>
+      ncr-protocol - Packet format to use when sending requests to D2.
+      Currently only JSON format is supported.  Other formats may be available
+      in future releases.
+      </para></listitem>
+      <listitem><para>
+      max-queue-size - maximum number of requests allowed to queue waiting to
+      be sent to D2. This value guards against requests accumulating
+      uncontrollably if they are being generated faster than they can be
+      delivered.  If the number of requests queued for transmission reaches
+      this value, DDNS updating will be turned off until the queue backlog has
+      been sufficiently reduced.  The intent is allow b10-dhcp6 to
+      continue lease operations.  The default value is 1024.
+      </para></listitem>
+      </orderedlist>
+      By default, D2 is assumed to running on the same machine as b10-dhcp6, and
+      all of the default values mentioned above should be sufficient.
+      If, however, D2 has been configured to listen on a different address or
+      port, these values must altered accordingly. For example, if D2 has been
+      configured to listen on 3001::5 port 900, the following commands
+      would be required:
 <screen>
-&gt; <userinput>config set Dhcp6/dhcp-ddns/server-ip "198.162.1.10"</userinput>
+&gt; <userinput>config set Dhcp6/dhcp-ddns/server-ip "3001::5"</userinput>
 &gt; <userinput>config set Dhcp6/dhcp-ddns/server-port 900</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
-      D2 can be configured to listen over IPv4 or IPv6, therefore server-ip
-      may be either an IPv4 or IPv6 address.
-      <para>
-      The socket protocol that DHCPv6 should use to communicate with D2 is
-      specified with the "ncr-protocol" parameter.  Currently only UDP is
-      supported.
-      </para>
-      <para>
-      The internal format for DDNS update requests sent by DHCPv6 is specified
-      with the "ncr-format" parameter. Currently only JSON is supported.
       </para>
       </section>
       <section id="dhcpv6-d2-rules-config">
-      <title>When does DHCPv6 generate DDNS request</title>
-      DHCPv6 follows the behavior prescribed for DHCP servers in RFC 4704.
-      It is important to keep in mind that DHCPv6 provides the initial decision
+      <title>When does b10-dhcp6 generate DDNS request</title>
+      b10-dhcp6 follows the behavior prescribed for DHCP servers in RFC 4704.
+      It is important to keep in mind that b10-dhcp6 provides the initial decision
       making of when and what to update and forwards that information to D2 in
       the form of NCRs. Carrying out the actual DNS updates and dealing with
       such things as conflict resolution are the purview of D2 (<xref linkend="dhcp-ddns-server"/>).
       <para>
-      This section describes when DHCPv6 will generate NCRs and the
+      This section describes when b10-dhcp6 will generate NCRs and the
       configuration parameters that can be used to influence this decision.
       It assumes that the "enable-updates" parameter is true.
       </para>
       <note>
         <para>
-        Currently the interface between DHCPv6 and D2 only supports requests
+        Currently the interface between b10-dhcp6 and D2 only supports requests
         which update DNS entries for a single IP address.  If a lease grants
-        more than one address, DHCPv6 will create the DDNS update request for
+        more than one address, b10-dhcp6 will create the DDNS update request for
         only the first of these addresses.  Support for multiple address
         mappings may be provided in a future release.
         </para>
       </note>
       <para>
-      In general, DHCPv6 will generate DDNS update requests when:
+      In general, b10-dhcp6 will generate DDNS update requests when:
       <orderedlist>
       <listitem><para>
       A new lease is granted in response to a DHCP REQUEST
@@ -4720,8 +4785,8 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
       discussed next.
       </para>
       <para>
-      DHCPv6 will generate a DDNS update request only if the DHCP REQUEST
-      contains the FQDN option (code 39). By default the DHCPv6 server will
+      b10-dhcp6 will generate a DDNS update request only if the DHCP REQUEST
+      contains the FQDN option (code 39). By default b10-dhcp6 will
       respect the FQDN N and S flags specified by the client as shown in the
       following table:
       </para>
@@ -4767,11 +4832,11 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
       <para>
       The first row in the table above represents "client delegation". Here
       the DHCP client states that it intends to do the forward DNS updates and
-      the server should do the reverse updates.  By default, DHCPv6 will honor
+      the server should do the reverse updates.  By default, b10-dhcp6 will honor
       the client's wishes and generate a DDNS request to D2 to update only
       reverse DNS data.  The parameter, "override-client-update", can be used
       to instruct the server to override client delegation requests.  When
-      this parameter is true, DHCPv6 will disregard requests for client
+      this parameter is true, b10-dhcp6 will disregard requests for client
       delegation and generate a DDNS request to update both forward and
       reverse DNS data.  In this case, the N-S-O flags in the server's
       response to the client will be 0-1-1 respectively.
@@ -4779,7 +4844,7 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
       <para>
       (Note that the flag combination N=1, S=1 is prohibited according to
       RFC 4702. If such a combination is received from the client, the packet
-      will be dropped by the DHCPv6 server.)
+      will be dropped by b10-dhcp6.)
       </para>
       <para>
       To override client delegation, issue the following commands:
@@ -4792,7 +4857,7 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
       The third row in the table above describes the case in which the client
       requests that no DNS updates be done. The parameter, "override-no-update",
       can be used to instruct the server to disregard the client's wishes. When
-      this parameter is true, DHCPv6 will generate DDNS update request to D2
+      this parameter is true, b10-dhcp6 will generate DDNS update request to D2
       even if the client requests no updates be done.  The N-S-O flags in the
       server's response to the client will be 0-1-1.
       </para>
@@ -4805,9 +4870,9 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
 </screen>
       </section>
       <section id="dhcpv6-fqdn-name-generation">
-      <title>DHCPv6 name generation for DDNS update requests</title>
+      <title>b10-dhcp6 name generation for DDNS update requests</title>
       Each NameChangeRequest must of course include the fully qualified domain
-      name whose DNS entries are to be affected.  DHCPv6 can be configured to
+      name whose DNS entries are to be affected.  b10-dhcp6 can be configured to
       supply a portion or all of that name based upon what it receives from
       the client in the DHCP REQUEST.
       <para>
@@ -4835,7 +4900,7 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
       </orderedlist>
       </para></listitem>
       </orderedlist>
-      To instruct DHCPv6 to always generate a FQDN, set the parameter
+      To instruct b10-dhcp6 to always generate a FQDN, set the parameter
       "replace-client-name" to true:
       </para>
 <screen>
@@ -4863,7 +4928,7 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
 </screen>
       </section>
       <para>
-      When qualifying a partial name, DHCPv6 will construct a name with the
+      When qualifying a partial name, b10-dhcp6 will construct a name with the
       format:
       </para>
       <para>
@@ -4878,7 +4943,7 @@ Dhcp6/dhcp-ddns/qualifying-suffix	"example.com"	string
         some-computer.example.com.
       </para>
       <para>
-      When generating a the entire name, DHCPv6 will construct name of the
+      When generating a the entire name, b10-dhcp6 will construct name of the
       format:
       </para>
       <para>
@@ -5108,9 +5173,9 @@ Dhcp6/renew-timer	1000	integer	(default)
   <chapter id="dhcp-ddns-server">
     <title>The DHCP-DDNS Server</title>
     <para>
-    The DHCP-DDNS Server (known informally as D2) conducts the client side of
+    The DHCP-DDNS Server (b10-dhcp-ddns, known informally as D2) conducts the client side of
     the DDNS protocol (defined in RFC 2136) on behalf of the DHCPv4 and DHCPv6
-    servers. The DHCP servers construct
+    servers (b10-dhcp4 and b10-dhcp6 respectively). The DHCP servers construct
     DDNS update requests, known as NameChangeRequests (NCRs), based upon DHCP
     lease change events and then post these to D2. D2 attempts to match
     each such request to the appropriate DNS server(s) and carry out the

+ 6 - 2
examples/m4/ax_isc_rpath.m4

@@ -41,8 +41,12 @@ if test x$rpath != xno; then
             ISC_RPATH_FLAG=-Wl,-R
         ],[ AC_MSG_RESULT(no)
             AC_MSG_CHECKING([whether -R flag is available in linker])
-            CXXFLAGS="$CXXFLAGS_SAVED -R"
-            CCFLAGS="$CCFLAGS_SAVED -R"
+
+	    # Apple clang 5.1 is now considers unknown parameters passed to linker (ld) as errors.
+	    # However, the same unknown parameters passed to compiler (g++ ) are merely threated
+	    # as warnings. To make sure that we pick those up, is to use -Werror.
+            CXXFLAGS="$CXXFLAGS_SAVED -R/usr/lib"
+            CCFLAGS="$CCFLAGS_SAVED -R/usr/lib"
         AC_TRY_LINK([], [],
             [ AC_MSG_RESULT([yes; note that -R is more sensitive about the position in option arguments])
                 ISC_RPATH_FLAG=-R

+ 96 - 90
src/bin/d2/d2_config.cc

@@ -201,30 +201,7 @@ TSIGKeyInfoParser::build(isc::data::ConstElementPtr key_config) {
         parser->build(config_pair.second);
         parser->commit();
     }
-}
-
-isc::dhcp::ParserPtr
-TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
-    DhcpConfigParser* parser = NULL;
-    // Based on the configuration id of the element, create the appropriate
-    // parser. Scalars are set to use the parser's local scalar storage.
-    if ((config_id == "name")  ||
-        (config_id == "algorithm") ||
-        (config_id == "secret")) {
-        parser = new isc::dhcp::StringParser(config_id,
-                                             local_scalars_.getStringStorage());
-    } else {
-        isc_throw(NotImplemented,
-                  "parser error: TSIGKeyInfo parameter not supported: "
-                  << config_id);
-    }
 
-    // Return the new parser instance.
-    return (isc::dhcp::ParserPtr(parser));
-}
-
-void
-TSIGKeyInfoParser::commit() {
     std::string name;
     std::string algorithm;
     std::string secret;
@@ -266,6 +243,33 @@ TSIGKeyInfoParser::commit() {
     (*keys_)[name]=key_info;
 }
 
+isc::dhcp::ParserPtr
+TSIGKeyInfoParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "name")  ||
+        (config_id == "algorithm") ||
+        (config_id == "secret")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: TSIGKeyInfo parameter not supported: "
+                  << config_id);
+    }
+
+    // Return the new parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+TSIGKeyInfoParser::commit() {
+    /// @todo if at some point  TSIG keys need some form of runtime resource
+    /// initialization, such as creating some sort of hash instance in
+    /// crytpolib.  Once TSIG is fully implemented under Trac #3432 we'll know.
+}
+
 // *********************** TSIGKeyInfoListParser  *************************
 
 TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
@@ -278,12 +282,12 @@ TSIGKeyInfoListParser::TSIGKeyInfoListParser(const std::string& list_name,
     }
 }
 
-TSIGKeyInfoListParser::~TSIGKeyInfoListParser(){
+TSIGKeyInfoListParser::~TSIGKeyInfoListParser() {
 }
 
 void
 TSIGKeyInfoListParser::
-build(isc::data::ConstElementPtr key_list){
+build(isc::data::ConstElementPtr key_list) {
     int i = 0;
     isc::data::ConstElementPtr key_config;
     // For each key element in the key list:
@@ -299,6 +303,10 @@ build(isc::data::ConstElementPtr key_list){
         parser->build(key_config);
         parsers_.push_back(parser);
     }
+
+    // Now that we know we have a valid list, commit that list to the
+    // area given to us during construction (i.e. to the d2 context).
+    *keys_ = *local_keys_;
 }
 
 void
@@ -308,10 +316,6 @@ TSIGKeyInfoListParser::commit() {
     BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
         parser->commit();
     }
-
-    // Now that we know we have a valid list, commit that list to the
-    // area given to us during construction (i.e. to the d2 context).
-    *keys_ = *local_keys_;
 }
 
 // *********************** DnsServerInfoParser  *************************
@@ -343,32 +347,6 @@ DnsServerInfoParser::build(isc::data::ConstElementPtr server_config) {
         parser->commit();
     }
 
-}
-
-isc::dhcp::ParserPtr
-DnsServerInfoParser::createConfigParser(const std::string& config_id) {
-    DhcpConfigParser* parser = NULL;
-    // Based on the configuration id of the element, create the appropriate
-    // parser. Scalars are set to use the parser's local scalar storage.
-    if ((config_id == "hostname")  ||
-        (config_id == "ip_address")) {
-        parser = new isc::dhcp::StringParser(config_id,
-                                             local_scalars_.getStringStorage());
-    } else if (config_id == "port") {
-        parser = new isc::dhcp::Uint32Parser(config_id,
-                                             local_scalars_.getUint32Storage());
-    } else {
-        isc_throw(NotImplemented,
-                  "parser error: DnsServerInfo parameter not supported: "
-                  << config_id);
-    }
-
-    // Return the new parser instance.
-    return (isc::dhcp::ParserPtr(parser));
-}
-
-void
-DnsServerInfoParser::commit() {
     std::string hostname;
     std::string ip_address;
     uint32_t port = DnsServerInfo::STANDARD_DNS_PORT;
@@ -407,6 +385,32 @@ DnsServerInfoParser::commit() {
     servers_->push_back(serverInfo);
 }
 
+isc::dhcp::ParserPtr
+DnsServerInfoParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "hostname")  ||
+        (config_id == "ip_address")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else if (config_id == "port") {
+        parser = new isc::dhcp::Uint32Parser(config_id,
+                                             local_scalars_.getUint32Storage());
+    } else {
+        isc_throw(NotImplemented,
+                  "parser error: DnsServerInfo parameter not supported: "
+                  << config_id);
+    }
+
+    // Return the new parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DnsServerInfoParser::commit() {
+}
+
 // *********************** DnsServerInfoListParser  *************************
 
 DnsServerInfoListParser::DnsServerInfoListParser(const std::string& list_name,
@@ -439,17 +443,16 @@ build(isc::data::ConstElementPtr server_list){
         parser->build(server_config);
         parsers_.push_back(parser);
     }
-}
 
-void
-DnsServerInfoListParser::commit() {
     // Domains must have at least one server.
     if (parsers_.size() == 0) {
         isc_throw (D2CfgError, "Server List must contain at least one server");
     }
+}
 
-    // Invoke commit on each server parser. This will cause each one to
-    // create it's server instance and commit it to storage.
+void
+DnsServerInfoListParser::commit() {
+    // Invoke commit on each server parser.
     BOOST_FOREACH(isc::dhcp::ParserPtr parser, parsers_) {
         parser->commit();
     }
@@ -486,34 +489,8 @@ DdnsDomainParser::build(isc::data::ConstElementPtr domain_config) {
         parser->build(config_pair.second);
         parser->commit();
     }
-}
 
-isc::dhcp::ParserPtr
-DdnsDomainParser::createConfigParser(const std::string& config_id) {
-    DhcpConfigParser* parser = NULL;
-    // Based on the configuration id of the element, create the appropriate
-    // parser. Scalars are set to use the parser's local scalar storage.
-    if ((config_id == "name")  ||
-        (config_id == "key_name")) {
-        parser = new isc::dhcp::StringParser(config_id,
-                                             local_scalars_.getStringStorage());
-    } else if (config_id == "dns_servers") {
-       // Server list parser is given in our local server storage. It will pass
-       // this down to its server parsers and is where they will write their
-       // server instances upon commit.
-       parser = new DnsServerInfoListParser(config_id, local_servers_);
-    } else {
-       isc_throw(NotImplemented,
-                "parser error: DdnsDomain parameter not supported: "
-                << config_id);
-    }
-
-    // Return the new domain parser instance.
-    return (isc::dhcp::ParserPtr(parser));
-}
-
-void
-DdnsDomainParser::commit() {
+    // Now construct the domain.
     std::string name;
     std::string key_name;
 
@@ -547,7 +524,35 @@ DdnsDomainParser::commit() {
     DdnsDomainPtr domain(new DdnsDomain(name, key_name, local_servers_));
 
     // Add the new domain to the domain storage.
-    (*domains_)[name]=domain;
+    (*domains_)[name] = domain;
+}
+
+isc::dhcp::ParserPtr
+DdnsDomainParser::createConfigParser(const std::string& config_id) {
+    DhcpConfigParser* parser = NULL;
+    // Based on the configuration id of the element, create the appropriate
+    // parser. Scalars are set to use the parser's local scalar storage.
+    if ((config_id == "name")  ||
+        (config_id == "key_name")) {
+        parser = new isc::dhcp::StringParser(config_id,
+                                             local_scalars_.getStringStorage());
+    } else if (config_id == "dns_servers") {
+       // Server list parser is given in our local server storage. It will pass
+       // this down to its server parsers and is where they will write their
+       // server instances upon commit.
+       parser = new DnsServerInfoListParser(config_id, local_servers_);
+    } else {
+       isc_throw(NotImplemented,
+                "parser error: DdnsDomain parameter not supported: "
+                << config_id);
+    }
+
+    // Return the new domain parser instance.
+    return (isc::dhcp::ParserPtr(parser));
+}
+
+void
+DdnsDomainParser::commit() {
 }
 
 // *********************** DdnsDomainListParser  *************************
@@ -620,6 +625,9 @@ DdnsDomainListMgrParser::build(isc::data::ConstElementPtr domain_config) {
         parser->build(config_pair.second);
         parser->commit();
     }
+
+    // Add the new domain to the domain storage.
+    mgr_->setDomains(local_domains_);
 }
 
 isc::dhcp::ParserPtr
@@ -641,8 +649,6 @@ DdnsDomainListMgrParser::createConfigParser(const std::string& config_id) {
 
 void
 DdnsDomainListMgrParser::commit() {
-    // Add the new domain to the domain storage.
-    mgr_->setDomains(local_domains_);
 }
 
 

+ 44 - 37
src/bin/d2/d2_config.h

@@ -68,7 +68,7 @@ namespace d2 {
 /// any scalars which belong to the manager as well as creating and invoking a
 /// DdnsDomainListParser to parse its list of domain entries.
 ///
-/// A DdnsDomainLiatParser creates and invokes DdnsDomainListParser for each
+/// A DdnsDomainListParser creates and invokes DdnsDomainListParser for each
 /// domain entry in its list.
 ///
 /// A DdnsDomainParser handles the scalars which belong to the domain as well as
@@ -191,7 +191,7 @@ public:
 private:
     /// @brief The name of the key.
     ///
-    /// This value is the unique identifeir thay domains use to
+    /// This value is the unique identifier that domains use to
     /// to specify which TSIG key they need.
     std::string name_;
 
@@ -540,8 +540,9 @@ public:
 
     /// @brief Performs the actual parsing of the given  "tsig_key" element.
     ///
-    /// The results of the parsing are retained internally for use during
-    /// commit.
+    /// Parses a configuration for the elements needed to instantiate a
+    /// TSIGKeyInfo, validates those entries, creates a TSIGKeyInfo instance
+    /// then attempts to add to a list of keys
     ///
     /// @param key_config is the "tsig_key" configuration to parse
     virtual void build(isc::data::ConstElementPtr key_config);
@@ -559,9 +560,9 @@ public:
     /// @return returns a pointer to newly created parser.
     virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
                                                     config_id);
-
-    /// @brief Instantiates a DnsServerInfo from internal data values
-    /// saves it to the storage area pointed to by servers_.
+    /// @brief Commits the TSIGKeyInfo configuration
+    /// Currently this method is a NOP, as the key instance is created and
+    /// then added to a local list of keys in build().
     virtual void commit();
 
 private:
@@ -611,14 +612,11 @@ public:
     /// @param key_list_config is the list of "tsig_key" elements to parse.
     virtual void build(isc::data::ConstElementPtr key_list_config);
 
-    /// @brief Iterates over the internal list of TSIGKeyInfoParsers,
-    /// invoking commit on each.  This causes each parser to instantiate a
-    /// TSIGKeyInfo from its internal data values and add that key
-    /// instance to the local key storage area, local_keys_.   If all of the
-    /// key parsers commit cleanly, then update the context key map (keys_)
-    /// with the contents of local_keys_.  This is done to allow for duplicate
-    /// key detection while parsing the keys, but not get stumped by it
-    /// updating the context with a valid list.
+    /// @brief Commits the list of TSIG keys
+    ///
+    /// Iterates over the internal list of TSIGKeyInfoParsers, invoking
+    /// commit on each one.  Then commits the local list of keys to
+    /// storage.
     virtual void commit();
 
 private:
@@ -629,7 +627,7 @@ private:
     /// the list of newly created TSIGKeyInfo instances. This is given to us
     /// as a constructor argument by an upper level.
     TSIGKeyInfoMapPtr keys_;
-    
+
     /// @brief Local storage area to which individual key parsers commit.
     TSIGKeyInfoMapPtr local_keys_;
 
@@ -657,8 +655,10 @@ public:
     virtual ~DnsServerInfoParser();
 
     /// @brief Performs the actual parsing of the given  "dns_server" element.
-    /// The results of the parsing are retained internally for use during
-    /// commit.
+    ///
+    /// Parses a configuration for the elements needed to instantiate a
+    /// DnsServerInfo, validates those entries, creates a DnsServerInfo instance
+    /// then attempts to add to a list of  servers.
     ///
     /// @param server_config is the "dns_server" configuration to parse
     virtual void build(isc::data::ConstElementPtr server_config);
@@ -677,8 +677,9 @@ public:
     virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
                                                     config_id);
 
-    /// @brief Instantiates a DnsServerInfo from internal data values
-    /// saves it to the storage area pointed to by servers_.
+    /// @brief Commits the configured DnsServerInfo
+    /// Currently this method is a NOP, as the server instance is created and
+    /// then added to the list of servers in build().
     virtual void commit();
 
 private:
@@ -729,10 +730,10 @@ public:
     /// @param server_list_config is the list of "dns_server" elements to parse.
     virtual void build(isc::data::ConstElementPtr server_list_config);
 
-    /// @brief Iterates over the internal list of DnsServerInfoParsers,
-    /// invoking commit on each.  This causes each parser to instantiate a
-    /// DnsServerInfo from its internal data values and add that that server
-    /// instance to the storage area, servers_.
+    /// @brief Commits the list of DnsServerInfos
+    ///
+    /// Iterates over the internal list of DdnsServerInfoParsers, invoking
+    /// commit on each one.
     virtual void commit();
 
 private:
@@ -769,8 +770,10 @@ public:
     virtual ~DdnsDomainParser();
 
     /// @brief Performs the actual parsing of the given  "ddns_domain" element.
-    /// The results of the parsing are retained internally for use during
-    /// commit.
+    ///
+    /// Parses a configuration for the elements needed to instantiate a
+    /// DdnsDomain, validates those entries, creates a DdnsDomain instance
+    /// then attempts to add it to a list of domains.
     ///
     /// @param domain_config is the "ddns_domain" configuration to parse
     virtual void build(isc::data::ConstElementPtr domain_config);
@@ -789,8 +792,9 @@ public:
     virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
                                                     config_id);
 
-    /// @brief Instantiates a DdnsDomain from internal data values
-    /// saves it to the storage area pointed to by domains_.
+    /// @brief Commits the configured DdnsDomain
+    /// Currently this method is a NOP, as the domain instance is created and
+    /// then added to the list of domains in build().
     virtual void commit();
 
 private:
@@ -842,7 +846,7 @@ public:
 
     /// @brief Performs the actual parsing of the given list "ddns_domain"
     /// elements.
-    /// It iterates over each server entry in the list:
+    /// It iterates over each domain entry in the list:
     ///   1. Instantiate a DdnsDomainParser for the entry
     ///   2. Pass the element configuration to the parser's build method
     ///   3. Add the parser instance to local storage
@@ -854,10 +858,10 @@ public:
     /// parse.
     virtual void build(isc::data::ConstElementPtr domain_list_config);
 
-    /// @brief Iterates over the internal list of DdnsDomainParsers, invoking
-    /// commit on each.  This causes each parser to instantiate a DdnsDomain
-    /// from its internal data values and add that domain instance to the
-    /// storage area, domains_.
+    /// @brief Commits the list of DdnsDomains
+    ///
+    /// Iterates over the internal list of DdnsDomainParsers, invoking
+    /// commit on each one.
     virtual void commit();
 
 private:
@@ -902,8 +906,10 @@ public:
     virtual ~DdnsDomainListMgrParser();
 
     /// @brief Performs the actual parsing of the given manager element.
-    /// The results of the parsing are retained internally for use during
-    /// commit.
+    ///
+    /// Parses a configuration for the elements needed to instantiate a
+    /// DdnsDomainListMgr, validates those entries, then creates a
+    /// DdnsDomainListMgr.
     ///
     /// @param mgr_config is the manager configuration to parse
     virtual void build(isc::data::ConstElementPtr mgr_config);
@@ -920,8 +926,9 @@ public:
     virtual isc::dhcp::ParserPtr createConfigParser(const std::string&
                                                     config_id);
 
-    /// @brief Populates the DdnsDomainListMgr from internal data values
-    /// set during parsing.
+    /// @brief Commits the configured DdsnDomainListMgr
+    /// Currently this method is a NOP, as the manager instance is created
+    /// in build().
     virtual void commit();
 
 private:

+ 23 - 40
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

@@ -261,9 +261,8 @@ TEST_F(TSIGKeyInfoTest, invalidEntry) {
                          "}";
     ASSERT_TRUE(fromJSON(config));
 
-    // Verify that build succeeds but commit fails on blank name.
-    EXPECT_NO_THROW(parser_->build(config_set_));
-    EXPECT_THROW(parser_->commit(), D2CfgError);
+    // Verify that build fails on blank name.
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 
     // Config with a blank algorithm entry.
     config = "{"
@@ -274,9 +273,8 @@ TEST_F(TSIGKeyInfoTest, invalidEntry) {
 
     ASSERT_TRUE(fromJSON(config));
 
-    // Verify that build succeeds but commit fails on blank algorithm.
-    EXPECT_NO_THROW(parser_->build(config_set_));
-    EXPECT_THROW(parser_->commit(), D2CfgError);
+    // Verify that build fails on blank algorithm.
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 
     // Config with a blank secret entry.
     config = "{"
@@ -287,9 +285,8 @@ TEST_F(TSIGKeyInfoTest, invalidEntry) {
 
     ASSERT_TRUE(fromJSON(config));
 
-    // Verify that build succeeds but commit fails on blank secret.
-    EXPECT_NO_THROW(parser_->build(config_set_));
-    EXPECT_THROW(parser_->commit(), D2CfgError);
+    // Verify that build fails blank secret
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 }
 
 /// @brief Verifies that TSIGKeyInfo parsing creates a proper TSIGKeyInfo
@@ -347,10 +344,7 @@ TEST_F(TSIGKeyInfoTest, invalidTSIGKeyList) {
     ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
 
     // Verify that the list builds without errors.
-    ASSERT_NO_THROW(parser->build(config_set_));
-
-    // Verify that the list commit fails.
-    EXPECT_THROW(parser->commit(), D2CfgError);
+    EXPECT_THROW(parser->build(config_set_), D2CfgError);
 }
 
 /// @brief Verifies that attempting to parse an invalid list of TSIGKeyInfo
@@ -380,10 +374,7 @@ TEST_F(TSIGKeyInfoTest, duplicateTSIGKey) {
     ASSERT_NO_THROW(parser.reset(new TSIGKeyInfoListParser("test", keys_)));
 
     // Verify that the list builds without errors.
-    ASSERT_NO_THROW(parser->build(config_set_));
-
-    // Verify that the list commit fails.
-    EXPECT_THROW(parser->commit(), D2CfgError);
+    EXPECT_THROW(parser->build(config_set_), D2CfgError);
 }
 
 /// @brief Verifies a valid list of TSIG Keys parses correctly.
@@ -450,20 +441,18 @@ TEST_F(TSIGKeyInfoTest, validTSIGKeyList) {
 /// 3. Specifying a negative port number is not allowed.
 TEST_F(DnsServerInfoTest, invalidEntry) {
     // Create a config in which both host and ip address are supplied.
-    // Verify that it builds without throwing but commit fails.
+    // Verify that build fails.
     std::string config = "{ \"hostname\": \"pegasus.tmark\", "
                          "  \"ip_address\": \"127.0.0.1\" } ";
     ASSERT_TRUE(fromJSON(config));
-    EXPECT_NO_THROW(parser_->build(config_set_));
-    EXPECT_THROW(parser_->commit(), D2CfgError);
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 
     // Neither host nor ip address supplied
-    // Verify that it builds without throwing but commit fails.
+    // Verify that builds fails.
     config = "{ \"hostname\": \"\", "
              "  \"ip_address\": \"\" } ";
     ASSERT_TRUE(fromJSON(config));
-    EXPECT_NO_THROW(parser_->build(config_set_));
-    EXPECT_THROW(parser_->commit(), D2CfgError);
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 
     // Create a config with a negative port number.
     // Verify that build fails.
@@ -554,11 +543,8 @@ TEST_F(ConfigParseTest, invalidServerList) {
     isc::dhcp::ParserPtr parser;
     ASSERT_NO_THROW(parser.reset(new DnsServerInfoListParser("test", servers)));
 
-    // Verify that the list builds without errors.
-    ASSERT_NO_THROW(parser->build(config_set_));
-
-    // Verify that the list commit fails.
-    EXPECT_THROW(parser->commit(), D2CfgError);
+    // Verify that build fails.
+    EXPECT_THROW(parser->build(config_set_), D2CfgError);
 }
 
 /// @brief Verifies that a list of DnsServerInfo entries parses correctly given
@@ -623,9 +609,8 @@ TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
                          "    \"port\": 300 } ] } ";
     ASSERT_TRUE(fromJSON(config));
 
-    // Verify that the domain configuration builds but commit fails.
-    ASSERT_NO_THROW(parser_->build(config_set_));
-    ASSERT_THROW(parser_->commit(), isc::dhcp::DhcpConfigError);
+    // Verify that the domain configuration builds fails.
+    EXPECT_THROW(parser_->build(config_set_), isc::dhcp::DhcpConfigError);
 
     // Create a domain configuration with an empty server list.
     config = "{ \"name\": \"tmark.org\" , "
@@ -635,7 +620,7 @@ TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
     ASSERT_TRUE(fromJSON(config));
 
     // Verify that the domain configuration build fails.
-    ASSERT_THROW(parser_->build(config_set_), D2CfgError);
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 
     // Create a domain configuration with a mal-formed server entry.
     config = "{ \"name\": \"tmark.org\" , "
@@ -646,7 +631,7 @@ TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
     ASSERT_TRUE(fromJSON(config));
 
     // Verify that the domain configuration build fails.
-    ASSERT_THROW(parser_->build(config_set_), isc::BadValue);
+    EXPECT_THROW(parser_->build(config_set_), isc::BadValue);
 
     // Create a domain configuration without an defined key name
     config = "{ \"name\": \"tmark.org\" , "
@@ -656,9 +641,8 @@ TEST_F(DdnsDomainTest, invalidDdnsDomainEntry) {
              "    \"port\": 300 } ] } ";
     ASSERT_TRUE(fromJSON(config));
 
-    // Verify that the domain configuration build succeeds but commit fails.
-    ASSERT_NO_THROW(parser_->build(config_set_));
-    ASSERT_THROW(parser_->commit(), D2CfgError);
+    // Verify that the domain configuration build fails.
+    EXPECT_THROW(parser_->build(config_set_), D2CfgError);
 }
 
 /// @brief Verifies the basics of parsing DdnsDomains.
@@ -853,9 +837,8 @@ TEST_F(DdnsDomainTest, duplicateDomain) {
     ASSERT_NO_THROW(list_parser.reset(
                     new DdnsDomainListParser("test", domains_, keys_)));
 
-    // Verify that the parse build succeeds but the commit fails.
-    ASSERT_NO_THROW(list_parser->build(config_set_));
-    ASSERT_THROW(list_parser->commit(), D2CfgError);
+    // Verify that the parse build fails.
+    EXPECT_THROW(list_parser->build(config_set_), D2CfgError);
 }
 
 /// @brief Tests construction of D2CfgMgr
@@ -1013,7 +996,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
     EXPECT_TRUE(cfg_mgr_->reverseUpdatesEnabled());
 
     // Verify that parsing the exact same configuration a second time
-    // does not cause a duplicate value errors. 
+    // does not cause a duplicate value errors.
     answer_ = cfg_mgr_->parseConfig(config_set_);
     ASSERT_TRUE(checkAnswer(0));
 }

+ 0 - 1
src/bin/d2/tests/nc_test_utils.cc

@@ -31,7 +31,6 @@ namespace d2 {
 const char* TEST_DNS_SERVER_IP = "127.0.0.1";
 size_t TEST_DNS_SERVER_PORT = 5301;
 
-const bool HAS_RDATA = true;
 const bool NO_RDATA = false;
 
 //*************************** FauxServer class ***********************

+ 48 - 19
src/bin/dhcp4/config_parser.cc

@@ -167,9 +167,13 @@ public:
         :SubnetConfigParser("", globalContext(), IOAddress("0.0.0.0")) {
     }
 
-    /// @brief Adds the created subnet to a server's configuration.
-    /// @throw throws Unexpected if dynamic cast fails.
-    void commit() {
+    /// @brief Parses a single IPv4 subnet configuration and adds to the
+    /// Configuration Manager.
+    ///
+    /// @param subnet A new subnet being configured.
+    void build(ConstElementPtr subnet) {
+        SubnetConfigParser::build(subnet);
+
         if (subnet_) {
             Subnet4Ptr sub4ptr = boost::dynamic_pointer_cast<Subnet4>(subnet_);
             if (!sub4ptr) {
@@ -183,10 +187,24 @@ public:
                 sub4ptr->setRelayInfo(*relay_info_);
             }
 
-            isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
+            // Adding a subnet to the Configuration Manager may fail if the
+            // subnet id is invalid (duplicate). Thus, we catch exceptions
+            // here to append a position in the configuration string.
+            try {
+                isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
+            } catch (const std::exception& ex) {
+                isc_throw(DhcpConfigError, ex.what() << " ("
+                          << subnet->getPosition() << ")");
+            }
         }
     }
 
+    /// @brief Commits subnet configuration.
+    ///
+    /// This function is currently no-op because subnet should already
+    /// be added into the Config Manager in the build().
+    void commit() { }
+
 protected:
 
     /// @brief Creates parsers for entries in subnet definition.
@@ -218,8 +236,7 @@ protected:
                                              global_context_,
                                              Dhcp4OptionDataParser::factory);
         } else {
-            isc_throw(NotImplemented,
-                "parser error: Subnet4 parameter not supported: " << config_id);
+            isc_throw(NotImplemented, "unsupported parameter: " << config_id);
         }
 
         return (parser);
@@ -294,6 +311,10 @@ protected:
             }
         } catch (const DhcpConfigError&) {
             // Don't care. next_server is optional. We can live without it
+        } catch (...) {
+            isc_throw(DhcpConfigError, "invalid parameter next-server ("
+                      << globalContext()->string_values_->getPosition("next-server")
+                      << ")");
         }
 
         // Try subnet specific value if it's available
@@ -304,8 +325,13 @@ protected:
             }
         } catch (const DhcpConfigError&) {
             // Don't care. next_server is optional. We can live without it
+        } catch (...) {
+            isc_throw(DhcpConfigError, "invalid parameter next-server ("
+                      << string_values_->getPosition("next-server")
+                      << ")");
         }
 
+
         // Try setting up client class (if specified)
         try {
             string client_class = string_values_->getParam("client-class");
@@ -338,6 +364,12 @@ public:
     ///
     /// @param subnets_list pointer to a list of IPv4 subnets
     void build(ConstElementPtr subnets_list) {
+        // @todo: Implement more subtle reconfiguration than toss
+        // the old one and replace with the new one.
+
+        // remove old subnets
+        CfgMgr::instance().deleteSubnets4();
+
         BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
             ParserPtr parser(new Subnet4ConfigParser("subnet"));
             parser->build(subnet);
@@ -350,12 +382,6 @@ public:
     /// Iterates over all Subnet4 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().deleteSubnets4();
-
         BOOST_FOREACH(ParserPtr subnet, subnets_) {
             subnet->commit();
         }
@@ -388,7 +414,8 @@ namespace dhcp {
 /// @return parser for specified global DHCPv4 parameter
 /// @throw NotImplemented if trying to create a parser for unknown
 /// config element
-DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
+    DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
+                                                    ConstElementPtr element) {
     DhcpConfigParser* parser = NULL;
     if ((config_id.compare("valid-lifetime") == 0)  ||
         (config_id.compare("renew-timer") == 0)  ||
@@ -405,8 +432,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
                                           globalContext(),
                                           Dhcp4OptionDataParser::factory);
     } else if (config_id.compare("option-def") == 0) {
-        parser  = new OptionDefListParser(config_id,
-                                          globalContext()->option_defs_);
+        parser  = new OptionDefListParser(config_id, globalContext());
     } else if ((config_id.compare("version") == 0) ||
                (config_id.compare("next-server") == 0)) {
         parser  = new StringParser(config_id,
@@ -420,9 +446,9 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     } else if (config_id.compare("dhcp-ddns") == 0) {
         parser = new D2ClientConfigParser(config_id);
     } else {
-        isc_throw(NotImplemented,
-                "Parser error: Global configuration parameter not supported: "
-                << config_id);
+        isc_throw(DhcpConfigError,
+                "unsupported global configuration parameter: "
+                  << config_id << " (" << element->getPosition() << ")");
     }
 
     return (parser);
@@ -500,7 +526,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         const std::map<std::string, ConstElementPtr>& values_map =
                                                         config_set->mapValue();
         BOOST_FOREACH(config_pair, values_map) {
-            ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+            ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first,
+                                                           config_pair.second));
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
                       .arg(config_pair.first);
             if (config_pair.first == "subnet4") {
@@ -534,6 +561,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         std::map<std::string, ConstElementPtr>::const_iterator option_config =
             values_map.find("option-data");
         if (option_config != values_map.end()) {
+            config_pair.first = "option-data";
             option_parser->build(option_config->second);
             option_parser->commit();
         }
@@ -542,6 +570,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
         std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
             values_map.find("subnet4");
         if (subnet_config != values_map.end()) {
+            config_pair.first = "subnet4";
             subnet_parser->build(subnet_config->second);
         }
 

+ 21 - 0
src/bin/dhcp4/dhcp4.spec

@@ -356,6 +356,27 @@
                 "item_description" : "port number of b10-dhcp-ddns"
             },
             {
+                "item_name": "sender-ip",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "",
+                "item_description" : "IP address from which to send to b10-dhcp-ddns (IPv4 or IPv6)"
+            },
+            {
+                "item_name": "sender-port",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 0,
+                "item_description" : "port number from which to send to b10-dhcp-ddns"
+            },
+            {
+                "item_name": "max-queue-size",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 1024,
+                "item_description" : "maximum number of requests allowed in the send queue"
+            },
+            {
                 "item_name": "ncr-protocol",
                 "item_type": "string",
                 "item_optional": true,

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -106,6 +106,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 endif
 
 noinst_PROGRAMS = $(TESTS)

+ 40 - 45
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -27,6 +27,7 @@
 #include <dhcp/classify.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/testutils/config_result_check.h>
 #include <hooks/hooks_manager.h>
 
 #include "marker_file.h"
@@ -286,9 +287,8 @@ public:
         std::string config = createConfigWithOption(param_value, parameter);
         ElementPtr json = Element::fromJSON(config);
         EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(1, rcode_);
+        checkResult(x, 1);
+        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
     }
 
     /// @brief Test invalid option paramater value.
@@ -304,9 +304,8 @@ public:
         std::string config = createConfigWithOption(params);
         ElementPtr json = Element::fromJSON(config);
         EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(1, rcode_);
+        checkResult(x, 1);
+        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
     }
 
     /// @brief Test option against given code and data.
@@ -580,9 +579,7 @@ TEST_F(Dhcp4ParserTest, multipleSubnets) {
 
     do {
         EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(0, rcode_);
+        checkResult(x, 0);
 
         const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
         ASSERT_TRUE(subnets);
@@ -635,9 +632,7 @@ TEST_F(Dhcp4ParserTest, multipleSubnetsExplicitIDs) {
     int cnt = 0; // Number of reconfigurations
     do {
         EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(0, rcode_);
+        checkResult(x, 0);
 
         const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
         ASSERT_TRUE(subnets);
@@ -686,9 +681,8 @@ TEST_F(Dhcp4ParserTest, multipleSubnetsOverlapingIDs) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    EXPECT_NE(rcode_, 0);
+    checkResult(x, 1);
+    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
 }
 
 // Goal of this test is to verify that a previously configured subnet can be
@@ -769,9 +763,7 @@ TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
 
     ElementPtr json = Element::fromJSON(config4);
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
     ASSERT_TRUE(subnets);
@@ -780,9 +772,7 @@ TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
     // Do the reconfiguration (the last subnet is removed)
     json = Element::fromJSON(config_first3);
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     subnets = CfgMgr::instance().getSubnets4();
     ASSERT_TRUE(subnets);
@@ -799,16 +789,12 @@ TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
     /// @todo: Uncomment subnet removal test as part of #3281.
     json = Element::fromJSON(config4);
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     // Do reconfiguration
     json = Element::fromJSON(config_second_removed);
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     subnets = CfgMgr::instance().getSubnets4();
     ASSERT_TRUE(subnets);
@@ -932,12 +918,15 @@ TEST_F(Dhcp4ParserTest, nextServerNegative) {
     // check if returned status is always a failure
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json1));
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json2));
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
     checkResult(status, 0);
+    EXPECT_FALSE(errorContainsPosition(status, "<string>"));
 }
 
 // Checks if the next-server defined as global value is overridden by subnet
@@ -1065,6 +1054,7 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
     // returned value must be 1 (values error)
     // as the pool does not belong to that subnet
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // Goal of this test is to verify if pools can be defined
@@ -1284,6 +1274,7 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // The goal of this test is to verify that the option definition
@@ -1392,6 +1383,7 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The purpose of this test is to verify that the option definition
@@ -1418,6 +1410,7 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The purpose of this test is to verify that the option definition
@@ -1444,6 +1437,7 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The goal of this test is to verify that the invalid encapsulated
@@ -1470,6 +1464,7 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidEncapsulatedSpace) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The goal of this test is to verify that the encapsulated
@@ -1498,6 +1493,7 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulatedSpaceAndArray) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The goal of this test is to verify that the option may not
@@ -1524,6 +1520,7 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The purpose of this test is to verify that it is not allowed
@@ -1588,6 +1585,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     /// @todo The option 65 is a standard DHCPv4 option. However, at this point
     /// there is no definition for this option in libdhcp++, so it should be
@@ -1655,9 +1653,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
                                                       classify_);
@@ -1960,9 +1956,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"),
                                                       classify_);
@@ -2113,9 +2107,7 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"),
                                                        classify_);
@@ -2218,9 +2210,7 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
                                                       classify_);
@@ -2263,9 +2253,7 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
                                                       classify_);
@@ -2343,6 +2331,7 @@ TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
 
     // returned value must be rejected (1 configuration error)
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     // CASE 4: -1 (UINT_MIN -1 ) should not work
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
@@ -2351,6 +2340,7 @@ TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
 
     // returned value must be rejected (1 configuration error)
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // The goal of this test is to verify that the standard option can
@@ -2856,6 +2846,9 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
         "     \"enable-updates\" : true, "
         "     \"server-ip\" : \"192.168.2.1\", "
         "     \"server-port\" : 777, "
+        "     \"sender-ip\" : \"192.168.2.2\", "
+        "     \"sender-port\" : 778, "
+        "     \"max-queue-size\" : 2048, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"always-include-fqdn\" : true, "
@@ -2888,6 +2881,9 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
     EXPECT_TRUE(d2_client_config->getEnableUpdates());
     EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
     EXPECT_EQ(777, d2_client_config->getServerPort());
+    EXPECT_EQ("192.168.2.2", d2_client_config->getSenderIp().toText());
+    EXPECT_EQ(778, d2_client_config->getSenderPort());
+    EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
@@ -2935,6 +2931,7 @@ TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
 
     // check if returned status is failed.
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     // Verify that the D2 configuraiton can be fetched and is set to disabled.
     D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
@@ -3008,9 +3005,7 @@ TEST_F(Dhcp4ParserTest, classifySubnets) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
     ASSERT_TRUE(subnets);

+ 17 - 8
src/bin/dhcp4/tests/d2_unittest.cc

@@ -88,8 +88,11 @@ Dhcp4SrvD2Test::reset() {
 
 void
 Dhcp4SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
-                            const std::string& ip_address,
-                            const uint32_t port) {
+                            const std::string& server_ip,
+                            const size_t port,
+                            const std::string& sender_ip,
+                            const size_t sender_port,
+                            const size_t max_queue_size) {
     std::ostringstream config;
     config <<
         "{ \"interfaces\": [ \"*\" ],"
@@ -100,8 +103,11 @@ Dhcp4SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         " \"dhcp-ddns\" : {"
         "     \"enable-updates\" : " << (enable_d2 ? "true" : "false") <<  ", "
-        "     \"server-ip\" : \"" << ip_address << "\", "
+        "     \"server-ip\" : \"" << server_ip << "\", "
         "     \"server-port\" : " << port << ", "
+        "     \"sender-ip\" : \"" << sender_ip << "\", "
+        "     \"sender-port\" : " << sender_port << ", "
+        "     \"max-queue-size\" : " << max_queue_size << ", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"always-include-fqdn\" : true, "
@@ -165,10 +171,10 @@ TEST_F(Dhcp4SrvD2Test, enableDisable) {
     ASSERT_FALSE(mgr.amSending());
 }
 
-// Tests Dhcp4 server's ability to correctly handle a flawed dhcp-ddns configuration.
-// It does so by first enabling updates by submitting a valid configuration and then
-// ensuring they remain on after submitting a flawed configuration.
-// and then invoking its startD2() method.
+// Tests Dhcp4 server's ability to correctly handle a flawed dhcp-ddns
+// configuration.  It does so by first enabling updates by submitting a valid
+// configuration and then ensuring they remain on after submitting a flawed
+// configuration and then invoking its startD2() method.
 TEST_F(Dhcp4SrvD2Test, badConfig) {
     // Grab the manager and verify that be default ddns is off
     // and a sender was not started.
@@ -293,7 +299,10 @@ TEST_F(Dhcp4SrvD2Test, forceUDPSendFailure) {
 
     // Configure it enabled and start it.
     // Using server address of 0.0.0.0/0 should induce failure on send.
-    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0));
+    // Pass in a non-zero sender port to avoid validation error when
+    // server-ip/port are same as sender-ip/port
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0,
+                                        "0.0.0.0", 53001));
     ASSERT_TRUE(mgr.ddnsEnabled());
     ASSERT_NO_THROW(srv_.startD2());
     ASSERT_TRUE(mgr.amSending());

+ 9 - 3
src/bin/dhcp4/tests/d2_unittest.h

@@ -77,11 +77,17 @@ public:
     ///
     /// @param enable_updates value to assign to the enable-updates parameter
     /// @param exp_result indicates if configuration should pass or fail
-    /// @param ip_address IP address for the D2 server
+    /// @param server_ip IP address for the D2 server
     /// @param port  port for the D2 server
+    /// @param sender_ip NCR sender's IP address
+    /// @param sender_port NCR sender port
+    /// @param max_queue_size maximum number of NCRs allowed in sender's queue
     void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
-                     const std::string& ip_address = "127.0.0.1",
-                     const uint32_t port = 53001);
+                     const std::string& server_ip = "127.0.0.1",
+                     const size_t port = 53001,
+                     const std::string& sender_ip = "0.0.0.0",
+                     const size_t sender_port = 0,
+                     const size_t max_queue_size = 1024);
 
     /// @brief Configures the server with the given configuration
     ///

+ 0 - 3
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -1706,9 +1706,6 @@ TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
 }
 
 
-// a dummy MAC address
-const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
-
 // A dummy MAC address, padded with 0s
 const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
                                  0, 0, 0, 0, 0, 0, 0, 0 };

+ 1 - 0
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -77,6 +77,7 @@ public:
 
         ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 53001,
+                                  isc::asiolink::IOAddress("0.0.0.0"), 0, 1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   (mask & ALWAYS_INCLUDE_FQDN),
                                   (mask & OVERRIDE_NO_UPDATE),

+ 21 - 0
src/bin/dhcp6/dhcp6.spec

@@ -393,6 +393,27 @@
                 "item_description" : "port number of b10-dhcp-ddns"
             },
             {
+                "item_name": "sender-ip",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "",
+                "item_description" : "IP address from which to send to b10-dhcp-ddns (IPv4 or IPv6)"
+            },
+            {
+                "item_name": "sender-port",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 0,
+                "item_description" : "port number from which to send to b10-dhcp-ddns"
+            },
+            {
+                "item_name": "max-queue-size",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 1024,
+                "item_description" : "maximum number of requests allowed in the send queue"
+            },
+            {
                 "item_name": "ncr-protocol",
                 "item_type": "string",
                 "item_optional": true,

+ 59 - 35
src/bin/dhcp6/json_config_parser.cc

@@ -231,30 +231,33 @@ public:
                                                              uint32_values_));
                 parser = code_parser;
             } else {
-                isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+                isc_throw(DhcpConfigError, "unsupported parameter: " << entry
+                          << " (" << param.second->getPosition() << ")");
             }
 
             parser->build(param.second);
             parser->commit();
         }
 
+        // Try to obtain the pool parameters. It will throw an exception if any
+        // of the required parameters are not present or invalid.
+        std::string addr_str;
+        uint32_t prefix_len;
+        uint32_t delegated_len;
         try {
-            // We should now have all of the pool elements we need to create
-            // the pool.  Fetch them and pass them into the Pool6 constructor.
-            // The constructor is expected to enforce any value validation.
-            const std::string addr_str = string_values_->getParam("prefix");
-            IOAddress addr(addr_str);
-
-            uint32_t prefix_len = uint32_values_->getParam("prefix-len");
-
-            uint32_t delegated_len = uint32_values_->getParam("delegated-len");
+            addr_str = string_values_->getParam("prefix");
+            prefix_len = uint32_values_->getParam("prefix-len");
+            delegated_len = uint32_values_->getParam("delegated-len");
 
             // Attempt to construct the local pool.
-            pool_.reset(new Pool6(Lease::TYPE_PD, addr, prefix_len,
-                                 delegated_len));
+            pool_.reset(new Pool6(Lease::TYPE_PD, IOAddress(addr_str),
+                                  prefix_len, delegated_len));
         } catch (const std::exception& ex) {
-            isc_throw(isc::dhcp::DhcpConfigError,
-                      "PdPoolParser failed to build pool: " << ex.what());
+            // Some parameters don't exist or are invalid. Since we are not
+            // aware whether they don't exist or are invalid, let's append
+            // the position of the pool map element.
+            isc_throw(isc::dhcp::DhcpConfigError, ex.what()
+                      << " (" << pd_pool_->getPosition() << ")");
         }
     }
 
@@ -319,7 +322,7 @@ public:
         // Make sure we have a configuration elements to parse.
         if (!pd_pool_list) {
             isc_throw(DhcpConfigError,
-                      "PdPoolListParser: list of pool definitions is empty");
+                      "PdPoolListParser: list of pool definitions is NULL");
         }
 
         // Loop through the list of pd pools.
@@ -371,15 +374,19 @@ public:
         :SubnetConfigParser("", globalContext(), IOAddress("::")) {
     }
 
-    /// @brief Adds the created subnet to a server's configuration.
-    /// @throw throws Unexpected if dynamic cast fails.
-    void commit() {
+    /// @brief Parses a single IPv4 subnet configuration and adds to the
+    /// Configuration Manager.
+    ///
+    /// @param subnet A new subnet being configured.
+    void build(ConstElementPtr subnet) {
+        SubnetConfigParser::build(subnet);
+
         if (subnet_) {
             Subnet6Ptr sub6ptr = boost::dynamic_pointer_cast<Subnet6>(subnet_);
             if (!sub6ptr) {
                 // If we hit this, it is a programming error.
                 isc_throw(Unexpected,
-                          "Invalid cast in Subnet4ConfigParser::commit");
+                          "Invalid cast in Subnet6ConfigParser::commit");
             }
 
             // Set relay infomation if it was provided
@@ -387,10 +394,25 @@ public:
                 sub6ptr->setRelayInfo(*relay_info_);
             }
 
-            isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
+            // Adding a subnet to the Configuration Manager may fail if the
+            // subnet id is invalid (duplicate). Thus, we catch exceptions
+            // here to append a position in the configuration string.
+            try {
+                isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
+            } catch (const std::exception& ex) {
+                isc_throw(DhcpConfigError, ex.what() << " ("
+                          << subnet->getPosition() << ")");
+            }
+
         }
     }
 
+    /// @brief Commits subnet configuration.
+    ///
+    /// This function is currently no-op because subnet should already
+    /// be added into the Config Manager in the build().
+    void commit() { }
+
 protected:
 
     /// @brief creates parsers for entries in subnet definition
@@ -425,8 +447,7 @@ protected:
                                              global_context_,
                                              Dhcp6OptionDataParser::factory);
         } else {
-            isc_throw(NotImplemented,
-                "parser error: Subnet6 parameter not supported: " << config_id);
+            isc_throw(NotImplemented, "unsupported parameter: " << config_id);
         }
 
         return (parser);
@@ -569,6 +590,12 @@ public:
     ///
     /// @param subnets_list pointer to a list of IPv6 subnets
     void build(ConstElementPtr subnets_list) {
+        // @todo: Implement more subtle reconfiguration than toss
+        // the old one and replace with the new one.
+
+        // remove old subnets
+        isc::dhcp::CfgMgr::instance().deleteSubnets6();
+
         BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
             ParserPtr parser(new Subnet6ConfigParser("subnet"));
             parser->build(subnet);
@@ -582,12 +609,6 @@ public:
     /// 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
-        isc::dhcp::CfgMgr::instance().deleteSubnets6();
-
         BOOST_FOREACH(ParserPtr subnet, subnets_) {
             subnet->commit();
         }
@@ -619,7 +640,8 @@ namespace dhcp {
 /// @return parser for specified global DHCPv6 parameter
 /// @throw NotImplemented if trying to create a parser for unknown config
 /// element
-DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
+    DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
+                                                    ConstElementPtr element) {
     DhcpConfigParser* parser = NULL;
     if ((config_id.compare("preferred-lifetime") == 0)  ||
         (config_id.compare("valid-lifetime") == 0)  ||
@@ -637,8 +659,7 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
                                           globalContext(),
                                           Dhcp6OptionDataParser::factory);
     } else if (config_id.compare("option-def") == 0) {
-        parser  = new OptionDefListParser(config_id,
-                                          globalContext()->option_defs_);
+        parser  = new OptionDefListParser(config_id, globalContext());
     } else if (config_id.compare("version") == 0) {
         parser  = new StringParser(config_id,
                                    globalContext()->string_values_);
@@ -649,9 +670,9 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id) {
     } else if (config_id.compare("dhcp-ddns") == 0) {
         parser = new D2ClientConfigParser(config_id);
     } else {
-        isc_throw(NotImplemented,
-                "Parser error: Global configuration parameter not supported: "
-                << config_id);
+        isc_throw(DhcpConfigError,
+                "unsupported global configuration parameter: "
+                  << config_id << " (" << element->getPosition() << ")");
     }
 
     return (parser);
@@ -716,7 +737,8 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         const std::map<std::string, ConstElementPtr>& values_map =
             config_set->mapValue();
         BOOST_FOREACH(config_pair, values_map) {
-            ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first));
+            ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first,
+                                                           config_pair.second));
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED)
                       .arg(config_pair.first);
             if (config_pair.first == "subnet6") {
@@ -751,6 +773,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         std::map<std::string, ConstElementPtr>::const_iterator option_config =
             values_map.find("option-data");
         if (option_config != values_map.end()) {
+            config_pair.first = "option-data";
             option_parser->build(option_config->second);
             option_parser->commit();
         }
@@ -759,6 +782,7 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
         std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
             values_map.find("subnet6");
         if (subnet_config != values_map.end()) {
+            config_pair.first = "subnet6";
             subnet_parser->build(subnet_config->second);
         }
 

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

@@ -114,6 +114,7 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la

+ 60 - 102
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -25,6 +25,7 @@
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/testutils/config_result_check.h>
 #include <hooks/hooks_manager.h>
 
 #include "test_data_files_config.h"
@@ -375,9 +376,8 @@ public:
         std::string config = createConfigWithOption(param_value, parameter);
         ElementPtr json = Element::fromJSON(config);
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(1, rcode_);
+        checkResult(x, 1);
+        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
     }
 
     /// @brief Test invalid option paramater value.
@@ -393,9 +393,8 @@ public:
         std::string config = createConfigWithOption(params);
         ElementPtr json = Element::fromJSON(config);
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(1, rcode_);
+        checkResult(x, 1);
+        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
     }
 
     /// @brief Test option against given code and data.
@@ -491,9 +490,7 @@ TEST_F(Dhcp6ParserTest, version) {
                     Element::fromJSON("{\"version\": 0}")));
 
     // returned value must be 0 (configuration accepted)
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    EXPECT_EQ(0, rcode_);
+    checkResult(x, 0);
 }
 
 /// The goal of this test is to verify that the code accepts only
@@ -506,9 +503,7 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
                     Element::fromJSON("{\"bogus\": 5}")));
 
     // returned value must be 1 (configuration parse error)
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    EXPECT_EQ(1, rcode_);
+    checkResult(x, 1);
 }
 
 /// The goal of this test is to verify if configuration without any
@@ -526,9 +521,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
                                       "\"valid-lifetime\": 4000 }")));
 
     // returned value should be 0 (success)
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(0, rcode_);
+    checkResult(status, 0);
 }
 
 /// The goal of this test is to verify if defined subnet uses global
@@ -551,9 +544,7 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
 
     // check if returned status is OK
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(0, rcode_);
+    checkResult(status, 0);
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
@@ -607,9 +598,7 @@ TEST_F(Dhcp6ParserTest, multipleSubnets) {
 
     do {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(0, rcode_);
+        checkResult(x, 0);
 
         const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
         ASSERT_TRUE(subnets);
@@ -664,9 +653,7 @@ TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
 
     do {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        ASSERT_EQ(0, rcode_);
+        checkResult(x, 0);
 
         const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
         ASSERT_TRUE(subnets);
@@ -716,9 +703,8 @@ TEST_F(Dhcp6ParserTest, multipleSubnetsOverlapingIDs) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_NE(rcode_, 0);
+    checkResult(x, 1);
+    EXPECT_TRUE(errorContainsPosition(x, "<string>"));
 }
 
 
@@ -803,9 +789,7 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
 
     ElementPtr json = Element::fromJSON(config4);
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
     ASSERT_TRUE(subnets);
@@ -814,9 +798,7 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
     // Do the reconfiguration (the last subnet is removed)
     json = Element::fromJSON(config_first3);
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     subnets = CfgMgr::instance().getSubnets6();
     ASSERT_TRUE(subnets);
@@ -831,16 +813,12 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
 
     json = Element::fromJSON(config4);
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     // Do reconfiguration
     json = Element::fromJSON(config_second_removed);
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     subnets = CfgMgr::instance().getSubnets6();
     ASSERT_TRUE(subnets);
@@ -878,9 +856,7 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     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_);
+    checkResult(status, 0);
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -915,9 +891,7 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
     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_);
+    checkResult(status, 0);
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -949,9 +923,8 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
 
     // returned value should be 1 (configuration error)
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(1, rcode_);
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -981,9 +954,8 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
 
     // returned value should be 1 (parse error)
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(1, rcode_);
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 
@@ -1012,9 +984,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
     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_);
+    checkResult(status, 0);
 
     // Try to get a subnet based on bogus interface-id option
     OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
@@ -1051,9 +1021,8 @@ TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
 
     // Returned value should be 1 (parse error)
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(1, rcode_);
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // This test checks if it is not possible to define a subnet with an
@@ -1076,10 +1045,8 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceAndInterfaceId) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
 
     // Returned value should be 1 (configuration error)
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(1, rcode_);
-
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 
@@ -1106,9 +1073,8 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
 
     // returned value must be 1 (values error)
     // as the pool does not belong to that subnet
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(1, rcode_);
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // Goal of this test is to verify if pools can be defined
@@ -1134,9 +1100,7 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     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_);
+    checkResult(x, 0);
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -1177,9 +1141,7 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     // Returned value must be non-empty ConstElementPtr to config result.
     // rcode should be 0 which indicates successful configuration processing.
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    EXPECT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     // Test that we can retrieve the subnet.
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
@@ -1251,9 +1213,7 @@ TEST_F(Dhcp6ParserTest, pdPoolList) {
     // Returned value must be non-empty ConstElementPtr to config result.
     // rcode should be 0 which indicates successful configuration processing.
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    EXPECT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     // Test that we can retrieve the subnet.
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
@@ -1309,9 +1269,7 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     // Returned value must be non-empty ConstElementPtr to config result.
     // rcode should be 0 which indicates successful configuration processing.
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    EXPECT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     // Test that we can retrieve the subnet.
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
@@ -1428,9 +1386,8 @@ TEST_F(Dhcp6ParserTest, invalidPdPools) {
 
         // Returned value must be non-empty ConstElementPtr to config result.
         // rcode should be 1 which indicates configuration error.
-        ASSERT_TRUE(x);
-        comment_ = parseAnswer(rcode_, x);
-        EXPECT_EQ(1, rcode_);
+        checkResult(x, 1);
+        EXPECT_TRUE(errorContainsPosition(x, "<string>"));
     }
 }
 
@@ -1616,6 +1573,7 @@ TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
     ASSERT_TRUE(status);
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // The goal of this test is to verify that the option definition
@@ -1723,6 +1681,7 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The purpose of this test is to verify that the option definition
@@ -1749,6 +1708,7 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The purpose of this test is to verify that the option definition
@@ -1775,6 +1735,7 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The goal of this test is to verify that the invalid encapsulated
@@ -1801,6 +1762,7 @@ TEST_F(Dhcp6ParserTest, optionDefInvalidEncapsulatedSpace) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The goal of this test is to verify that the encapsulated
@@ -1829,6 +1791,7 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulatedSpaceAndArray) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The goal of this test is to verify that the option may not
@@ -1855,6 +1818,7 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 /// The purpose of this test is to verify that it is not allowed
@@ -1920,6 +1884,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     /// @todo The option 59 is a standard DHCPv6 option. However, at this point
     /// there is no definition for this option in libdhcp++, so it should be
@@ -1987,9 +1952,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -2297,9 +2260,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                        classify_);
@@ -2494,9 +2455,7 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -2539,9 +2498,7 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
                                                       classify_);
@@ -3030,9 +2987,7 @@ TEST_F(Dhcp6ParserTest, selectedInterfaces) {
 
     // returned value must be 1 (values error)
     // as the pool does not belong to that subnet
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(0, rcode_);
+    checkResult(status, 0);
 
     // eth0 and eth1 were explicitly selected. eth2 was not.
     EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
@@ -3068,9 +3023,7 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
 
     // returned value must be 1 (values error)
     // as the pool does not belong to that subnet
-    ASSERT_TRUE(status);
-    comment_ = parseAnswer(rcode_, status);
-    EXPECT_EQ(0, rcode_);
+    checkResult(status, 0);
 
     // All interfaces should be now active.
     EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
@@ -3142,9 +3095,7 @@ TEST_F(Dhcp6ParserTest, classifySubnets) {
     ElementPtr json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
-    ASSERT_TRUE(x);
-    comment_ = parseAnswer(rcode_, x);
-    ASSERT_EQ(0, rcode_);
+    checkResult(x, 0);
 
     const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
     ASSERT_TRUE(subnets);
@@ -3218,8 +3169,11 @@ TEST_F(Dhcp6ParserTest, d2ClientConfig) {
         "    \"subnet\": \"2001:db8:1::/64\" } ], "
         " \"dhcp-ddns\" : {"
         "     \"enable-updates\" : true, "
-        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-ip\" : \"3001::1\", "
         "     \"server-port\" : 777, "
+        "     \"sender-ip\" : \"3001::2\", "
+        "     \"sender-port\" : 778, "
+        "     \"max-queue-size\" : 2048, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"always-include-fqdn\" : true, "
@@ -3250,8 +3204,11 @@ TEST_F(Dhcp6ParserTest, d2ClientConfig) {
 
     // Verify that the configuration values are correct.
     EXPECT_TRUE(d2_client_config->getEnableUpdates());
-    EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
+    EXPECT_EQ("3001::1", d2_client_config->getServerIp().toText());
     EXPECT_EQ(777, d2_client_config->getServerPort());
+    EXPECT_EQ("3001::2", d2_client_config->getSenderIp().toText());
+    EXPECT_EQ(778, d2_client_config->getSenderPort());
+    EXPECT_EQ(2048, d2_client_config->getMaxQueueSize());
     EXPECT_EQ(dhcp_ddns::NCR_UDP, d2_client_config->getNcrProtocol());
     EXPECT_EQ(dhcp_ddns::FMT_JSON, d2_client_config->getNcrFormat());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
@@ -3299,6 +3256,7 @@ TEST_F(Dhcp6ParserTest, invalidD2ClientConfig) {
 
     // check if returned status is failed.
     checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 
     // Verify that the D2 configuraiton can be fetched and is set to disabled.
     D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();

+ 18 - 9
src/bin/dhcp6/tests/d2_unittest.cc

@@ -90,8 +90,11 @@ Dhcp6SrvD2Test::reset() {
 
 void
 Dhcp6SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
-                            const std::string& ip_address,
-                            const uint32_t port) {
+                            const std::string& server_ip,
+                            const size_t port,
+                            const std::string& sender_ip,
+                            const size_t sender_port,
+                            const size_t max_queue_size) {
     std::ostringstream config;
     config <<
         "{ \"interfaces\": [ \"*\" ],"
@@ -104,8 +107,11 @@ Dhcp6SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
         "    \"subnet\": \"2001:db8:1::/64\" } ],"
         " \"dhcp-ddns\" : {"
         "     \"enable-updates\" : " << (enable_d2 ? "true" : "false") <<  ", "
-        "     \"server-ip\" : \"" << ip_address << "\", "
+        "     \"server-ip\" : \"" << server_ip << "\", "
         "     \"server-port\" : " << port << ", "
+        "     \"sender-ip\" : \"" << sender_ip << "\", "
+        "     \"sender-port\" : " << sender_port << ", "
+        "     \"max-queue-size\" : " << max_queue_size << ", "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"always-include-fqdn\" : true, "
@@ -169,10 +175,10 @@ TEST_F(Dhcp6SrvD2Test, enableDisable) {
     ASSERT_FALSE(mgr.amSending());
 }
 
-// Tests Dhcp6 server's ability to correctly handle a flawed dhcp-ddns configuration.
-// It does so by first enabling updates by submitting a valid configuration and then
-// ensuring they remain on after submitting a flawed configuration.
-// and then invoking its startD2() method.
+// Tests Dhcp6 server's ability to correctly handle a flawed dhcp-ddns
+// configuration.  It does so by first enabling updates by submitting a valid
+// configuration and then ensuring they remain on after submitting a flawed
+// configuration and then invoking its startD2() method.
 TEST_F(Dhcp6SrvD2Test, badConfig) {
     // Grab the manager and verify that be default ddns is off
     // and a sender was not started.
@@ -244,7 +250,7 @@ TEST_F(Dhcp6SrvD2Test, differentConfig) {
     ASSERT_TRUE(mgr.amSending());
 
     // Now enable it on a different port.
-    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001));
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "::1", 54001));
 
     // Configuration was altered, so ddns should still enabled but not sending.
     ASSERT_TRUE(mgr.ddnsEnabled());
@@ -297,7 +303,10 @@ TEST_F(Dhcp6SrvD2Test, forceUDPSendFailure) {
 
     // Configure it enabled and start it.
     // Using server address of 0.0.0.0/0 should induce failure on send.
-    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "0.0.0.0", 0));
+    // Pass in a non-zero sender port to avoid validation error when
+    // server-ip/port are same as sender-ip/port
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "::", 0,
+                                        "::", 53001));
     ASSERT_TRUE(mgr.ddnsEnabled());
     ASSERT_NO_THROW(srv_.startD2());
     ASSERT_TRUE(mgr.amSending());

+ 9 - 4
src/bin/dhcp6/tests/d2_unittest.h

@@ -76,12 +76,17 @@ public:
     /// parameters given and passes it into the server's configuration handler.
     ///
     /// @param enable_updates value to assign to the enable-updates parameter
-    /// @param exp_result indicates if configuration should pass or fail
-    /// @param ip_address IP address for the D2 server
+    /// @param server_ip IP address for the D2 server
     /// @param port  port for the D2 server
+    /// @param sender_ip NCR sender's IP address
+    /// @param sender_port NCR sender port
+    /// @param max_queue_size maximum number of NCRs allowed in sender's queue
     void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
-                     const std::string& ip_address = "127.0.0.1",
-                     const uint32_t port = 53001);
+                     const std::string& server_ip = "::1",
+                     const size_t port = 53001,
+                     const std::string& sender_ip = "::",
+                     const size_t sender_port = 0,
+                     const size_t max_queue_size = 1024);
 
     /// @brief Configures the server with the given configuration
     ///

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

@@ -273,7 +273,7 @@ Dhcpv6SrvTest::testRenewBasic(Lease::Type type, const std::string& existing_addr
         EXPECT_EQ(pd_pool_->getLength(), prefix_opt->getLength());
 
         // Check that the lease is really in the database
-        l = checkLease(duid_, reply->getOption(D6O_IA_PD), prefix_opt);
+        l = checkPdLease(duid_, reply->getOption(D6O_IA_PD), prefix_opt);
         ASSERT_TRUE(l);
         break;
     }
@@ -428,7 +428,7 @@ Dhcpv6SrvTest::testReleaseBasic(Lease::Type type, const IOAddress& existing,
     ASSERT_TRUE(subnet_->inPool(type, existing));
 
     // Let's prepopulate the database
-    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, existing, duid_, iaid,
+    Lease6Ptr lease(new Lease6(type, existing, duid_, iaid,
                                501, 502, 503, 504, subnet_->getID(),
                                prefix_len));
     ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

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

@@ -109,7 +109,9 @@ public:
         D2ClientConfigPtr cfg;
 
         ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
-                                  isc::asiolink::IOAddress("127.0.0.1"), 53001,
+                                  isc::asiolink::IOAddress("::1"), 53001,
+                                  isc::asiolink::IOAddress("::"), 0,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   (mask & ALWAYS_INCLUDE_FQDN),
                                   (mask & OVERRIDE_NO_UPDATE),

+ 1 - 1
src/lib/asiodns/tests/io_fetch_unittest.cc

@@ -106,7 +106,7 @@ public:
         udp_fetch_(IOFetch::UDP, service_, question_, IOAddress(TEST_HOST),
             TEST_PORT, result_buff_, this, 100),
         tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST),
-            TEST_PORT, result_buff_, this, (16 * SEND_INTERVAL)),
+            TEST_PORT, result_buff_, this, (24 * SEND_INTERVAL)),
                                         // Timeout interval chosen to ensure no timeout
         protocol_(IOFetch::TCP),        // for initialization - will be changed
         cumulative_(0),

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

@@ -43,6 +43,19 @@ namespace isc {
 namespace data {
 
 std::string
+Element::Position::str() const {
+    std::ostringstream ss;
+    ss << file_ << ":" << line_ << ":" << pos_;
+    return (ss.str());
+}
+
+std::ostream&
+operator<<(std::ostream& out, const Element::Position& pos) {
+    out << pos.str();
+    return (out);
+}
+
+std::string
 Element::str() const {
     std::stringstream ss;
     toJSON(ss);
@@ -426,7 +439,7 @@ fromStringstreamNumber(std::istream& in, const std::string& file,
     if (number.find_first_of(".eE") < number.size()) {
         try {
             return (Element::create(boost::lexical_cast<double>(number),
-                                    Element::Position(line, start_pos)));
+                                    Element::Position(file, line, start_pos)));
         } catch (const boost::bad_lexical_cast&) {
             throwJSONError(std::string("Number overflow: ") + number,
                            file, line, start_pos);
@@ -434,7 +447,7 @@ fromStringstreamNumber(std::istream& in, const std::string& file,
     } else {
         try {
             return (Element::create(boost::lexical_cast<int64_t>(number),
-                                    Element::Position(line, start_pos)));
+                                    Element::Position(file, line, start_pos)));
         } catch (const boost::bad_lexical_cast&) {
             throwJSONError(std::string("Number overflow: ") + number, file,
                            line, start_pos);
@@ -454,9 +467,11 @@ fromStringstreamBool(std::istream& in, const std::string& file,
     const std::string word = wordFromStringstream(in, pos);
 
     if (boost::iequals(word, "True")) {
-        return (Element::create(true, Element::Position(line, start_pos)));
+        return (Element::create(true, Element::Position(file, line,
+                                                        start_pos)));
     } else if (boost::iequals(word, "False")) {
-        return (Element::create(false, Element::Position(line, start_pos)));
+        return (Element::create(false, Element::Position(file, line,
+                                                         start_pos)));
     } else {
         throwJSONError(std::string("Bad boolean value: ") + word, file,
                        line, start_pos);
@@ -474,7 +489,7 @@ fromStringstreamNull(std::istream& in, const std::string& file,
     // This will move the pos to the end of the value.
     const std::string word = wordFromStringstream(in, pos);
     if (boost::iequals(word, "null")) {
-        return (Element::create(Element::Position(line, start_pos)));
+        return (Element::create(Element::Position(file, line, start_pos)));
     } else {
         throwJSONError(std::string("Bad null value: ") + word, file,
                        line, start_pos);
@@ -491,7 +506,8 @@ fromStringstreamString(std::istream& in, const std::string& file, int& line,
     const uint32_t start_pos = pos;
     // This will move the pos to the end of the value.
     const std::string string_value = strFromStringstream(in, file, line, pos);
-    return (Element::create(string_value, Element::Position(line, start_pos)));
+    return (Element::create(string_value, Element::Position(file, line,
+                                                            start_pos)));
 }
 
 ElementPtr
@@ -499,7 +515,7 @@ fromStringstreamList(std::istream& in, const std::string& file, int& line,
                      int& pos)
 {
     int c = 0;
-    ElementPtr list = Element::createList(Element::Position(line, pos));
+    ElementPtr list = Element::createList(Element::Position(file, line, pos));
     ConstElementPtr cur_list_element;
 
     skipChars(in, WHITESPACE, line, pos);
@@ -520,7 +536,7 @@ ElementPtr
 fromStringstreamMap(std::istream& in, const std::string& file, int& line,
                     int& pos)
 {
-    ElementPtr map = Element::createMap(Element::Position(line, pos));
+    ElementPtr map = Element::createMap(Element::Position(file, line, pos));
     skipChars(in, WHITESPACE, line, pos);
     int c = in.peek();
     if (c == EOF) {

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

@@ -77,8 +77,8 @@ public:
     /// \brief Represents the position of the data element within a
     /// configuration string.
     ///
-    /// Position comprises a line number and an offset within this line
-    /// where the element value starts. For example, if the JSON string is
+    /// Position comprises a file name, line number and an offset within this
+    /// line where the element value starts. For example, if the JSON string is
     ///
     /// \code
     /// { "foo": "some string",
@@ -94,26 +94,39 @@ public:
     /// uint32_t arguments holding line number and position within the line are
     /// not confused with the @c Element values passed to these functions.
     struct Position {
-        uint32_t line_; ///< Line number.
-        uint32_t pos_;  ///< Position within the line.
+        std::string file_; ///< File name.
+        uint32_t line_;    ///< Line number.
+        uint32_t pos_;     ///< Position within the line.
+
+        /// \brief Default constructor.
+        Position() : file_(""), line_(0), pos_(0) {
+        }
 
         /// \brief Constructor.
         ///
+        /// \param file File name.
         /// \param line Line number.
         /// \param pos Position within the line.
-        Position(const uint32_t line, const uint32_t pos)
-            : line_(line), pos_(pos) {
+        Position(const std::string& file, const uint32_t line,
+                 const uint32_t pos)
+            : file_(file), line_(line), pos_(pos) {
         }
+
+        /// \brief Returns the position in the textual format.
+        ///
+        /// The returned position has the following format: file:line:pos.
+        std::string str() const;
     };
 
-    /// \brief Returns @c Position object with line_ and pos_ set to 0.
+    /// \brief Returns @c Position object with line_ and pos_ set to 0, and
+    /// with an empty file name.
     ///
     /// The object containing two zeros is a default for most of the
     /// methods creating @c Element objects. The returned value is static
     /// so as it is not created everytime the function with the default
     /// position argument is called.
     static const Position& ZERO_POSITION() {
-        static Position position(0, 0);
+        static Position position("", 0, 0);
         return (position);
     }
 
@@ -700,6 +713,19 @@ ConstElementPtr removeIdentical(ConstElementPtr a, ConstElementPtr b);
 void merge(ElementPtr element, ConstElementPtr other);
 
 ///
+/// \brief Insert Element::Position as a string into stream.
+///
+/// This operator converts the \c Element::Position into a string and
+/// inserts it into the output stream \c out.
+///
+/// \param out A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param pos The \c Element::Position structure to insert.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c out after the insertion operation.
+std::ostream& operator<<(std::ostream& out, const Element::Position& pos);
+
+///
 /// \brief Insert the Element as a string into stream.
 ///
 /// This method converts the \c ElementPtr into a string with

+ 34 - 13
src/lib/cc/tests/data_unittests.cc

@@ -30,6 +30,15 @@ using std::setw;
 using std::string;
 
 namespace {
+
+TEST(Position, str) {
+    Element::Position position("kea.conf", 30, 20);
+    EXPECT_EQ("kea.conf:30:20", position.str());
+
+    Element::Position position2("another.conf", 123, 24);
+    EXPECT_EQ("another.conf:123:24", position2.str());
+}
+
 TEST(Element, type) {
     // this tests checks whether the getType() function returns the
     // correct type
@@ -985,22 +994,24 @@ TEST(Element, preprocessor) {
 }
 
 TEST(Element, getPosition) {
+    std::istringstream ss("{\n"
+                          "    \"a\":  2,\n"
+                          "    \"b\":true,\n"
+                          "    \"cy\": \"a string\",\n"
+                          "    \"dyz\": {\n"
+                          "\n"
+                          "      \"e\": 3,\n"
+                          "        \"f\": null\n"
+                          "\n"
+                          "    },\n"
+                          "    \"g\": [ 5, 6,\n"
+                          "             7 ]\n"
+                          "}\n");
+
     // Create a JSON string holding different type of values. Some of the
     // values in the config string are not aligned, so as we can check that
     // the position is set correctly for the elements.
-    ElementPtr top = Element::fromJSON("{\n"
-                                       "    \"a\":  2,\n"
-                                       "    \"b\":true,\n"
-                                       "    \"cy\": \"a string\",\n"
-                                       "    \"dyz\": {\n"
-                                       "\n"
-                                       "      \"e\": 3,\n"
-                                       "        \"f\": null\n"
-                                       "\n"
-                                       "    },\n"
-                                       "    \"g\": [ 5, 6,\n"
-                                       "             7 ]\n"
-                                       "}\n");
+    ElementPtr top = Element::fromJSON(ss, "kea.conf");
     ASSERT_TRUE(top);
 
     // Element "a"
@@ -1008,36 +1019,42 @@ TEST(Element, getPosition) {
     ASSERT_TRUE(level1_el);
     EXPECT_EQ(2, level1_el->getPosition().line_);
     EXPECT_EQ(11, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
 
     // Element "b"
     level1_el = top->get("b");
     ASSERT_TRUE(level1_el);
     EXPECT_EQ(3, level1_el->getPosition().line_);
     EXPECT_EQ(9, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
 
     // Element "cy"
     level1_el = top->get("cy");
     ASSERT_TRUE(level1_el);
     EXPECT_EQ(4, level1_el->getPosition().line_);
     EXPECT_EQ(11, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
 
     // Element "dyz"
     level1_el = top->get("dyz");
     ASSERT_TRUE(level1_el);
     EXPECT_EQ(5, level1_el->getPosition().line_);
     EXPECT_EQ(13, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
 
     // Element "e" is a sub element of "dyz".
     ConstElementPtr level2_el = level1_el->get("e");
     ASSERT_TRUE(level2_el);
     EXPECT_EQ(7, level2_el->getPosition().line_);
     EXPECT_EQ(12, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
 
     // Element "f" is also a sub element of "dyz"
     level2_el = level1_el->get("f");
     ASSERT_TRUE(level2_el);
     EXPECT_EQ(8, level2_el->getPosition().line_);
     EXPECT_EQ(14, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
 
     // Element "g" is a list.
     level1_el = top->get("g");
@@ -1045,24 +1062,28 @@ TEST(Element, getPosition) {
     EXPECT_EQ(11, level1_el->getPosition().line_);
     // Position indicates where the values start (excluding the "[" character)"
     EXPECT_EQ(11, level1_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level1_el->getPosition().file_);
 
     // First element from the list.
     level2_el = level1_el->get(0);
     ASSERT_TRUE(level2_el);
     EXPECT_EQ(11, level2_el->getPosition().line_);
     EXPECT_EQ(12, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
 
     // Second element from the list.
     level2_el = level1_el->get(1);
     ASSERT_TRUE(level2_el);
     EXPECT_EQ(11, level2_el->getPosition().line_);
     EXPECT_EQ(15, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
 
     // Third element from the list.
     level2_el = level1_el->get(2);
     ASSERT_TRUE(level2_el);
     EXPECT_EQ(12, level2_el->getPosition().line_);
     EXPECT_EQ(14, level2_el->getPosition().pos_);
+    EXPECT_EQ("kea.conf", level2_el->getPosition().file_);
 
 }
 

+ 0 - 30
src/lib/dhcp/option_custom.cc

@@ -81,36 +81,6 @@ OptionCustom::checkIndex(const uint32_t index) const {
     }
 }
 
-template<typename T>
-void
-OptionCustom::checkDataType(const uint32_t index) const {
-    // Check that the requested return type is a supported integer.
-    if (!OptionDataTypeTraits<T>::integer_type) {
-        isc_throw(isc::dhcp::InvalidDataType, "specified data type"
-                  " is not a supported integer type.");
-    }
-
-    // Get the option definition type.
-    OptionDataType data_type = definition_.getType();
-    if (data_type == OPT_RECORD_TYPE) {
-        const OptionDefinition::RecordFieldsCollection& record_fields =
-            definition_.getRecordFields();
-        // When we initialized buffers we have already checked that
-        // the number of these buffers is equal to number of option
-        // fields in the record so the condition below should be met.
-        assert(index < record_fields.size());
-        // Get the data type to be returned.
-        data_type = record_fields[index];
-    }
-
-    if (OptionDataTypeTraits<T>::type != data_type) {
-        isc_throw(isc::dhcp::InvalidDataType,
-                  "specified data type " << data_type << " does not"
-                  " match the data type in an option definition for field"
-                  " index " << index);
-    }
-}
-
 void
 OptionCustom::createBuffers() {
     definition_.validate();

+ 29 - 0
src/lib/dhcp/option_custom.h

@@ -353,6 +353,35 @@ private:
 /// A pointer to the OptionCustom object.
 typedef boost::shared_ptr<OptionCustom> OptionCustomPtr;
 
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+    // Check that the requested return type is a supported integer.
+    if (!OptionDataTypeTraits<T>::integer_type) {
+        isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+                  " is not a supported integer type.");
+    }
+
+    // Get the option definition type.
+    OptionDataType data_type = definition_.getType();
+    if (data_type == OPT_RECORD_TYPE) {
+        const OptionDefinition::RecordFieldsCollection& record_fields =
+            definition_.getRecordFields();
+        // When we initialized buffers we have already checked that
+        // the number of these buffers is equal to number of option
+        // fields in the record so the condition below should be met.
+        assert(index < record_fields.size());
+        // Get the data type to be returned.
+        data_type = record_fields[index];
+    }
+
+    if (OptionDataTypeTraits<T>::type != data_type) {
+        isc_throw(isc::dhcp::InvalidDataType,
+                  "specified data type " << data_type << " does not"
+                  " match the data type in an option definition for field"
+                  " index " << index);
+    }
+}
 } // namespace isc::dhcp
 } // namespace isc
 

+ 1 - 1
src/lib/dhcp/option_definition.cc

@@ -273,7 +273,7 @@ OptionDefinition::validate() const {
 
     } else if (type_ >= OPT_UNKNOWN_TYPE) {
         // Option definition must be of a known type.
-        err_str << "option type value " << type_ << " is out of range.";
+        err_str << "option type " << type_ << " not supported.";
 
     } else if (array_type_) {
         if (type_ == OPT_STRING_TYPE) {

+ 1 - 1
src/lib/dhcpsrv/Makefile.am

@@ -1,4 +1,4 @@
-SUBDIRS = . tests
+SUBDIRS = . testutils tests
 
 dhcp_data_dir = @localstatedir@/@PACKAGE@
 

+ 57 - 24
src/lib/dhcpsrv/d2_client_cfg.cc

@@ -25,6 +25,10 @@ namespace dhcp {
 
 const char *D2ClientConfig::DFT_SERVER_IP = "127.0.0.1";
 const size_t D2ClientConfig::DFT_SERVER_PORT = 53001;
+const char *D2ClientConfig::DFT_V4_SENDER_IP = "0.0.0.0";
+const char *D2ClientConfig::DFT_V6_SENDER_IP = "::";
+const size_t D2ClientConfig::DFT_SENDER_PORT = 0;
+const size_t D2ClientConfig::DFT_MAX_QUEUE_SIZE = 1024;
 const char *D2ClientConfig::DFT_NCR_PROTOCOL = "UDP";
 const char *D2ClientConfig::DFT_NCR_FORMAT = "JSON";
 const bool D2ClientConfig::DFT_ALWAYS_INCLUDE_FQDN = false;
@@ -37,6 +41,9 @@ const char *D2ClientConfig::DFT_QUALIFYING_SUFFIX = "example.com";
 D2ClientConfig::D2ClientConfig(const  bool enable_updates,
                                const isc::asiolink::IOAddress& server_ip,
                                const size_t server_port,
+                               const isc::asiolink::IOAddress& sender_ip,
+                               const size_t sender_port,
+                               const size_t max_queue_size,
                                const dhcp_ddns::
                                      NameChangeProtocol& ncr_protocol,
                                const dhcp_ddns::
@@ -48,31 +55,37 @@ D2ClientConfig::D2ClientConfig(const  bool enable_updates,
                                const std::string& generated_prefix,
                                const std::string& qualifying_suffix)
     : enable_updates_(enable_updates),
-    server_ip_(server_ip),
-    server_port_(server_port),
-    ncr_protocol_(ncr_protocol),
-    ncr_format_(ncr_format),
-    always_include_fqdn_(always_include_fqdn),
-    override_no_update_(override_no_update),
-    override_client_update_(override_client_update),
-    replace_client_name_(replace_client_name),
-    generated_prefix_(generated_prefix),
-    qualifying_suffix_(qualifying_suffix) {
+      server_ip_(server_ip),
+      server_port_(server_port),
+      sender_ip_(sender_ip),
+      sender_port_(sender_port),
+      max_queue_size_(max_queue_size),
+      ncr_protocol_(ncr_protocol),
+      ncr_format_(ncr_format),
+      always_include_fqdn_(always_include_fqdn),
+      override_no_update_(override_no_update),
+      override_client_update_(override_client_update),
+      replace_client_name_(replace_client_name),
+      generated_prefix_(generated_prefix),
+      qualifying_suffix_(qualifying_suffix) {
     validateContents();
 }
 
 D2ClientConfig::D2ClientConfig()
     : enable_updates_(false),
-      server_ip_(isc::asiolink::IOAddress("0.0.0.0")),
-      server_port_(0),
-      ncr_protocol_(dhcp_ddns::NCR_UDP),
-      ncr_format_(dhcp_ddns::FMT_JSON),
-      always_include_fqdn_(false),
-      override_no_update_(false),
-      override_client_update_(false),
-      replace_client_name_(false),
-      generated_prefix_("myhost"),
-      qualifying_suffix_("example.com") {
+      server_ip_(isc::asiolink::IOAddress(DFT_SERVER_IP)),
+      server_port_(DFT_SERVER_PORT),
+      sender_ip_(isc::asiolink::IOAddress(DFT_V4_SENDER_IP)),
+      sender_port_(DFT_SENDER_PORT),
+      max_queue_size_(DFT_MAX_QUEUE_SIZE),
+      ncr_protocol_(dhcp_ddns::stringToNcrProtocol(DFT_NCR_PROTOCOL)),
+      ncr_format_(dhcp_ddns::stringToNcrFormat(DFT_NCR_FORMAT)),
+      always_include_fqdn_(DFT_ALWAYS_INCLUDE_FQDN),
+      override_no_update_(DFT_OVERRIDE_NO_UPDATE),
+      override_client_update_(DFT_OVERRIDE_CLIENT_UPDATE),
+      replace_client_name_(DFT_REPLACE_CLIENT_NAME),
+      generated_prefix_(DFT_GENERATED_PREFIX),
+      qualifying_suffix_(DFT_QUALIFYING_SUFFIX) {
     validateContents();
 }
 
@@ -86,15 +99,29 @@ D2ClientConfig::enableUpdates(bool enable) {
 void
 D2ClientConfig::validateContents() {
     if (ncr_format_ != dhcp_ddns::FMT_JSON) {
-        isc_throw(D2ClientError, "D2ClientConfig: NCR Format:"
+        isc_throw(D2ClientError, "D2ClientConfig: NCR Format: "
                     << dhcp_ddns::ncrFormatToString(ncr_format_)
                     << " is not yet supported");
     }
 
     if (ncr_protocol_ != dhcp_ddns::NCR_UDP) {
-        isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol:"
-                    << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
-                    << " is not yet supported");
+        isc_throw(D2ClientError, "D2ClientConfig: NCR Protocol: "
+                  << dhcp_ddns::ncrProtocolToString(ncr_protocol_)
+                  << " is not yet supported");
+    }
+
+    if (sender_ip_.getFamily() != server_ip_.getFamily()) {
+        isc_throw(D2ClientError, "D2ClientConfig: address family mismatch: "
+                  << "server-ip: " << server_ip_.toText()
+                  << " is: " << (server_ip_.isV4() ? "IPv4" : "IPv6")
+                  << " while sender-ip: "  << sender_ip_.toText()
+                  << " is: " << (sender_ip_.isV4() ? "IPv4" : "IPv6"));
+    }
+
+    if (server_ip_ == sender_ip_ && server_port_ == sender_port_) {
+        isc_throw(D2ClientError, "D2ClientConfig: server and sender cannot"
+                  " share the exact same IP address/port: "
+                  << server_ip_.toText() << "/" << server_port_);
     }
 
     /// @todo perhaps more validation we should do yet?
@@ -106,6 +133,9 @@ D2ClientConfig::operator == (const D2ClientConfig& other) const {
     return ((enable_updates_ == other.enable_updates_) &&
             (server_ip_ == other.server_ip_) &&
             (server_port_ == other.server_port_) &&
+            (sender_ip_ == other.sender_ip_) &&
+            (sender_port_ == other.sender_port_) &&
+            (max_queue_size_ == other.max_queue_size_) &&
             (ncr_protocol_ == other.ncr_protocol_) &&
             (ncr_format_ == other.ncr_format_) &&
             (always_include_fqdn_ == other.always_include_fqdn_) &&
@@ -129,6 +159,9 @@ D2ClientConfig::toText() const {
     if (enable_updates_) {
         stream << ", server_ip: " << server_ip_.toText()
                << ", server_port: " << server_port_
+               << ", sender_ip: " << sender_ip_.toText()
+               << ", sender_port: " << sender_port_
+               << ", max_queue_size: " << max_queue_size_
                << ", ncr_protocol: " << ncr_protocol_
                << ", ncr_format: " << ncr_format_
                << ", always_include_fqdn: " << (always_include_fqdn_ ?

+ 34 - 0
src/lib/dhcpsrv/d2_client_cfg.h

@@ -62,6 +62,10 @@ public:
     /// readily provide them (see Trac #3358).
     static const char *DFT_SERVER_IP;
     static const size_t DFT_SERVER_PORT;
+    static const char *DFT_V4_SENDER_IP;
+    static const char *DFT_V6_SENDER_IP;
+    static const size_t DFT_SENDER_PORT;
+    static const size_t DFT_MAX_QUEUE_SIZE;
     static const char *DFT_NCR_PROTOCOL;
     static const char *DFT_NCR_FORMAT;
     static const bool DFT_ALWAYS_INCLUDE_FQDN;
@@ -76,6 +80,9 @@ public:
     /// @param enable_updates Enables DHCP-DDNS updates
     /// @param server_ip IP address of the b10-dhcp-ddns server (IPv4 or IPv6)
     /// @param server_port IP port of the b10-dhcp-ddns server
+    /// @param sender_ip IP address of the b10-dhcp-ddns server (IPv4 or IPv6)
+    /// @param sender_port IP port of the b10-dhcp-ddns server
+    /// @param max_queue_size  maximum NCRs allowed in sender's queue
     /// @param ncr_protocol Socket protocol to use with b10-dhcp-ddns
     /// Currently only UDP is supported.
     /// @param ncr_format Format of the b10-dhcp-ddns requests.
@@ -95,6 +102,9 @@ public:
     D2ClientConfig(const bool enable_updates,
                    const isc::asiolink::IOAddress& server_ip,
                    const size_t server_port,
+                   const isc::asiolink::IOAddress& sender_ip,
+                   const size_t sender_port,
+                   const size_t max_queue_size,
                    const dhcp_ddns::NameChangeProtocol& ncr_protocol,
                    const dhcp_ddns::NameChangeFormat& ncr_format,
                    const bool always_include_fqdn,
@@ -126,6 +136,21 @@ public:
         return(server_port_);
     }
 
+    /// @brief Return the IP address client should use to send
+    const isc::asiolink::IOAddress& getSenderIp() const {
+        return(sender_ip_);
+    }
+
+    /// @brief Return the IP port client should use to send
+    size_t getSenderPort() const {
+        return(sender_port_);
+    }
+
+    /// @brief Return Maximun sender queue size
+    size_t getMaxQueueSize() const {
+        return(max_queue_size_);
+    }
+
     /// @brief Return the socket protocol to use with b10-dhcp-ddns.
     const dhcp_ddns::NameChangeProtocol& getNcrProtocol() const {
          return(ncr_protocol_);
@@ -202,6 +227,15 @@ private:
     /// @brief IP port of the b10-dhcp-ddns server.
     size_t server_port_;
 
+    /// @brief IP address on which the client should send
+    isc::asiolink::IOAddress sender_ip_;
+
+    /// @brief IP port on which the client should send
+    size_t sender_port_;
+
+    /// @brief Maxium number of NCRs allowed to queue waiting to send
+    size_t max_queue_size_;
+
     /// @brief The socket protocol to use with b10-dhcp-ddns.
     /// Currently only UDP is supported.
     dhcp_ddns::NameChangeProtocol ncr_protocol_;

+ 4 - 10
src/lib/dhcpsrv/d2_client_mgr.cc

@@ -68,21 +68,15 @@ D2ClientMgr::setD2ClientConfig(D2ClientConfigPtr& new_config) {
             dhcp_ddns::NameChangeSenderPtr new_sender;
             switch (new_config->getNcrProtocol()) {
             case dhcp_ddns::NCR_UDP: {
-                /// @todo Should we be able to configure a sender's client
-                /// side ip and port?  We should certainly be able to
-                /// configure a maximum queue size.  These were overlooked
-                /// but are covered in Trac# 3328.
-                isc::asiolink::IOAddress any_addr("0.0.0.0");
-                uint32_t any_port = 0;
-                uint32_t queue_max = 1024;
-
                 // Instantiate a new sender.
                 new_sender.reset(new dhcp_ddns::NameChangeUDPSender(
-                                                any_addr, any_port,
+                                                new_config->getSenderIp(),
+                                                new_config->getSenderPort(),
                                                 new_config->getServerIp(),
                                                 new_config->getServerPort(),
                                                 new_config->getNcrFormat(),
-                                                *this, queue_max));
+                                                *this,
+                                                new_config->getMaxQueueSize()));
                 break;
                 }
             default:

+ 17 - 10
src/lib/dhcpsrv/dbaccess_parser.cc

@@ -57,14 +57,20 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
 
     // 3. Update the copy with the passed keywords.
     BOOST_FOREACH(ConfigPair param, config_value->mapValue()) {
-        // The persist parameter is the only boolean parameter at the
-        // moment. It needs special handling.
-        if (param.first != "persist") {
-            values_copy[param.first] = param.second->stringValue();
-
-        } else {
-            values_copy[param.first] = (param.second->boolValue() ?
-                                        "true" : "false");
+        try {
+            // The persist parameter is the only boolean parameter at the
+            // moment. It needs special handling.
+            if (param.first != "persist") {
+                values_copy[param.first] = param.second->stringValue();
+
+            } else {
+                values_copy[param.first] = (param.second->boolValue() ?
+                                            "true" : "false");
+            }
+        } catch (const isc::data::TypeError& ex) {
+            // Append position of the element.
+            isc_throw(isc::data::TypeError, ex.what() << " ("
+                      << param.second->getPosition() << ")");
         }
     }
 
@@ -75,13 +81,14 @@ DbAccessParser::build(isc::data::ConstElementPtr config_value) {
     if (type_ptr == values_copy.end()) {
         isc_throw(TypeKeywordMissing, "lease database access parameters must "
                   "include the keyword 'type' to determine type of database "
-                  "to be accessed");
+                  "to be accessed (" << config_value->getPosition() << ")");
     }
 
     // b. Check if the 'type; keyword known and throw an exception if not.
     string dbtype = type_ptr->second;
     if ((dbtype != "memfile") && (dbtype != "mysql") && (dbtype != "postgresql")) {
-        isc_throw(BadValue, "unknown backend database type: " << dbtype);
+        isc_throw(BadValue, "unknown backend database type: " << dbtype
+                  << " (" << config_value->getPosition() << ")");
     }
 
     // 5. If all is OK, update the stored keyword/value pairs.  We do this by

+ 323 - 181
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -29,6 +29,7 @@
 #include <vector>
 
 using namespace std;
+using namespace isc::asiolink;
 using namespace isc::data;
 using namespace isc::hooks;
 
@@ -125,6 +126,8 @@ DebugParser::commit() {
 // **************************** BooleanParser  *************************
 
 template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
+    // Invoke common code for all specializations of build().
+    buildCommon(value);
     // The Config Manager checks if user specified a
     // valid value for a boolean parameter: True or False.
     // We should have a boolean Element, use value directly
@@ -132,28 +135,35 @@ template<> void ValueParser<bool>::build(isc::data::ConstElementPtr value) {
         value_ = value->boolValue();
     } catch (const isc::data::TypeError &) {
         isc_throw(BadValue, " Wrong value type for " << param_name_
-                  << " : build called with a non-boolean element.");
+                  << " : build called with a non-boolean element "
+                  << "(" << value->getPosition() << ").");
     }
 }
 
 // **************************** Uin32Parser  *************************
 
 template<> void ValueParser<uint32_t>::build(ConstElementPtr value) {
+    // Invoke common code for all specializations of build().
+    buildCommon(value);
+
     int64_t check;
     string x = value->str();
     try {
         check = boost::lexical_cast<int64_t>(x);
     } catch (const boost::bad_lexical_cast &) {
         isc_throw(BadValue, "Failed to parse value " << value->str()
-                  << " as unsigned 32-bit integer.");
+                  << " as unsigned 32-bit integer "
+                  "(" << value->getPosition() << ").");
     }
     if (check > std::numeric_limits<uint32_t>::max()) {
-        isc_throw(BadValue, "Value " << value->str() << "is too large"
-                  << " for unsigned 32-bit integer.");
+        isc_throw(BadValue, "Value " << value->str() << " is too large"
+                  " for unsigned 32-bit integer "
+                  "(" << value->getPosition() << ").");
     }
     if (check < 0) {
-        isc_throw(BadValue, "Value " << value->str() << "is negative."
-               << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+        isc_throw(BadValue, "Value " << value->str() << " is negative."
+               << " Only 0 or larger are allowed for unsigned 32-bit integer "
+                  "(" << value->getPosition() << ").");
     }
 
     // value is small enough to fit
@@ -163,6 +173,9 @@ template<> void ValueParser<uint32_t>::build(ConstElementPtr value) {
 // **************************** StringParser  *************************
 
 template <> void ValueParser<std::string>::build(ConstElementPtr value) {
+    // Invoke common code for all specializations of build().
+    buildCommon(value);
+
     value_ = value->str();
     boost::erase_all(value_, "\"");
 }
@@ -194,7 +207,8 @@ InterfaceListConfigParser::build(ConstElementPtr value) {
             if (isIfaceAdded(iface_name)) {
                 isc_throw(isc::dhcp::DhcpConfigError, "duplicate interface"
                           << " name '" << iface_name << "' specified in '"
-                          << param_name_ << "' configuration parameter");
+                          << param_name_ << "' configuration parameter "
+                          "(" << value->getPosition() << ")");
             }
             // @todo check that this interface exists in the system!
             // The IfaceMgr exposes mechanisms to check this.
@@ -285,7 +299,8 @@ HooksLibrariesParser::build(ConstElementPtr value) {
             error_list += (string(", ") + error_libs[i]);
         }
         isc_throw(DhcpConfigError, "hooks libraries failed to validate - "
-                  "library or libraries in error are: " + error_list);
+                  "library or libraries in error are: " << error_list
+                  << " (" << value->getPosition() << ")");
     }
 
     // The library list has changed and the libraries are valid, so flag for
@@ -349,8 +364,8 @@ OptionDataParser::build(ConstElementPtr option_data_entries) {
             parser = value_parser;
         } else {
             isc_throw(DhcpConfigError,
-                      "Parser error: option-data parameter not supported: "
-                      << param.first);
+                      "option-data parameter not supported: " << param.first
+                      << " (" << param.second->getPosition() << ")");
         }
 
         parser->build(param.second);
@@ -364,7 +379,7 @@ OptionDataParser::build(ConstElementPtr option_data_entries) {
     }
 
     // Try to create the option instance.
-    createOption();
+    createOption(option_data_entries);
 }
 
 void
@@ -397,70 +412,93 @@ OptionDataParser::commit() {
 }
 
 void
-OptionDataParser::createOption() {
+OptionDataParser::createOption(ConstElementPtr option_data) {
+    // Check if mandatory parameters are specified.
+    uint32_t code;
+    std::string name;
+    std::string data;
+    try {
+        code = uint32_values_->getParam("code");
+        name = string_values_->getParam("name");
+        data = string_values_->getParam("data");
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError,
+                  ex.what() << "(" << option_data->getPosition() << ")");
+    }
+    // Check parameters having default values.
+    std::string space = string_values_->getOptionalParam("space",
+              global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
+    bool csv_format = boolean_values_->getOptionalParam("csv-format", false);
+
     // Option code is held in the uint32_t storage but is supposed to
     // be uint16_t value. We need to check that value in the configuration
     // does not exceed range of uint8_t for DHCPv4, uint16_t for DHCPv6 and
     // is not zero.
-    uint32_t option_code = uint32_values_->getParam("code");
-    if (option_code == 0) {
-        isc_throw(DhcpConfigError, "option code must not be zero."
-                << " Option code '0' is reserved.");
+    if (code == 0) {
+        isc_throw(DhcpConfigError, "option code must not be zero "
+                  "(" << uint32_values_->getPosition("code") << ")");
 
     } else if (global_context_->universe_ == Option::V4 &&
-               option_code > std::numeric_limits<uint8_t>::max()) {
-        isc_throw(DhcpConfigError, "invalid option code '" << option_code
+               code > std::numeric_limits<uint8_t>::max()) {
+        isc_throw(DhcpConfigError, "invalid option code '" << code
                 << "', it must not exceed '"
                   << static_cast<int>(std::numeric_limits<uint8_t>::max())
-                  << "'");
+                  << "' (" << uint32_values_->getPosition("code") << ")");
 
     } else if (global_context_->universe_ == Option::V6 &&
-               option_code > std::numeric_limits<uint16_t>::max()) {
-        isc_throw(DhcpConfigError, "invalid option code '" << option_code
+               code > std::numeric_limits<uint16_t>::max()) {
+        isc_throw(DhcpConfigError, "invalid option code '" << code
                 << "', it must not exceed '"
                   << std::numeric_limits<uint16_t>::max()
-                  << "'");
+                  << "' (" << uint32_values_->getPosition("code") << ")");
 
     }
 
-    // Check that the option name has been specified, is non-empty and does not
-    // contain spaces
-    std::string option_name = string_values_->getParam("name");
-    if (option_name.empty()) {
+    // Check that the option name is non-empty and does not contain spaces
+    if (name.empty()) {
         isc_throw(DhcpConfigError, "name of the option with code '"
-                << option_code << "' is empty");
-    } else if (option_name.find(" ") != std::string::npos) {
-        isc_throw(DhcpConfigError, "invalid option name '" << option_name
-                << "', space character is not allowed");
+                  << code << "' is empty ("
+                  << string_values_->getPosition("name") << ")");
+    } else if (name.find(" ") != std::string::npos) {
+        isc_throw(DhcpConfigError, "invalid option name '" << name
+                  << "', space character is not allowed ("
+                  << string_values_->getPosition("name") << ")");
     }
 
-    std::string option_space = string_values_->getParam("space");
-    if (!OptionSpace::validateName(option_space)) {
+    if (!OptionSpace::validateName(space)) {
         isc_throw(DhcpConfigError, "invalid option space name '"
-                << option_space << "' specified for option '"
-                << option_name << "' (code '" << option_code
-                << "')");
+                << space << "' specified for option '"
+                << name << "', code '" << code
+                  << "' (" << string_values_->getPosition("space") << ")");
     }
 
-    const bool csv_format = boolean_values_->getParam("csv-format");
-
     // Find the Option Definition for the option by its option code.
     // findOptionDefinition will throw if not found, no need to test.
+    // Find the definition for the option by its code. This function
+    // may throw so we catch exceptions to log the culprit line of the
+    // configuration.
     OptionDefinitionPtr def;
-    if (!(def = findServerSpaceOptionDefinition(option_space, option_code))) {
+    try {
+        def = findServerSpaceOptionDefinition(space, code);
+
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, ex.what()
+                  << " (" << string_values_->getPosition("space") << ")");
+    }
+    if (!def) {
         // If we are not dealing with a standard option then we
         // need to search for its definition among user-configured
         // options. They are expected to be in the global storage
         // already.
         OptionDefContainerPtr defs =
-            global_context_->option_defs_->getItems(option_space);
+            global_context_->option_defs_->getItems(space);
 
         // The getItems() should never return the NULL pointer. If there are
         // no option definitions for the particular option space a pointer
         // to an empty container should be returned.
         assert(defs);
         const OptionDefContainerTypeIndex& idx = defs->get<1>();
-        OptionDefContainerTypeRange range = idx.equal_range(option_code);
+        OptionDefContainerTypeRange range = idx.equal_range(code);
         if (std::distance(range.first, range.second) > 0) {
             def = *range.first;
         }
@@ -469,15 +507,13 @@ OptionDataParser::createOption() {
         // specified as hex
         if (!def && csv_format) {
             isc_throw(DhcpConfigError, "definition for the option '"
-                      << option_space << "." << option_name
-                      << "' having code '" <<  option_code
-                      << "' does not exist");
+                      << space << "." << name
+                      << "' having code '" << code
+                      << "' does not exist ("
+                      << string_values_->getPosition("name") << ")");
         }
     }
 
-    // Get option data from the configuration database ('data' field).
-    std::string option_data = string_values_->getParam("data");
-
     // Transform string of hexadecimal digits into binary format.
     std::vector<uint8_t> binary;
     std::vector<std::string> data_tokens;
@@ -487,7 +523,7 @@ OptionDataParser::createOption() {
         // separated values then we need to split this string into
         // individual values - each value will be used to initialize
         // one data field of an option.
-        data_tokens = isc::util::str::tokens(option_data, ",");
+        data_tokens = isc::util::str::tokens(data, ",");
     } else {
         // Otherwise, the option data is specified as a string of
         // hexadecimal digits that we have to turn into binary format.
@@ -495,13 +531,14 @@ OptionDataParser::createOption() {
             // The decodeHex function expects that the string contains an
             // even number of digits. If we don't meet this requirement,
             // we have to insert a leading 0.
-            if (!option_data.empty() && option_data.length() % 2) {
-                option_data = option_data.insert(0, "0");
+            if (!data.empty() && data.length() % 2) {
+                data = data.insert(0, "0");
             }
-            util::encode::decodeHex(option_data, binary);
+            util::encode::decodeHex(data, binary);
         } catch (...) {
             isc_throw(DhcpConfigError, "option data is not a valid"
-                      << " string of hexadecimal digits: " << option_data);
+                      << " string of hexadecimal digits: " << data
+                      << " (" << string_values_->getPosition("data") << ")");
         }
     }
 
@@ -510,17 +547,18 @@ OptionDataParser::createOption() {
         if (csv_format) {
             isc_throw(DhcpConfigError, "the CSV option data format can be"
                       " used to specify values for an option that has a"
-                      " definition. The option with code " << option_code
-                      << " does not have a definition.");
+                      " definition. The option with code " << code
+                      << " does not have a definition ("
+                      << boolean_values_->getPosition("csv-format") << ")");
         }
 
-        // @todo We have a limited set of option definitions intiialized at
+        // @todo We have a limited set of option definitions initalized at
         // the moment.  In the future we want to initialize option definitions
         // for all options.  Consequently an error will be issued if an option
         // definition does not exist for a particular option code. For now it is
         // ok to create generic option if definition does not exist.
         OptionPtr option(new Option(global_context_->universe_,
-                        static_cast<uint16_t>(option_code), binary));
+                        static_cast<uint16_t>(code), binary));
         // The created option is stored in option_descriptor_ class member
         // until the commit stage when it is inserted into the main storage.
         // If an option with the same code exists in main storage already the
@@ -534,11 +572,12 @@ OptionDataParser::createOption() {
         // to reference options and definitions using their names
         // and/or option codes so keeping the option name in the
         // definition of option value makes sense.
-        if (def->getName() != option_name) {
+        if (def->getName() != name) {
             isc_throw(DhcpConfigError, "specified option name '"
-                      << option_name << "' does not match the "
-                      << "option definition: '" << option_space
-                      << "." << def->getName() << "'");
+                      << name << "' does not match the "
+                      << "option definition: '" << space
+                      << "." << def->getName() << "' ("
+                      << string_values_->getPosition("name") << ")");
         }
 
         // Option definition has been found so let's use it to create
@@ -546,22 +585,23 @@ OptionDataParser::createOption() {
         try {
             OptionPtr option = csv_format ?
                 def->optionFactory(global_context_->universe_,
-                                  option_code, data_tokens) :
+                                  code, data_tokens) :
                 def->optionFactory(global_context_->universe_,
-                                  option_code, binary);
+                                   code, binary);
             Subnet::OptionDescriptor desc(option, false);
             option_descriptor_.option = option;
             option_descriptor_.persistent = false;
         } catch (const isc::Exception& ex) {
             isc_throw(DhcpConfigError, "option data does not match"
-                      << " option definition (space: " << option_space
-                      << ", code: " << option_code << "): "
-                      << ex.what());
+                      << " option definition (space: " << space
+                      << ", code: " << code << "): "
+                      << ex.what() << " ("
+                      << string_values_->getPosition("data") << ")");
         }
     }
 
     // All went good, so we can set the option space name.
-    option_space_ = option_space;
+    option_space_ = space;
 }
 
 // **************************** OptionDataListParser *************************
@@ -617,9 +657,13 @@ OptionDataListParser::commit() {
 
 // ******************************** OptionDefParser ****************************
 OptionDefParser::OptionDefParser(const std::string&,
-                                OptionDefStoragePtr storage)
-    : storage_(storage), boolean_values_(new BooleanStorage()),
-    string_values_(new StringStorage()), uint32_values_(new Uint32Storage()) {
+                                 OptionDefStoragePtr storage,
+                                 ParserContextPtr global_context)
+    : storage_(storage),
+      boolean_values_(new BooleanStorage()),
+      string_values_(new StringStorage()),
+      uint32_values_(new Uint32Storage()),
+      global_context_(global_context) {
     if (!storage_) {
         isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
              << "options storage may not be NULL");
@@ -629,7 +673,7 @@ OptionDefParser::OptionDefParser(const std::string&,
 void
 OptionDefParser::build(ConstElementPtr option_def) {
     // Parse the elements that make up the option definition.
-     BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+    BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
         std::string entry(param.first);
         ParserPtr parser;
         if (entry == "name" || entry == "type" || entry == "record-types"
@@ -646,15 +690,15 @@ OptionDefParser::build(ConstElementPtr option_def) {
                                          boolean_values_));
             parser = array_parser;
         } else {
-            isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+            isc_throw(DhcpConfigError, "invalid parameter '" << entry
+                      << "' (" << param.second->getPosition() << ")");
         }
 
         parser->build(param.second);
         parser->commit();
     }
-
     // Create an instance of option definition.
-    createOptionDef();
+    createOptionDef(option_def);
 
     // Get all items we collected so far for the particular option space.
     OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
@@ -670,7 +714,8 @@ OptionDefParser::build(ConstElementPtr option_def) {
     // option definitions within an option space.
     if (std::distance(range.first, range.second) > 0) {
         isc_throw(DhcpConfigError, "duplicated option definition for"
-                << " code '" << option_definition_->getCode() << "'");
+                  << " code '" << option_definition_->getCode() << "' ("
+                  << option_def->getPosition() << ")");
     }
 }
 
@@ -683,22 +728,34 @@ OptionDefParser::commit() {
 }
 
 void
-OptionDefParser::createOptionDef() {
-    // Get the option space name and validate it.
-    std::string space = string_values_->getParam("space");
+OptionDefParser::createOptionDef(ConstElementPtr option_def_element) {
+    // Check if mandatory parameters have been specified.
+    std::string name;
+    uint32_t code;
+    std::string type;
+    try {
+        name = string_values_->getParam("name");
+        code = uint32_values_->getParam("code");
+        type = string_values_->getParam("type");
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << option_def_element->getPosition() << ")");
+    }
+
+    bool array_type = boolean_values_->getOptionalParam("array", false);
+    std::string record_types =
+        string_values_->getOptionalParam("record-types", "");
+    std::string space = string_values_->getOptionalParam("space",
+              global_context_->universe_ == Option::V4 ? "dhcp4" : "dhcp6");
+    std::string encapsulates =
+        string_values_->getOptionalParam("encapsulate", "");
+
     if (!OptionSpace::validateName(space)) {
         isc_throw(DhcpConfigError, "invalid option space name '"
-                  << space << "'");
+                  << space << "' ("
+                  << string_values_->getPosition("space") << ")");
     }
 
-    // Get other parameters that are needed to create the
-    // option definition.
-    std::string name = string_values_->getParam("name");
-    uint32_t code = uint32_values_->getParam("code");
-    std::string type = string_values_->getParam("type");
-    bool array_type = boolean_values_->getParam("array");
-    std::string encapsulates = string_values_->getParam("encapsulate");
-
     // Create option definition.
     OptionDefinitionPtr def;
     // We need to check if user has set encapsulated option space
@@ -708,13 +765,15 @@ OptionDefParser::createOptionDef() {
         if (array_type) {
             isc_throw(DhcpConfigError, "option '" << space << "."
                       << "name" << "', comprising an array of data"
-                      << " fields may not encapsulate any option space");
+                      << " fields may not encapsulate any option space ("
+                      << option_def_element->getPosition() << ")");
 
         } else if (encapsulates == space) {
             isc_throw(DhcpConfigError, "option must not encapsulate"
                       << " an option space it belongs to: '"
                       << space << "." << name << "' is set to"
-                      << " encapsulate '" << space << "'");
+                      << " encapsulate '" << space << "' ("
+                      << option_def_element->getPosition() << ")");
 
         } else {
             def.reset(new OptionDefinition(name, code, type,
@@ -726,10 +785,6 @@ OptionDefParser::createOptionDef() {
 
     }
 
-    // The record-types field may carry a list of comma separated names
-    // of data types that form a record.
-    std::string record_types = string_values_->getParam("record-types");
-
     // Split the list of record types into tokens.
     std::vector<std::string> record_tokens =
     isc::util::str::tokens(record_types, ",");
@@ -744,16 +799,17 @@ OptionDefParser::createOptionDef() {
         } catch (const Exception& ex) {
             isc_throw(DhcpConfigError, "invalid record type values"
                       << " specified for the option definition: "
-                      << ex.what());
+                      << ex.what() << " ("
+                      << string_values_->getPosition("record-types") << ")");
         }
     }
 
-    // Check the option definition parameters are valid.
+    // Validate the definition.
     try {
         def->validate();
-    } catch (const isc::Exception& ex) {
-        isc_throw(DhcpConfigError, "invalid option definition"
-                  << " parameters: " << ex.what());
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, ex.what()
+                  << " (" << option_def_element->getPosition() << ")");
     }
 
     // Option definition has been created successfully.
@@ -763,7 +819,9 @@ OptionDefParser::createOptionDef() {
 
 // ******************************** OptionDefListParser ************************
 OptionDefListParser::OptionDefListParser(const std::string&,
-    OptionDefStoragePtr storage) :storage_(storage) {
+                                         ParserContextPtr global_context)
+    : storage_(global_context->option_defs_),
+      global_context_(global_context) {
     if (!storage_) {
         isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
              << "storage may not be NULL");
@@ -778,26 +836,24 @@ OptionDefListParser::build(ConstElementPtr option_def_list) {
 
     if (!option_def_list) {
         isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
-                  << " option definitions is NULL");
+                  << " option definitions is NULL ("
+                  << option_def_list->getPosition() << ")");
     }
 
     BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
         boost::shared_ptr<OptionDefParser>
-                parser(new OptionDefParser("single-option-def", storage_));
+            parser(new OptionDefParser("single-option-def", storage_,
+                                       global_context_));
         parser->build(option_def);
         parser->commit();
     }
-}
 
-void
-OptionDefListParser::commit() {
     CfgMgr& cfg_mgr = CfgMgr::instance();
     cfg_mgr.deleteOptionDefs();
 
     // We need to move option definitions from the temporary
     // storage to the storage.
-    std::list<std::string> space_names =
-    storage_->getOptionSpaceNames();
+    std::list<std::string> space_names = storage_->getOptionSpaceNames();
 
     BOOST_FOREACH(std::string space_name, space_names) {
         BOOST_FOREACH(OptionDefinitionPtr def,
@@ -806,11 +862,24 @@ OptionDefListParser::commit() {
             // values. The validation is expected to be made by the
             // OptionDefParser when creating an option definition.
             assert(def);
-            cfg_mgr.addOptionDef(def, space_name);
+            // The Config Manager may thrown an exception if the duplicated
+            // definition is being added. Catch the exceptions here to and
+            // append the position in the config.
+            try {
+                cfg_mgr.addOptionDef(def, space_name);
+            } catch (const std::exception& ex) {
+                isc_throw(DhcpConfigError, ex.what() << " ("
+                          << option_def_list->getPosition() << ")");
+            }
         }
     }
 }
 
+void
+OptionDefListParser::commit() {
+    // Do nothing.
+}
+
 //****************************** RelayInfoParser ********************************
 RelayInfoParser::RelayInfoParser(const std::string&,
                                  const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
@@ -840,14 +909,16 @@ RelayInfoParser::build(ConstElementPtr relay_info) {
         ip.reset(new asiolink::IOAddress(string_values_->getParam("ip-address")));
     } catch (...)  {
         isc_throw(DhcpConfigError, "Failed to parse ip-address "
-                  "value: " << string_values_->getParam("ip-address"));
+                  "value: " << string_values_->getParam("ip-address")
+                  << " (" << string_values_->getPosition("ip-address") << ")");
     }
 
     if ( (ip->isV4() && family_ != Option::V4) ||
          (ip->isV6() && family_ != Option::V6) ) {
         isc_throw(DhcpConfigError, "ip-address field " << ip->toText()
                   << "does not have IP address of expected family type: "
-                  << (family_ == Option::V4?"IPv4":"IPv6"));
+                  << (family_ == Option::V4 ? "IPv4" : "IPv6")
+                  << " (" << string_values_->getPosition("ip-address") << ")");
     }
 
     local_.addr_ = *ip;
@@ -916,7 +987,8 @@ PoolParser::build(ConstElementPtr pools_list) {
                 len = boost::lexical_cast<int>(prefix_len);
             } catch (...)  {
                 isc_throw(DhcpConfigError, "Failed to parse pool "
-                          "definition: " << text_pool->stringValue());
+                          "definition: " << text_pool->stringValue()
+                          << " (" << text_pool->getPosition() << ")");
             }
 
             PoolPtr pool(poolMaker(addr, len));
@@ -936,9 +1008,11 @@ PoolParser::build(ConstElementPtr pools_list) {
             continue;
         }
 
-        isc_throw(DhcpConfigError, "Failed to parse pool definition:"
+        isc_throw(DhcpConfigError, "invalid pool definition: "
                   << text_pool->stringValue() <<
-                  ". Does not contain - (for min-max) nor / (prefix/len)");
+                  ". There are two acceptable formats <min address-max address>"
+                  " or <prefix/len> ("
+                  << text_pool->getPosition() << ")");
         }
 }
 
@@ -972,7 +1046,16 @@ SubnetConfigParser::SubnetConfigParser(const std::string&,
 void
 SubnetConfigParser::build(ConstElementPtr subnet) {
     BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
-        ParserPtr parser(createSubnetConfigParser(param.first));
+        ParserPtr parser;
+        // When unsupported parameter is specified, the function called
+        // below will thrown an exception. We have to catch this exception
+        // to append the line number where the parameter is.
+        try {
+            parser.reset(createSubnetConfigParser(param.first));
+        } catch (const std::exception& ex) {
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << param.second->getPosition() << ")");
+        }
         parser->build(param.second);
         parsers_.push_back(parser);
     }
@@ -990,7 +1073,13 @@ SubnetConfigParser::build(ConstElementPtr subnet) {
     }
 
     // Create a subnet.
-    createSubnet();
+    try {
+        createSubnet();
+    } catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError,
+                  "subnet configuration failed (" << subnet->getPosition()
+                  << "): " << ex.what());
+    }
 }
 
 void
@@ -1056,7 +1145,8 @@ SubnetConfigParser::createSubnet() {
     } catch (const DhcpConfigError &) {
         // rethrow with precise error
         isc_throw(DhcpConfigError,
-                 "Mandatory subnet definition in subnet missing");
+                 "mandatory 'subnet' parameter is missing for a subnet being"
+                  " configured");
     }
 
     // Remove any spaces or tabs.
@@ -1071,7 +1161,8 @@ SubnetConfigParser::createSubnet() {
     size_t pos = subnet_txt.find("/");
     if (pos == string::npos) {
         isc_throw(DhcpConfigError,
-                  "Invalid subnet syntax (prefix/len expected):" << subnet_txt);
+                  "Invalid subnet syntax (prefix/len expected):" << subnet_txt
+                  << " (" << string_values_->getPosition("subnet") << ")");
     }
 
     // Try to create the address object. It also validates that
@@ -1102,8 +1193,9 @@ SubnetConfigParser::createSubnet() {
     if (!iface.empty()) {
         if (!IfaceMgr::instance().getIface(iface)) {
             isc_throw(DhcpConfigError, "Specified interface name " << iface
-                     << " for subnet " << subnet_->toText()
-                     << " is not present" << " in the system.");
+                      << " for subnet " << subnet_->toText()
+                      << " is not present" << " in the system ("
+                      << string_values_->getPosition("interface") << ")");
         }
 
         subnet_->setIface(iface);
@@ -1257,89 +1349,139 @@ D2ClientConfigParser::~D2ClientConfigParser() {
 void
 D2ClientConfigParser::build(isc::data::ConstElementPtr client_config) {
     BOOST_FOREACH(ConfigPair param, client_config->mapValue()) {
-        ParserPtr parser(createConfigParser(param.first));
+        ParserPtr parser;
+        try {
+            parser = createConfigParser(param.first);
+        } catch (std::exception& ex) {
+            // Catch exception in case the configuration contains the
+            // unsupported parameter. In this case, we will need to
+            // append the position of this element.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << param.second->getPosition() << ")");
+        }
+
         parser->build(param.second);
         parser->commit();
     }
 
-    bool enable_updates = boolean_values_->getParam("enable-updates");
-    if (!enable_updates && (client_config->mapValue().size() == 1)) {
-        // If enable-updates is the only parameter and it is false then
-        // we're done.  This allows for an abbreviated configuration entry
-        // that only contains that flag.  Use the default D2ClientConfig
-        // constructor to a create a disabled instance.
-        local_client_config_.reset(new D2ClientConfig());
-        return;
-    }
+    /// @todo Create configuration from the configuration parameters. Because
+    /// the validation of the D2 configuration is atomic, there is no way to
+    /// tell which parameter is invalid. Therefore, we catch all exceptions
+    /// and append the line number of the parent element. In the future we
+    /// may should extend D2ClientConfig code so as it returns the name of
+    /// the invalid parameter.
+    try {
+        bool enable_updates = boolean_values_->getParam("enable-updates");
+        if (!enable_updates && (client_config->mapValue().size() == 1)) {
+            // If enable-updates is the only parameter and it is false then
+            // we're done.  This allows for an abbreviated configuration entry
+            // that only contains that flag.  Use the default D2ClientConfig
+            // constructor to a create a disabled instance.
+            local_client_config_.reset(new D2ClientConfig());
 
-    // Get all parameters that are needed to create the D2ClientConfig.
-    asiolink::IOAddress server_ip(string_values_->
-                                  getOptionalParam("server-ip",
-                                                   D2ClientConfig::
-                                                   DFT_SERVER_IP));
-
-    uint32_t server_port = uint32_values_->getOptionalParam("server-port",
-                                                             D2ClientConfig::
-                                                             DFT_SERVER_PORT);
-    dhcp_ddns::NameChangeProtocol ncr_protocol
-        = dhcp_ddns::stringToNcrProtocol(string_values_->
-                                         getOptionalParam("ncr-protocol",
-                                                          D2ClientConfig::
-                                                          DFT_NCR_PROTOCOL));
-    dhcp_ddns::NameChangeFormat ncr_format
-        = dhcp_ddns::stringToNcrFormat(string_values_->
-                                       getOptionalParam("ncr-format",
-                                                          D2ClientConfig::
-                                                          DFT_NCR_FORMAT));
-    std::string generated_prefix = string_values_->
-                                   getOptionalParam("generated-prefix",
-                                                    D2ClientConfig::
-                                                    DFT_GENERATED_PREFIX);
-    std::string qualifying_suffix = string_values_->
-                                    getOptionalParam("qualifying-suffix",
-                                                     D2ClientConfig::
-                                                     DFT_QUALIFYING_SUFFIX);
-
-    bool always_include_fqdn = boolean_values_->
-                               getOptionalParam("always-include-fqdn",
-                                               D2ClientConfig::
-                                               DFT_ALWAYS_INCLUDE_FQDN);
+            return;
+        }
 
-    bool override_no_update = boolean_values_->
-                              getOptionalParam("override-no-update",
+        // Get all parameters that are needed to create the D2ClientConfig.
+        IOAddress server_ip =
+            IOAddress(string_values_->getOptionalParam("server-ip",
+                                                       D2ClientConfig::
+                                                       DFT_SERVER_IP));
+
+        uint32_t server_port =
+            uint32_values_->getOptionalParam("server-port",
+                                             D2ClientConfig::DFT_SERVER_PORT);
+
+        // The default sender IP depends on the server IP family
+        asiolink::IOAddress
+            sender_ip(string_values_->
+                      getOptionalParam("sender-ip",
+                                       (server_ip.isV4() ?
+                                        D2ClientConfig::DFT_V4_SENDER_IP :
+                                        D2ClientConfig::DFT_V6_SENDER_IP)));
+
+        uint32_t sender_port =
+            uint32_values_->getOptionalParam("sender-port",
+                                             D2ClientConfig::
+                                             DFT_SENDER_PORT);
+        uint32_t max_queue_size
+            = uint32_values_->getOptionalParam("max-queue-size",
                                                D2ClientConfig::
-                                               DFT_OVERRIDE_NO_UPDATE);
-
-    bool override_client_update = boolean_values_->
-                                  getOptionalParam("override-client-update",
-                                                   D2ClientConfig::
-                                                   DFT_OVERRIDE_CLIENT_UPDATE);
-    bool replace_client_name = boolean_values_->
-                               getOptionalParam("replace-client-name",
+                                               DFT_MAX_QUEUE_SIZE);
+
+        dhcp_ddns::NameChangeProtocol ncr_protocol =
+            dhcp_ddns::stringToNcrProtocol(string_values_->
+                                           getOptionalParam("ncr-protocol",
+                                                            D2ClientConfig::
+                                                            DFT_NCR_PROTOCOL));
+        dhcp_ddns::NameChangeFormat ncr_format
+            = dhcp_ddns::stringToNcrFormat(string_values_->
+                                           getOptionalParam("ncr-format",
+                                                            D2ClientConfig::
+                                                            DFT_NCR_FORMAT));
+        std::string generated_prefix =
+            string_values_->getOptionalParam("generated-prefix",
+                                             D2ClientConfig::
+                                             DFT_GENERATED_PREFIX);
+        std::string qualifying_suffix =
+            string_values_->getOptionalParam("qualifying-suffix",
+                                             D2ClientConfig::
+                                             DFT_QUALIFYING_SUFFIX);
+
+        bool always_include_fqdn =
+            boolean_values_->getOptionalParam("always-include-fqdn",
                                                 D2ClientConfig::
-                                                DFT_REPLACE_CLIENT_NAME);
-
-    // Attempt to create the new client config.
-    local_client_config_.reset(new D2ClientConfig(enable_updates, server_ip,
-                                                  server_port, ncr_protocol,
-                                                  ncr_format,
-                                                  always_include_fqdn,
-                                                  override_no_update,
-                                                  override_client_update,
-                                                  replace_client_name,
-                                                  generated_prefix,
-                                                  qualifying_suffix));
+                                                DFT_ALWAYS_INCLUDE_FQDN);
+
+        bool override_no_update =
+            boolean_values_->getOptionalParam("override-no-update",
+                                              D2ClientConfig::
+                                              DFT_OVERRIDE_NO_UPDATE);
+
+        bool override_client_update =
+            boolean_values_->getOptionalParam("override-client-update",
+                                              D2ClientConfig::
+                                              DFT_OVERRIDE_CLIENT_UPDATE);
+
+        bool replace_client_name =
+            boolean_values_->getOptionalParam("replace-client-name",
+                                              D2ClientConfig::
+                                              DFT_REPLACE_CLIENT_NAME);
+
+        // Attempt to create the new client config.
+        local_client_config_.reset(new D2ClientConfig(enable_updates,
+                                                      server_ip,
+                                                      server_port,
+                                                      sender_ip,
+                                                      sender_port,
+                                                      max_queue_size,
+                                                      ncr_protocol,
+                                                      ncr_format,
+                                                      always_include_fqdn,
+                                                      override_no_update,
+                                                      override_client_update,
+                                                      replace_client_name,
+                                                      generated_prefix,
+                                                      qualifying_suffix));
+
+    }  catch (const std::exception& ex) {
+        isc_throw(DhcpConfigError, ex.what() << " ("
+                  << client_config->getPosition() << ")");
+    }
 }
 
 isc::dhcp::ParserPtr
 D2ClientConfigParser::createConfigParser(const std::string& config_id) {
     DhcpConfigParser* parser = NULL;
-    if (config_id.compare("server-port") == 0) {
+    if ((config_id.compare("server-port") == 0) ||
+        (config_id.compare("sender-port") == 0) ||
+        (config_id.compare("max-queue-size") == 0)) {
         parser = new Uint32Parser(config_id, uint32_values_);
     } else if ((config_id.compare("server-ip") == 0) ||
         (config_id.compare("ncr-protocol") == 0) ||
         (config_id.compare("ncr-format") == 0) ||
         (config_id.compare("generated-prefix") == 0) ||
+        (config_id.compare("sender-ip") == 0) ||
         (config_id.compare("qualifying-suffix") == 0)) {
         parser = new StringParser(config_id, string_values_);
     } else if ((config_id.compare("enable-updates") == 0) ||

+ 158 - 82
src/lib/dhcpsrv/dhcp_parsers.h

@@ -51,86 +51,127 @@ typedef boost::shared_ptr<std::vector<std::string> > HooksLibsStoragePtr;
 
 /// @brief A template class that stores named elements of a given data type.
 ///
-/// This template class is provides data value storage for configuration parameters
-/// of a given data type.  The values are stored by parameter name and as instances
-/// of type "ValueType".
+/// This template class is provides data value storage for configuration
+/// parameters of a given data type.  The values are stored by parameter name
+/// and as instances of type "ValueType". Each value held in the storage has
+/// a corresponding position within a configuration string (file) specified
+/// as a: file name, line number and position within the line. The position
+/// information is used for logging when the particular configuration value
+/// causes a configuration error.
 ///
-/// @param ValueType is the data type of the elements to store.
+/// @tparam ValueType is the data type of the elements to store.
 template<typename ValueType>
 class ValueStorage {
-    public:
-        /// @brief  Stores the the parameter and its value in the store.
-        ///
-        /// If the parameter does not exist in the store, then it will be added,
-        /// otherwise its data value will be updated with the given value.
-        ///
-        /// @param name is the name of the paramater to store.
-        /// @param value is the data value to store.
-        void setParam(const std::string& name, const ValueType& value) {
-            values_[name] = value;
-        }
+public:
+    /// @brief  Stores the the parameter, its value and the position in the
+    /// store.
+    ///
+    /// If the parameter does not exist in the store, then it will be added,
+    /// otherwise its data value and the position will be updated with the
+    /// given values.
+    ///
+    /// @param name is the name of the paramater to store.
+    /// @param value is the data value to store.
+    /// @param position is the position of the data element within a
+    /// configuration string (file).
+    void setParam(const std::string& name, const ValueType& value,
+                  const data::Element::Position& position) {
+        values_[name] = value;
+        positions_[name] = position;
+    }
 
-        /// @brief Returns the data value for the given parameter.
-        ///
-        /// Finds and returns the data value for the given parameter.
-        /// @param name is the name of the parameter for which the data
-        /// value is desired.
-        ///
-        /// @return The paramater's data value of type @c ValueType.
-        /// @throw DhcpConfigError if the parameter is not found.
-        ValueType getParam(const std::string& name) const {
-            typename std::map<std::string, ValueType>::const_iterator param
-                = values_.find(name);
-
-            if (param == values_.end()) {
-                isc_throw(DhcpConfigError, "Missing parameter '"
-                       << name << "'");
-            }
-
-            return (param->second);
-        }
+    /// @brief Returns the data value for the given parameter.
+    ///
+    /// Finds and returns the data value for the given parameter.
+    /// @param name is the name of the parameter for which the data
+    /// value is desired.
+    ///
+    /// @return The paramater's data value of type @c ValueType.
+    /// @throw DhcpConfigError if the parameter is not found.
+    ValueType getParam(const std::string& name) const {
+        typename std::map<std::string, ValueType>::const_iterator param
+            = values_.find(name);
 
-        /// @brief Returns the data value for an optional parameter.
-        ///
-        /// Finds and returns the data value for the given parameter or
-        /// a supplied default value if it is not found.
-        ///
-        /// @param name is the name of the parameter for which the data
-        /// value is desired.
-        /// @param default_value value to use the default
-        ///
-        /// @return The paramater's data value of type @c ValueType.
-        ValueType getOptionalParam(const std::string& name,
-                                   const ValueType& default_value) const {
-            typename std::map<std::string, ValueType>::const_iterator param
-                = values_.find(name);
-
-            if (param == values_.end()) {
-                return (default_value);
-            }
-
-            return (param->second);
+        if (param == values_.end()) {
+            isc_throw(DhcpConfigError, "Missing parameter '"
+                      << name << "'");
         }
 
-        /// @brief  Remove the parameter from the store.
-        ///
-        /// Deletes the entry for the given parameter from the store if it
-        /// exists.
-        ///
-        /// @param name is the name of the paramater to delete.
-        void delParam(const std::string& name) {
-            values_.erase(name);
+        return (param->second);
+    }
+
+    /// @brief Returns position of the data element in the configuration string.
+    ///
+    /// The returned object comprises file name, line number and the position
+    /// within the particular line of the configuration string where the data
+    /// element holding a particular value is located.
+    ///
+    /// @param name is the name of the parameter which position is desired.
+    ///
+    /// @return Position of the data element or the position holding empty
+    /// file name and two zeros if the position hasn't been specified for the
+    /// particular value.
+    const data::Element::Position& getPosition(const std::string& name) const {
+        typename std::map<std::string, data::Element::Position>::const_iterator
+            pos = positions_.find(name);
+        if (pos == positions_.end()) {
+            return (data::Element::ZERO_POSITION());
         }
 
-        /// @brief Deletes all of the entries from the store.
-        ///
-        void clear() {
-            values_.clear();
+        return (pos->second);
+    }
+
+    /// @brief Returns the data value for an optional parameter.
+    ///
+    /// Finds and returns the data value for the given parameter or
+    /// a supplied default value if it is not found.
+    ///
+    /// @param name is the name of the parameter for which the data
+    /// value is desired.
+    /// @param default_value value to use the default
+    ///
+    /// @return The paramater's data value of type @c ValueType.
+    ValueType getOptionalParam(const std::string& name,
+                               const ValueType& default_value) const {
+        typename std::map<std::string, ValueType>::const_iterator param
+            = values_.find(name);
+
+        if (param == values_.end()) {
+            return (default_value);
         }
 
-    private:
-        /// @brief An std::map of the data values, keyed by parameter names.
-        std::map<std::string, ValueType> values_;
+        return (param->second);
+    }
+
+    /// @brief  Remove the parameter from the store.
+    ///
+    /// Deletes the entry for the given parameter from the store if it
+    /// exists.
+    ///
+    /// @param name is the name of the paramater to delete.
+    void delParam(const std::string& name) {
+        values_.erase(name);
+        positions_.erase(name);
+    }
+
+    /// @brief Deletes all of the entries from the store.
+    ///
+    void clear() {
+        values_.clear();
+        positions_.clear();
+    }
+
+private:
+    /// @brief An std::map of the data values, keyed by parameter names.
+    std::map<std::string, ValueType> values_;
+
+    /// @brief An std::map holding positions of the data elements in the
+    /// configuration, which values are held in @c values_.
+    ///
+    /// The position is used for logging, when the particular value
+    /// causes a configuration error.
+    std::map<std::string, data::Element::Position> positions_;
+
 };
 
 
@@ -237,7 +278,7 @@ public:
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
     ValueParser(const std::string& param_name,
         boost::shared_ptr<ValueStorage<ValueType> > storage)
-        : storage_(storage), param_name_(param_name), value_() {
+        : storage_(storage), param_name_(param_name), value_(), pos_() {
         // Empty parameter name is invalid.
         if (param_name_.empty()) {
             isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
@@ -251,7 +292,6 @@ public:
         }
     }
 
-
     /// @brief Parse a given element into a value of type @c ValueType
     ///
     /// @param value a value to be parsed.
@@ -264,10 +304,23 @@ public:
     void commit() {
         // If a given parameter already exists in the storage we override
         // its value. If it doesn't we insert a new element.
-        storage_->setParam(param_name_, value_);
+        storage_->setParam(param_name_, value_, pos_);
     }
 
 private:
+
+    /// @brief Performs operations common for all specializations of the
+    /// @c build function.
+    ///
+    /// This method should be called by all specializations of the @c build
+    /// method.
+    ///
+    /// @param value a value being parsed.
+    void buildCommon(isc::data::ConstElementPtr value) {
+        // Remember position of the data element.
+        pos_ = value->getPosition();
+    }
+
     /// Pointer to the storage where committed value is stored.
     boost::shared_ptr<ValueStorage<ValueType> > storage_;
 
@@ -276,6 +329,8 @@ private:
 
     /// Parsed value.
     ValueType value_;
+
+    data::Element::Position pos_;
 };
 
 /// @brief typedefs for simple data type parsers
@@ -535,9 +590,12 @@ private:
     /// is intitialized but this check is not needed here because it is done
     /// in the \ref build function.
     ///
+    /// @param option_data An element holding data for a single option being
+    /// created.
+    ///
     /// @throw DhcpConfigError if parameters provided in the configuration
     /// are invalid.
-    void createOption();
+    void createOption(isc::data::ConstElementPtr option_data);
 
     /// Storage for boolean values.
     BooleanStoragePtr boolean_values_;
@@ -635,8 +693,11 @@ public:
     /// accept string as first argument.
     /// @param storage is the definition storage in which to store the parsed
     /// definition upon "commit".
+    /// @param global_context is a pointer to the global context which
+    /// stores global scope parameters, options, option defintions.
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    OptionDefParser(const std::string& dummy, OptionDefStoragePtr storage);
+    OptionDefParser(const std::string& dummy, OptionDefStoragePtr storage,
+                    ParserContextPtr global_context);
 
     /// @brief Parses an entry that describes single option definition.
     ///
@@ -651,7 +712,10 @@ public:
 private:
 
     /// @brief Create option definition from the parsed parameters.
-    void createOptionDef();
+    ///
+    /// @param option_def_element A data element holding the configuration
+    /// for an option definition.
+    void createOptionDef(isc::data::ConstElementPtr option_def_element);
 
     /// Instance of option definition being created by this parser.
     OptionDefinitionPtr option_definition_;
@@ -670,6 +734,10 @@ private:
 
     /// Storage for uint32 values.
     Uint32StoragePtr uint32_values_;
+
+    /// Parsing context which contains global values, options and option
+    /// definitions.
+    ParserContextPtr global_context_;
 };
 
 /// @brief Parser for a list of option definitions.
@@ -684,27 +752,35 @@ public:
     ///
     /// @param dummy first argument is ignored, all Parser constructors
     /// accept string as first argument.
-    /// @param storage is the definition storage in which to store the parsed
-    /// definitions in this list
+    /// @param global_context is a pointer to the global context which
+    /// stores global scope parameters, options, option defintions.
     /// @throw isc::dhcp::DhcpConfigError if storage is null.
-    OptionDefListParser(const std::string& dummy, OptionDefStoragePtr storage);
+    OptionDefListParser(const std::string& dummy,
+                        ParserContextPtr global_context);
 
     /// @brief Parse configuration entries.
     ///
-    /// This function parses configuration entries and creates instances
-    /// of option definitions.
+    /// This function parses configuration entries, creates instances
+    /// of option definitions and tries to add them to the Configuration
+    /// Manager.
     ///
     /// @param option_def_list pointer to an element that holds entries
     /// that define option definitions.
     /// @throw DhcpConfigError if configuration parsing fails.
     void build(isc::data::ConstElementPtr option_def_list);
 
-    /// @brief Stores option definitions in the CfgMgr.
+    /// @brief Commits option definitions.
+    ///
+    /// Currently this function is no-op, because option definitions are
+    /// added to the Configuration Manager in the @c build method.
     void commit();
 
-private:
     /// @brief storage for option definitions.
     OptionDefStoragePtr storage_;
+
+    /// Parsing context which contains global values, options and option
+    /// definitions.
+    ParserContextPtr global_context_;
 };
 
 /// @brief a collection of pools

+ 3 - 3
src/lib/dhcpsrv/dhcpsrv_messages.mes

@@ -236,7 +236,7 @@ address.
 A debug message issued when the server is attempting to obtain an IPv4
 lease from the memory file database for the specified address.
 
-% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1 and lease type %2
 A debug message issued when the server is attempting to obtain an IPv6
 lease from the memory file database for the specified address.
 
@@ -255,12 +255,12 @@ A debug message issued when the server is attempting to obtain a set of
 IPv4 leases from the memory file database for a client with the specified
 hardware address.
 
-% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv6 leases for IAID %1 and DUID %2
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv6 leases for IAID %1 and DUID %2 and lease type %3
 A debug message issued when the server is attempting to obtain a set of
 IPv6 lease from the memory file database for a client with the specified
 IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
 
-% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv6 leases for IAID %1, Subnet ID %2 and DUID %3
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv6 leases for IAID %1, Subnet ID %2, DUID %3 and lease type %4
 A debug message issued when the server is attempting to obtain an IPv6
 lease from the memory file database for a client with the specified IAID
 (Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).

+ 36 - 22
src/lib/dhcpsrv/memfile_lease_mgr.cc

@@ -235,13 +235,14 @@ Memfile_LeaseMgr::getLease4(const ClientId& client_id,
 }
 
 Lease6Ptr
-Memfile_LeaseMgr::getLease6(Lease::Type /* not used yet */,
+Memfile_LeaseMgr::getLease6(Lease::Type type,
                             const isc::asiolink::IOAddress& addr) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
-
+              DHCPSRV_MEMFILE_GET_ADDR6)
+        .arg(addr.toText())
+        .arg(Lease::typeToText(type));
     Lease6Storage::iterator l = storage6_.find(addr);
-    if (l == storage6_.end()) {
+    if (l == storage6_.end() || !(*l) || ((*l)->type_ != type)) {
         return (Lease6Ptr());
     } else {
         return (Lease6Ptr(new Lease6(**l)));
@@ -249,42 +250,55 @@ Memfile_LeaseMgr::getLease6(Lease::Type /* not used yet */,
 }
 
 Lease6Collection
-Memfile_LeaseMgr::getLeases6(Lease::Type /* not used yet */,
+Memfile_LeaseMgr::getLeases6(Lease::Type type,
                             const DUID& duid, uint32_t iaid) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
-              DHCPSRV_MEMFILE_GET_IAID_DUID).arg(iaid).arg(duid.toText());
+              DHCPSRV_MEMFILE_GET_IAID_DUID)
+        .arg(iaid)
+        .arg(duid.toText())
+        .arg(Lease::typeToText(type));
 
-    /// @todo Not implemented.
+    // We are going to use index #1 of the multi index container.
+    typedef Lease6Storage::nth_index<1>::type SearchIndex;
+    // Get the index.
+    const SearchIndex& idx = storage6_.get<1>();
+    // Try to get the lease using the DUID, IAID and lease type.
+    std::pair<SearchIndex::iterator, SearchIndex::iterator> l =
+        idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
+    Lease6Collection collection;
+    for(SearchIndex::iterator lease = l.first; lease != l.second; ++lease) {
+        collection.push_back(Lease6Ptr(new Lease6(**lease)));
+    }
 
-    return (Lease6Collection());
+    return (collection);
 }
 
 Lease6Collection
-Memfile_LeaseMgr::getLeases6(Lease::Type /* not used yet */,
+Memfile_LeaseMgr::getLeases6(Lease::Type type,
                              const DUID& duid, uint32_t iaid,
                              SubnetID subnet_id) const {
     LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
               DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
-              .arg(iaid).arg(subnet_id).arg(duid.toText());
+        .arg(iaid)
+        .arg(subnet_id)
+        .arg(duid.toText())
+        .arg(Lease::typeToText(type));
 
     // We are going to use index #1 of the multi index container.
-    // We define SearchIndex locally in this function because
-    // currently only this function uses this index.
     typedef Lease6Storage::nth_index<1>::type SearchIndex;
     // Get the index.
     const SearchIndex& idx = storage6_.get<1>();
-    // Try to get the lease using the DUID, IAID and Subnet ID.
-    SearchIndex::const_iterator lease =
-        idx.find(boost::make_tuple(duid.getDuid(), iaid, subnet_id));
-    // Lease was not found. Return empty pointer.
-    if (lease == idx.end()) {
-        return (Lease6Collection());
+    // Try to get the lease using the DUID, IAID and lease type.
+    std::pair<SearchIndex::iterator, SearchIndex::iterator> l =
+        idx.equal_range(boost::make_tuple(duid.getDuid(), iaid, type));
+    Lease6Collection collection;
+    for(SearchIndex::iterator lease = l.first; lease != l.second; ++lease) {
+        // Filter out the leases which subnet id doesn't match.
+        if((*lease)->subnet_id_ == subnet_id) {
+            collection.push_back(Lease6Ptr(new Lease6(**lease)));
+        }
     }
 
-    // Lease was found, return it to the caller.
-    /// @todo: allow multiple leases for a single duid+iaid+subnet_id tuple
-    Lease6Collection collection;
-    collection.push_back(Lease6Ptr(new Lease6(**lease)));
     return (collection);
 }
 

+ 10 - 11
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -190,9 +190,8 @@ public:
     virtual Lease6Ptr getLease6(Lease::Type type,
                                 const isc::asiolink::IOAddress& addr) const;
 
-    /// @brief Returns existing IPv6 lease for a given DUID+IA combination
-    ///
-    /// @todo Not implemented yet
+    /// @brief Returns existing IPv6 lease for a given DUID + IA + lease type
+    /// combination
     ///
     /// @param type specifies lease type: (NA, TA or PD)
     /// @param duid client DUID
@@ -202,7 +201,8 @@ public:
     virtual Lease6Collection getLeases6(Lease::Type type,
                                         const DUID& duid, uint32_t iaid) const;
 
-    /// @brief Returns existing IPv6 lease for a given DUID/IA/subnet-id tuple
+    /// @brief Returns existing IPv6 lease for a given DUID + IA + subnet-id +
+    /// lease type combination.
     ///
     /// This function returns a copy of the lease. The modification in the
     /// return lease does not affect the instance held in the lease storage.
@@ -214,7 +214,8 @@ public:
     ///
     /// @return lease collection (may be empty if no lease is found)
     virtual Lease6Collection getLeases6(Lease::Type type, const DUID& duid,
-                                        uint32_t iaid, SubnetID subnet_id) const;
+                                        uint32_t iaid,
+                                        SubnetID subnet_id) const;
 
     /// @brief Updates IPv4 lease.
     ///
@@ -372,8 +373,6 @@ protected:
     /// will not be written to disk.
     ///
     /// @param u Universe (v4 or v6)
-    /// @param parameters Map holding parameters of the Lease Manager, passed to
-    /// the constructor.
     ///
     /// @return The location of the lease file that should be assigned to the
     /// lease_file4_ or lease_file6_, depending on the universe specified as an
@@ -394,9 +393,9 @@ protected:
             >,
 
             // Specification of the second index starts here.
-            boost::multi_index::ordered_unique<
+            boost::multi_index::ordered_non_unique<
                 // This is a composite index that will be used to search for
-                // the lease using three attributes: DUID, IAID, Subnet Id.
+                // the lease using three attributes: DUID, IAID and lease type.
                 boost::multi_index::composite_key<
                     Lease6,
                     // The DUID can be retrieved from the Lease6 object using
@@ -404,9 +403,9 @@ protected:
                     boost::multi_index::const_mem_fun<Lease6, const std::vector<uint8_t>&,
                                                       &Lease6::getDuidVector>,
                     // The two other ingredients of this index are IAID and
-                    // subnet id.
+                    // lease type.
                     boost::multi_index::member<Lease6, uint32_t, &Lease6::iaid_>,
-                    boost::multi_index::member<Lease, SubnetID, &Lease::subnet_id_>
+                    boost::multi_index::member<Lease6, Lease::Type, &Lease6::type_>
                 >
             >
         >

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

@@ -108,6 +108,7 @@ libdhcpsrv_unittests_CXXFLAGS += -Wno-unused-variable -Wno-unused-parameter
 endif
 
 libdhcpsrv_unittests_LDADD  = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
+libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la

+ 128 - 28
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -29,6 +29,7 @@
 
 using namespace std;
 using namespace isc::asiolink;
+using namespace isc::data;
 using namespace isc::dhcp;
 using namespace isc::dhcp::test;
 using namespace isc::util;
@@ -40,34 +41,67 @@ using boost::scoped_ptr;
 
 namespace {
 
+template <typename Storage>
+bool isZeroPosition(const Storage& storage, const std::string& param_name) {
+    Element::Position position = storage.getPosition(param_name);
+    return ((position.line_ == 0) && (position.pos_ == 0) &&
+            (position.file_.empty()));
+}
+
 // This test verifies that BooleanStorage functions properly.
 TEST(ValueStorageTest, BooleanTesting) {
     BooleanStorage testStore;
 
     // Verify that we can add and retrieve parameters.
-    testStore.setParam("firstBool", false);
-    testStore.setParam("secondBool", true);
+    testStore.setParam("firstBool", false, Element::Position("kea.conf", 123, 234));
+    testStore.setParam("secondBool", true, Element::Position("keax.conf", 10, 20));
 
     EXPECT_FALSE(testStore.getParam("firstBool"));
     EXPECT_TRUE(testStore.getParam("secondBool"));
 
+    EXPECT_EQ(123, testStore.getPosition("firstBool").line_);
+    EXPECT_EQ(234, testStore.getPosition("firstBool").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("firstBool").file_);
+
+    EXPECT_EQ(10, testStore.getPosition("secondBool").line_);
+    EXPECT_EQ(20, testStore.getPosition("secondBool").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("secondBool").file_);
+
     // Verify that we can update parameters.
-    testStore.setParam("firstBool", true);
-    testStore.setParam("secondBool", false);
+    testStore.setParam("firstBool", true, Element::Position("keax.conf", 555, 111));
+    testStore.setParam("secondBool", false, Element::Position("kea.conf", 1, 3));
 
     EXPECT_TRUE(testStore.getParam("firstBool"));
     EXPECT_FALSE(testStore.getParam("secondBool"));
 
+    EXPECT_EQ(555, testStore.getPosition("firstBool").line_);
+    EXPECT_EQ(111, testStore.getPosition("firstBool").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("firstBool").file_);
+
+    EXPECT_EQ(1, testStore.getPosition("secondBool").line_);
+    EXPECT_EQ(3, testStore.getPosition("secondBool").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("secondBool").file_);
+
     // Verify that we can delete a parameter and it will no longer be found.
     testStore.delParam("firstBool");
     EXPECT_THROW(testStore.getParam("firstBool"), isc::dhcp::DhcpConfigError);
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "firstBool"));
+
     // Verify that the delete was safe and the store still operates.
     EXPECT_FALSE(testStore.getParam("secondBool"));
 
+    EXPECT_EQ(1, testStore.getPosition("secondBool").line_);
+    EXPECT_EQ(3, testStore.getPosition("secondBool").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("secondBool").file_);
+
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusBool"), isc::dhcp::DhcpConfigError);
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "bogusBool"));
+
     // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusBool"));
 
@@ -75,35 +109,60 @@ TEST(ValueStorageTest, BooleanTesting) {
     testStore.clear();
     EXPECT_THROW(testStore.getParam("secondBool"), isc::dhcp::DhcpConfigError);
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "secondBool"));
 }
 
 // This test verifies that Uint32Storage functions properly.
 TEST(ValueStorageTest, Uint32Testing) {
     Uint32Storage testStore;
 
-    uint32_t intOne = 77;
-    uint32_t intTwo = 33;
+    uint32_t int_one = 77;
+    uint32_t int_two = 33;
 
     // Verify that we can add and retrieve parameters.
-    testStore.setParam("firstInt", intOne);
-    testStore.setParam("secondInt", intTwo);
+    testStore.setParam("firstInt", int_one, Element::Position("kea.conf", 123, 234));
+    testStore.setParam("secondInt", int_two, Element::Position("keax.conf", 10, 20));
+
+    EXPECT_EQ(testStore.getParam("firstInt"), int_one);
+    EXPECT_EQ(testStore.getParam("secondInt"), int_two);
 
-    EXPECT_EQ(testStore.getParam("firstInt"), intOne);
-    EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+    EXPECT_EQ(123, testStore.getPosition("firstInt").line_);
+    EXPECT_EQ(234, testStore.getPosition("firstInt").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("firstInt").file_);
+
+    EXPECT_EQ(10, testStore.getPosition("secondInt").line_);
+    EXPECT_EQ(20, testStore.getPosition("secondInt").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("secondInt").file_);
 
     // Verify that we can update parameters.
-    testStore.setParam("firstInt", --intOne);
-    testStore.setParam("secondInt", ++intTwo);
+    testStore.setParam("firstInt", --int_one, Element::Position("keax.conf", 555, 111));
+    testStore.setParam("secondInt", ++int_two, Element::Position("kea.conf", 1, 3));
+
+    EXPECT_EQ(testStore.getParam("firstInt"), int_one);
+    EXPECT_EQ(testStore.getParam("secondInt"), int_two);
+
+    EXPECT_EQ(555, testStore.getPosition("firstInt").line_);
+    EXPECT_EQ(111, testStore.getPosition("firstInt").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("firstInt").file_);
 
-    EXPECT_EQ(testStore.getParam("firstInt"), intOne);
-    EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+    EXPECT_EQ(1, testStore.getPosition("secondInt").line_);
+    EXPECT_EQ(3, testStore.getPosition("secondInt").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("secondInt").file_);
 
     // Verify that we can delete a parameter and it will no longer be found.
     testStore.delParam("firstInt");
     EXPECT_THROW(testStore.getParam("firstInt"), isc::dhcp::DhcpConfigError);
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "firstInt"));
+
     // Verify that the delete was safe and the store still operates.
-    EXPECT_EQ(testStore.getParam("secondInt"), intTwo);
+    EXPECT_EQ(testStore.getParam("secondInt"), int_two);
+
+    EXPECT_EQ(1, testStore.getPosition("secondInt").line_);
+    EXPECT_EQ(3, testStore.getPosition("secondInt").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("secondInt").file_);
 
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusInt"), isc::dhcp::DhcpConfigError);
@@ -111,41 +170,74 @@ TEST(ValueStorageTest, Uint32Testing) {
     // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusInt"));
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "bogusInt"));
+
     // Verify that we can empty the list.
     testStore.clear();
     EXPECT_THROW(testStore.getParam("secondInt"), isc::dhcp::DhcpConfigError);
+
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "secondInt"));
 }
 
 // This test verifies that StringStorage functions properly.
 TEST(ValueStorageTest, StringTesting) {
     StringStorage testStore;
 
-    std::string stringOne = "seventy-seven";
-    std::string stringTwo = "thirty-three";
+    std::string string_one = "seventy-seven";
+    std::string string_two = "thirty-three";
 
     // Verify that we can add and retrieve parameters.
-    testStore.setParam("firstString", stringOne);
-    testStore.setParam("secondString", stringTwo);
+    testStore.setParam("firstString", string_one,
+                       Element::Position("kea.conf", 123, 234));
+    testStore.setParam("secondString", string_two,
+                       Element::Position("keax.conf", 10, 20));
+
+    EXPECT_EQ(testStore.getParam("firstString"), string_one);
+    EXPECT_EQ(testStore.getParam("secondString"), string_two);
+
+    EXPECT_EQ(123, testStore.getPosition("firstString").line_);
+    EXPECT_EQ(234, testStore.getPosition("firstString").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("firstString").file_);
 
-    EXPECT_EQ(testStore.getParam("firstString"), stringOne);
-    EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+    EXPECT_EQ(10, testStore.getPosition("secondString").line_);
+    EXPECT_EQ(20, testStore.getPosition("secondString").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("secondString").file_);
 
     // Verify that we can update parameters.
-    stringOne.append("-boo");
-    stringTwo.append("-boo");
+    string_one.append("-boo");
+    string_two.append("-boo");
 
-    testStore.setParam("firstString", stringOne);
-    testStore.setParam("secondString", stringTwo);
+    testStore.setParam("firstString", string_one,
+                       Element::Position("kea.conf", 555, 111));
+    testStore.setParam("secondString", string_two,
+                       Element::Position("keax.conf", 1, 3));
 
-    EXPECT_EQ(testStore.getParam("firstString"), stringOne);
-    EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+    EXPECT_EQ(testStore.getParam("firstString"), string_one);
+    EXPECT_EQ(testStore.getParam("secondString"), string_two);
+
+    EXPECT_EQ(555, testStore.getPosition("firstString").line_);
+    EXPECT_EQ(111, testStore.getPosition("firstString").pos_);
+    EXPECT_EQ("kea.conf", testStore.getPosition("firstString").file_);
+
+    EXPECT_EQ(1, testStore.getPosition("secondString").line_);
+    EXPECT_EQ(3, testStore.getPosition("secondString").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("secondString").file_);
 
     // Verify that we can delete a parameter and it will no longer be found.
     testStore.delParam("firstString");
     EXPECT_THROW(testStore.getParam("firstString"), isc::dhcp::DhcpConfigError);
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "firstString"));
+
     // Verify that the delete was safe and the store still operates.
-    EXPECT_EQ(testStore.getParam("secondString"), stringTwo);
+    EXPECT_EQ(testStore.getParam("secondString"), string_two);
+
+    EXPECT_EQ(1, testStore.getPosition("secondString").line_);
+    EXPECT_EQ(3, testStore.getPosition("secondString").pos_);
+    EXPECT_EQ("keax.conf", testStore.getPosition("secondString").file_);
 
     // Verify that looking for a parameter that never existed throws.
     ASSERT_THROW(testStore.getParam("bogusString"), isc::dhcp::DhcpConfigError);
@@ -153,9 +245,15 @@ TEST(ValueStorageTest, StringTesting) {
     // Verify that attempting to delete a parameter that never existed does not throw.
     EXPECT_NO_THROW(testStore.delParam("bogusString"));
 
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "bogusString"));
+
     // Verify that we can empty the list.
     testStore.clear();
     EXPECT_THROW(testStore.getParam("secondString"), isc::dhcp::DhcpConfigError);
+
+    // Verify that the "zero" position is returned when parameter doesn't exist.
+    EXPECT_TRUE(isZeroPosition(testStore, "secondString"));
 }
 
 
@@ -991,6 +1089,8 @@ TEST_F(CfgMgrTest, d2ClientConfig) {
     // Create a new, enabled configuration.
     ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   true, true, true, true,
                                   "pre-fix", "suf-fix")));

+ 81 - 11
src/lib/dhcpsrv/tests/d2_client_unittest.cc

@@ -47,6 +47,9 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
     bool enable_updates = true;
     isc::asiolink::IOAddress server_ip("127.0.0.1");
     size_t server_port = 477;
+    isc::asiolink::IOAddress sender_ip("127.0.0.1");
+    size_t sender_port = 478;
+    size_t max_queue_size = 2048;
     dhcp_ddns::NameChangeProtocol ncr_protocol = dhcp_ddns::NCR_UDP;
     dhcp_ddns::NameChangeFormat ncr_format = dhcp_ddns::FMT_JSON;
     bool always_include_fqdn = true;
@@ -61,6 +64,9 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
                                            D2ClientConfig(enable_updates,
                                                           server_ip,
                                                           server_port,
+                                                          sender_ip,
+                                                          sender_port,
+                                                          max_queue_size,
                                                           ncr_protocol,
                                                           ncr_format,
                                                           always_include_fqdn,
@@ -77,6 +83,9 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
 
     EXPECT_EQ(d2_client_config->getServerIp(), server_ip);
     EXPECT_EQ(d2_client_config->getServerPort(), server_port);
+    EXPECT_EQ(d2_client_config->getSenderIp(), sender_ip);
+    EXPECT_EQ(d2_client_config->getSenderPort(), sender_port);
+    EXPECT_EQ(d2_client_config->getMaxQueueSize(), max_queue_size);
     EXPECT_EQ(d2_client_config->getNcrProtocol(), ncr_protocol);
     EXPECT_EQ(d2_client_config->getNcrFormat(), ncr_format);
     EXPECT_EQ(d2_client_config->getAlwaysIncludeFqdn(), always_include_fqdn);
@@ -97,6 +106,9 @@ TEST(D2ClientConfigTest, constructorsAndAccessors) {
                                         D2ClientConfig(enable_updates,
                                                        server_ip,
                                                        server_port,
+                                                       sender_ip,
+                                                       sender_port,
+                                                       max_queue_size,
                                                        dhcp_ddns::NCR_TCP,
                                                        ncr_format,
                                                        always_include_fqdn,
@@ -121,7 +133,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Create an instance to use as a reference.
     ASSERT_NO_THROW(ref_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "pre-fix", "suf-fix")));
@@ -129,7 +141,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that is identical to reference configuration.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "pre-fix", "suf-fix")));
@@ -139,7 +151,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by enable flag.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(false,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "pre-fix", "suf-fix")));
@@ -149,7 +161,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by server ip.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    test_address, 477,
+                    test_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "pre-fix", "suf-fix")));
@@ -159,7 +171,37 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by server port.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 333,
+                    ref_address, 333, ref_address, 478, 1024,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by sender ip.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477, test_address, 478, 1024,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by sender port.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477, ref_address, 333, 1024,
+                    dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                    true, true, true, true,
+                    "pre-fix", "suf-fix")));
+    ASSERT_TRUE(test_config);
+    EXPECT_FALSE(*ref_config == *test_config);
+    EXPECT_TRUE(*ref_config != *test_config);
+
+    // Check a configuration that differs only by max queue size.
+    ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
+                    ref_address, 477, ref_address, 478, 2048,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "pre-fix", "suf-fix")));
@@ -169,7 +211,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by always_include_fqdn.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     false, true, true, true,
                     "pre-fix", "suf-fix")));
@@ -179,7 +221,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by override_no_update.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, false, true, true,
                     "pre-fix", "suf-fix")));
@@ -189,7 +231,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by override_client_update.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, false, true,
                     "pre-fix", "suf-fix")));
@@ -199,7 +241,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by replace_client_name.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, false,
                     "pre-fix", "suf-fix")));
@@ -209,7 +251,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by generated_prefix.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "bogus", "suf-fix")));
@@ -219,7 +261,7 @@ TEST(D2ClientConfigTest, equalityOperator) {
 
     // Check a configuration that differs only by qualifying_suffix.
     ASSERT_NO_THROW(test_config.reset(new D2ClientConfig(true,
-                    ref_address, 477,
+                    ref_address, 477, ref_address, 478, 1024,
                     dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                     true, true, true, true,
                     "pre-fix", "bogus")));
@@ -263,6 +305,8 @@ TEST(D2ClientMgr, validConfig) {
     // Create a new, enabled config.
     ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   true, true, true, true,
                                   "pre-fix", "suf-fix")));
@@ -305,6 +349,8 @@ TEST(D2ClientMgr, analyzeFqdnInvalidCombination) {
     // Create enabled configuration with all controls off (no overrides).
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, false, false,
                                   "pre-fix", "suf-fix")));
@@ -327,6 +373,8 @@ TEST(D2ClientMgr, analyzeFqdnEnabledNoOverrides) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, false, false,
                                   "pre-fix", "suf-fix")));
@@ -369,6 +417,8 @@ TEST(D2ClientMgr, analyzeFqdnEnabledOverrideNoUpdate) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, true, false, false,
                                   "pre-fix", "suf-fix")));
@@ -410,6 +460,8 @@ TEST(D2ClientMgr, analyzeFqdnEnabledOverrideClientUpdate) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, true, false,
                                   "pre-fix", "suf-fix")));
@@ -452,6 +504,8 @@ TEST(D2ClientMgr, adjustFqdnFlagsV4) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, true, false, false,
                                   "pre-fix", "suf-fix")));
@@ -549,6 +603,8 @@ TEST(D2ClientMgr, qualifyName) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, true, false,
                                   "prefix", "suffix.com")));
@@ -561,6 +617,8 @@ TEST(D2ClientMgr, qualifyName) {
 
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, true, false,
                                   "prefix", "hasdot.com.")));
@@ -580,6 +638,8 @@ TEST(D2ClientMgr, generateFqdn) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, true, false,
                                   "prefix", "suffix.com")));
@@ -612,6 +672,8 @@ TEST(D2ClientMgr, adjustDomainNameV4) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, false, false,
                                   "prefix", "suffix.com")));
@@ -653,6 +715,8 @@ TEST(D2ClientMgr, adjustDomainNameV4) {
     // Create enabled configuration.
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, false, true,
                                   "prefix", "suffix.com")));
@@ -701,6 +765,8 @@ TEST(D2ClientMgr, adjustDomainNameV6) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, false, false,
                                   "prefix", "suffix.com")));
@@ -739,6 +805,8 @@ TEST(D2ClientMgr, adjustDomainNameV6) {
     // Create enabled configuration.
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, false, false, true,
                                   "prefix", "suffix.com")));
@@ -787,6 +855,8 @@ TEST(D2ClientMgr, adjustFqdnFlagsV6) {
     D2ClientConfigPtr cfg;
     ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
                                   isc::asiolink::IOAddress("127.0.0.1"), 477,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 478,
+                                  1024,
                                   dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
                                   false, true, false, false,
                                   "pre-fix", "suf-fix")));

+ 44 - 3
src/lib/dhcpsrv/tests/d2_udp_unittest.cc

@@ -81,12 +81,20 @@ public:
                     const dhcp_ddns::NameChangeProtocol protocol) {
         // Update the configuration with one that is enabled.
         D2ClientConfigPtr new_cfg;
+
+        isc::asiolink::IOAddress server_ip(server_address);
+        isc::asiolink::IOAddress sender_ip(server_ip.isV4() ?
+                                           D2ClientConfig::DFT_V4_SENDER_IP :
+                                           D2ClientConfig::DFT_V6_SENDER_IP);
+
         ASSERT_NO_THROW(new_cfg.reset(new D2ClientConfig(true,
-                                  isc::asiolink::IOAddress(server_address),
-                                  server_port,
+                                  server_ip, server_port,
+                                  sender_ip, D2ClientConfig::DFT_SENDER_PORT,
+                                  D2ClientConfig::DFT_MAX_QUEUE_SIZE,
                                   protocol, dhcp_ddns::FMT_JSON,
                                   true, true, true, true,
                                   "myhost", ".example.com.")));
+
         ASSERT_NO_THROW(setD2ClientConfig(new_cfg));
         ASSERT_TRUE(ddnsEnabled());
     }
@@ -300,7 +308,7 @@ TEST_F(D2ClientMgrTest, udpSend) {
 /// an external IOService.
 TEST_F(D2ClientMgrTest, udpSendExternalIOService) {
     // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
-    enableDdns("127.0.0.1", 530001, dhcp_ddns::NCR_UDP);
+    enableDdns("127.0.0.1", 53001, dhcp_ddns::NCR_UDP);
 
     // Place sender in send mode using an external IO service.
     asiolink::IOService io_service;
@@ -328,6 +336,39 @@ TEST_F(D2ClientMgrTest, udpSendExternalIOService) {
     ASSERT_NO_THROW(stopSender());
 }
 
+/// @brief Checks that D2ClientMgr can send with a UDP sender and
+/// an external IOService.
+TEST_F(D2ClientMgrTest, udpSendExternalIOService6) {
+    // Enable DDNS with server at 127.0.0.1/prot 53001 via UDP.
+    enableDdns("::1", 53001, dhcp_ddns::NCR_UDP);
+
+    // Place sender in send mode using an external IO service.
+    asiolink::IOService io_service;
+    ASSERT_NO_THROW(startSender(getErrorHandler(), io_service));
+
+    // select_fd should evaluate to NOT ready to read.
+    selectCheck(false);
+
+    // Build a test request and send it.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(sendRequest(ncr));
+
+    // select_fd should evaluate to ready to read.
+    selectCheck(true);
+
+    // Call service handler.
+    runReadyIO();
+
+    // select_fd should evaluate to not ready to read.
+    selectCheck(false);
+
+    // Explicitly stop the sender. This ensures the sender's
+    // ASIO socket is closed prior to the local io_service
+    // instance goes out of scope.
+    ASSERT_NO_THROW(stopSender());
+}
+
+
 /// @brief Checks that D2ClientMgr invokes the client error handler
 /// when send errors occur.
 TEST_F(D2ClientMgrTest, udpSendErrorHandler) {

+ 160 - 14
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -14,6 +14,7 @@
 
 #include <config.h>
 #include <config/ccsession.h>
+#include <cc/data.h>
 #include <dhcp/option.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
@@ -21,6 +22,7 @@
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/tests/test_libraries.h>
+#include <dhcpsrv/testutils/config_result_check.h>
 #include <exceptions/exceptions.h>
 #include <hooks/hooks_manager.h>
 
@@ -36,6 +38,7 @@ using namespace isc;
 using namespace isc::config;
 using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::dhcp::test;
 using namespace isc::hooks;
 
 namespace {
@@ -383,7 +386,7 @@ public:
 
         } else if (config_id.compare("option-def") == 0) {
             parser.reset(new OptionDefListParser(config_id,
-                                              parser_context_->option_defs_));
+                                                 parser_context_));
 
         } else if (config_id.compare("hooks-libraries") == 0) {
             parser.reset(new HooksLibrariesParser(config_id));
@@ -418,6 +421,11 @@ public:
             ConstElementPtr status = parseElementSet(json);
             ConstElementPtr comment = parseAnswer(rcode_, status);
             error_text_ = comment->stringValue();
+            // If error was reported, the error string should contain
+            // position of the data element which caused failure.
+            if (rcode_ != 0) {
+                EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+            }
         }
 
         return (rcode_);
@@ -718,6 +726,9 @@ TEST_F(ParseConfigTest, validD2Config) {
         "     \"enable-updates\" : true, "
         "     \"server-ip\" : \"192.0.2.0\", "
         "     \"server-port\" : 3432, "
+        "     \"sender-ip\" : \"192.0.2.1\", "
+        "     \"sender-port\" : 3433, "
+        "     \"max-queue-size\" : 2048, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"always-include-fqdn\" : true, "
@@ -761,6 +772,9 @@ TEST_F(ParseConfigTest, validD2Config) {
         "     \"enable-updates\" : false, "
         "     \"server-ip\" : \"2001:db8::\", "
         "     \"server-port\" : 43567, "
+        "     \"sender-ip\" : \"2001:db8::1\", "
+        "     \"sender-port\" : 3433, "
+        "     \"max-queue-size\" : 2048, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
         "     \"always-include-fqdn\" : false, "
@@ -956,6 +970,44 @@ TEST_F(ParseConfigTest, invalidD2Config) {
         "     \"qualifying-suffix\" : \"test.suffix.\" "
         "    }"
         "}",
+        // Mismatched server and sender IPs
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"192.0.2.0\", "
+        "     \"server-port\" : 3432, "
+        "     \"sender-ip\" : \"3001::5\", "
+        "     \"sender-port\" : 3433, "
+        "     \"max-queue-size\" : 2048, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
+        // Identical server and sender IP/port
+        "{ \"dhcp-ddns\" :"
+        "    {"
+        "     \"enable-updates\" : true, "
+        "     \"server-ip\" : \"3001::5\", "
+        "     \"server-port\" : 3433, "
+        "     \"sender-ip\" : \"3001::5\", "
+        "     \"sender-port\" : 3433, "
+        "     \"max-queue-size\" : 2048, "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" "
+        "    }"
+        "}",
         // stop
         ""
     };
@@ -1009,6 +1061,32 @@ public:
         EXPECT_EQ(ref_values->getParam("foo"), values->getParam("foo"));
     }
 
+    /// @brief Check that the storages of the specific type hold the same
+    /// position.
+    ///
+    /// This function assumes that the @c ref_values storage holds exactly
+    /// one parameter called 'foo'.
+    ///
+    /// @param ref_values A storage holding reference position. In the typical
+    /// case it is a storage held in the original context, which is assigned
+    /// to another context.
+    /// @param values A storage holding position to be checked.
+    /// @tparam ContainerType A type of the storage.
+    /// @tparam ValueType A type of the value in the container.
+    template<typename ContainerType, typename ValueType>
+    void checkPositionEq(const boost::shared_ptr<ContainerType>& ref_values,
+                         const boost::shared_ptr<ContainerType>& values) {
+        // Verify that the position is correct.
+        EXPECT_EQ(ref_values->getPosition("foo").line_,
+                  values->getPosition("foo").line_);
+
+        EXPECT_EQ(ref_values->getPosition("foo").pos_,
+                  values->getPosition("foo").pos_);
+
+        EXPECT_EQ(ref_values->getPosition("foo").file_,
+                  values->getPosition("foo").file_);
+    }
+
     /// @brief Check that the storages of the specific type hold different
     /// value.
     ///
@@ -1028,6 +1106,30 @@ public:
         EXPECT_NE(ref_values->getParam("foo"), values->getParam("foo"));
     }
 
+    /// @brief Check that the storages of the specific type hold fifferent
+    /// position.
+    ///
+    /// This function assumes that the ref_values storage holds exactly
+    /// one parameter called 'foo'.
+    ///
+    /// @param ref_values A storage holding reference position. In the typical
+    /// case it is a storage held in the original context, which is assigned
+    /// to another context.
+    /// @param values A storage holding position to be checked.
+    /// @tparam ContainerType A type of the storage.
+    /// @tparam ValueType A type of the value in the container.
+    template<typename ContainerType, typename ValueType>
+    void checkPositionNeq(const boost::shared_ptr<ContainerType>& ref_values,
+                          const boost::shared_ptr<ContainerType>& values) {
+        // At least one of the position fields must be different.
+        EXPECT_TRUE((ref_values->getPosition("foo").line_ !=
+                     values->getPosition("foo").line_) ||
+                    (ref_values->getPosition("foo").pos_ !=
+                     values->getPosition("foo").pos_) ||
+                    (ref_values->getPosition("foo").pos_ !=
+                     values->getPosition("foo").pos_));
+    }
+
     /// @brief Check that option definition storage in the context holds
     /// one option definition of the specified type.
     ///
@@ -1102,15 +1204,18 @@ public:
 
         // Set boolean parameter 'foo'.
         ASSERT_TRUE(ctx.boolean_values_);
-        ctx.boolean_values_->setParam("foo", true);
+        ctx.boolean_values_->setParam("foo", true,
+                                      Element::Position("kea.conf", 123, 234));
 
         // Set uint32 parameter 'foo'.
         ASSERT_TRUE(ctx.uint32_values_);
-        ctx.uint32_values_->setParam("foo", 123);
+        ctx.uint32_values_->setParam("foo", 123,
+                                     Element::Position("kea.conf", 123, 234));
 
         // Ser string parameter 'foo'.
         ASSERT_TRUE(ctx.string_values_);
-        ctx.string_values_->setParam("foo", "some string");
+        ctx.string_values_->setParam("foo", "some string",
+                                     Element::Position("kea.conf", 123, 234));
 
         // Add new option, with option code 10, to the context.
         ASSERT_TRUE(ctx.options_);
@@ -1147,6 +1252,14 @@ public:
                                                ctx_new->boolean_values_);
         }
 
+        // New context has the same boolean value position.
+        {
+            SCOPED_TRACE("Check that positions of boolean values are equal"
+                         " in both contexts");
+            checkPositionEq<BooleanStorage, bool>(ctx.boolean_values_,
+                                                  ctx_new->boolean_values_);
+        }
+
         // New context has the same uint32 value.
         ASSERT_TRUE(ctx_new->uint32_values_);
         {
@@ -1156,6 +1269,14 @@ public:
                                                   ctx_new->uint32_values_);
         }
 
+        // New context has the same uint32 value position.
+        {
+            SCOPED_TRACE("Check that positions of uint32_t values are equal"
+                         " in both contexts");
+            checkPositionEq<Uint32Storage, uint32_t>(ctx.uint32_values_,
+                                                     ctx_new->uint32_values_);
+        }
+
         // New context has the same string value.
         ASSERT_TRUE(ctx_new->string_values_);
         {
@@ -1164,6 +1285,14 @@ public:
                                                      ctx_new->string_values_);
         }
 
+        // New context has the same string value position.
+        {
+            SCOPED_TRACE("Check that position of string values are equal"
+                         " in both contexts");
+            checkPositionEq<StringStorage, std::string>(ctx.string_values_,
+                                                        ctx_new->string_values_);
+        }
+
         // New context has the same option.
         ASSERT_TRUE(ctx_new->options_);
         {
@@ -1193,31 +1322,49 @@ public:
         // Change the value of the boolean parameter. This should not affect the
         // corresponding value in the new context.
         {
-            SCOPED_TRACE("Check that boolean value isn't changed when original"
-                         " value is changed");
-            ctx.boolean_values_->setParam("foo", false);
+            SCOPED_TRACE("Check that boolean value and position isn't changed"
+                         " when original value and position is changed");
+            ctx.boolean_values_->setParam("foo", false,
+                                          Element::Position("kea.conf",
+                                                            12, 10));
             checkValueNeq<BooleanStorage, bool>(ctx.boolean_values_,
                                                 ctx_new->boolean_values_);
+
+            checkPositionNeq<BooleanStorage, bool>(ctx.boolean_values_,
+                                                   ctx_new->boolean_values_);
         }
 
         // Change the value of the uint32_t parameter. This should not affect
         // the corresponding value in the new context.
         {
-            SCOPED_TRACE("Check that uint32_t value isn't changed when original"
-                         " value is changed");
-            ctx.uint32_values_->setParam("foo", 987);
+            SCOPED_TRACE("Check that uint32_t value and position isn't changed"
+                         " when original value and position is changed");
+            ctx.uint32_values_->setParam("foo", 987,
+                                         Element::Position("kea.conf",
+                                                           10, 11));
             checkValueNeq<Uint32Storage, uint32_t>(ctx.uint32_values_,
                                                    ctx_new->uint32_values_);
+
+            checkPositionNeq<Uint32Storage, uint32_t>(ctx.uint32_values_,
+                                                      ctx_new->uint32_values_);
+
         }
 
         // Change the value of the string parameter. This should not affect the
         // corresponding value in the new context.
         {
-            SCOPED_TRACE("Check that string value isn't changed when original"
-                         " value is changed");
-            ctx.string_values_->setParam("foo", "different string");
+            SCOPED_TRACE("Check that string value and position isn't changed"
+                         " when original value and position is changed");
+            ctx.string_values_->setParam("foo", "different string",
+                                         Element::Position("kea.conf",
+                                                           10, 11));
             checkValueNeq<StringStorage, std::string>(ctx.string_values_,
                                                       ctx_new->string_values_);
+
+            checkPositionNeq<
+                StringStorage, std::string>(ctx.string_values_,
+                                            ctx_new->string_values_);
+
         }
 
         // Change the option. This should not affect the option instance in the
@@ -1262,7 +1409,6 @@ public:
         EXPECT_EQ(Option::V6, ctx_new->universe_);
 
     }
-
 };
 
 // Check that the assignment operator of the ParserContext class copies all

+ 3 - 3
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -319,7 +319,7 @@ TEST_F(MemfileLeaseMgrTest, basicLease6) {
 /// a combination of DUID and IAID.
 /// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
 /// const DUID& duid, uint32_t iaid) const is not implemented yet.
-TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidIaid) {
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidIaid) {
     startBackend(V6);
     testGetLeases6DuidIaid();
 }
@@ -328,7 +328,7 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidIaid) {
 
 /// @todo: test disabled, because Memfile_LeaseMgr::getLeases6(Lease::Type,
 /// const DUID& duid, uint32_t iaid) const is not implemented yet.
-TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidSize) {
+TEST_F(MemfileLeaseMgrTest, getLeases6DuidSize) {
     startBackend(V6);
     testGetLeases6DuidSize();
 }
@@ -341,7 +341,7 @@ TEST_F(MemfileLeaseMgrTest, DISABLED_getLeases6DuidSize) {
 /// discriminate between the leases based on lease type alone.
 /// @todo: Disabled, because type parameter in Memfile_LeaseMgr::getLease6
 /// (Lease::Type, const isc::asiolink::IOAddress& addr) const is not used.
-TEST_F(MemfileLeaseMgrTest, DISABLED_lease6LeaseTypeCheck) {
+TEST_F(MemfileLeaseMgrTest, lease6LeaseTypeCheck) {
     startBackend(V6);
     testLease6LeaseTypeCheck();
 }

+ 25 - 0
src/lib/dhcpsrv/testutils/Makefile.am

@@ -0,0 +1,25 @@
+SUBDIRS = .
+
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += $(BOOST_INCLUDES)
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+CLEANFILES = *.gcno *.gcda
+
+if HAVE_GTEST
+
+noinst_LTLIBRARIES = libdhcpsrvtest.la
+
+libdhcpsrvtest_la_SOURCES  = config_result_check.cc config_result_check.h
+libdhcpsrvtest_la_CXXFLAGS = $(AM_CXXFLAGS)
+libdhcpsrvtest_la_CPPFLAGS = $(AM_CPPFLAGS)
+libdhcpsrvtest_la_LDFLAGS  = $(AM_LDFLAGS)
+libdhcpsrvtest_la_LIBADD   = $(top_builddir)/src/lib/cc/libkea-cc.la
+libdhcpsrvtest_la_LIBADD  += $(top_builddir)/src/lib/log/libkea-log.la
+
+endif

+ 94 - 0
src/lib/dhcpsrv/testutils/config_result_check.cc

@@ -0,0 +1,94 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <cc/proto_defs.h>
+#include <dhcpsrv/testutils/config_result_check.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/constants.hpp>
+#include <boost/algorithm/string/split.hpp>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+using namespace isc;
+using namespace isc::data;
+
+bool errorContainsPosition(ConstElementPtr error_element,
+                           const std::string& file_name) {
+    if (error_element->contains(isc::cc::CC_PAYLOAD_RESULT)) {
+        ConstElementPtr result = error_element->get(isc::cc::CC_PAYLOAD_RESULT);
+        if ((result->getType() != Element::list) || (result->size() < 2) ||
+            (result->get(1)->getType() != Element::string)) {
+            return (false);
+        }
+
+        // Get the error message in the textual format.
+        std::string error_string = result->get(1)->stringValue();
+
+        // The position of the data element causing an error has the following
+        // format: <filename>:<linenum>:<pos>. The <filename> has been specified
+        // by a caller, so let's first check if this file name is present in the
+        // error message.
+        size_t pos = error_string.find(file_name);
+
+        // If the file name is present, check that it is followed by the line
+        // number and position within the line.
+        if (pos != std::string::npos) {
+            // Split the string starting at the begining of the <filename>. It
+            // should return a vector of strings.
+            std::string sub = error_string.substr(pos);
+            std::vector<std::string> split_pos;
+            boost::split(split_pos, sub, boost::is_any_of(":"),
+                         boost::algorithm::token_compress_off);
+
+            // There should be at least three elements: <filename>, <linenum>
+            // and <pos>. There can be even more, because one error string may
+            // contain more positions of data elements for multiple
+            // configuration nesting levels. We want at least one position.
+            if ((split_pos.size() >= 3) && (split_pos[0] == file_name) &&
+                (!split_pos[1].empty()) && !(split_pos[2].empty())) {
+
+                // Make sure that the line number comprises only digits.
+                for (int i = 0; i < split_pos[1].size(); ++i) {
+                    if (!isdigit(split_pos[1][i])) {
+                        return (false);
+                    }
+                }
+
+                // Go over digits of the position within the line.
+                int i = 0;
+                while (isdigit(split_pos[2][i])) {
+                    ++i;
+                }
+
+                // Make sure that there has been at least one digit and that the
+                // position is followed by the paren.
+                if ((i == 0) || (split_pos[2][i] != ')')) {
+                    return (false);
+                }
+
+                // All checks passed.
+                return (true);
+            }
+        }
+    }
+
+    return (false);
+}
+
+}
+}
+}

+ 56 - 0
src/lib/dhcpsrv/testutils/config_result_check.h

@@ -0,0 +1,56 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CONFIG_RESULT_CHECK_H
+#define CONFIG_RESULT_CHECK_H
+
+#include <cc/data.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Checks that the error string created by the configuration parsers
+/// contains the location of the data element...
+///
+/// This function checks that the error string returned by the configuration
+/// parsers contains the position of the element which caused an error. The
+/// error string is expected to contain at least one occurence of the following:
+///
+/// @code
+///     [filename]:[linenum]:[pos]
+/// @endcode
+///
+/// where:
+/// - [filename] is a configuration file name (provided by a caller),
+/// - [linenum] is a line number of the element,
+/// - [pos] is a position of the element within the line.
+///
+/// Both [linenum] and [pos] must contain decimal digits. The [filename]
+/// must match the name provided by the caller.
+///
+/// @param error_element A result returned by the configuration.
+/// @param file_name A configuration file name.
+///
+/// @return true if the provided configuration result comprises a string
+/// which holds a position of the data element which caused the error;
+/// false otherwise.
+bool errorContainsPosition(isc::data::ConstElementPtr error_element,
+                           const std::string& file_name);
+
+}
+}
+}
+
+#endif

+ 7 - 0
src/lib/dns/python/Makefile.am

@@ -4,6 +4,13 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
+# There is a build problem with python3.4 (a new field tp_finalize) has been
+# added and now compiler complains about it not being properly initialized in
+# construtor. Since the whole python thing goes away, it's counter-productive
+# to spend any time on making this work on both python3.3 and 3.4, so
+# ingoring the warning seems the way to go.
+AM_CXXFLAGS += -Wno-error
+
 lib_LTLIBRARIES = libkea-pydnspp.la
 libkea_pydnspp_la_SOURCES = pydnspp_common.cc pydnspp_common.h
 libkea_pydnspp_la_SOURCES += pydnspp_config.h pydnspp_towire.h

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

@@ -649,13 +649,6 @@ const char* const root_servers[] = {
     "j.root-servers.net", "k.root-servers.net", "l.root-servers.net",
     "m.root-servers.net", NULL
 };
-const char* const gtld_servers[] = {
-    "a.gtld-servers.net", "b.gtld-servers.net", "c.gtld-servers.net",
-    "d.gtld-servers.net", "e.gtld-servers.net", "f.gtld-servers.net",
-    "g.gtld-servers.net", "h.gtld-servers.net", "i.gtld-servers.net",
-    "j.gtld-servers.net", "k.gtld-servers.net", "l.gtld-servers.net",
-    "m.gtld-servers.net", NULL
-};
 const char* const jp_servers[] = {
     "a.dns.jp", "b.dns.jp", "c.dns.jp", "d.dns.jp", "e.dns.jp",
     "f.dns.jp", "g.dns.jp", NULL

+ 7 - 0
src/lib/python/isc/log/Makefile.am

@@ -4,6 +4,13 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
+# There is a build problem with python3.4 (a new field tp_finalize) has been
+# added and now compiler complains about it not being properly initialized in
+# construtor. Since the whole python thing goes away, it's counter-productive
+# to spend any time on making this work on both python3.3 and 3.4, so
+# ingoring the warning seems the way to go.
+AM_CXXFLAGS += -Wno-error
+
 pythondir = $(pyexecdir)/isc
 python_LTLIBRARIES = log.la
 log_la_SOURCES = log.cc

+ 7 - 0
src/lib/python/isc/util/cio/Makefile.am

@@ -4,6 +4,13 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
 
+# There is a build problem with python3.4 (a new field tp_finalize) has been
+# added and now compiler complains about it not being properly initialized in
+# construtor. Since the whole python thing goes away, it's counter-productive
+# to spend any time on making this work on both python3.3 and 3.4, so
+# ingoring the warning seems the way to go.
+AM_CXXFLAGS += -Wno-error
+
 python_PYTHON = __init__.py
 pythondir = $(PYTHON_SITEPKG_DIR)/isc/util/cio