Parcourir la source

Merge branch 'master' into trac2000

Mukund Sivaraman il y a 11 ans
Parent
commit
6b2038265d
100 fichiers modifiés avec 4740 ajouts et 1406 suppressions
  1. 131 0
      ChangeLog
  2. 3 1
      doc/devel/contribute.dox
  3. 4 4
      doc/devel/mainpage.dox
  4. 296 27
      doc/guide/bind10-guide.xml
  5. 3 3
      src/bin/auth/auth_messages.mes
  6. 16 23
      src/bin/auth/auth_srv.cc
  7. 2 0
      src/bin/auth/auth_srv.h
  8. 11 14
      src/bin/auth/query.cc
  9. 12 0
      src/bin/auth/query.h
  10. 12 13
      src/bin/auth/tests/auth_srv_unittest.cc
  11. 15 0
      src/bin/auth/tests/query_unittest.cc
  12. 3 2
      src/bin/bindctl/bindcmd.py
  13. 1 1
      src/bin/d2/dns_client.cc
  14. 5 5
      src/bin/d2/dns_client.h
  15. 15 6
      src/bin/d2/nc_add.cc
  16. 14 4
      src/bin/d2/tests/dns_client_unittests.cc
  17. 22 7
      src/bin/d2/tests/nc_test_utils.cc
  18. 1 1
      src/bin/d2/tests/nc_trans_unittests.cc
  19. 1 4
      src/bin/ddns/ddns.py.in
  20. 1 4
      src/bin/ddns/tests/ddns_test.py
  21. 18 2
      src/bin/dhcp4/config_parser.cc
  22. 21 6
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  23. 2 1
      src/bin/dhcp4/ctrl_dhcp4_srv.h
  24. 7 1
      src/bin/dhcp4/dhcp4.dox
  25. 22 7
      src/bin/dhcp4/dhcp4.spec
  26. 47 23
      src/bin/dhcp4/dhcp4_messages.mes
  27. 222 198
      src/bin/dhcp4/dhcp4_srv.cc
  28. 108 22
      src/bin/dhcp4/dhcp4_srv.h
  29. 5 1
      src/bin/dhcp4/tests/Makefile.am
  30. 2 5
      src/bin/dhcp4/tests/callout_library_common.h
  31. 406 54
      src/bin/dhcp4/tests/config_parser_unittest.cc
  32. 379 0
      src/bin/dhcp4/tests/d2_unittest.cc
  33. 117 0
      src/bin/dhcp4/tests/d2_unittest.h
  34. 350 84
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  35. 23 62
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  36. 130 165
      src/bin/dhcp4/tests/dhcp4_test_utils.h
  37. 413 0
      src/bin/dhcp4/tests/direct_client_unittest.cc
  38. 360 102
      src/bin/dhcp4/tests/fqdn_unittest.cc
  39. 18 1
      src/bin/dhcp6/config_parser.cc
  40. 7 4
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  41. 2 1
      src/bin/dhcp6/ctrl_dhcp6_srv.h
  42. 7 1
      src/bin/dhcp6/dhcp6.dox
  43. 24 0
      src/bin/dhcp6/dhcp6.spec
  44. 11 8
      src/bin/dhcp6/dhcp6_messages.mes
  45. 223 137
      src/bin/dhcp6/dhcp6_srv.cc
  46. 76 41
      src/bin/dhcp6/dhcp6_srv.h
  47. 2 1
      src/bin/dhcp6/tests/Makefile.am
  48. 2 5
      src/bin/dhcp6/tests/callout_library_common.h
  49. 413 46
      src/bin/dhcp6/tests/config_parser_unittest.cc
  50. 105 1
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  51. 3 0
      src/bin/dhcp6/tests/dhcp6_test_utils.cc
  52. 146 117
      src/bin/dhcp6/tests/dhcp6_test_utils.h
  53. 195 67
      src/bin/dhcp6/tests/fqdn_unittest.cc
  54. 2 0
      src/bin/dhcp6/tests/hooks_unittest.cc
  55. 2 0
      src/bin/dhcp6/tests/wireshark.cc
  56. 2 2
      src/bin/resolver/resolver.cc
  57. 1 4
      src/bin/xfrin/tests/xfrin_test.py
  58. 2 1
      src/bin/xfrin/xfrin.py.in
  59. 3 3
      src/bin/xfrin/xfrin_messages.mes
  60. 4 9
      src/bin/xfrout/xfrout.py.in
  61. 2 1
      src/hooks/dhcp/user_chk/Makefile.am
  62. 1 4
      src/hooks/dhcp/user_chk/user_chk.h
  63. 1 1
      src/hooks/dhcp/user_chk/user_file.cc
  64. 4 6
      src/hooks/dhcp/user_chk/user_file.h
  65. 3 5
      src/hooks/dhcp/user_chk/user_registry.h
  66. 1 1
      src/lib/acl/ip_check.cc
  67. 1 1
      src/lib/asiodns/io_fetch.cc
  68. 6 2
      src/lib/asiodns/tests/io_fetch_unittest.cc
  69. 22 10
      src/lib/asiolink/dummy_io_cb.h
  70. 16 5
      src/lib/asiolink/io_address.cc
  71. 12 10
      src/lib/asiolink/io_address.h
  72. 1 1
      src/lib/asiolink/tcp_socket.h
  73. 1 0
      src/lib/asiolink/tests/Makefile.am
  74. 36 0
      src/lib/asiolink/tests/dummy_io_callback_unittest.cc
  75. 35 0
      src/lib/asiolink/tests/io_address_unittest.cc
  76. 4 4
      src/lib/asiolink/tests/tcp_socket_unittest.cc
  77. 1 1
      src/lib/asiolink/tests/udp_socket_unittest.cc
  78. 2 3
      src/lib/cache/message_cache.h
  79. 1 0
      src/lib/cache/message_entry.cc
  80. 3 5
      src/lib/cache/message_entry.h
  81. 0 2
      src/lib/cache/rrset_cache.h
  82. 1 0
      src/lib/cache/rrset_entry.cc
  83. 3 5
      src/lib/cache/rrset_entry.h
  84. 2 6
      src/lib/cache/tests/cache_test_messagefromfile.h
  85. 2 6
      src/lib/cache/tests/cache_test_sectioncount.h
  86. 1 0
      src/lib/cache/tests/message_cache_unittest.cc
  87. 1 1
      src/lib/cc/session.h
  88. 6 6
      src/lib/cc/tests/data_unittests.cc
  89. 2 2
      src/lib/config/config_data.h
  90. 1 1
      src/lib/config/tests/testdata/spec22.spec
  91. 1 1
      src/lib/config/tests/testdata/spec27.spec
  92. 4 2
      src/lib/datasrc/cache_config.cc
  93. 1 1
      src/lib/datasrc/cache_config.h
  94. 1 1
      src/lib/datasrc/client_list.h
  95. 12 9
      src/lib/datasrc/database.cc
  96. 4 0
      src/lib/datasrc/memory/domaintree.h
  97. 14 1
      src/lib/datasrc/memory/memory_client.cc
  98. 1 1
      src/lib/datasrc/memory/rdataset.h
  99. 80 0
      src/lib/datasrc/memory/treenode_rrset.cc
  100. 0 0
      src/lib/datasrc/memory/treenode_rrset.h

+ 131 - 0
ChangeLog

@@ -1,3 +1,134 @@
+753.	[func]		muks
+	libdns++: the unknown/generic (RFC 3597) RDATA class now uses the
+	generic lexer in constructors from text.
+	(Trac #2426, git 0770d2df84e5608371db3a47e0456eb2a340b5f4)
+
+752.	[func]		tmark
+	If configured to do so, b10-dhcp4 will now create DHCP-DDNS update
+	requests and send them to b10-dhcp-ddns for processing.
+	(Trac# 3329, git 4546dd186782eec5cfcb4ddb61b0a3aa5c700751)
+
+751.	[func]		muks
+	The BIND 10 zone loader now supports the $GENERATE directive (a
+	BIND 9 extension).
+	(Trac #2430, git b05064f681231fe7f8571253c5786f4ff0f2ca03)
+
+750.	[func]		tomek
+	b10-dhcp4, b10-dhcp6: Simple client classification has been
+	implemented. Incoming packets can be assigned to zero or more
+	client classes. It is possible to restrict subnet usage to a given
+	client class. User's Guide and Developer's Guide has been updated.
+	(Trac #3274, git 1791d19899b92a6ee411199f664bdfc690ec08b2)
+
+749.	[bug]		tmark
+	b10-dhcp-ddns now sets the TTL value in RRs that add A, AAAA, or PTR DNS
+	entries to the lease length provided in instigating NameChangeRequest.
+	This corrected a bug in which the TTL was always set to 0.
+	(Trac# 3299, git dbacf27ece77f3d857da793341c6bd31ef1ea239)
+
+748.	[bug]		marcin
+	b10-dhcp4 server picks a subnet, to assign address for a directly
+	connected client, using IP address of the interface on which the
+	client's message has been received. If the message is received on
+	the interface for which there is no suitable subnet, the message
+	is discarded. Also, the subnet for renewing client which unicasts
+	its request, is selected using ciaddr.
+	(Trac #3242, git 9e571cc217d6b1a2fd6fdae1565fcc6fde6d08b1)
+
+747.	[bug]		marcin
+	libdhcpsrv: server configuration mechanism allows creating definitions
+	for standard options for which Kea doesn't provide a definition yet.
+	Without this, the server administrator couldn't configure options for
+	which a definition didn't exist.
+	(Trac# 3309, git 16a6ed6e48a6a950670c4874a2e81b1faf287d99)
+
+746.	[func]		tomek
+	IOAddress no longer exposes underlying asio objects. The getAddress()
+	method has been removed and replaced with several convenience methods.
+	(Trac #1485, git ecdb62db16b3f3d447db4a9d2a4079d5260431f0)
+
+745.	[bug]		muks
+	b10-auth now returns rcode=REFUSED for all questions with
+	qtype=RRSIG (i.e., where RRSIGs are queried directly). This is
+	because RRSIGs are meaningless without being bundled alongside the
+	RRs they cover.
+	(Trac #2226, git 68d24e65c9c3dfee38adfbe1c93367b0083f9a58)
+
+744.	[func]		marcin
+	b10-dhcp6: Refactored the code which is processing Client FQDN option.
+	The major user-visible change is that server generates DDNS
+	NameChangeRequest for the first IPv6 address (instead of all)
+	acquired by a client. Also, the server generates fully qualified domain
+	name from acquired IPv6 address, if the client sends an empty name in
+	Client FQDN option.
+	(Trac# 3295, git aa1c94a54114e848c64771fde308fc9ac0c00fd0)
+
+743.	[func]		tmark
+	b10-dhcp4 now responds with changes in DDNS behavior based upon
+	configuration parameters specified through its dhcp-ddns configuration
+	element. The parameters now supported are override-no-update,
+	override-client-update, replace-client-name, generated-prefix, and
+	qualifying-suffix.
+	(Trac# 3282, git 42b1f1e4c4f5aa48b7588233402876f5012c043c)
+
+742.	[func]		muks
+	The authoritative server now includes the datasource configuration
+	when logging some errors with the
+	AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_ERROR message ID.
+	(Trac #2756, git 31872754f36c840b4ec0b412a86afe9f38be86e0)
+
+741.	[bug]		shane
+	Remove hard-coded (and unnecessary) TSIG key from error message.
+	This also prevents a crash if the TSIG name is missing.
+	(Trac #3099, git 0ba8bbabe09756a4627e80aacdbb5050407faaac)
+
+740.	[func]		muks
+	When displaying messages about mismatched configuration data types
+	in entered values (between the supplied value type and expected
+	schema type), bindctl now includes both the supplied and expected
+	configuration data types in the returned error. The user has more
+	information on what caused the error now.
+	(Trac #3239, git 84d5eda2a6ae0d737aef68d56023fc33fef623e6)
+
+739.	[bug]		muks
+	Various minor updates were made to the SSHFP RDATA parser. Mainly,
+	the SSHFP constructor no longer throws an isc::BadValue exception.
+	generic::SSHFP::getFingerprintLen() was also renamed to
+	getFingerprintLength().
+	(Trac #3287, git 2f26d781704618c6007ba896ad3d9e0c107d04b0)
+
+738.	[bug]		muks
+	b10-auth now correctly processes NXDOMAIN results in the root zone
+	when using a SQLite3 data source.
+	(Trac #2951, git 13685cc4580660eaf5b041b683a2d2f31fd24de3)
+
+737.	[func]		muks
+	b10-auth now additionally logs the source address and port when
+	DNS messages with unsupported opcodes are received.
+	(Trac #1516, git 71611831f6d1aaaea09143d4837eddbd1d67fbf4)
+
+736.    [bug]           wlodek
+	b10-dhcp6 is now capable to determine if a received
+	message is addressed to it, using server identifier option.
+	The messages with non-matching server identifier are dropped.
+	(Trac #2892, git 3bd69e9b4ab9be231f7c966fd62b95a4e1595901)
+
+735.	[doc]		stephen
+	Expanded Developer's Guide to include chapter on logging.
+	(Trac #2566, git a08d702839d9df6cddefeccab1e7e657377145de)
+
+734.	[bug]		marcin
+	libdhcp++: fixed a bug which caused an error when setting boolean
+	values for an option. Also, bind10-guide has been updated with the
+	examples how to set the boolean values for an option.
+	(Trac# 3292, git 7c4c0514ede3cffc52d8c2874cdbdb74ced5f4ac)
+
+733.	[bug]		marcin
+	libdhcp++: a function which opens IPv6/UDPv6 sockets for the
+	DHCPv6 server, gracefully handles errors to bind socket to
+	a multicast address.
+	(Trac #3288, git 76ace0c46a5fe0e53a29dad093b817ad6c891f1b)
+
 732.	[func]		tomek
 	b10-dhcp4, b10-dhcp6: Support for simplified client classification
         added. Incoming packets are now assigned to a client class based on

+ 3 - 1
doc/devel/contribute.dox

@@ -109,7 +109,9 @@ make distcheck
 
 There are other useful switches which can be passed to configure. It is
 always a good idea to use \c --enable-logger-checks, which does sanity
-checks on logger parameters. If you happen to modify anything in the
+checks on logger parameters. Use \c --enable-debug to enable various
+additional consistency checks that reduce performance but help during
+development. If you happen to modify anything in the
 documentation, use \c --enable-generate-docs. If you are modifying DHCP
 code, you are likely to be interested in enabling the MySQL backend for
 DHCP. Note that if the backend is not enabled, MySQL specific unit-tests

+ 4 - 4
doc/devel/mainpage.dox

@@ -86,10 +86,10 @@
  * - @subpage libdhcp_ddns
  *
  * @section miscellaneousTopics Miscellaneous Topics
- * - @subpage LoggingApi
- *   - @subpage LoggingApiOverview
- *   - @subpage LoggingApiLoggerNames
- *   - @subpage LoggingApiLoggingMessages
+ * - @subpage logBind10Logging
+ *   - @subpage logBasicIdeas
+ *   - @subpage logDeveloperUse
+ *   - @subpage logNotes
  * - @subpage SocketSessionUtility
  * - <a href="./doxygen-error.log">Documentation warnings and errors</a>
  *

+ 296 - 27
doc/guide/bind10-guide.xml

@@ -7,7 +7,7 @@
 ]>
 
 <!--
- - Copyright (C) 2010-2013  Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010-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
@@ -39,7 +39,7 @@
       servers with development managed by Internet Systems Consortium (ISC).
       It includes DNS libraries, modular components for controlling
       authoritative and recursive DNS servers, and experimental DHCPv4
-      and DHCPv6 servers.
+      and DHCPv6 servers (codenamed Kea).
       </para>
       <para>
         This is the reference guide for BIND 10 version &__VERSION__;.
@@ -1554,7 +1554,10 @@ Parameters:
                 <term>integer</term>
                 <listitem>
                     <simpara>
-                        A basic integer; can be set directly with <command>config set</command>, to any integer value.
+                        A basic integer; can be set directly with
+                        <command>config set</command>, to any integer
+                        value. The value must not be quoted, or else, it
+                        will be parsed as a string.
                     </simpara>
                 </listitem>
             </varlistentry>
@@ -1562,7 +1565,10 @@ Parameters:
                 <term>real</term>
                 <listitem>
                     <simpara>
-                        A basic floating point number; can be set directly with <command>config set</command>, to any floating point value.
+                        A basic floating point number; can be set
+                        directly with <command>config set</command>, to
+                        any floating point value. The value must not be
+                        quoted, or else, it will be parsed as a string.
                     </simpara>
                 </listitem>
             </varlistentry>
@@ -1570,7 +1576,12 @@ Parameters:
                 <term>boolean</term>
                 <listitem>
                     <simpara>
-                        A basic boolean value; can be set directly with <command>config set</command>, to either <command>true</command> or <command>false</command>.
+                        A basic boolean value; can be set directly with
+                        <command>config set</command>, to either
+                        <command>true</command> or
+                        <command>false</command>. The value must not be
+                        quoted, or else, it will be parsed as a
+                        string. Integer values are not allowed.
                     </simpara>
                 </listitem>
             </varlistentry>
@@ -1578,7 +1589,9 @@ Parameters:
                 <term>string</term>
                 <listitem>
                     <simpara>
-                        A basic string value; can be set directly with <command>config set,</command> so any string. Double quotation marks are optional.
+                        A basic string value; can be set directly with
+                        <command>config set</command> to any
+                        string. Double quotation marks are optional.
                     </simpara>
                 </listitem>
             </varlistentry>
@@ -3523,7 +3536,9 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
     clients. Even though principles of both DHCPv4 and DHCPv6 are
     somewhat similar, these are two radically different
     protocols. BIND 10 offers two server implementations, one for DHCPv4
-    and one for DHCPv6.</para>
+    and one for DHCPv6. The DHCP part of the BIND 10 project is codenamed
+    Kea. The DHCPv4 component is colloquially referred to as Kea4 and its
+    DHCPv6 counterpart is called Kea6.</para>
     <para>This chapter covers those parts of BIND 10 that are common to
     both servers.  DHCPv4-specific details are covered in <xref linkend="dhcp4"/>,
     while those details specific to DHCPv6 are described in <xref linkend="dhcp6"/>
@@ -3633,6 +3648,14 @@ $</screen>
 &gt; <userinput>config commit</userinput>
 </screen>
       </para>
+      <para>
+        Note that the server was only removed from the list, so BIND10 will not
+        restart it, but the server itself is still running. Hence it is usually
+        desired to stop it:
+<screen>
+&gt; <userinput>Dhcp4 shutdown</userinput>
+</screen>
+      </para>
 
       <para>
         On start-up, the server will detect available network interfaces
@@ -3803,7 +3826,7 @@ Dhcp4/subnet4	[]	list	(default)
       </section>
 
       <section id="dhcp4-address-config">
-      <title>Configuration of Address Pools</title>
+      <title>Configuration of IPv4 Address Pools</title>
       <para>
         The essential role of DHCPv4 server is address assignment. The server
         has to be configured with at least one subnet and one pool of dynamic
@@ -3928,7 +3951,7 @@ Dhcp4/subnet4	[]	list	(default)
       <para>
         The following commands override the global
         DNS servers option for a particular subnet, setting a single DNS
-        server with address 2001:db8:1::3.
+        server with address 192.0.2.3.
         <screen>
 &gt; <userinput>config add Dhcp4/subnet4[0]/option-data</userinput>
 &gt; <userinput>config set Dhcp4/subnet4[0]/option-data[0]/name "domain-name-servers"</userinput>
@@ -3963,6 +3986,20 @@ Dhcp4/subnet4	[]	list	(default)
       <!-- @todo: describe record types -->
 
       <para>
+        The <xref linkend="dhcp4-custom-options"/> describes the configuration
+        syntax to create custom option definitions (formats). It is generally not
+        allowed to create custom definitions for standard options, even if the
+        definition being created matches the actual option format defined in the
+        RFCs. There is an exception from this rule for standard options for which
+        Kea does not provide a definition yet. In order to use such options,
+        a server administrator must create a definition as described in
+        <xref linkend="dhcp4-custom-options"/> in the 'dhcp4' option space. This
+        definition should match the option format described in the relevant
+        RFC but configuration mechanism would allow any option format as it has
+        no means to validate it at the moment.
+      </para>
+
+      <para>
         <table frame="all" id="dhcp4-std-options-list">
           <title>List of standard DHCPv4 options</title>
           <tgroup cols='4'>
@@ -4172,10 +4209,10 @@ Dhcp4/subnet4	[]	list	(default)
       primitives (uint8, string, ipv4-address etc): it is possible to
       define an option comprising a number of existing primitives.
       </para>
-      <para>Assume we
-      want to define a new option that will consist of an IPv4
-      address, followed by unsigned 16 bit integer, followed by a text
-      string. Such an option could be defined in the following way:
+      <para>Assume we want to define a new option that will consist of
+      an IPv4 address, followed by unsigned 16 bit integer, followed by
+      a boolean value, followed by a text string. Such an option could
+      be defined in the following way:
 <screen>
 &gt; <userinput>config add Dhcp4/option-def</userinput>
 &gt; <userinput>config set Dhcp4/option-def[0]/name "bar"</userinput>
@@ -4183,7 +4220,7 @@ Dhcp4/subnet4	[]	list	(default)
 &gt; <userinput>config set Dhcp4/option-def[0]/space "dhcp4"</userinput>
 &gt; <userinput>config set Dhcp4/option-def[0]/type "record"</userinput>
 &gt; <userinput>config set Dhcp4/option-def[0]/array false</userinput>
-&gt; <userinput>config set Dhcp4/option-def[0]/record-types "ipv4-address, uint16, string"</userinput>
+&gt; <userinput>config set Dhcp4/option-def[0]/record-types "ipv4-address, uint16, boolean, string"</userinput>
 &gt; <userinput>config set Dhcp4/option-def[0]/encapsulate ""</userinput>
 </screen>
       The "type" is set to "record" to indicate that the option contains
@@ -4198,12 +4235,23 @@ Dhcp4/subnet4	[]	list	(default)
 &gt; <userinput>config set Dhcp4/option-data[0]/space "dhcp4"</userinput>
 &gt; <userinput>config set Dhcp4/option-data[0]/code 223</userinput>
 &gt; <userinput>config set Dhcp4/option-data[0]/csv-format true</userinput>
-&gt; <userinput>config set Dhcp4/option-data[0]/data "192.0.2.100, 123, Hello World"</userinput>
+&gt; <userinput>config set Dhcp4/option-data[0]/data "192.0.2.100, 123, true, Hello World"</userinput>
 &gt; <userinput>config commit</userinput></screen>
-      </para>
       "csv-format" is set "true" to indicate that the "data" field comprises a command-separated
       list of values.  The values in the "data" must correspond to the types set in
       the "record-types" field of the option definition.
+     </para>
+     <note>
+       <para>
+         It is recommended that boolean values are specified using "true" and "false"
+         strings. This helps to prevent errors when typing multiple comma separated
+         values, as it make it easier to identify the type of the value being typed,
+         and compare it with the order of data fields. Nevetheless, it is possible
+         to use integer values: "1" and "0", instead of "true" and "false"
+         accordingly. If other integer value is specified, the configuration is
+         rejected.
+       </para>
+     </note>
     </section>
 
     <section id="dhcp4-vendor-opts">
@@ -4372,7 +4420,87 @@ Dhcp4/subnet4	[]	list	(default)
     e.g. "123" - would then be assigned to the uint16 field in the "container" option.
     </para>
     </section>
-        </section>
+
+    <section id="dhcp4-client-classifier">
+      <title>Client Classification in DHCPv4</title>
+      <note>
+      <para>
+        DHCPv4 server has been extended to support limited client classification.
+        Although the current capability is modest, it is expected to be expanded
+        in the future. It is envisaged that the majority of client classification
+        extensions will be using hooks extensions.
+      </para>
+      </note>
+      <para>In certain cases it is useful to differentiate between different types
+      of clients and treat them differently. The process of doing classification
+      is conducted in two steps. The first step is to assess incoming packet and
+      assign it to zero or more classes. This classification is currently simple,
+      but is expected to grow in capability soon. Currently the server checks whether
+      incoming packet has vendor class identifier option (60). If it has, content
+      of that option is interpreted as a class. For example, modern cable modems
+      will send this option with value &quot;docsis3.0&quot; and as a result the
+      packet will belong to class &quot;docsis3.0&quot;.
+      </para>
+
+      <para>It is envisaged that the client classification will be used for changing
+      behavior of almost any part of the DHCP message processing, including assigning
+      leases from different pools, assigning different option (or different values of
+      the same options) etc. For now, there are only two mechanisms that are taking
+      advantage of client classification: specific processing for cable modems and
+      subnet selection.</para>
+
+      <para>
+        For clients that belong to the docsis3.0 class, the siaddr field is set to
+        the value of next-server (if specified in a subnet). If there is
+        boot-file-name option specified, its value is also set in the file field
+        in the DHCPv4 packet. For eRouter1.0 class, the siaddr is always set to
+        0.0.0.0. That capability is expected to be moved to external hook
+        library that will be dedicated to cable modems.
+      </para>
+
+      <para>
+        Kea can be instructed to limit access to given subnets based on class information.
+        This is particularly useful for cases where two types of devices share the
+        same link and are expected to be served from two different subnets. The
+        primary use case for such a scenario is cable networks. There are two
+        classes of devices: cable modem itself, which should be handled a lease
+        from subnet A and all other devices behind modems that should get a lease
+        from subnet B. That segregation is essential to prevent overly curious
+        users from playing with their cable modems. For details on how to set up
+        class restrictions on subnets, see <xref linkend="dhcp4-subnet-class"/>.
+      </para>
+
+    </section>
+
+    <section id="dhcp4-subnet-class">
+      <title>Limiting access to IPv4 subnet to certain classes</title>
+      <para>
+        In certain cases it beneficial to restrict access to certain subnets
+        only to clients that belong to a given subnet. For details on client
+        classes, see <xref linkend="dhcp4-client-classifier"/>. This is an
+        extension of a previous example from <xref linkend="dhcp4-address-config"/>.
+        Let's assume that the server is connected to a network segment that uses
+        the 192.0.2.0/24 prefix. The Administrator of that network has decided
+        that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
+        managed by the Dhcp4 server. Only clients belonging to client class
+        docsis3.0 are allowed to use this subnet. Such a configuration can be
+        achieved in the following way:
+        <screen>
+&gt; <userinput>config add Dhcp4/subnet4</userinput>
+&gt; <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
+&gt; <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+&gt; <userinput>config set Dhcp4/subnet4[0]/client-class "docsis3.0"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+      </para>
+
+      <para>
+        Care should be taken with client classification as it is easy to prevent
+        clients that do not meet class criteria to be denied any service altogether.
+      </para>
+    </section>
+
+  </section> <!-- end of configuring DHCPv4 server section with many subsections -->
+
     <section id="dhcp4-serverid">
       <title>Server Identifier in DHCPv4</title>
       <para>
@@ -4390,6 +4518,7 @@ Dhcp4/subnet4	[]	list	(default)
       </para>
     </section>
 
+
     <section id="dhcp4-next-server">
       <title>Next server (siaddr)</title>
       <para>In some cases, clients want to obtain configuration from the TFTP server.
@@ -4437,6 +4566,40 @@ Dhcp4/subnet4	[]	list	(default)
 
     </section>
 
+    <section id="dhcp4-subnet-selection">
+      <title>How DHCPv4 server selects subnet for a client</title>
+      <para>
+        The DHCPv4 server differentiates between the directly connected clients,
+        clients trying to renew leases and clients sending their messages through
+        relays. For the directly connected clients the server will check the
+        configuration of the interface on which the message has been received, and
+        if the server configuration doesn't match any configured subnet the
+        message is discarded.</para>
+        <para>Assuming that the server's interface is configured with the 192.0.2.3
+        IPv4 address, the server will only process messages received through
+        this interface from the directly connected client, if there is a subnet
+        configured, to which this IPv4 address belongs, e.g. 192.0.2.0/24.
+        The server will use this subnet to assign IPv4 address for the client.
+      </para>
+      <para>
+        The rule above does not apply when the client unicasts its message, i.e.
+        is trying to renew its lease. Such message is accepted through any
+        interface. The renewing client sets ciaddr to the currently used IPv4
+        address. The server uses this address to select the subnet for the client
+        (in particular, to extend the lease using this address).
+      </para>
+      <para>
+        If the message is relayed it is accepted through any interface. The giaddr
+        set by the relay agent is used to select the subnet for the client.
+      </para>
+      <note>
+        <para>The subnet selection mechanism described in this section is based
+        on the assumption that client classification is not used. The classification
+        mechanism alters the way in which subnet is selected for the client,
+        depending on the clasess that the client belongs to.</para>
+      </note>
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
@@ -4456,6 +4619,8 @@ Dhcp4/subnet4	[]	list	(default)
           <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc3046">RFC 3046</ulink>:
             Relay Agent Information option is supported.</simpara>
+          </listitem>
+          <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc6842">RFC 6842</ulink>:
             Server by default sends back client-id option. That capability may be
             disabled. See <xref linkend="dhcp4-echo-client-id"/> for details.
@@ -4550,13 +4715,21 @@ Dhcp4/renew-timer	1000	integer	(default)
       </para>
       <para>
          To remove <command>b10-dhcp6</command> from the set of running services,
-         the <command>b10-dhcp4</command> is removed from list of Init components:
+         the <command>b10-dhcp6</command> is removed from list of Init components:
 <screen>
 &gt; <userinput>config remove Init/components b10-dhcp6</userinput>
 &gt; <userinput>config commit</userinput>
 </screen>
       </para>
 
+      <para>
+        Note that the server was only removed from the list, so BIND10 will not
+        restart it, but the server itself is still running. Hence it is usually
+        desired to stop it:
+<screen>
+&gt; <userinput>Dhcp6 shutdown</userinput>
+</screen>
+      </para>
 
       <para>
         During start-up the server will detect available network interfaces
@@ -4762,7 +4935,7 @@ Dhcp6/subnet6/	list
       </para>
     </section>
 
-    <section>
+    <section id="dhcp6-address-config">
       <title>Subnet and Address Pool</title>
       <para>
         The essential role of a DHCPv6 server is address assignment. For this,
@@ -4954,6 +5127,21 @@ Dhcp6/subnet6/	list
 
 <!-- @todo: describe record types -->
 
+      <para>
+        The <xref linkend="dhcp6-custom-options"/> describes the configuration
+        syntax to create custom option definitions (formats). It is generally not
+        allowed to create custom definitions for standard options, even if the
+        definition being created matches the actual option format defined in the
+        RFCs. There is an exception from this rule for standard options for which
+        Kea does not provide a definition yet. In order to use such options,
+        a server administrator must create a definition as described in
+        <xref linkend="dhcp6-custom-options"/> in the 'dhcp6' option space. This
+        definition should match the option format described in the relevant
+        RFC but configuration mechanism would allow any option format as it has
+        no means to validate it at the moment.
+      </para>
+
+
     <para>
       <table frame="all" id="dhcp6-std-options-list">
         <title>List of standard DHCPv6 options</title>
@@ -5067,10 +5255,10 @@ Dhcp6/subnet6/	list
       define an option comprising a number of existing primitives.
       </para>
       <para>
-      Assume we
-      want to define a new option that will consist of an IPv6
-      address, followed by unsigned 16 bit integer, followed by a text
-      string. Such an option could be defined in the following way:
+      Assume we want to define a new option that will consist of an IPv6
+      address, followed by an unsigned 16 bit integer, followed by a
+      boolean value, followed by a text string. Such an option could
+      be defined in the following way:
 <screen>
 &gt; <userinput>config add Dhcp6/option-def</userinput>
 &gt; <userinput>config set Dhcp6/option-def[0]/name "bar"</userinput>
@@ -5078,7 +5266,7 @@ Dhcp6/subnet6/	list
 &gt; <userinput>config set Dhcp6/option-def[0]/space "dhcp6"</userinput>
 &gt; <userinput>config set Dhcp6/option-def[0]/type "record"</userinput>
 &gt; <userinput>config set Dhcp6/option-def[0]/array false</userinput>
-&gt; <userinput>config set Dhcp6/option-def[0]/record-types "ipv6-address, uint16, string"</userinput>
+&gt; <userinput>config set Dhcp6/option-def[0]/record-types "ipv6-address, uint16, boolean, string"</userinput>
 &gt; <userinput>config set Dhcp6/option-def[0]/encapsulate ""</userinput>
 </screen>
       The "type" is set to "record" to indicate that the option contains
@@ -5093,12 +5281,23 @@ Dhcp6/subnet6/	list
 &gt; <userinput>config set Dhcp6/option-data[0]/space "dhcp6"</userinput>
 &gt; <userinput>config set Dhcp6/option-data[0]/code 101</userinput>
 &gt; <userinput>config set Dhcp6/option-data[0]/csv-format true</userinput>
-&gt; <userinput>config set Dhcp6/option-data[0]/data "2001:db8:1::10, 123, Hello World"</userinput>
+&gt; <userinput>config set Dhcp6/option-data[0]/data "2001:db8:1::10, 123, false, Hello World"</userinput>
 &gt; <userinput>config commit</userinput></screen>
-      </para>
       "csv-format" is set "true" to indicate that the "data" field comprises a command-separated
       list of values.  The values in the "data" must correspond to the types set in
       the "record-types" field of the option definition.
+      </para>
+      <note>
+        <para>
+          It is recommended that boolean values are specified using "true" and "false"
+          strings. This helps to prevent errors when typing multiple comma separated
+          values, as it make it easier to identify the type of the value being typed,
+          and compare it with the order of data fields. Nevetheless, it is possible
+          to use integer values: "1" and "0", instead of "true" and "false"
+          accordingly. If other integer value is specified, the configuration is
+          rejected.
+        </para>
+      </note>
     </section>
 
     <section id="dhcp6-vendor-opts">
@@ -5255,7 +5454,7 @@ should include options from the isc option space:
     </section>
 
       <section id="dhcp6-config-subnets">
-        <title>Subnet Selection</title>
+        <title>IPv6 Subnet Selection</title>
           <para>
           The DHCPv6 server may receive requests from local (connected to the
           same subnet as the server) and remote (connecting via relays) clients.
@@ -5343,6 +5542,76 @@ should include options from the isc option space:
         </para>
       </section>
 
+    <section id="dhcp6-client-classifier">
+      <title>Client Classification in DHCPv6</title>
+      <note>
+      <para>
+        DHCPv6 server has been extended to support limited client classification.
+        Although the current capability is modest, it is expected to be expanded
+        in the future. It is envisaged that the majority of client classification
+        extensions will be using hooks extensions.
+      </para>
+      </note>
+      <para>In certain cases it is useful to differentiate between different types
+      of clients and treat them differently. The process of doing classification
+      is conducted in two steps. The first step is to assess incoming packet and
+      assign it to zero or more classes. This classification is currently simple,
+      but is expected to grow in capability soon. Currently the server checks whether
+      incoming packet has vendor class option (16). If it has, content
+      of that option is interpreted as a class. For example, modern cable modems
+      will send this option with value &quot;docsis3.0&quot; and as a result the
+      packet will belong to class &quot;docsis3.0&quot;.
+      </para>
+
+      <para>It is envisaged that the client classification will be used for changing
+      behavior of almost any part of the DHCP engine processing, including assigning
+      leases from different pools, assigning different option (or different values of
+      the same options) etc. For now, there is only one mechanism that is taking
+      advantage of client classification: subnet selection.</para>
+
+      <para>
+        Kea can be instructed to limit access to given subnets based on class information.
+        This is particularly useful for cases where two types of devices share the
+        same link and are expected to be served from two different subnets. The
+        primary use case for such a scenario are cable networks. There are two
+        classes of devices: cable modem itself, which should be handled a lease
+        from subnet A and all other devices behind modems that should get a lease
+        from subnet B. That segregation is essential to prevent overly curious
+        users from playing with their cable modems. For details on how to set up
+        class restrictions on subnets, see <xref linkend="dhcp6-subnet-class"/>.
+      </para>
+
+    </section>
+
+    <section id="dhcp6-subnet-class">
+      <title>Limiting access to IPv6 subnet to certain classes</title>
+      <para>
+        In certain cases it beneficial to restrict access to certains subnets
+        only to clients that belong to a given subnet. For details on client
+        classes, see <xref linkend="dhcp6-client-classifier"/>. This is an
+        extension of a previous example from <xref linkend="dhcp6-address-config"/>.
+
+        Let's assume that the server is connected to a network segment that uses
+        the 2001:db8:1::/64 prefix. The Administrator of that network has
+        decided that addresses from range 2001:db8:1::1 to 2001:db8:1::ffff are
+        going to be managed by the Dhcp6 server. Only clients belonging to the
+        eRouter1.0 client class are allowed to use that pool. Such a
+        configuration can be achieved in the following way:
+
+        <screen>
+&gt; <userinput>config add Dhcp6/subnet6</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/subnet "2001:db8:1::/64"</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/pool [ "2001:db8:1::0 - 2001:db8:1::ffff" ]</userinput>
+&gt; <userinput>config set Dhcp6/subnet6[0]/client-class "eRouter1.0"</userinput>
+&gt; <userinput>config commit</userinput></screen>
+      </para>
+
+      <para>
+        Care should be taken with client classification as it is easy to prevent
+        clients that do not meet class criteria to be denied any service altogether.
+      </para>
+    </section>
+
    </section>
 
     <section id="dhcp6-serverid">

+ 3 - 3
src/bin/auth/auth_messages.mes

@@ -383,11 +383,11 @@ This message is also logged when the forwarding is restarted (for instance
 if b10-ddns is restarted and the internal connection needs to be created
 again), in which case it should be followed by AUTH_START_DDNS_FORWARDER.
 
-% AUTH_UNSUPPORTED_OPCODE unsupported opcode: %1
+% AUTH_UNSUPPORTED_OPCODE unsupported opcode %1 received from %2
 This is a debug message, produced when a received DNS packet being
 processed by the authoritative server has been found to contain an
-unsupported opcode. (The opcode is included in the message.) The server
-will return an error code of NOTIMPL to the sender.
+unsupported opcode. (The opcode and sender details are included in the
+message.) The server will return an error code of NOTIMPL to the sender.
 
 % AUTH_XFRIN_CHANNEL_CREATED XFRIN session channel created
 This is a debug message indicating that the authoritative server has

+ 16 - 23
src/bin/auth/auth_srv.cc

@@ -297,6 +297,8 @@ public:
     ///
     /// \param server The DNSServer as passed to processMessage()
     /// \param message The response as constructed by processMessage()
+    /// \param stats_attrs Object to store message attributes in for use
+    ///                    with statistics
     /// \param done If true, it indicates there is a response.
     ///             this value will be passed to server->resume(bool)
     void resumeServer(isc::asiodns::DNSServer* server,
@@ -440,12 +442,9 @@ makeErrorMessage(MessageRenderer& renderer, Message& message,
     message.setRcode(rcode);
 
     RendererHolder holder(renderer, &buffer, stats_attrs);
-    if (tsig_context.get() != NULL) {
-        message.toWire(renderer, *tsig_context);
-        stats_attrs.setResponseTSIG(true);
-    } else {
-        message.toWire(renderer);
-    }
+    message.toWire(renderer, tsig_context.get());
+    stats_attrs.setResponseTSIG(tsig_context.get() != NULL);
+
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_ERROR_RESPONSE)
               .arg(renderer.getLength()).arg(message);
 }
@@ -499,7 +498,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
             impl_->resumeServer(server, message, stats_attrs, false);
             return;
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_HEADER_PARSE_FAIL)
                   .arg(ex.what());
         impl_->resumeServer(server, message, stats_attrs, false);
@@ -523,7 +522,7 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                          stats_attrs);
         impl_->resumeServer(server, message, stats_attrs, true);
         return;
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_FAILED)
                   .arg(ex.what());
         makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL(),
@@ -582,8 +581,9 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                                  Rcode::NOTIMP(), stats_attrs, tsig_context);
             }
         } else if (opcode != Opcode::QUERY()) {
+            const IOEndpoint& remote_ep = io_message.getRemoteEndpoint();
             LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
-                      .arg(message.getOpcode().toText());
+                .arg(message.getOpcode().toText()).arg(remote_ep);
             makeErrorMessage(impl_->renderer_, message, buffer,
                              Rcode::NOTIMP(), stats_attrs, tsig_context);
         } else if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
@@ -660,7 +660,7 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message,
                              stats_attrs);
             return (true);
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
         makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
                          stats_attrs);
@@ -671,12 +671,9 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message,
     const bool udp_buffer =
         (io_message.getSocket().getProtocol() == IPPROTO_UDP);
     renderer_.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
-    if (tsig_context.get() != NULL) {
-        message.toWire(renderer_, *tsig_context);
-        stats_attrs.setResponseTSIG(true);
-    } else {
-        message.toWire(renderer_);
-    }
+    message.toWire(renderer_, tsig_context.get());
+    stats_attrs.setResponseTSIG(tsig_context.get() != NULL);
+
     LOG_DEBUG(auth_logger, DBG_AUTH_MESSAGES, AUTH_SEND_NORMAL_RESPONSE)
               .arg(renderer_.getLength()).arg(message);
     return (true);
@@ -823,7 +820,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
                       .arg(parsed_answer->str());
             return (false);
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_ZONEMGR_COMMS).arg(ex.what());
         return (false);
     }
@@ -833,12 +830,8 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     message.setRcode(Rcode::NOERROR());
 
     RendererHolder holder(renderer_, &buffer, stats_attrs);
-    if (tsig_context.get() != NULL) {
-        message.toWire(renderer_, *tsig_context);
-        stats_attrs.setResponseTSIG(true);
-    } else {
-        message.toWire(renderer_);
-    }
+    message.toWire(renderer_, tsig_context.get());
+    stats_attrs.setResponseTSIG(tsig_context.get() != NULL);
     return (true);
 }
 

+ 2 - 0
src/bin/auth/auth_srv.h

@@ -104,6 +104,8 @@ public:
     /// process.  It's normally a reference to an xfr::XfroutClient object,
     /// but can refer to a local mock object for testing (or other
     /// experimental) purposes.
+    /// \param ddns_forwarder Forwarder to which DDNS UPDATE requests
+    ///                       are passed to
     AuthSrv(isc::xfr::AbstractXfroutClient& xfrout_client,
             isc::util::io::BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrv();

+ 11 - 14
src/bin/auth/query.cc

@@ -37,20 +37,6 @@ using namespace isc::dns;
 using namespace isc::datasrc;
 using namespace isc::dns::rdata;
 
-// This is a "constant" vector storing desired RR types for the additional
-// section.  The vector is filled first time it's used.
-namespace {
-const vector<RRType>&
-A_AND_AAAA() {
-    static vector<RRType> needed_types;
-    if (needed_types.empty()) {
-        needed_types.push_back(RRType::A());
-        needed_types.push_back(RRType::AAAA());
-    }
-    return (needed_types);
-}
-}
-
 namespace isc {
 namespace auth {
 
@@ -393,6 +379,17 @@ Query::process(datasrc::ClientList& client_list,
         response_->setRcode(Rcode::SERVFAIL());
         return;
     }
+
+    if (qtype == RRType::RRSIG()) {
+        // We will not serve RRSIGs directly. See #2226 and the
+        // following thread for discussion why:
+        // http://www.ietf.org/mail-archive/web/dnsext/current/msg07123.html
+        // RRSIGs go together with their covered RRset.
+        response_->setHeaderFlag(Message::HEADERFLAG_AA);
+        response_->setRcode(Rcode::REFUSED());
+        return;
+    }
+
     ZoneFinder& zfinder = *result.finder_;
 
     // We have authority for a zone that contain the query name (possibly

+ 12 - 0
src/bin/auth/query.h

@@ -286,6 +286,9 @@ public:
         answers_.reserve(RESERVE_RRSETS);
         authorities_.reserve(RESERVE_RRSETS);
         additionals_.reserve(RESERVE_RRSETS);
+
+        a_and_aaaa_.push_back(isc::dns::RRType::A());
+        a_and_aaaa_.push_back(isc::dns::RRType::AAAA());
     }
 
 
@@ -488,6 +491,15 @@ private:
     std::vector<isc::dns::ConstRRsetPtr> answers_;
     std::vector<isc::dns::ConstRRsetPtr> authorities_;
     std::vector<isc::dns::ConstRRsetPtr> additionals_;
+
+private:
+    /// \brief Returns a reference to a pre-initialized vector (see the
+    /// \c Query constructor).
+    const std::vector<isc::dns::RRType>& A_AND_AAAA() const {
+        return (a_and_aaaa_);
+    }
+
+    std::vector<isc::dns::RRType> a_and_aaaa_;
 };
 
 }

+ 12 - 13
src/bin/auth/tests/auth_srv_unittest.cc

@@ -44,6 +44,7 @@
 
 #include <util/unittests/mock_socketsession.h>
 #include <dns/tests/unittest_util.h>
+#include <util/unittests/wiredata.h>
 #include <testutils/dnsmessage_test.h>
 #include <testutils/srv_test.h>
 #include <testutils/mockups.h>
@@ -82,8 +83,9 @@ using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
 using namespace isc::auth::unittest;
 using isc::UnitTestUtil;
-using boost::scoped_ptr;
 using isc::auth::statistics::Counters;
+using isc::util::unittests::matchWireData;
+using boost::scoped_ptr;
 
 namespace {
 const char* const CONFIG_TESTDB =
@@ -1072,10 +1074,9 @@ TEST_F(AuthSrvTest, builtInQueryViaDNSServer) {
                                      response_message, response_obuffer);
 
     createBuiltinVersionResponse(default_qid, response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    matchWireData(&response_data[0], response_data.size(),
+                  response_obuffer->getData(),
+                  response_obuffer->getLength());
 
     // After it has been run, the message should be cleared
     EXPECT_EQ(0, parse_message->getRRCount(Message::SECTION_QUESTION));
@@ -1095,10 +1096,9 @@ TEST_F(AuthSrvTest, builtInQuery) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     createBuiltinVersionResponse(default_qid, response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    matchWireData(&response_data[0], response_data.size(),
+                  response_obuffer->getData(),
+                  response_obuffer->getLength());
     checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
 }
 
@@ -1114,10 +1114,9 @@ TEST_F(AuthSrvTest, iqueryViaDNSServer) {
 
     UnitTestUtil::readWireData("iquery_response_fromWire.wire",
                                response_data);
-    EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
-                        response_obuffer->getData(),
-                        response_obuffer->getLength(),
-                        &response_data[0], response_data.size());
+    matchWireData(&response_data[0], response_data.size(),
+                  response_obuffer->getData(),
+                  response_obuffer->getLength());
 }
 
 // Install a Sqlite3 data source with testing data.

+ 15 - 0
src/bin/auth/tests/query_unittest.cc

@@ -22,6 +22,7 @@
 #include <dns/message.h>
 #include <dns/master_loader.h>
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 #include <dns/nsec3hash.h>
 #include <dns/opcode.h>
 #include <dns/rcode.h>
@@ -245,6 +246,13 @@ public:
         isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
                   << name);
     }
+    virtual string calculate(const LabelSequence& ls) const {
+        assert(ls.isAbsolute());
+        // This is not very optimal, but it's only going to be used in
+        // tests.
+        const Name name(ls.toText());
+        return (calculate(name));
+    }
     virtual bool match(const rdata::generic::NSEC3PARAM&) const {
         return (true);
     }
@@ -1215,6 +1223,13 @@ TEST_P(QueryTest, exactMatchMultipleQueries) {
                   www_a_txt, zone_ns_txt, ns_addrs_txt);
 }
 
+TEST_P(QueryTest, qtypeIsRRSIG) {
+    // Directly querying for RRSIGs should result in rcode=REFUSED.
+    EXPECT_NO_THROW(query.process(*list_, qname, RRType::RRSIG(), response));
+    responseCheck(response, Rcode::REFUSED(), AA_FLAG, 0, 0, 0,
+                  "", "", "");
+}
+
 TEST_P(QueryTest, exactMatchIgnoreSIG) {
     // Check that we do not include the RRSIG when not requested even when
     // we receive it from the data source.

+ 3 - 2
src/bin/bindctl/bindcmd.py

@@ -446,8 +446,9 @@ WARNING: The Python readline module isn't available, so some command line
                     raise CmdMissParamSyntaxError(cmd.module, cmd.command, name)
                 param_nr += 1
 
-        # Convert parameter value according parameter spec file.
-        # Ignore check for commands belongs to module 'config' or 'execute
+        # Convert parameter value according to parameter spec
+        # file. Ignore check for commands belonging to module 'config'
+        # or 'execute'.
         if cmd.module != CONFIG_MODULE_NAME and\
            cmd.module != command_sets.EXECUTE_MODULE_NAME:
             for param_name in cmd.params:

+ 1 - 1
src/bin/d2/dns_client.cc

@@ -142,7 +142,7 @@ DNSClientImpl::operator()(asiodns::IOFetch::Result result) {
         try {
             response_->fromWire(response_buf);
 
-        } catch (const Exception& ex) {
+        } catch (const isc::Exception& ex) {
             status = DNSClient::INVALID_RESPONSE;
             LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL,
                       DHCP_DDNS_INVALID_RESPONSE).arg(ex.what());

+ 5 - 5
src/bin/d2/dns_client.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -143,8 +143,8 @@ public:
     /// @param ns_addr DNS server address.
     /// @param ns_port DNS server port.
     /// @param update A DNS Update message to be sent to the server.
-    /// @param wait A timeout (in seconds) for the response. If a response is
-    /// not received within the timeout, exchange is interrupted. This value
+    /// @param wait A timeout (in milliseconds) for the response. If a response
+    /// is not received within the timeout, exchange is interrupted. This value
     /// must not exceed maximal value for 'int' data type.
     /// @param tsig_key An @c isc::dns::TSIGKey object representing TSIG
     /// context which will be used to render the DNS Update message.
@@ -173,8 +173,8 @@ public:
     /// @param ns_addr DNS server address.
     /// @param ns_port DNS server port.
     /// @param update A DNS Update message to be sent to the server.
-    /// @param wait A timeout (in seconds) for the response. If a response is
-    /// not received within the timeout, exchange is interrupted. This value
+    /// @param wait A timeout (in milliseconds) for the response. If a response
+    /// is not received within the timeout, exchange is interrupted. This value
     /// must not exceed maximal value for 'int' data type.
     void doUpdate(asiolink::IOService& io_service,
                   const asiolink::IOAddress& ns_addr,

+ 15 - 6
src/bin/d2/nc_add.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// 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
@@ -588,10 +588,13 @@ NameAddTransaction::buildAddFwdAddressRequest() {
 
     // Next build the Update Section.
 
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
     // Create the FQDN/IP 'add' RR and add it to the to update section.
     // Based on RFC 2136, section 2.5.1
     dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::IN(),
-                         getAddressRRType(), dns::RRTTL(0)));
+                         getAddressRRType(), lease_ttl));
 
     addLeaseAddressRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
@@ -599,7 +602,7 @@ NameAddTransaction::buildAddFwdAddressRequest() {
     // Now create the FQDN/DHCID 'add' RR and add it to update section.
     // Based on RFC 2136, section 2.5.1
     update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
-                                dns::RRType::DHCID(), dns::RRTTL(0)));
+                                dns::RRType::DHCID(), lease_ttl));
     addDhcidRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
@@ -635,6 +638,9 @@ NameAddTransaction::buildReplaceFwdAddressRequest() {
 
     // Next build the Update Section.
 
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
     // Create the FQDN/IP 'delete' RR and add it to the update section.
     // Based on RFC 2136, section 2.5.2
     dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
@@ -644,7 +650,7 @@ NameAddTransaction::buildReplaceFwdAddressRequest() {
     // Create the FQDN/IP 'add' RR and add it to the update section.
     // Based on RFC 2136, section 2.5.1
     update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
-                                getAddressRRType(), dns::RRTTL(0)));
+                                getAddressRRType(), lease_ttl));
     addLeaseAddressRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
@@ -661,6 +667,9 @@ NameAddTransaction::buildReplaceRevPtrsRequest() {
     std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
     dns::Name rev_ip(rev_addr);
 
+    // Create the TTL based on lease length.
+    dns::RRTTL lease_ttl(getNcr()->getLeaseLength());
+
     // Content on this request is based on RFC 4703, section 5.4
     // Reverse replacement has no prerequisites so straight on to
     // building the Update section.
@@ -678,14 +687,14 @@ NameAddTransaction::buildReplaceRevPtrsRequest() {
     // Create the FQDN/IP PTR 'add' RR, add the FQDN as the PTR Rdata
     // then add it to update section.
     update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
-                                dns::RRType::PTR(), dns::RRTTL(0)));
+                                dns::RRType::PTR(), lease_ttl));
     addPtrRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
     // Create the FQDN/IP PTR 'add' RR, add the DHCID Rdata
     // then add it to update section.
     update.reset(new dns::RRset(rev_ip, dns::RRClass::IN(),
-                                dns::RRType::DHCID(), dns::RRTTL(0)));
+                                dns::RRType::DHCID(), lease_ttl));
     addDhcidRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 

+ 14 - 4
src/bin/d2/tests/dns_client_unittests.cc

@@ -263,8 +263,16 @@ public:
         ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
         ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
 
-        // Set the response wait time to 0 so as our test is not hanging. This
-        // should cause instant timeout.
+        /// @todo The timeout value could be set to 0 to trigger timeout
+        /// instantly. However, it may lead to situations that the message sent
+        /// in one test will not be dropped by the kernel by the time, the next
+        /// test starts. This will lead to intermittent unit test errors as
+        /// described in the ticket http://bind10.isc.org/ticket/3265.
+        /// Increasing the timeout to a non-zero value mitigates this problem.
+        /// The proper way to solve this problem is to receive the packet
+        /// on our own and drop it. Such a fix will need to be applied not only
+        /// to this test but also for other tests that rely on arbitrary timeout
+        /// values.
         const int timeout = 500;
         // The doUpdate() function starts asynchronous message exchange with DNS
         // server. When message exchange is done or timeout occurs, the
@@ -324,8 +332,10 @@ public:
                                                   corrupt_response));
 
         // The socket is now ready to receive the data. Let's post some request
-        // message then.
-        const int timeout = 5;
+        // message then. Set timeout to some reasonable value to make sure that
+        // there is sufficient amount of time for the test to generate a
+        // response.
+        const int timeout = 500;
         expected_++;
         dns_client_->doUpdate(service_, IOAddress(TEST_ADDRESS), TEST_PORT,
                              message, timeout);

+ 22 - 7
src/bin/d2/tests/nc_test_utils.cc

@@ -16,6 +16,8 @@
 #include <dns/opcode.h>
 #include <dns/messagerenderer.h>
 #include <nc_test_utils.h>
+#include <asio.hpp>
+#include <asiolink/udp_endpoint.h>
 
 #include <gtest/gtest.h>
 
@@ -42,7 +44,9 @@ FauxServer::FauxServer(asiolink::IOService& io_service,
     server_socket_.reset(new asio::ip::udp::socket(io_service_.get_io_service(),
                                                    asio::ip::udp::v4()));
     server_socket_->set_option(asio::socket_base::reuse_address(true));
-    server_socket_->bind(asio::ip::udp::endpoint(address_.getAddress(), port_));
+
+    isc::asiolink::UDPEndpoint endpoint(address_, port_);
+    server_socket_->bind(endpoint.getASIOEndpoint());
 }
 
 FauxServer::FauxServer(asiolink::IOService& io_service,
@@ -53,7 +57,8 @@ FauxServer::FauxServer(asiolink::IOService& io_service,
     server_socket_.reset(new asio::ip::udp::socket(io_service_.get_io_service(),
                                                    asio::ip::udp::v4()));
     server_socket_->set_option(asio::socket_base::reuse_address(true));
-    server_socket_->bind(asio::ip::udp::endpoint(address_.getAddress(), port_));
+    isc::asiolink::UDPEndpoint endpoint(address_, port_);
+    server_socket_->bind(endpoint.getASIOEndpoint());
 }
 
 
@@ -422,15 +427,19 @@ void checkAddFwdAddressRequest(NameChangeTransaction& tran) {
     // Should be 2 RRs: 1 to add the FQDN/IP and one to add the DHCID RR
     checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
 
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
     // First, Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 0));
-    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, 0, ncr);
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Now, verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 1));
-    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(), 0, ncr);
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+            ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
     checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
@@ -478,6 +487,9 @@ void checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
     // adds the new one.
     checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 2);
 
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
     // Verify the FQDN delete RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 0));
@@ -486,7 +498,7 @@ void checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
     // Verify the FQDN/IP add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 1));
-    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, 0, ncr);
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), exp_ip_rr_type, ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
     checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);
@@ -515,6 +527,9 @@ void checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
     // Verify there are no RRs in the PREREQUISITE Section.
     checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 0);
 
+    // Fetch ttl.
+    uint32_t ttl = ncr->getLeaseLength();
+
     // Verify the UPDATE Section.
     // It should contain 4 RRs:
     // 1. A delete all PTR RRs for the given IP
@@ -540,13 +555,13 @@ void checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 2));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
-            0, ncr);
+            ttl, ncr);
 
     // Verify the DHCID add RR.
     ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
                                                   SECTION_UPDATE, 3));
     checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::DHCID(),
-            0, ncr);
+            ttl, ncr);
 
     // Verify there are no RRs in the ADDITIONAL Section.
     checkRRCount(request, D2UpdateMessage::SECTION_ADDITIONAL, 0);

+ 1 - 1
src/bin/d2/tests/nc_trans_unittests.cc

@@ -326,8 +326,8 @@ public:
         ASSERT_EQ(NameChangeTransaction::NOP_EVT,
                   name_change->getNextEvent());
 
-        int cnt = 0;
         while (name_change->getNextEvent() == NameChangeTransaction::NOP_EVT) {
+            int cnt = 0;
             ASSERT_NO_THROW(cnt = runTimedIO(run_time));
             if (cnt == 0) {
                 FAIL() << "IO Service stopped unexpectedly";

+ 1 - 4
src/bin/ddns/ddns.py.in

@@ -495,10 +495,7 @@ class DDNSServer:
             return False
         msg = update_session.get_message()
         self.__response_renderer.clear()
-        if tsig_ctx is not None:
-            msg.to_wire(self.__response_renderer, tsig_ctx)
-        else:
-            msg.to_wire(self.__response_renderer)
+        msg.to_wire(self.__response_renderer, tsig_ctx)
 
         ret = self.__send_response(sock, self.__response_renderer.get_data(),
                                    remote_addr)

+ 1 - 4
src/bin/ddns/tests/ddns_test.py

@@ -900,10 +900,7 @@ def create_msg(opcode=Opcode.UPDATE, zones=[TEST_ZONE_RECORD], prereq=[],
         msg.add_rrset(SECTION_PREREQUISITE, p)
 
     renderer = MessageRenderer()
-    if tsigctx is not None:
-        msg.to_wire(renderer, tsigctx)
-    else:
-        msg.to_wire(renderer)
+    msg.to_wire(renderer, tsigctx)
 
     # re-read the created data in the parse mode
     msg.clear(Message.PARSE)

+ 18 - 2
src/bin/dhcp4/config_parser.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -164,7 +164,7 @@ public:
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
     Subnet4ConfigParser(const std::string&)
-        :SubnetConfigParser("", globalContext()) {
+        :SubnetConfigParser("", globalContext(), IOAddress("0.0.0.0")) {
     }
 
     /// @brief Adds the created subnet to a server's configuration.
@@ -178,6 +178,11 @@ public:
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
 
+            // Set relay information if it was parsed
+            if (relay_info_) {
+                sub4ptr->setRelayInfo(*relay_info_);
+            }
+
             isc::dhcp::CfgMgr::instance().addSubnet4(sub4ptr);
         }
     }
@@ -200,10 +205,13 @@ protected:
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
                    (config_id.compare("next-server") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool4Parser(config_id, pools_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
         } else if (config_id.compare("option-data") == 0) {
            parser = new OptionDataListParser(config_id, options_,
                                              global_context_,
@@ -292,6 +300,14 @@ protected:
         } catch (const DhcpConfigError&) {
             // Don't care. next_server is optional. We can live without it
         }
+
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet4->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
     }
 };
 

+ 21 - 6
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -114,6 +114,16 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
         return (answer);
     }
 
+    // Server will start DDNS communications if its enabled.
+    try {
+        server_->startD2();
+    } catch (const std::exception& ex) {
+        std::ostringstream err;
+        err << "error starting DHCP_DDNS client "
+                " after server reconfiguration: " << ex.what();
+        return (isc::config::createAnswer(1, err.str()));
+    }
+
     // Configuration may change active interfaces. Therefore, we have to reopen
     // sockets according to new configuration. This operation is not exception
     // safe and we really don't want to emit exceptions to the callback caller.
@@ -211,11 +221,15 @@ void ControlledDhcpv4Srv::establishSession() {
 
     try {
         configureDhcp4Server(*this, config_session_->getFullConfig());
+
+        // Server will start DDNS communications if its enabled.
+        server_->startD2();
+
         // Configuration may disable or enable interfaces so we have to
         // reopen sockets according to new configuration.
         openActiveSockets(getPort(), useBroadcast());
 
-    } catch (const DhcpConfigError& ex) {
+    } catch (const std::exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
 
     }
@@ -226,7 +240,7 @@ void ControlledDhcpv4Srv::establishSession() {
     int ctrl_socket = cc_session_->getSocketDesc();
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
               .arg(ctrl_socket);
-    IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
+    IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
 }
 
 void ControlledDhcpv4Srv::disconnectSession() {
@@ -235,13 +249,14 @@ void ControlledDhcpv4Srv::disconnectSession() {
         config_session_ = NULL;
     }
     if (cc_session_) {
+
+        int ctrl_socket = cc_session_->getSocketDesc();
         cc_session_->disconnect();
+
+        IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
         delete cc_session_;
         cc_session_ = NULL;
     }
-
-    // deregister session socket
-    IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL);
 }
 
 ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)

+ 2 - 1
src/bin/dhcp4/ctrl_dhcp4_srv.h

@@ -107,7 +107,8 @@ protected:
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
-    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
+    /// be later accessed with
+    /// \ref isc::config::ConfigData::getFullConfig().
     ///
     /// @param new_config new configuration.
     ///

+ 7 - 1
src/bin/dhcp4/dhcp4.dox

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -185,6 +185,12 @@ library. See ticket #3275. The class specific behavior is:
 
 Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
 
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt4::classes_
+being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet4().
+Currently this capability is usable, but the number of scenarios it supports is
+limited.
+
 @section dhcpv4Other Other DHCPv4 topics
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

+ 22 - 7
src/bin/dhcp4/dhcp4.spec

@@ -250,6 +250,28 @@
                     }
                 },
 
+                { "item_name": "client-class",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": "",
+                  "item_description" : "Restricts access to this subnet to specified client class (if defined)"
+                },
+
+                { "item_name": "relay",
+                  "item_type": "map",
+                  "item_optional": false,
+                  "item_default": {},
+                  "item_description" : "Structure holding relay information.",
+                  "map_item_spec": [
+                      {
+                          "item_name": "ip-address",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": "0.0.0.0",
+                          "item_description" : "IPv4 address of the relay (defaults to 0.0.0.0 if not specified)."
+                      }
+                   ]
+                },
                 { "item_name": "option-data",
                   "item_type": "list",
                   "item_optional": false,
@@ -336,13 +358,6 @@
                 "item_description" : "Format of the update request packet"
             },
             {
-                "item_name": "remove-on-renew",
-                "item_type": "boolean",
-                "item_optional": true,
-                "item_default": false,
-                "item_description": "Enable requesting a DNS Remove, before a DNS Update on renewals"
-            },
-            {
 
                 "item_name": "always-include-fqdn",
                 "item_type": "boolean",

+ 47 - 23
src/bin/dhcp4/dhcp4_messages.mes

@@ -20,11 +20,11 @@ to receive DHCPv4 traffic. IPv4 socket on this interface will be opened once
 Interface Manager starts up procedure of opening sockets.
 
 % DHCP4_CCSESSION_STARTED control channel session started on socket %1
-A debug message issued during startup after the IPv4 DHCP server has
+A debug message issued during startup after the DHCPv4 server has
 successfully established a session with the BIND 10 control channel.
 
 % DHCP4_CCSESSION_STARTING starting control channel session, specfile: %1
-This debug message is issued just before the IPv4 DHCP server attempts
+This debug message is issued just before the DHCPv4 server attempts
 to establish a session with the BIND 10 control channel.
 
 % DHCP4_CLIENT_NAME_PROC_FAIL failed to process the fqdn or hostname sent by a client: %1
@@ -42,7 +42,7 @@ class or classes. This is a norma
 
 % DHCP4_COMMAND_RECEIVED received command %1, arguments: %2
 A debug message listing the command (and possible arguments) received
-from the BIND 10 control system by the IPv4 DHCP server.
+from the BIND 10 control system by the DHCPv4 server.
 
 % DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
 This is an informational message announcing the successful processing of a
@@ -70,7 +70,7 @@ configuration. That happens at start up and also when a server configuration
 change is committed by the administrator.
 
 % DHCP4_CONFIG_UPDATE updated configuration received: %1
-A debug message indicating that the IPv4 DHCP server has received an
+A debug message indicating that the DHCPv4 server has received an
 updated configuration from the BIND 10 configuration system.
 
 % DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
@@ -135,6 +135,18 @@ A "libreload" command was issued to reload the hooks libraries but for
 some reason the reload failed.  Other error messages issued from the
 hooks framework will indicate the nature of the problem.
 
+% DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE received message (transaction id %1) has unrecognized type %2 in option 53
+This debug message indicates that the message type carried in DHCPv4 option
+53 is unrecognized by the server. The valid message types are listed
+on the IANA website: http://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53.
+The message will not be processed by the server.
+
+% DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE received message (transaction id %1), having type %2 is not supported
+This debug message indicates that the message type carried in DHCPv4 option
+53 is valid but the message will not be processed by the server. This includes
+messages being normally sent by the server to the client, such as Offer, ACK,
+NAK etc.
+
 % DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
 This debug message indicates that the server successfully advertised
 a lease. It is up to the client to choose one server out of othe advertised
@@ -175,12 +187,19 @@ This warning message is issued when current server configuration specifies
 no interfaces that server should listen on, or specified interfaces are not
 configured to receive the traffic.
 
-% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
+% DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT no suitable subnet configured for a direct client sending packet with transaction id %1, on interface %2, received message is dropped
+This info messsage is logged when received a message from a directly connected
+client but there is no suitable subnet configured for the interface on
+which this message has been received. The IPv4 address assigned on this
+interface must belong to one of the configured subnets. Otherwise
+received message is dropped.
+
+% DHCP4_NOT_RUNNING DHCPv4 server is not running
 A warning message is issued when an attempt is made to shut down the
-IPv4 DHCP server but it is not running.
+DHCPv4 server but it is not running.
 
 % DHCP4_OPEN_SOCKET opening sockets on port %1
-A debug message issued during startup, this indicates that the IPv4 DHCP
+A debug message issued during startup, this indicates that the DHCPv4
 server is about to open sockets on the specified port.
 
 % DHCP4_PACKET_NOT_FOR_US received DHCPv4 message (transid=%1, iface=%2) dropped because it contains foreign server identifier
@@ -195,7 +214,7 @@ A warning message issued when IfaceMgr fails to open and bind a socket. The reas
 for the failure is appended as an argument of the log message.
 
 % DHCP4_PACKET_PARSE_FAIL failed to parse incoming packet: %1
-The IPv4 DHCP server has received a packet that it is unable to
+The DHCPv4 server has received a packet that it is unable to
 interpret. The reason why the packet is invalid is included in the message.
 
 % DHCP4_PACKET_PROCESS_FAIL failed to process packet received from %1: %2
@@ -214,35 +233,35 @@ may well be a valid DHCP packet, just a type not expected by the server
 (e.g. it will report a received OFFER packet as UNKNOWN).
 
 % DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
-The IPv4 DHCP server tried to receive a packet but an error
+The DHCPv4 server tried to receive a packet but an error
 occurred during this attempt. The reason for the error is included in
 the message.
 
 % DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1
-This error is output if the IPv4 DHCP server fails to send an assembled
+This error is output if the DHCPv4 server fails to send an assembled
 DHCP message to a client. The reason for the error is included in the
 message.
 
 % DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
-On receipt of message containing details to a change of the IPv4 DHCP
+On receipt of message containing details to a change of the DHCPv4
 server configuration, a set of parsers were successfully created, but one
 of them failed to commit its changes due to a low-level system exception
 being raised.  Additional messages may be output indicating the reason.
 
 % DHCP4_PARSER_COMMIT_FAIL parser failed to commit changes: %1
-On receipt of message containing details to a change of the IPv4 DHCP
+On receipt of message containing details to a change of the DHCPv4
 server configuration, a set of parsers were successfully created, but
 one of them failed to commit its changes.  The reason for the failure
 is given in the message.
 
 % DHCP4_PARSER_CREATED created parser for configuration element %1
-A debug message output during a configuration update of the IPv4 DHCP
+A debug message output during a configuration update of the DHCPv4
 server, notifying that the parser for the specified configuration element
 has been successfully created.
 
 % DHCP4_PARSER_EXCEPTION failed to create or run parser for configuration element %1
 On receipt of message containing details to a change of its configuration,
-the IPv4 DHCP server failed to create a parser to decode the contents of
+the DHCPv4 server failed to create a parser to decode the contents of
 the named configuration element, or the creation succeeded but the parsing
 actions and committal of changes failed.  The message has been output in
 response to a non-BIND 10 exception being raised.  Additional messages
@@ -250,7 +269,7 @@ may give further information.
 
 % DHCP4_PARSER_FAIL failed to create or run parser for configuration element %1: %2
 On receipt of message containing details to a change of its configuration,
-the IPv4 DHCP server failed to create a parser to decode the contents
+the DHCPv4 server failed to create a parser to decode the contents
 of the named configuration element, or the creation succeeded but the
 parsing actions and committal of changes failed.  The reason for the
 failure is given in the message.
@@ -305,40 +324,40 @@ both clones use the same client-id.
 A debug message listing the data returned to the client.
 
 % DHCP4_SERVER_FAILED server failed: %1
-The IPv4 DHCP server has encountered a fatal error and is terminating.
+The DHCPv4 server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.
 
 % DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
 The server has failed to establish communication with the rest of BIND
 10 and is running in stand-alone mode.  (This behavior will change once
-the IPv4 DHCP server is properly integrated with the rest of BIND 10.)
+the DHCPv4 server is properly integrated with the rest of BIND 10.)
 
 % DHCP4_SHUTDOWN server shutdown
-The IPv4 DHCP server has terminated normally.
+The DHCPv4 server has terminated normally.
 
 % DHCP4_SHUTDOWN_REQUEST shutdown of server requested
-This debug message indicates that a shutdown of the IPv4 server has
+This debug message indicates that a shutdown of the DHCPv4 server has
 been requested via a call to the 'shutdown' method of the core Dhcpv4Srv
 object.
 
 % DHCP4_SRV_CONSTRUCT_ERROR error creating Dhcpv4Srv object, reason: %1
 This error message indicates that during startup, the construction of a
-core component within the IPv4 DHCP server (the Dhcpv4 server object)
+core component within the DHCPv4 server (the Dhcpv4 server object)
 has failed.  As a result, the server will exit.  The reason for the
 failure is given within the message.
 
 % DHCP4_STANDALONE skipping message queue, running standalone
-This is a debug message indicating that the IPv4 server is running in
+This is a debug message indicating that the DHCPv4 server is running in
 standalone mode, not connected to the message queue.  Standalone mode
 is only useful during program development, and should not be used in a
 production environment.
 
 % DHCP4_STARTING server starting
-This informational message indicates that the IPv4 DHCP server has
+This informational message indicates that the DHCPv4 server has
 processed any command-line switches and is starting.
 
 % DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
-This is a debug message issued during the IPv4 DHCP server startup.
+This is a debug message issued during the DHCPv4 server startup.
 It lists some information about the parameters with which the server
 is running.
 
@@ -351,3 +370,8 @@ steps in the processing of incoming client message.
 This warning message is output when a packet was received from a subnet
 for which the DHCPv4 server has not been configured. The most probable
 cause is a misconfiguration of the server.
+
+% DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error: %1,  ncr: %2
+This error message indicates that DHCP4 server attempted to send a DDNS
+update reqeust to the DHCP-DDNS server.  This is most likely a configuration or
+networking error.

+ 222 - 198
src/bin/dhcp4/dhcp4_srv.cc

@@ -80,36 +80,6 @@ Dhcp4Hooks Hooks;
 namespace isc {
 namespace dhcp {
 
-namespace {
-
-// @todo The following constants describe server's behavior with respect to the
-// DHCPv4 Client FQDN Option sent by a client. They will be removed
-// when DDNS parameters for DHCPv4 are implemented with the ticket #3033.
-
-// @todo Additional configuration parameter which we may consider is the one
-// that controls whether the DHCP server sends the removal NameChangeRequest
-// if it discovers that the entry for the particular client exists or that
-// it always updates the DNS.
-
-// Should server always include the FQDN option in its response, regardless
-// if it has been requested in Parameter Request List Option (Disabled).
-const bool FQDN_ALWAYS_INCLUDE = false;
-// Enable A RR update delegation to the client (Disabled).
-const bool FQDN_ALLOW_CLIENT_UPDATE = false;
-// Globally enable updates (Enabled).
-const bool FQDN_ENABLE_UPDATE = true;
-// Do update, even if client requested no updates with N flag (Disabled).
-const bool FQDN_OVERRIDE_NO_UPDATE = false;
-// Server performs an update when client requested delegation (Enabled).
-const bool FQDN_OVERRIDE_CLIENT_UPDATE = true;
-// The fully qualified domain-name suffix if partial name provided by
-// a client.
-const char* FQDN_PARTIAL_SUFFIX = "example.com";
-// Should server replace the domain-name supplied by the client (Disabled).
-const bool FQDN_REPLACE_CLIENT_NAME = false;
-
-}
-
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
                      const bool direct_response_desired)
 : shutdown_(true), alloc_engine_(), port_(port),
@@ -260,26 +230,15 @@ Dhcpv4Srv::run() {
             }
         }
 
-        // Check if the DHCPv4 packet has been sent to us or to someone else.
-        // If it hasn't been sent to us, drop it!
-        if (!acceptServerId(query)) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
-                .arg(query->getTransid())
-                .arg(query->getIface());
-            continue;
-        }
-
-        // When receiving a packet without message type option, getType() will
-        // throw. Let's set type to -1 as default error indicator.
-        int type = -1;
-        try {
-            type = query->getType();
-        } catch (...) {
-            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
-                .arg(query->getIface());
+        // Check whether the message should be further processed or discarded.
+        // There is no need to log anything here. This function logs by itself.
+        if (!accept(query)) {
             continue;
         }
 
+        // We have sanity checked (in accept() that the Message Type option
+        // exists, so we can safely get it here.
+        int type = query->getType();
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_RECEIVED)
             .arg(serverReceivedPacketName(type))
             .arg(type)
@@ -461,9 +420,6 @@ Dhcpv4Srv::run() {
             LOG_ERROR(dhcp4_logger, DHCP4_PACKET_SEND_FAIL)
                 .arg(e.what());
         }
-
-        // Send NameChangeRequests to the b10-dhcp_ddns module.
-        sendNameChangeRequests();
     }
 
     return (true);
@@ -596,8 +552,8 @@ Dhcpv4Srv::appendServerID(const Pkt4Ptr& response) {
     // The source address for the outbound message should have been set already.
     // This is the address that to the best of the server's knowledge will be
     // available from the client.
-    // @todo: perhaps we should consider some more sophisticated server id
-    // generation, but for the current use cases, it should be ok.
+    /// @todo: perhaps we should consider some more sophisticated server id
+    /// generation, but for the current use cases, it should be ok.
     response->addOption(OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
                                                      response->getLocalAddr()))
                         );
@@ -666,8 +622,8 @@ Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer
     uint32_t vendor_id = vendor_req->getVendorId();
 
     // Let's try to get ORO within that vendor-option
-    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
-    /// may have different policies.
+    /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
+    /// vendors may have different policies.
     OptionUint8ArrayPtr oro =
         boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
 
@@ -773,68 +729,19 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
     // response to a client.
     Option4ClientFqdnPtr fqdn_resp(new Option4ClientFqdn(*fqdn));
 
-    // RFC4702, section 4 - set 'NOS' flags to 0.
-    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, 0);
-    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, 0);
-    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, 0);
-
-    // Conditions when N flag has to be set to indicate that server will not
-    // perform DNS updates:
-    // 1. Updates are globally disabled,
-    // 2. Client requested no update and server respects it,
-    // 3. Client requested that the forward DNS update is delegated to the
-    //    client but server neither respects requests for forward update
-    //    delegation nor it is configured to send update on its own when
-    //    client requested delegation.
-    if (!FQDN_ENABLE_UPDATE ||
-        (fqdn->getFlag(Option4ClientFqdn::FLAG_N) &&
-         !FQDN_OVERRIDE_NO_UPDATE) ||
-        (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
-         !FQDN_ALLOW_CLIENT_UPDATE && !FQDN_OVERRIDE_CLIENT_UPDATE)) {
-        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_N, true);
-
-    // Conditions when S flag is set to indicate that server will perform DNS
-    // update on its own:
-    // 1. Client requested that server performs DNS update and DNS updates are
-    //    globally enabled.
-    // 2. Client requested that server delegates forward update to the client
-    //    but server doesn't respect requests for delegation and it is
-    // configured to perform an update on its own when client requested the
-    // delegation.
-    } else  if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) ||
-                (!fqdn->getFlag(Option4ClientFqdn::FLAG_S) &&
-                 !FQDN_ALLOW_CLIENT_UPDATE && FQDN_OVERRIDE_CLIENT_UPDATE)) {
-        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_S, true);
-    }
-
-    // Server MUST set the O flag if it has overriden the client's setting
-    // of S flag.
-    if (fqdn->getFlag(Option4ClientFqdn::FLAG_S) !=
-        fqdn_resp->getFlag(Option4ClientFqdn::FLAG_S)) {
-        fqdn_resp->setFlag(Option4ClientFqdn::FLAG_O, true);
-    }
-
-    // If client suppled partial or empty domain-name, server should generate
-    // one.
-    if (fqdn->getDomainNameType() == Option4ClientFqdn::PARTIAL) {
-        std::ostringstream name;
-        if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
-            fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL);
+    // Set the server S, N, and O flags based on client's flags and
+    // current configuration.
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    d2_mgr.adjustFqdnFlags<Option4ClientFqdn>(*fqdn, *fqdn_resp);
 
-        } else {
-            name << fqdn->getDomainName();
-            name << "." << FQDN_PARTIAL_SUFFIX;
-            fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL);
+    // Carry over the client's E flag.
+    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
+                       fqdn->getFlag(Option4ClientFqdn::FLAG_E));
 
-        }
 
-    // Server may be configured to replace a name supplied by a client, even if
-    // client supplied fully qualified domain-name. The empty domain-name is
-    // is set to indicate that the name must be generated when the new lease
-    // is acquired.
-    } else if(FQDN_REPLACE_CLIENT_NAME) {
-        fqdn_resp->setDomainName("", Option4ClientFqdn::PARTIAL);
-    }
+    // Adjust the domain name based on domain name value and type sent by the
+    // client and current configuration.
+    d2_mgr.adjustDomainName<Option4ClientFqdn>(*fqdn, *fqdn_resp);
 
     // Add FQDN option to the response message. Note that, there may be some
     // cases when server may choose not to include the FQDN option in a
@@ -854,15 +761,20 @@ Dhcpv4Srv::processClientFqdnOption(const Option4ClientFqdnPtr& fqdn,
 void
 Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
                                  Pkt4Ptr& answer) {
+    // Fetch D2 configuration.
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+
     // Do nothing if the DNS updates are disabled.
-    if (!FQDN_ENABLE_UPDATE) {
+    if (!d2_mgr.ddnsEnabled()) {
         return;
     }
 
     std::string hostname = isc::util::str::trim(opt_hostname->readString());
     unsigned int label_count = OptionDataTypeUtil::getLabelCount(hostname);
     // The hostname option sent by the client should be at least 1 octet long.
-    // If it isn't we ignore this option.
+    // If it isn't we ignore this option. (Per RFC 2131, section 3.14)
+    /// @todo It would be more liberal to accept this and let it fall into
+    /// the case  of replace or less than two below.
     if (label_count == 0) {
         LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_EMPTY_HOSTNAME);
         return;
@@ -880,21 +792,20 @@ Dhcpv4Srv::processHostnameOption(const OptionCustomPtr& opt_hostname,
     // By checking the number of labels present in the hostname we may infer
     // whether client has sent the fully qualified or unqualified hostname.
 
-    // @todo We may want to reconsider whether it is appropriate for the
-    // client to send a root domain name as a Hostname. There are
-    // also extensions to the auto generation of the client's name,
-    // e.g. conversion to the puny code which may be considered at some point.
-    // For now, we just remain liberal and expect that the DNS will handle
-    // conversion if needed and possible.
-    if (FQDN_REPLACE_CLIENT_NAME || (label_count < 2)) {
+    /// @todo We may want to reconsider whether it is appropriate for the
+    /// client to send a root domain name as a Hostname. There are
+    /// also extensions to the auto generation of the client's name,
+    /// e.g. conversion to the puny code which may be considered at some point.
+    /// For now, we just remain liberal and expect that the DNS will handle
+    /// conversion if needed and possible.
+    if ((d2_mgr.getD2ClientConfig()->getReplaceClientName()) ||
+        (label_count < 2)) {
         opt_hostname_resp->writeString("");
-    // If there are two labels, it means that the client has specified
-    // the unqualified name. We have to concatenate the unqalified name
-    // with the domain name.
     } else if (label_count == 2) {
-        std::ostringstream resp_hostname;
-        resp_hostname << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
-        opt_hostname_resp->writeString(resp_hostname.str());
+        // If there are two labels, it means that the client has specified
+        // the unqualified name. We have to concatenate the unqalified name
+        // with the domain name.
+        opt_hostname_resp->writeString(d2_mgr.qualifyName(hostname));
     }
 
     answer->addOption(opt_hostname_resp);
@@ -927,17 +838,14 @@ Dhcpv4Srv::createNameChangeRequests(const Lease4Ptr& lease,
             //   removal request for non-existent hostname.
             // - A server has performed reverse, forward or both updates.
             // - FQDN data between the new and old lease do not match.
-            if  ((lease->hostname_ != old_lease->hostname_) ||
-                 (lease->fqdn_fwd_ != old_lease->fqdn_fwd_) ||
-                 (lease->fqdn_rev_ != old_lease->fqdn_rev_)) {
+            if (!lease->hasIdenticalFqdn(*old_lease)) {
                 queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE,
                                        old_lease);
 
             // If FQDN data from both leases match, there is no need to update.
-            } else if ((lease->hostname_ == old_lease->hostname_) &&
-                       (lease->fqdn_fwd_ == old_lease->fqdn_fwd_) &&
-                       (lease->fqdn_rev_ == old_lease->fqdn_rev_)) {
+            } else if (lease->hasIdenticalFqdn(*old_lease)) {
                 return;
+
             }
 
         }
@@ -970,26 +878,23 @@ queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
             .arg(ex.what());
         return;
     }
+
     // Create NameChangeRequest
-    NameChangeRequest ncr(chg_type, lease->fqdn_fwd_, lease->fqdn_rev_,
-                          lease->hostname_, lease->addr_.toText(),
-                          dhcid, lease->cltt_ + lease->valid_lft_,
-                          lease->valid_lft_);
-    // And queue it.
+    NameChangeRequestPtr ncr(new NameChangeRequest(chg_type, lease->fqdn_fwd_,
+                                                   lease->fqdn_rev_,
+                                                   lease->hostname_,
+                                                   lease->addr_.toText(),
+                                                   dhcid,
+                                                   (lease->cltt_ +
+                                                    lease->valid_lft_),
+                                                   lease->valid_lft_));
+
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUEUE_NCR)
         .arg(chg_type == CHG_ADD ? "add" : "remove")
-        .arg(ncr.toText());
-    name_change_reqs_.push(ncr);
-}
+        .arg(ncr->toText());
 
-void
-Dhcpv4Srv::sendNameChangeRequests() {
-    while (!name_change_reqs_.empty()) {
-        // @todo Once next NameChangeRequest is picked from the queue
-        // we should send it to the b10-dhcp_ddns module. Currently we
-        // just drop it.
-        name_change_reqs_.pop();
-    }
+    // And pass it to the the manager.
+    CfgMgr::instance().getD2ClientMgr().sendRequest(ncr);
 }
 
 void
@@ -1006,8 +911,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         // thing this client can get is some global information (like DNS
         // servers).
 
-        // perhaps this should be logged on some higher level? This is most likely
-        // configuration bug.
+        // perhaps this should be logged on some higher level? This is most
+        // likely configuration bug.
         LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
             .arg(question->getRemoteAddr().toText())
             .arg(serverReceivedPacketName(question->getType()));
@@ -1020,7 +925,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // as siaddr has nothing to do with a lease, but otherwise we would have
     // to select subnet twice (performance hit) or update too many functions
     // at once.
-    // @todo: move subnet selection to a common code
+    /// @todo: move subnet selection to a common code
     answer->setSiaddr(subnet->getSiaddr());
 
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
@@ -1063,8 +968,8 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
             (answer->getOption(DHO_HOST_NAME));
         if (opt_hostname) {
             hostname = opt_hostname->readString();
-            // @todo It could be configurable what sort of updates the server
-            // is doing when Hostname option was sent.
+            /// @todo It could be configurable what sort of updates the
+            /// server is doing when Hostname option was sent.
             fqdn_fwd = true;
             fqdn_rev = true;
         }
@@ -1074,7 +979,7 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
-    // @todo pass the actual FQDN data.
+    /// @todo pass the actual FQDN data.
     Lease4Ptr old_lease;
     Lease4Ptr lease = alloc_engine_->allocateLease4(subnet, client_id, hwaddr,
                                                       hint, fqdn_fwd, fqdn_rev,
@@ -1099,14 +1004,9 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         // generating the entire hostname for the client. The example of the
         // client's name, generated from the IP address is: host-192-0-2-3.
         if ((fqdn || opt_hostname) && lease->hostname_.empty()) {
-            hostname = lease->addr_.toText();
-            // Replace dots with hyphens.
-            std::replace(hostname.begin(), hostname.end(), '.', '-');
-            ostringstream stream;
-            // The partial suffix will need to be replaced with the actual
-            // domain-name for the client when configuration is implemented.
-            stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
-            lease->hostname_ = stream.str();
+            lease->hostname_ = CfgMgr::instance()
+                               .getD2ClientMgr().generateFqdn(lease->addr_);
+
             // The operations below are rather safe, but we want to catch
             // any potential exceptions (e.g. invalid lease database backend
             // implementation) and log an error.
@@ -1138,22 +1038,18 @@ Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         // Subnet mask (type 1)
         answer->addOption(getNetmaskOption(subnet));
 
-        // @todo: send renew timer option (T1, option 58)
-        // @todo: send rebind timer option (T2, option 59)
+        /// @todo: send renew timer option (T1, option 58)
+        /// @todo: send rebind timer option (T2, option 59)
 
-        // @todo Currently the NameChangeRequests are always generated if
-        // real (not fake) allocation is being performed. Should we have
-        // control switch to enable/disable NameChangeRequest creation?
-        // Perhaps we need a way to detect whether the b10-dhcp-ddns module
-        // is up an running?
-        if (!fake_allocation) {
+        // Create NameChangeRequests if DDNS is enabled and this is a
+        // real allocation.
+        if (!fake_allocation && CfgMgr::instance().ddnsEnabled()) {
             try {
                 createNameChangeRequests(lease, old_lease);
             } catch (const Exception& ex) {
                 LOG_ERROR(dhcp4_logger, DHCP4_NCR_CREATION_FAILED)
                     .arg(ex.what());
             }
-
         }
 
     } else {
@@ -1194,8 +1090,8 @@ Dhcpv4Srv::adjustIfaceData(const Pkt4Ptr& query, const Pkt4Ptr& response) {
     // address for the response. Instead, we have to check what address our
     // socket is bound to and use it as a source address. This operation
     // may throw if for some reason the socket is closed.
-    // @todo Consider an optimization that we use local address from
-    // the query if this address is not broadcast.
+    /// @todo Consider an optimization that we use local address from
+    /// the query if this address is not broadcast.
     SocketInfo sock_info = IfaceMgr::instance().getSocket(*query);
     // Set local adddress, port and interface.
     response->setLocalAddr(sock_info.addr_);
@@ -1310,7 +1206,7 @@ Pkt4Ptr
 Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
 
     /// @todo Uncomment this (see ticket #3116)
-    // sanityCheck(request, MANDATORY);
+    /// sanityCheck(request, MANDATORY);
 
     Pkt4Ptr ack = Pkt4Ptr
         (new Pkt4(DHCPACK, request->getTransid()));
@@ -1352,7 +1248,7 @@ void
 Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 
     /// @todo Uncomment this (see ticket #3116)
-    // sanityCheck(release, MANDATORY);
+    /// sanityCheck(release, MANDATORY);
 
     // Try to find client-id
     ClientIdPtr client_id;
@@ -1377,7 +1273,7 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
         // Does the hardware address match? We don't want one client releasing
         // second client's leases.
         if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
-            // @todo: Print hwaddr from lease as part of ticket #2589
+            /// @todo: Print hwaddr from lease as part of ticket #2589
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
                 .arg(release->getCiaddr().toText())
                 .arg(client_id ? client_id->toText() : "(no client-id)")
@@ -1435,9 +1331,10 @@ Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
                     .arg(client_id ? client_id->toText() : "(no client-id)")
                     .arg(release->getHWAddr()->toText());
 
-                // Remove existing DNS entries for the lease, if any.
-                queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
-
+                if (CfgMgr::instance().ddnsEnabled()) {
+                    // Remove existing DNS entries for the lease, if any.
+                    queueNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, lease);
+                }
             } else {
                 // Release failed -
                 LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
@@ -1499,24 +1396,41 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
 }
 
 Subnet4Ptr
-Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) const {
 
     Subnet4Ptr subnet;
-    // Is this relayed message?
-    IOAddress relay = question->getGiaddr();
     static const IOAddress notset("0.0.0.0");
+    static const IOAddress bcast("255.255.255.255");
 
-    if (relay != notset) {
-        // Yes: Use relay address to select subnet
-        subnet = CfgMgr::instance().getSubnet4(relay);
+    // If a message is relayed, use the relay (giaddr) address to select subnet
+    // for the client. Note that this may result in exception if the value
+    // of hops does not correspond with the Giaddr. Such message is considered
+    // to be malformed anyway and the message will be dropped by the higher
+    // level functions.
+    if (question->isRelayed()) {
+        subnet = CfgMgr::instance().getSubnet4(question->getGiaddr(),
+                                               question->classes_);
+
+    // The message is not relayed so it is sent directly by a client. But
+    // the client may be renewing its lease and in such case it unicasts
+    // its message to the server. Note that the unicast Request bypasses
+    // relays and the client may be in a different network, so we can't
+    // use IP address on the local interface to get the subnet. Instead,
+    // we rely on the client's address to get the subnet.
+    } else if ((question->getLocalAddr() != bcast) &&
+               (question->getCiaddr() != notset)) {
+        subnet = CfgMgr::instance().getSubnet4(question->getCiaddr(),
+                                               question->classes_);
+
+    // The message has been received from a directly connected client
+    // and this client appears to have no address. The IPv4 address
+    // assigned to the interface on which this message has been received,
+    // will be used to determine the subnet suitable for the client.
     } else {
-
-        // No: Use client's address to select subnet
-        subnet = CfgMgr::instance().getSubnet4(question->getRemoteAddr());
+        subnet = CfgMgr::instance().getSubnet4(question->getIface(),
+                                               question->classes_);
     }
 
-    /// @todo Implement getSubnet4(interface-name)
-
     // Let's execute all callouts registered for subnet4_select
     if (HooksManager::calloutsPresent(hook_index_subnet4_select_)) {
         CalloutHandlePtr callout_handle = getCalloutHandle(question);
@@ -1551,7 +1465,93 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 }
 
 bool
-Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
+Dhcpv4Srv::accept(const Pkt4Ptr& query) const {
+    // Check if the message from directly connected client (if directly
+    // connected) should be dropped or processed.
+    if (!acceptDirectRequest(query)) {
+        LOG_INFO(dhcp4_logger, DHCP4_NO_SUBNET_FOR_DIRECT_CLIENT)
+            .arg(query->getTransid())
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // Check if the DHCPv4 packet has been sent to us or to someone else.
+    // If it hasn't been sent to us, drop it!
+    if (!acceptServerId(query)) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_NOT_FOR_US)
+            .arg(query->getTransid())
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // Check that the message type is accepted by the server. We rely on the
+    // function called to log a message if needed.
+    if (!acceptMessageType(query)) {
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::acceptDirectRequest(const Pkt4Ptr& pkt) const {
+    try {
+        if (pkt->isRelayed()) {
+            return (true);
+        }
+    } catch (const Exception& ex) {
+        return (false);
+    }
+    static const IOAddress bcast("255.255.255.255");
+    return ((pkt->getLocalAddr() != bcast || selectSubnet(pkt)));
+}
+
+bool
+Dhcpv4Srv::acceptMessageType(const Pkt4Ptr& query) const {
+    // When receiving a packet without message type option, getType() will
+    // throw.
+    int type;
+    try {
+        type = query->getType();
+
+    } catch (...) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PACKET_DROP_NO_TYPE)
+            .arg(query->getIface());
+        return (false);
+    }
+
+    // If we receive a message with a non-existing type, we are logging it.
+    if (type > DHCPLEASEQUERYDONE) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_UNRECOGNIZED_RCVD_PACKET_TYPE)
+            .arg(type)
+            .arg(query->getTransid());
+        return (false);
+    }
+
+    // Once we know that the message type is within a range of defined DHCPv4
+    // messages, we do a detailed check to make sure that the received message
+    // is targeted at server. Note that we could have received some Offer
+    // message broadcasted by the other server to a relay. Even though, the
+    // server would rather unicast its response to a relay, let's be on the
+    // safe side. Also, we want to drop other messages which we don't support.
+    // All these valid messages that we are not going to process are dropped
+    // silently.
+    if ((type != DHCPDISCOVER) && (type != DHCPREQUEST) &&
+        (type != DHCPRELEASE) && (type != DHCPDECLINE) &&
+        (type != DHCPINFORM)) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL,
+                  DHCP4_UNSUPPORTED_RCVD_PACKET_TYPE)
+            .arg(type)
+            .arg(query->getTransid());
+        return (false);
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
     // This function is meant to be called internally by the server class, so
     // we rely on the caller to sanity check the pointer and we don't check
     // it here.
@@ -1561,7 +1561,7 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) const {
     // Note that we don't check cases that server identifier is mandatory
     // but not present. This is meant to be sanity checked in other
     // functions.
-    OptionPtr option = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+    OptionPtr option = query->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     if (!option) {
         return (true);
     }
@@ -1678,8 +1678,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
     }
     // Let's reopen active sockets. openSockets4 will check internally whether
     // sockets are marked active or inactive.
-    // @todo Optimization: we should not reopen all sockets but rather select
-    // those that have been affected by the new configuration.
+    /// @todo Optimization: we should not reopen all sockets but rather select
+    /// those that have been affected by the new configuration.
     isc::dhcp::IfaceMgrErrorMsgCallback error_handler =
         boost::bind(&Dhcpv4Srv::ifaceMgrSocket4ErrorHandler, _1);
     if (!IfaceMgr::instance().openSockets4(port, use_bcast, error_handler)) {
@@ -1689,8 +1689,8 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
 
 size_t
 Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
-                          const std::string& option_space,
-                          isc::dhcp::OptionCollection& options) {
+                         const std::string& option_space,
+                         isc::dhcp::OptionCollection& options) {
     size_t offset = 0;
 
     OptionDefContainer option_defs;
@@ -1863,5 +1863,29 @@ bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp
     return (true);
 }
 
+void
+Dhcpv4Srv::startD2() {
+    D2ClientMgr& d2_mgr = CfgMgr::instance().getD2ClientMgr();
+    if (d2_mgr.ddnsEnabled()) {
+        // Updates are enabled, so lets start the sender, passing in
+        // our error handler.
+        // This may throw so wherever this is called needs to ready.
+        d2_mgr.startSender(boost::bind(&Dhcpv4Srv::d2ClientErrorHandler,
+                                       this, _1, _2));
+    }
+}
+
+void
+Dhcpv4Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    LOG_ERROR(dhcp4_logger, DHCP4_DDNS_REQUEST_SEND_FAILED).
+              arg(result).arg((ncr ? ncr->toText() : " NULL "));
+    // We cannot communicate with b10-dhcp-ddns, suspend futher updates.
+    /// @todo We may wish to revisit this, but for now we will simpy turn
+    /// them off.
+    CfgMgr::instance().getD2ClientMgr().suspendUpdates();
+}
+
 }   // namespace dhcp
 }   // namespace isc

+ 108 - 22
src/bin/dhcp4/dhcp4_srv.h

@@ -21,6 +21,7 @@
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_custom.h>
 #include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/d2_client_mgr.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/alloc_engine.h>
 #include <hooks/callout_handle.h>
@@ -165,8 +166,109 @@ public:
     /// @param use_bcast should broadcast flags be set on the sockets.
     static void openActiveSockets(const uint16_t port, const bool use_bcast);
 
+    /// @brief Starts DHCP_DDNS client IO if DDNS updates are enabled.
+    ///
+    /// If updates are enabled, it Instructs the D2ClientMgr singleton to
+    /// enter send mode.  If D2ClientMgr encounters errors it may throw
+    /// D2ClientErrors. This method does not catch exceptions.
+    void startD2();
+
+    /// @brief Implements the error handler for DHCP_DDNS IO errors
+    ///
+    /// Invoked when a NameChangeRequest send to b10-dhcp-ddns completes with
+    /// a failed status.  These are communications errors, not data related
+    /// failures.
+    ///
+    /// This method logs the failure and then suspends all further updates.
+    /// Updating can only be restored by reconfiguration or restarting the
+    /// server.  There is currently no retry logic so the first IO error that
+    /// occurs will suspend updates.
+    /// @todo We may wish to make this more robust or sophisticated.
+    ///
+    /// @param result Result code of the send operation.
+    /// @param ncr NameChangeRequest which failed to send.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::
+                                      NameChangeSender::Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
 protected:
 
+    /// @name Functions filtering and sanity-checking received messages.
+    ///
+    /// @todo These functions are supposed to be moved to a new class which
+    /// will manage different rules for accepting and rejecting messages.
+    /// Perhaps ticket #3116 is a good opportunity to do it.
+    ///
+    //@{
+    /// @brief Checks whether received message should be processed or discarded.
+    ///
+    /// This function checks whether received message should be processed or
+    /// discarded. It should be called on the beginning of message processing
+    /// (just after the message has been decoded). This message calls a number
+    /// of other functions which check whether message should be processed,
+    /// using different criteria.
+    ///
+    /// This function should be extended when new criteria for accepting
+    /// received message have to be implemented. This function is meant to
+    /// aggregate all early filtering checks on the received message. By having
+    /// a single function like this, we are avoiding bloat of the server's main
+    /// loop.
+    ///
+    /// @warning This function should remain exception safe.
+    ///
+    /// @param query Received message.
+    ///
+    /// @return true if the message should be further processed, or false if
+    /// the message should be discarded.
+    bool accept(const Pkt4Ptr& query) const;
+
+    /// @brief Check if a message sent by directly connected client should be
+    /// accepted or discared.
+    ///
+    /// This function checks if the received message is from directly connected
+    /// client. If it is, it checks that it should be processed or discarded.
+    ///
+    /// Note that this function doesn't validate all addresses being carried in
+    /// the message. The primary purpose of this function is to filter out
+    /// direct messages in the local network for which there is no suitable
+    /// subnet configured. For example, this function accepts unicast messages
+    /// because unicasts may be used by clients located in remote networks to
+    /// to renew existing leases. If their notion of address is wrong, the
+    /// server will have to sent a NAK, instead of dropping the message.
+    /// Detailed validation of such messages is performed at later stage of
+    /// processing.
+    ///
+    /// This function accepts the following messages:
+    /// - all valid relayed messages,
+    /// - all unicast messages,
+    /// - all broadcast messages received on the interface for which the
+    /// suitable subnet exists (is configured).
+    ///
+    /// @param query Message sent by a client.
+    ///
+    /// @return true if message is accepted for further processing, false
+    /// otherwise.
+    bool acceptDirectRequest(const Pkt4Ptr& query) const;
+
+    /// @brief Check if received message type is valid for the server to
+    /// process.
+    ///
+    /// This function checks that the received message type belongs to the range
+    /// of types regonized by the server and that the message of this type
+    /// should be processed by the server.
+    ///
+    /// The messages types accepted for processing are:
+    /// - Discover
+    /// - Request
+    /// - Release
+    /// - Decline
+    /// - Inform
+    ///
+    /// @param query Message sent by a client.
+    ///
+    /// @return true if message is accepted for further processing, false
+    /// otherwise.
+    bool acceptMessageType(const Pkt4Ptr& query) const;
+
     /// @brief Verifies if the server id belongs to our server.
     ///
     /// This function checks if the server identifier carried in the specified
@@ -181,6 +283,7 @@ protected:
     /// @return true, if the server identifier is absent or matches one of the
     /// server identifiers that the server is using; false otherwise.
     bool acceptServerId(const Pkt4Ptr& pkt) const;
+    //@}
 
     /// @brief verifies if specified packet meets RFC requirements
     ///
@@ -377,10 +480,10 @@ protected:
     /// @brief Creates the NameChangeRequest and adds to the queue for
     /// processing.
     ///
-    /// This function adds the @c isc::dhcp_ddns::NameChangeRequest to the
-    /// queue and emits the debug message which indicates whether the request
-    /// being added is to remove DNS entry or add a new entry. This function
-    /// is exception free.
+    /// This creates the @c isc::dhcp_ddns::NameChangeRequest; emits a
+    /// the debug message which indicates whether the request being added is
+    /// to remove DNS entry or add a new entry; and then sends the request
+    /// to the D2ClientMgr for transmission to b10-dhcp-ddns.
     ///
     /// @param chg_type A type of the NameChangeRequest (ADD or REMOVE).
     /// @param lease A lease for which the NameChangeRequest is created and
@@ -388,17 +491,6 @@ protected:
     void queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
                                 const Lease4Ptr& lease);
 
-    /// @brief Sends all outstanding NameChangeRequests to b10-dhcp-ddns module.
-    ///
-    /// The purpose of this function is to pick all outstanding
-    /// NameChangeRequests from the FIFO queue and send them to b10-dhcp-ddns
-    /// module.
-    ///
-    /// @todo Currently this function simply removes all requests from the
-    /// queue but doesn't send them anywhere. In the future, the
-    /// NameChangeSender will be used to deliver requests to the other module.
-    void sendNameChangeRequests();
-
     /// @brief Attempts to renew received addresses
     ///
     /// Attempts to renew existing lease. This typically includes finding a lease that
@@ -529,7 +621,7 @@ protected:
     ///
     /// @param question client's message
     /// @return selected subnet (or NULL if no suitable subnet was found)
-    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
+    isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question) const;
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
@@ -608,12 +700,6 @@ private:
     int hook_index_pkt4_receive_;
     int hook_index_subnet4_select_;
     int hook_index_pkt4_send_;
-
-protected:
-
-    /// Holds a list of @c isc::dhcp_ddns::NameChangeRequest objects which
-    /// are waiting for sending  to b10-dhcp-ddns module.
-    std::queue<isc::dhcp_ddns::NameChangeRequest> name_change_reqs_;
 };
 
 }; // namespace isc::dhcp

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

@@ -58,7 +58,8 @@ if HAVE_GTEST
 # to unexpected errors. For this reason, the --enable-static-link option is
 # ignored for unit tests built here.
 
-lib_LTLIBRARIES = libco1.la libco2.la
+nodistdir=$(abs_top_builddir)/src/bin/dhcp4/tests
+nodist_LTLIBRARIES = libco1.la libco2.la
 
 libco1_la_SOURCES  = callout_library_1.cc callout_library_common.h
 libco1_la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -75,10 +76,12 @@ TESTS += dhcp4_unittests
 dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
 dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
 dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
+dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.h
 dhcp4_unittests_SOURCES += dhcp4_unittests.cc
 dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
+dhcp4_unittests_SOURCES += direct_client_unittest.cc
 dhcp4_unittests_SOURCES += wireshark.cc
 dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
@@ -94,6 +97,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/tests/libdhcptest.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp_ddns/libb10-dhcp_ddns.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
 dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la

+ 2 - 5
src/bin/dhcp4/tests/callout_library_common.h

@@ -34,9 +34,6 @@
 
 #include <fstream>
 
-using namespace isc::hooks;
-using namespace std;
-
 extern "C" {
 
 /// @brief Append digit to marker file
@@ -51,7 +48,7 @@ extern "C" {
 int
 appendDigit(const char* name) {
     // Open the file and check if successful.
-    fstream file(name, fstream::out | fstream::app);
+    std::fstream file(name, std::fstream::out | std::fstream::app);
     if (!file.good()) {
         return (1);
     }
@@ -70,7 +67,7 @@ version() {
 }
 
 int
-load(LibraryHandle&) {
+load(isc::hooks::LibraryHandle&) {
     return (appendDigit(LOAD_MARKER_FILE));
 }
 

+ 406 - 54
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -24,6 +24,7 @@
 #include <dhcp/option_custom.h>
 #include <dhcp/option_int.h>
 #include <dhcp/docsis3_option_defs.h>
+#include <dhcp/classify.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <hooks/hooks_manager.h>
@@ -137,19 +138,19 @@ public:
             params["name"] = param_value;
             params["space"] = "dhcp4";
             params["code"] = "56";
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "space") {
             params["name"] = "dhcp-message";
             params["space"] = param_value;
             params["code"] = "56";
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "code") {
             params["name"] = "dhcp-message";
             params["space"] = "dhcp4";
             params["code"] = param_value;
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "data") {
             params["name"] = "dhcp-message";
@@ -161,7 +162,7 @@ public:
             params["name"] = "dhcp-message";
             params["space"] = "dhcp4";
             params["code"] = "56";
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = param_value;
         }
         return (createConfigWithOption(params));
@@ -212,6 +213,63 @@ public:
         return (stream.str());
     }
 
+    /// @brief Returns an option from the subnet.
+    ///
+    /// This function returns an option from a subnet to which the
+    /// specified subnet address belongs. The option is identified
+    /// by its code.
+    ///
+    /// @param subnet_address Address which belongs to the subnet from
+    /// which the option is to be returned.
+    /// @param option_code Code of the option to be returned.
+    /// @param expected_options_count Expected number of options in
+    /// the particular subnet.
+    ///
+    /// @return Descriptor of the option. If the descriptor holds a
+    /// NULL option pointer, it means that there was no such option
+    /// in the subnet.
+    Subnet::OptionDescriptor
+    getOptionFromSubnet(const IOAddress& subnet_address,
+                        const uint16_t option_code,
+                        const uint16_t expected_options_count = 1) {
+        Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(subnet_address,
+                                                          classify_);
+        if (!subnet) {
+            /// @todo replace toText() with the use of operator <<.
+            ADD_FAILURE() << "A subnet for the specified address "
+                          << subnet_address.toText()
+                          << "does not exist in Config Manager";
+        }
+        Subnet::OptionContainerPtr options =
+            subnet->getOptionDescriptors("dhcp4");
+        if (expected_options_count != options->size()) {
+            ADD_FAILURE() << "The number of options in the subnet '"
+                          << subnet_address.toText() << "' is different "
+                " than expected number of options '"
+                          << expected_options_count << "'";
+        }
+
+        // Get the search index. Index #1 is to search using option code.
+        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+        // Get the options for specified index. Expecting one option to be
+        // returned but in theory we may have multiple options with the same
+        // code so we get the range.
+        std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+                  Subnet::OptionContainerTypeIndex::const_iterator> range =
+            idx.equal_range(option_code);
+        if (std::distance(range.first, range.second) > 1) {
+            ADD_FAILURE() << "There is more than one option having the"
+                " option code '" << option_code << "' in a subnet '"
+                          << subnet_address.toText() << "'. Expected "
+                " at most one option";
+        } else if (std::distance(range.first, range.second) == 0) {
+            return (Subnet::OptionDescriptor(OptionPtr(), false));
+        }
+
+        return (*range.first);
+    }
+
     /// @brief Test invalid option parameter value.
     ///
     /// This test function constructs the simple configuration
@@ -233,6 +291,24 @@ public:
         ASSERT_EQ(1, rcode_);
     }
 
+    /// @brief Test invalid option paramater value.
+    ///
+    /// This test function constructs the simple configuration
+    /// string and injects invalid option configuration into it.
+    /// It expects that parser will fail with provided option code.
+    ///
+    /// @param params Map of parameters defining an option.
+    void
+    testInvalidOptionParam(const std::map<std::string, std::string>& params) {
+        ConstElementPtr x;
+        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_);
+    }
+
     /// @brief Test option against given code and data.
     ///
     /// @param option_desc option descriptor that carries the option to
@@ -274,6 +350,39 @@ public:
                             expected_data_len));
     }
 
+    /// @brief Test option configuration.
+    ///
+    /// This function creates a configuration for a specified option using
+    /// a map of parameters specified as the argument. The map holds
+    /// name/value pairs which identifies option's configuration parameters:
+    /// - name
+    /// - space
+    /// - code
+    /// - data
+    /// - csv-format.
+    /// This function applies a new server configuration and checks that the
+    /// option being configured is inserted into CfgMgr. The raw contents of
+    /// this option are compared with the binary data specified as expected
+    /// data passed to this function.
+    ///
+    /// @param params Map of parameters defining an option.
+    /// @param option_code Option code.
+    /// @param expected_data Array containing binary data expected to be stored
+    /// in the configured option.
+    /// @param expected_data_len Length of the array holding reference data.
+    void testConfiguration(const std::map<std::string, std::string>& params,
+                           const uint16_t option_code,
+                           const uint8_t* expected_data,
+                           const size_t expected_data_len) {
+        std::string config = createConfigWithOption(params);
+        ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
+        // The subnet should now hold one option with the specified option code.
+        Subnet::OptionDescriptor desc =
+            getOptionFromSubnet(IOAddress("192.0.2.24"), option_code);
+        ASSERT_TRUE(desc.option);
+        testOption(desc, option_code, expected_data, expected_data_len);
+    }
+
     /// @brief Parse and Execute configuration
     ///
     /// Parses a configuration and executes a configuration of the server.
@@ -347,9 +456,10 @@ public:
     }
 
 
-    boost::scoped_ptr<Dhcpv4Srv> srv_;      // DHCP4 server under test
-    int rcode_;                             // Return code from element parsing
-    ConstElementPtr comment_;               // Reason for parse fail
+    boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
+    int rcode_;                         ///< Return code from element parsing
+    ConstElementPtr comment_;           ///< Reason for parse fail
+    isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 
 // Goal of this test is a verification if a very simple config update
@@ -424,7 +534,8 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -642,7 +753,8 @@ TEST_F(Dhcp4ParserTest, nextServerGlobal) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
@@ -671,7 +783,8 @@ TEST_F(Dhcp4ParserTest, nextServerSubnet) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
@@ -758,7 +871,8 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
@@ -828,7 +942,8 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
     // returned value should be 0 (configuration success)
     checkResult(status, 0);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
@@ -880,7 +995,8 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
     // returned value must be 0 (configuration accepted)
     checkResult(status, 0);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -1318,14 +1434,13 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
 
 /// The purpose of this test is to verify that it is not allowed
 /// to override the standard option (that belongs to dhcp4 option
-/// space) and that it is allowed to define option in the dhcp4
-/// option space that has a code which is not used by any of the
-/// standard options.
+/// space and has its definition) and that it is allowed to define
+/// option in the dhcp4 option space that has a code which is not
+/// used by any of the standard options.
 TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
 
-    // Configuration string. The option code 109 is unassigned
-    // so it can be used for a custom option definition in
-    // dhcp4 option space.
+    // Configuration string. The option code 109 is unassigned so it
+    // can be used for a custom option definition in dhcp4 option space.
     std::string config =
         "{ \"option-def\": [ {"
         "      \"name\": \"foo\","
@@ -1358,15 +1473,14 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     EXPECT_EQ(OPT_STRING_TYPE, def->getType());
     EXPECT_FALSE(def->getArrayType());
 
-    // The combination of option space and code is
-    // invalid. The 'dhcp4' option space groups
-    // standard options and the code 100 is reserved
-    // for one of them.
+    // The combination of option space and code is invalid. The 'dhcp4' option
+    // space groups standard options and the code 3 is reserved for one of
+    // them.
     config =
         "{ \"option-def\": [ {"
-        "      \"name\": \"foo\","
-        "      \"code\": 100,"
-        "      \"type\": \"string\","
+        "      \"name\": \"routers\","
+        "      \"code\": 3,"
+        "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
         "      \"space\": \"dhcp4\","
@@ -1380,6 +1494,40 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+
+    /// @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
+    /// allowed to define it from the configuration interface. This test will
+    /// have to be removed once definitions for remaining standard options are
+    /// created.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"nis-server-addr\","
+        "      \"code\": 65,"
+        "      \"type\": \"ipv4-address\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp4\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+    json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting success.
+    checkResult(status, 0);
+
+    def = CfgMgr::instance().getOptionDef("dhcp4", 65);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("nis-server-addr", def->getName());
+    EXPECT_EQ(65, def->getCode());
+    EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+
 }
 
 // Goal of this test is to verify that global option
@@ -1394,7 +1542,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
         "    \"name\": \"dhcp-message\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1417,7 +1565,8 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(2, options->size());
@@ -1467,7 +1616,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
         "    \"name\": \"dhcp-message\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1501,7 +1650,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp4.
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
@@ -1644,7 +1794,6 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
         " } ]"
         "}";
 
-
     json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1652,7 +1801,8 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -1700,7 +1850,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
         "          \"name\": \"dhcp-message\","
         "          \"space\": \"dhcp4\","
         "          \"code\": 56,"
-        "          \"data\": \"AB CDEF0105\","
+        "          \"data\": \"ABCDEF0105\","
         "          \"csv-format\": False"
         "        },"
         "        {"
@@ -1720,7 +1870,8 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(2, options->size());
@@ -1751,6 +1902,89 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
     testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
 }
 
+// The goal of this test is to check that the option carrying a boolean
+// value can be configured using one of the values: "true", "false", "0"
+// or "1".
+TEST_F(Dhcp4ParserTest, optionDataBoolean) {
+    // Create configuration. Use standard option 19 (ip-forwarding).
+    std::map<std::string, std::string> params;
+    params["name"] = "ip-forwarding";
+    params["space"] = "dhcp4";
+    params["code"] = "19";
+    params["data"] = "true";
+    params["csv-format"] = "true";
+
+    std::string config = createConfigWithOption(params);
+    ASSERT_TRUE(executeConfiguration(config, "parse configuration with a"
+                                     " boolean value"));
+
+    // The subnet should now hold one option with the code 19.
+    Subnet::OptionDescriptor desc = getOptionFromSubnet(IOAddress("192.0.2.24"),
+                                                        19);
+    ASSERT_TRUE(desc.option);
+
+    // This option should be set to "true", represented as 0x1 in the option
+    // buffer.
+    uint8_t expected_option_data[] = {
+        0x1
+    };
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Configure the option with the "1" value. This should have the same
+    // effect as if "true" was specified.
+    params["data"] = "1";
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // The value of "1" with a few leading zeros should work too.
+    params["data"] = "00001";
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Configure the option with the "false" value.
+    params["data"] = "false";
+    // The option buffer should now hold the value of 0.
+    expected_option_data[0] = 0;
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Specifying "0" should have the same effect as "false".
+    params["data"] = "0";
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // The same effect should be for multiple 0 chars.
+    params["data"] = "00000";
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Bogus values should not be accepted.
+    params["data"] = "bugus";
+    testInvalidOptionParam(params);
+
+    params["data"] = "2";
+    testInvalidOptionParam(params);
+
+    // Now let's test that it is possible to use binary format.
+    params["data"] = "0";
+    params["csv-format"] = "false";
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // The binary 1 should work as well.
+    params["data"] = "1";
+    expected_option_data[0] = 1;
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // As well as an even number of digits.
+    params["data"] = "01";
+    testConfiguration(params, 19, expected_option_data,
+                      sizeof(expected_option_data));
+
+}
+
 // Goal of this test is to verify options configuration
 // for multiple subnets.
 TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
@@ -1789,7 +2023,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
+    Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"),
+                                                       classify_);
     ASSERT_TRUE(subnet1);
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options1->size());
@@ -1813,7 +2048,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
 
     // Test another subnet in the same way.
-    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
+    Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"),
+                                                       classify_);
     ASSERT_TRUE(subnet2);
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options2->size());
@@ -1828,6 +2064,8 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
     testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
 }
 
+
+
 // Verify that empty option name is rejected in the configuration.
 TEST_F(Dhcp4ParserTest, optionNameEmpty) {
     // Empty option names not allowed.
@@ -1878,14 +2116,6 @@ TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
     testInvalidOptionParam("0x0102", "data");
 }
 
-// Verify that option data consisting od an odd number of
-// hexadecimal digits is rejected in the configuration.
-TEST_F(Dhcp4ParserTest, optionDataOddLength) {
-    // Option code 0 is reserved and should not be accepted
-    // by configuration parser.
-    testInvalidOptionParam("123", "data");
-}
-
 // Verify that either lower or upper case characters are allowed
 // to specify the option data.
 TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
@@ -1898,7 +2128,8 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
     ASSERT_EQ(1, options->size());
@@ -1942,7 +2173,8 @@ TEST_F(Dhcp4ParserTest, stdOptionData) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options =
         subnet->getOptionDescriptors("dhcp4");
@@ -2144,7 +2376,8 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2201,7 +2434,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
         "    \"name\": \"option-one\","
         "    \"space\": \"vendor-4491\"," // VENDOR_ID_CABLE_LABS = 4491
         "    \"code\": 100," // just a random code
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -2226,7 +2459,8 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2285,7 +2519,8 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2333,7 +2568,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
         "    \"name\": \"dhcp-message\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -2529,7 +2764,6 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
         "     \"server-port\" : 777, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"allow-client-update\" : true, "
         "     \"override-no-update\" : true, "
@@ -2562,7 +2796,6 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
     EXPECT_EQ(777, d2_client_config->getServerPort());
     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->getRemoveOnRenew());
     EXPECT_TRUE(d2_client_config->getAlwaysIncludeFqdn());
     EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
     EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
@@ -2589,7 +2822,6 @@ TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
         "     \"server-port\" : 5301, "
         "     \"ncr-protocol\" : \"UDP\", "
         "     \"ncr-format\" : \"JSON\", "
-        "     \"remove-on-renew\" : true, "
         "     \"always-include-fqdn\" : true, "
         "     \"allow-client-update\" : true, "
         "     \"override-no-update\" : true, "
@@ -2617,4 +2849,124 @@ TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
 }
 
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp4ParserTest, subnetRelayInfo) {
+
+    ConstElementPtr status;
+
+    // A config with relay information.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"renew-timer\": 1, "
+        "    \"rebind-timer\": 2, "
+        "    \"valid-lifetime\": 4,"
+        "    \"relay\": { "
+        "        \"ip-address\": \"192.0.2.123\""
+        "    },"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+    // returned value should be 0 (configuration success)
+    checkResult(status, 0);
+
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("192.0.2.123", subnet->getRelayInfo().addr_.toText());
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp4ParserTest, classifySubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"client-class\": \"alpha\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"client-class\": \"beta\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\", "
+        "    \"client-class\": \"gamma\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+        "    \"subnet\": \"192.0.5.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+    // Let's check if client belonging to alpha class is supported in subnet[0]
+    // and not supported in any other subnet (except subnet[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in subnet[1]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in subnet[2]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in subnet[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last subnet, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+}
+
 }

+ 379 - 0
src/bin/dhcp4/tests/d2_unittest.cc

@@ -0,0 +1,379 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/tests/d2_unittest.h>
+#include <dhcpsrv/cfgmgr.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+D2Dhcpv4Srv::d2ClientErrorHandler(const
+                                dhcp_ddns::NameChangeSender::Result result,
+                                dhcp_ddns::NameChangeRequestPtr& ncr) {
+    ++error_count_;
+    // call base class error handler
+    Dhcpv4Srv::d2ClientErrorHandler(result, ncr);
+}
+
+const bool Dhcp4SrvD2Test::SHOULD_PASS;
+const bool Dhcp4SrvD2Test::SHOULD_FAIL;
+
+Dhcp4SrvD2Test::Dhcp4SrvD2Test() {
+}
+
+Dhcp4SrvD2Test::~Dhcp4SrvD2Test() {
+    reset();
+}
+
+dhcp_ddns::NameChangeRequestPtr
+Dhcp4SrvD2Test::buildTestNcr(uint32_t dhcid_id_num) {
+    // Build an NCR from json string.
+    std::ostringstream stream;
+
+    stream <<
+        "{"
+        " \"change_type\" : 0 , "
+        " \"forward_change\" : true , "
+        " \"reverse_change\" : false , "
+        " \"fqdn\" : \"myhost.example.com.\" , "
+        " \"ip_address\" : \"192.168.2.1\" , "
+        " \"dhcid\" : \""
+
+        << std::hex << std::setfill('0') << std::setw(16)
+        << dhcid_id_num << "\" , "
+
+        " \"lease_expires_on\" : \"20140121132405\" , "
+        " \"lease_length\" : 1300 "
+        "}";
+
+    return (dhcp_ddns::NameChangeRequest::fromJSON(stream.str()));
+}
+
+void
+Dhcp4SrvD2Test::reset() {
+    std::string config = "{ \"interfaces\": [ \"*\" ],"
+            "\"hooks-libraries\": [ ], "
+            "\"rebind-timer\": 2000, "
+            "\"renew-timer\": 1000, "
+            "\"valid-lifetime\": 4000, "
+            "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+    configure(config, SHOULD_PASS);
+}
+
+void
+Dhcp4SrvD2Test::configureD2(bool enable_d2, const bool exp_result,
+                            const std::string& ip_address,
+                            const uint32_t port) {
+    std::ostringstream config;
+    config <<
+        "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        " \"dhcp-ddns\" : {"
+        "     \"enable-updates\" : " << (enable_d2 ? "true" : "false") <<  ", "
+        "     \"server-ip\" : \"" << ip_address << "\", "
+        "     \"server-port\" : " << port << ", "
+        "     \"ncr-protocol\" : \"UDP\", "
+        "     \"ncr-format\" : \"JSON\", "
+        "     \"always-include-fqdn\" : true, "
+        "     \"allow-client-update\" : true, "
+        "     \"override-no-update\" : true, "
+        "     \"override-client-update\" : true, "
+        "     \"replace-client-name\" : true, "
+        "     \"generated-prefix\" : \"test.prefix\", "
+        "     \"qualifying-suffix\" : \"test.suffix.\" },"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str(), exp_result);
+}
+
+void
+Dhcp4SrvD2Test::configure(const std::string& config, bool exp_result) {
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+    ASSERT_TRUE(status);
+
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    if (exp_result == SHOULD_PASS) {
+        ASSERT_EQ(0, rcode);
+    } else {
+        ASSERT_EQ(1, rcode);
+    }
+}
+
+// Tests ability to turn on and off ddns updates by submitting
+// by submitting the appropriate configuration to Dhcp4 server
+// and then invoking its startD2() method.
+TEST_F(Dhcp4SrvD2Test, enableDisable) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify a valid config with ddns enabled configures ddns properly,
+    // but does not start the sender.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start does not throw and starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify a valid config with ddns disabled configures ddns properly.
+    // Sender should not have been started.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false));
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that the sender does NOT get started when ddns is disabled.
+    srv_.startD2();
+    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.
+TEST_F(Dhcp4SrvD2Test, badConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now attempt to give it an invalid configuration.
+    // Result should indicate failure.
+    ASSERT_NO_FATAL_FAILURE(configureD2(false, SHOULD_FAIL, "bogus_ip"));
+
+    // Configure was not altered, so ddns should be enabled and still sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+}
+
+// Checks that submitting an identical dhcp-ddns configuration
+// is handled properly.  Not effect should be no change in
+// status for ddns updating.  Updates should still enabled and
+// in send mode.  This indicates that the sender was not stopped.
+TEST_F(Dhcp4SrvD2Test, sameConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now submit an identical configuration.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+
+    // Configuration was not altered, so ddns should still enabled and sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that calling start does not throw or stop the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that submitting an different, but valid dhcp-ddns configuration
+// is handled properly.  Updates should be enabled, however they should
+// not yet be running.  This indicates that the sender was stopped and
+// replaced, but not yet started.
+TEST_F(Dhcp4SrvD2Test, differentConfig) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Now enable it on a different port.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true, SHOULD_PASS, "127.0.0.1", 54001));
+
+    // Configuration was altered, so ddns should still enabled but not sending.
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify that calling start starts the sender.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+}
+
+// Checks that given a valid, enabled configuration and placing
+// sender in send mode, permits NCR requests to be sent via UPD
+// socket.  Note this test does not employ any sort of receiving
+// client to verify actual transmission.  These types of tests
+// are including under dhcp_ddns and d2 unit testing.
+TEST_F(Dhcp4SrvD2Test, simpleUDPSend) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify that we can queue up a message.
+    dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr();
+    ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    EXPECT_EQ(1, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the queue is now empty.
+    EXPECT_EQ(0, mgr.getQueueSize());
+}
+
+// Checks that an IO error in sending a request to D2, results in ddns updates being
+// suspended.  This indicates that Dhcp4Srv's error handler has been invoked as expected.
+// Note that this unit test relies on an attempt to send to a server address of 0.0.0.0
+// port 0 fails under all OSs.
+TEST_F(Dhcp4SrvD2Test, forceUDPSendFailure) {
+    // Grab the manager and verify that be default ddns is off
+    // and a sender was not started.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_FALSE(mgr.ddnsEnabled());
+
+    // 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));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Queue up 3 messages.
+    for (int i = 0; i < 3; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Calling receive should detect the ready IO on the sender's select-fd,
+    // and invoke callback, which should complete the send, which should
+    // fail.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // Verify that we can't just restart it.
+    /// @todo This may change if we add ability to resume.
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_FALSE(mgr.amSending());
+
+    // Configure it enabled and start it.
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Verify message is still in the queue.
+    EXPECT_EQ(3, mgr.getQueueSize());
+
+    // This will finish sending the 1st message in queue
+    // and initiate send of 2nd message.
+    ASSERT_NO_THROW(IfaceMgr::instance().receive4(0,0));
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // First message is off the queue.
+    EXPECT_EQ(2, mgr.getQueueSize());
+}
+
+// Tests error handling of D2ClientMgr::sendRequest() failure
+// by attempting to queue maximum number of messages.
+TEST_F(Dhcp4SrvD2Test, queueMaxError) {
+    // Configure it enabled and start it.
+    dhcp::D2ClientMgr& mgr = CfgMgr::instance().getD2ClientMgr();
+    ASSERT_NO_FATAL_FAILURE(configureD2(true));
+    ASSERT_TRUE(mgr.ddnsEnabled());
+    ASSERT_NO_THROW(srv_.startD2());
+    ASSERT_TRUE(mgr.amSending());
+
+    // Attempt to queue more then the maximum allowed.
+    int max_msgs = mgr.getQueueMaxSize();
+    for (int i = 0; i < max_msgs + 1; i++) {
+        dhcp_ddns::NameChangeRequestPtr ncr = buildTestNcr(i + 1);
+        ASSERT_NO_THROW(mgr.sendRequest(ncr));
+    }
+
+    // Stopping sender will complete the first message so there
+    // should be max less one.
+    EXPECT_EQ(max_msgs - 1, mgr.getQueueSize());
+
+    // Verify the error handler was invoked.
+    EXPECT_EQ(1, srv_.error_count_);
+
+    // Verify that updates are disabled and we are no longer sending.
+    ASSERT_FALSE(mgr.ddnsEnabled());
+    ASSERT_FALSE(mgr.amSending());
+}
+
+
+} // namespace test
+} // namespace dhcp
+} // namespace isc
+

+ 117 - 0
src/bin/dhcp4/tests/d2_unittest.h

@@ -0,0 +1,117 @@
+// 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.
+
+/// @file d2_unittest.h Defines classes for testing Dhcpv4srv with D2ClientMgr
+
+#ifndef D2_UNITTEST_H
+#define D2_UNITTEST_H
+
+#include <dhcp4/dhcp4_srv.h>
+#include <config/ccsession.h>
+
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Test derivation of Dhcpv4Srv class used in D2 testing.
+/// Use of this class allows the intervention at strategic points in testing
+/// by permitting overridden methods and access to scope protected members.
+class D2Dhcpv4Srv : public  Dhcpv4Srv {
+public:
+    /// @brief Counts the number of times the client error handler is called.
+    int error_count_;
+
+    /// @brief Constructor
+    D2Dhcpv4Srv()
+        : Dhcpv4Srv(0, "type=memfile", false, false), error_count_(0) {
+    }
+
+    /// @brief virtual Destructor.
+    virtual ~D2Dhcpv4Srv() {
+    }
+
+    /// @brief Override the error handler.
+    virtual void d2ClientErrorHandler(const dhcp_ddns::NameChangeSender::
+                                      Result result,
+                                      dhcp_ddns::NameChangeRequestPtr& ncr);
+};
+
+/// @brief Test fixture which permits testing the interaction between the
+/// D2ClientMgr and Dhcpv4Srv.
+class Dhcp4SrvD2Test : public ::testing::Test {
+public:
+    /// @brief Mnemonic constants for calls to configuration methods.
+    static const bool SHOULD_PASS = true;
+    static const bool SHOULD_FAIL = false;
+
+    /// @brief Constructor
+    Dhcp4SrvD2Test();
+
+    /// @brief virtual Destructor
+    virtual ~Dhcp4SrvD2Test();
+
+    /// @brief Resets the CfgMgr singleton to defaults.
+    /// Primarily used in the test destructor as gtest doesn't exit between
+    /// tests.
+    /// @todo CfgMgr should provide a method to reset everything or maybe
+    /// reconstruct the singleton.
+    void reset();
+
+    /// @brief Configures the server with D2 enabled or disabled
+    ///
+    /// Constructs a configuration string including dhcp-ddns with the
+    /// 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 port  port for the D2 server
+    void configureD2(bool enable_updates, bool exp_result = SHOULD_PASS,
+                     const std::string& ip_address = "127.0.0.1",
+                     const uint32_t port = 53001);
+
+    /// @brief Configures the server with the given configuration
+    ///
+    /// Passes the given configuration string into the server's configuration
+    /// handler.  It accepts a flag indicating whether or not the configuration
+    /// is expected to succeed or fail.  This permits testing the server's
+    /// response to both valid and invalid configurations.
+    ///
+    /// @param config JSON string containing the configuration
+    /// @param exp_result indicates if configuration should pass or fail
+    void configure(const std::string& config, bool exp_result = SHOULD_PASS);
+
+    /// @brief Contructs a NameChangeRequest message from a fixed JSON string.
+    ///
+    /// @param dhcid_id_num Integer value to use as the DHCID.
+    dhcp_ddns::NameChangeRequestPtr buildTestNcr(uint32_t
+                                                 dhcid_id_num = 0xdeadbeef);
+
+    /// @brief Stores the return code of the last configuration attempt.
+    int rcode_;
+
+    /// @brief Stores the message component of the last configuration tattempt.
+    isc::data::ConstElementPtr comment_;
+
+    /// @brief Server object under test.
+    D2Dhcpv4Srv srv_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // D2_UNITTEST_H

Fichier diff supprimé car celui-ci est trop grand
+ 350 - 84
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc


+ 23 - 62
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -15,12 +15,15 @@
 #include <config.h>
 
 #include <asiolink/io_address.h>
+#include <cc/data.h>
 #include <config/ccsession.h>
+#include <dhcp4/config_parser.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_custom.h>
 #include <dhcp/iface_mgr.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/lease.h>
 #include <dhcpsrv/lease_mgr.h>
@@ -28,17 +31,14 @@
 
 using namespace std;
 using namespace isc::asiolink;
-
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
 namespace test {
 
-/// dummy server-id file location
-static const char* SRVID_FILE = "server-id-test.txt";
-
 Dhcpv4SrvTest::Dhcpv4SrvTest()
-:rcode_(-1) {
+:rcode_(-1), srv_(0) {
     subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
                                      2000, 3000));
     pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
@@ -52,9 +52,6 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
     opt_routers->setAddress(IOAddress("192.0.2.2"));
     subnet_->addOption(opt_routers, false, "dhcp4");
-
-    // it's ok if that fails. There should not be such a file anyway
-    unlink(SRVID_FILE);
 }
 
 Dhcpv4SrvTest::~Dhcpv4SrvTest() {
@@ -387,9 +384,6 @@ void Dhcpv4SrvTest::TearDown() {
 
     CfgMgr::instance().deleteSubnets4();
 
-    // Let's clean up if there is such a file.
-    unlink(SRVID_FILE);
-
     // Close all open sockets.
     IfaceMgr::instance().closeSockets();
 
@@ -413,58 +407,11 @@ void Dhcpv4SrvTest::TearDown() {
 
 }
 
-Dhcpv4SrvFakeIfaceTest::Dhcpv4SrvFakeIfaceTest()
-: Dhcpv4SrvTest(), current_pkt_filter_() {
-    // Remove current interface configuration. Instead we want to add
-    // a couple of fake interfaces.
-    IfaceMgr& ifacemgr = IfaceMgr::instance();
-    ifacemgr.closeSockets();
-    ifacemgr.clearIfaces();
-
-    // Add fake interfaces.
-    ifacemgr.addInterface(createIface("lo", 0, "127.0.0.1"));
-    ifacemgr.addInterface(createIface("eth0", 1, "192.0.3.1"));
-    ifacemgr.addInterface(createIface("eth1", 2, "10.0.0.1"));
-
-    // In order to use fake interfaces we have to supply the custom
-    // packet filtering class, which can mimic opening sockets on
-    // fake interafaces.
-    current_pkt_filter_.reset(new PktFilterTest());
-    ifacemgr.setPacketFilter(current_pkt_filter_);
-    ifacemgr.openSockets4();
-}
-
 void
-Dhcpv4SrvFakeIfaceTest::TearDown() {
-    // The base class function restores the original packet filtering class.
-    Dhcpv4SrvTest::TearDown();
-    // The base class however, doesn't re-detect real interfaces.
-    try {
-        IfaceMgr::instance().clearIfaces();
-        IfaceMgr::instance().detectIfaces();
+Dhcpv4SrvTest::testDiscoverRequest(const uint8_t msg_type) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
 
-    } catch (const Exception& ex) {
-        FAIL() << "Failed to restore interface configuration after using"
-            " fake interfaces";
-    }
-}
-
-Iface
-Dhcpv4SrvFakeIfaceTest::createIface(const std::string& name, const int ifindex,
-                                    const std::string& addr) {
-    Iface iface(name, ifindex);
-    iface.addAddress(IOAddress(addr));
-    if (name == "lo") {
-        iface.flag_loopback_ = true;
-    }
-    iface.flag_up_ = true;
-    iface.flag_running_ = true;
-    iface.inactive4_ = false;
-    return (iface);
-}
-
-void
-Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     // Create an instance of the tested class.
     boost::scoped_ptr<NakedDhcpv4Srv> srv(new NakedDhcpv4Srv(0));
 
@@ -608,6 +555,20 @@ Dhcpv4SrvFakeIfaceTest::testDiscoverRequest(const uint8_t msg_type) {
     EXPECT_TRUE(noBasicOptions(rsp));
 }
 
+void
+Dhcpv4SrvTest::configure(const std::string& config) {
+    ElementPtr json = Element::fromJSON(config);
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv_, json));
+    ASSERT_TRUE(status);
+    int rcode;
+    ConstElementPtr comment = config::parseAnswer(rcode, status);
+    ASSERT_EQ(0, rcode);
+}
+
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 130 - 165
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -87,6 +87,120 @@ public:
 
 typedef boost::shared_ptr<PktFilterTest> PktFilterTestPtr;
 
+/// @brief "Naked" DHCPv4 server, exposes internal fields
+class NakedDhcpv4Srv: public Dhcpv4Srv {
+public:
+
+    /// @brief Constructor.
+    ///
+    /// This constructor disables default modes of operation used by the
+    /// Dhcpv4Srv class:
+    /// - Send/receive broadcast messages through sockets on interfaces
+    /// which support broadcast traffic.
+    /// - Direct DHCPv4 traffic - communication with clients which do not
+    /// have IP address assigned yet.
+    ///
+    /// Enabling these modes requires root privilges so they must be
+    /// disabled for unit testing.
+    ///
+    /// Note, that disabling broadcast options on sockets does not impact
+    /// the operation of these tests because they use local loopback
+    /// interface which doesn't have broadcast capability anyway. It rather
+    /// prevents setting broadcast options on other (broadcast capable)
+    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
+    ///
+    /// The Direct DHCPv4 Traffic capability can be disabled here because
+    /// it is tested with PktFilterLPFTest unittest. The tests which belong
+    /// to PktFilterLPFTest can be enabled on demand when root privileges can
+    /// be guaranteed.
+    ///
+    /// @param port port number to listen on; the default value 0 indicates
+    /// that sockets should not be opened.
+    NakedDhcpv4Srv(uint16_t port = 0)
+        : Dhcpv4Srv(port, "type=memfile", false, false) {
+        // Create fixed server id.
+        server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                            asiolink::IOAddress("192.0.3.1")));
+    }
+
+    /// @brief Returns fixed server identifier assigned to the naked server
+    /// instance.
+    OptionPtr getServerID() const {
+        return (server_id_);
+    }
+
+    /// @brief fakes packet reception
+    /// @param timeout ignored
+    ///
+    /// The method receives all packets queued in receive queue, one after
+    /// another. Once the queue is empty, it initiates the shutdown procedure.
+    ///
+    /// See fake_received_ field for description
+    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
+
+        // If there is anything prepared as fake incoming traffic, use it
+        if (!fake_received_.empty()) {
+            Pkt4Ptr pkt = fake_received_.front();
+            fake_received_.pop_front();
+            return (pkt);
+        }
+
+        // If not, just trigger shutdown and return immediately
+        shutdown();
+        return (Pkt4Ptr());
+    }
+
+    /// @brief fake packet sending
+    ///
+    /// Pretend to send a packet, but instead just store it in fake_send_ list
+    /// where test can later inspect server's response.
+    virtual void sendPacket(const Pkt4Ptr& pkt) {
+        fake_sent_.push_back(pkt);
+    }
+
+    /// @brief adds a packet to fake receive queue
+    ///
+    /// See fake_received_ field for description
+    void fakeReceive(const Pkt4Ptr& pkt) {
+        fake_received_.push_back(pkt);
+    }
+
+    virtual ~NakedDhcpv4Srv() {
+    }
+
+    /// @brief Dummy server identifier option used by various tests.
+    OptionPtr server_id_;
+
+    /// @brief packets we pretend to receive
+    ///
+    /// Instead of setting up sockets on interfaces that change between OSes, it
+    /// is much easier to fake packet reception. This is a list of packets that
+    /// we pretend to have received. You can schedule new packets to be received
+    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
+    std::list<Pkt4Ptr> fake_received_;
+
+    std::list<Pkt4Ptr> fake_sent_;
+
+    using Dhcpv4Srv::adjustIfaceData;
+    using Dhcpv4Srv::appendServerID;
+    using Dhcpv4Srv::processDiscover;
+    using Dhcpv4Srv::processRequest;
+    using Dhcpv4Srv::processRelease;
+    using Dhcpv4Srv::processDecline;
+    using Dhcpv4Srv::processInform;
+    using Dhcpv4Srv::processClientName;
+    using Dhcpv4Srv::computeDhcid;
+    using Dhcpv4Srv::createNameChangeRequests;
+    using Dhcpv4Srv::acceptServerId;
+    using Dhcpv4Srv::sanityCheck;
+    using Dhcpv4Srv::srvidToString;
+    using Dhcpv4Srv::unpackOptions;
+    using Dhcpv4Srv::classifyPacket;
+    using Dhcpv4Srv::accept;
+    using Dhcpv4Srv::acceptMessageType;
+    using Dhcpv4Srv::selectSubnet;
+};
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 
@@ -267,61 +381,6 @@ public:
     /// @return created packet
     Pkt4Ptr packetFromCapture(const std::string& hex_string);
 
-    /// @brief This function cleans up after the test.
-    virtual void TearDown();
-
-    /// @brief A subnet used in most tests
-    Subnet4Ptr subnet_;
-
-    /// @brief A pool used in most tests
-    Pool4Ptr pool_;
-
-    /// @brief A client-id used in most tests
-    ClientIdPtr client_id_;
-
-    int rcode_;
-
-    isc::data::ConstElementPtr comment_;
-
-};
-
-/// @brief Test fixture class to be used for tests which require fake
-/// interfaces.
-///
-/// The DHCPv4 server must always append the server identifier to its response.
-/// The server identifier is typically an IP address assigned to the interface
-/// on which the query has been received. The DHCPv4 server uses IfaceMgr to
-/// check this address. In order to test this functionality, a set of interfaces
-/// must be known to the test. This test fixture class creates a set of well
-/// known (fake) interfaces which can be assigned to the test DHCPv4 messages
-/// so as the response (including server identifier) can be validated.
-/// The real interfaces are removed from the IfaceMgr in the constructor and
-/// they are re-assigned in the destructor.
-class Dhcpv4SrvFakeIfaceTest : public Dhcpv4SrvTest {
-public:
-    /// @brief Constructor.
-    ///
-    /// Creates a set of fake interfaces:
-    /// - lo, index: 0, address: 127.0.0.1
-    /// - eth0, index: 1, address: 192.0.3.1
-    /// - eth1, index: 2, address: 10.0.0.1
-    ///
-    /// These interfaces replace the real interfaces detected by the IfaceMgr.
-    Dhcpv4SrvFakeIfaceTest();
-
-    /// @brief Restores the original interface configuration.
-    virtual void TearDown();
-
-    /// @brief Creates an instance of the interface.
-    ///
-    /// @param name Name of the interface.
-    /// @param ifindex Index of the interface.
-    /// @param addr IP address assigned to the interface, represented as string.
-    ///
-    /// @return Iface Instance of the interface.
-    static Iface createIface(const std::string& name, const int ifindex,
-                             const std::string& addr);
-
     /// @brief Tests if Discover or Request message is processed correctly
     ///
     /// This test verifies that the Parameter Request List option is handled
@@ -335,123 +394,29 @@ public:
     /// @param msg_type DHCPDISCOVER or DHCPREQUEST
     void testDiscoverRequest(const uint8_t msg_type);
 
-    /// @brief Holds a pointer to the packet filter object currently used
-    /// by the IfaceMgr.
-    PktFilterTestPtr current_pkt_filter_;
-
-};
-
-/// @brief "Naked" DHCPv4 server, exposes internal fields
-class NakedDhcpv4Srv: public Dhcpv4Srv {
-public:
-
-    /// @brief Constructor.
-    ///
-    /// This constructor disables default modes of operation used by the
-    /// Dhcpv4Srv class:
-    /// - Send/receive broadcast messages through sockets on interfaces
-    /// which support broadcast traffic.
-    /// - Direct DHCPv4 traffic - communication with clients which do not
-    /// have IP address assigned yet.
-    ///
-    /// Enabling these modes requires root privilges so they must be
-    /// disabled for unit testing.
-    ///
-    /// Note, that disabling broadcast options on sockets does not impact
-    /// the operation of these tests because they use local loopback
-    /// interface which doesn't have broadcast capability anyway. It rather
-    /// prevents setting broadcast options on other (broadcast capable)
-    /// sockets which are opened on other interfaces in Dhcpv4Srv constructor.
-    ///
-    /// The Direct DHCPv4 Traffic capability can be disabled here because
-    /// it is tested with PktFilterLPFTest unittest. The tests which belong
-    /// to PktFilterLPFTest can be enabled on demand when root privileges can
-    /// be guaranteed.
+    /// @brief Runs DHCPv4 configuration from the JSON string.
     ///
-    /// @param port port number to listen on; the default value 0 indicates
-    /// that sockets should not be opened.
-    NakedDhcpv4Srv(uint16_t port = 0)
-        : Dhcpv4Srv(port, "type=memfile", false, false) {
-        // Create fixed server id.
-        server_id_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
-                                            asiolink::IOAddress("192.0.3.1")));
-    }
-
-    /// @brief Returns fixed server identifier assigned to the naked server
-    /// instance.
-    OptionPtr getServerID() const {
-        return (server_id_);
-    }
+    /// @param config String holding server configuration in JSON format.
+    void configure(const std::string& config);
 
-    /// @brief fakes packet reception
-    /// @param timeout ignored
-    ///
-    /// The method receives all packets queued in receive queue, one after
-    /// another. Once the queue is empty, it initiates the shutdown procedure.
-    ///
-    /// See fake_received_ field for description
-    virtual Pkt4Ptr receivePacket(int /*timeout*/) {
-
-        // If there is anything prepared as fake incoming traffic, use it
-        if (!fake_received_.empty()) {
-            Pkt4Ptr pkt = fake_received_.front();
-            fake_received_.pop_front();
-            return (pkt);
-        }
-
-        // If not, just trigger shutdown and return immediately
-        shutdown();
-        return (Pkt4Ptr());
-    }
-
-    /// @brief fake packet sending
-    ///
-    /// Pretend to send a packet, but instead just store it in fake_send_ list
-    /// where test can later inspect server's response.
-    virtual void sendPacket(const Pkt4Ptr& pkt) {
-        fake_sent_.push_back(pkt);
-    }
+    /// @brief This function cleans up after the test.
+    virtual void TearDown();
 
-    /// @brief adds a packet to fake receive queue
-    ///
-    /// See fake_received_ field for description
-    void fakeReceive(const Pkt4Ptr& pkt) {
-        pkt->setIface("eth0");
-        fake_received_.push_back(pkt);
-    }
+    /// @brief A subnet used in most tests
+    Subnet4Ptr subnet_;
 
-    virtual ~NakedDhcpv4Srv() {
-    }
+    /// @brief A pool used in most tests
+    Pool4Ptr pool_;
 
-    /// @brief Dummy server identifier option used by various tests.
-    OptionPtr server_id_;
+    /// @brief A client-id used in most tests
+    ClientIdPtr client_id_;
 
-    /// @brief packets we pretend to receive
-    ///
-    /// Instead of setting up sockets on interfaces that change between OSes, it
-    /// is much easier to fake packet reception. This is a list of packets that
-    /// we pretend to have received. You can schedule new packets to be received
-    /// using fakeReceive() and NakedDhcpv4Srv::receivePacket() methods.
-    std::list<Pkt4Ptr> fake_received_;
+    int rcode_;
 
-    std::list<Pkt4Ptr> fake_sent_;
+    isc::data::ConstElementPtr comment_;
 
-    using Dhcpv4Srv::adjustIfaceData;
-    using Dhcpv4Srv::appendServerID;
-    using Dhcpv4Srv::processDiscover;
-    using Dhcpv4Srv::processRequest;
-    using Dhcpv4Srv::processRelease;
-    using Dhcpv4Srv::processDecline;
-    using Dhcpv4Srv::processInform;
-    using Dhcpv4Srv::processClientName;
-    using Dhcpv4Srv::computeDhcid;
-    using Dhcpv4Srv::createNameChangeRequests;
-    using Dhcpv4Srv::acceptServerId;
-    using Dhcpv4Srv::sanityCheck;
-    using Dhcpv4Srv::srvidToString;
-    using Dhcpv4Srv::unpackOptions;
-    using Dhcpv4Srv::name_change_reqs_;
-    using Dhcpv4Srv::classifyPacket;
+    /// @brief Server object under test.
+    NakedDhcpv4Srv srv_;
 };
 
 }; // end of isc::dhcp::test namespace

+ 413 - 0
src/bin/dhcp4/tests/direct_client_unittest.cc

@@ -0,0 +1,413 @@
+// 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 <dhcp/iface_mgr.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/classify.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <gtest/gtest.h>
+#include <string>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @brief Test fixture class for testing message processing from directly
+/// connected clients.
+///
+/// This class provides mechanisms for testing processing of DHCPv4 messages
+/// from directly connected clients.
+class DirectClientTest : public Dhcpv4SrvTest {
+public:
+    /// @brief Constructor.
+    ///
+    /// Initializes DHCPv4 server object used by various tests.
+    DirectClientTest();
+
+    /// @brief Configures the server with one subnet.
+    ///
+    /// This creates new configuration for the DHCPv4 with one subnet having
+    /// a specified prefix.
+    ///
+    /// The subnet parameters (such as options, timers etc.) are aribitrarily
+    /// selected. The subnet and pool mask is always /24. The real configuration
+    /// would exclude .0 (network address) and .255 (broadcast address), but we
+    /// ignore that fact for the sake of test simplicity.
+    ///
+    /// @param prefix Prefix for a subnet.
+    void configureSubnet(const std::string& prefix);
+
+    /// @brief Configures the server with two subnets.
+    ///
+    /// This function configures DHCPv4 server with two different subnets.
+    /// The subnet parameters (such as options, timers etc.) are aribitrarily
+    /// selected. The subnet and pool mask is /24. The real configuration
+    /// would exclude .0 (network address) and .255 (broadcast address), but we
+    /// ignore that fact for the sake of test simplicity.
+    ///
+    /// @param prefix1 Prefix of the first subnet to be configured.
+    /// @param prefix2 Prefix of the second subnet to be configured.
+    void configureTwoSubnets(const std::string& prefix1,
+                             const std::string& prefix2);
+
+    /// @brief Creates simple message from a client.
+    ///
+    /// This function creates a DHCPv4 message having a specified type
+    /// (e.g. Discover, Request) and sets some properties of this
+    /// message: client identifier, address and interface. The copy of
+    /// this message is then created by parsing wire data of the original
+    /// message. This simulates the case when the message is received and
+    /// parsed by the server.
+    ///
+    /// @param msg_type Type of the message to be created.
+    /// @param iface Name of the interface on which the message has been
+    /// "received" by the server.
+    ///
+    /// @return Generated message.
+    Pkt4Ptr createClientMessage(const uint16_t msg_type,
+                                const std::string& iface);
+
+    /// @brief Creates simple message from a client.
+    ///
+    /// This function configures a client's message by adding client identifier,
+    /// setting interface and addresses. The copy of this message is then
+    /// created by parsing wire data of the original message. This simulates the
+    /// case when the message is received and parsed by the server.
+    ///
+    /// @param msg Caller supplied message to be configured. This object must
+    /// not be NULL.
+    /// @param iface Name of the interface on which the message has been
+    /// "received" by the server.
+    ///
+    /// @return Configured and parsed message.
+    Pkt4Ptr createClientMessage(const Pkt4Ptr &msg, const std::string& iface);
+
+    /// @brief classes the client belongs to
+    ///
+    /// This is empty in most cases, but it is needed as a parameter for all
+    /// getSubnet4() calls.
+    ClientClasses classify_;
+};
+
+DirectClientTest::DirectClientTest() : Dhcpv4SrvTest() {
+}
+
+void
+DirectClientTest::configureSubnet(const std::string& prefix) {
+    std::ostringstream config;
+    config << "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"" << prefix << "/24\" ],"
+        "    \"subnet\": \"" << prefix << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str());
+
+}
+
+void
+DirectClientTest::configureTwoSubnets(const std::string& prefix1,
+                                      const std::string& prefix2) {
+    std::ostringstream config;
+    config << "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"option-data\": [ ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"" << prefix1 << "/24\" ],"
+        "    \"subnet\": \"" << prefix1 << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        " },"
+        "{ "
+        "    \"pool\": [ \"" << prefix2 << "/24\" ],"
+        "    \"subnet\": \"" << prefix2 << "/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000"
+        "} ],"
+        "\"valid-lifetime\": 4000 }";
+
+    configure(config.str());
+}
+
+Pkt4Ptr
+DirectClientTest:: createClientMessage(const uint16_t msg_type,
+                                       const std::string& iface) {
+    // Create a source packet.
+    Pkt4Ptr msg = Pkt4Ptr(new Pkt4(msg_type, 1234));
+    return (createClientMessage(msg, iface));
+
+}
+
+Pkt4Ptr
+DirectClientTest::createClientMessage(const Pkt4Ptr& msg,
+                                      const std::string& iface) {
+    msg->setRemoteAddr(IOAddress("255.255.255.255"));
+    msg->addOption(generateClientId());
+    msg->setIface(iface);
+
+    // Create copy of this packet by parsing its wire data. Make sure that the
+    // local and remote address are set like it was a message sent from the
+    // directly connected client.
+    Pkt4Ptr received;
+    createPacketFromBuffer(msg, received);
+    received->setIface(iface);
+    received->setLocalAddr(IOAddress("255.255.255.255"));
+    received->setRemoteAddr(IOAddress("0.0.0.0"));
+
+    return (received);
+}
+
+// This test checks that the message from directly connected client
+// is processed and that client is offered IPv4 address from the subnet which
+// is suitable for the local interface on which the client's message is
+// received. This test uses two subnets, with two active interfaces which IP
+// addresses belong to these subnets. The address offered to the client
+// which message has been sent over eth0 should belong to a different
+// subnet than the address offered for the client sending its message
+// via eth1.
+TEST_F(DirectClientTest,  twoSubnets) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add two subnets: address on eth0 belongs to the second subnet,
+    // address on eth1 belongs to the first subnet.
+    ASSERT_NO_FATAL_FAILURE(configureTwoSubnets("192.0.2.0", "10.0.0.0"));
+    // Create Discover and simulate reception of this message through eth0.
+    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
+    srv_.fakeReceive(dis);
+    // Create Request and simulate reception of this message through eth1.
+    Pkt4Ptr req = createClientMessage(DHCPREQUEST, "eth1");
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send reposonses.
+    ASSERT_EQ(2, srv_.fake_sent_.size());
+
+    // Make sure that we received a response.
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+    srv_.fake_sent_.pop_front();
+
+    // Client should get an Offer (not a NAK).
+    ASSERT_EQ(DHCPOFFER, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+    // A client that sent Request over the other interface should get Ack.
+    response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    // Client should get an Ack (not a NAK).
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+
+}
+
+// This test checks that server selects a subnet when receives a message
+// through an interface for which the subnet has been configured. This
+// interface has IPv4 address assigned which belongs to this subnet.
+// This test also verifies that when the message is received through
+// the interface for which there is no suitable subnet, the message
+// is discarded.
+TEST_F(DirectClientTest, oneSubnet) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet which will be selected when a message from directly
+    // connected client is received through interface eth0.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Create Discover and simulate reception of this message through eth0.
+    Pkt4Ptr dis = createClientMessage(DHCPDISCOVER, "eth0");
+    srv_.fakeReceive(dis);
+    // Create Request and simulate reception of this message through eth1.
+    Pkt4Ptr req = createClientMessage(DHCPDISCOVER, "eth1");
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server sent one response for the message received
+    // through eth0. The other client's message should be dicarded.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+
+    // Check the response. The first Discover was sent via eth0 for which
+    // the subnet has been configured.
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+    srv_.fake_sent_.pop_front();
+
+    // Since Discover has been received through the interface for which
+    // the subnet has been configured, the server should respond with
+    // an Offer message.
+    ASSERT_EQ(DHCPOFFER, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+// This test verifies that the server uses ciaddr to select a subnet for a
+// client which renews its lease.
+TEST_F(DirectClientTest, renew) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Make sure that the subnet has been really added. Also, the subnet
+    // will be needed to create a lease for a client.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"),
+                                                      classify_);
+    // Create a lease for a client that we will later renewed. By explicitly
+    // creating a lease we will get to know the lease parameters, such as
+    // leased address etc.
+    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
+    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
+                               &generateClientId()->getData()[0],
+                               generateClientId()->getData().size(),
+                               100, 50, 75, time(NULL),
+                               subnet->getID()));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    // Create a Request to renew client's lease. The renew request is unicast
+    // through eth1. Note, that in case of renewal the client unicasts its
+    // Request and sets the ciaddr. The server is supposed to use ciaddr to
+    // pick the subnet for the client. In order to make sure that the server
+    // uses ciaddr, we simulate reception of the packet through eth1, for which
+    // there is no subnet for directly connected clients.
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth1");
+    req->setLocalAddr(IOAddress("10.0.0.1"));
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send reposonse.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+// This test verifies that when a client in the Rebinding state broadcasts
+// a Request message through an interface for which a subnet is configured,
+// the server responds to this Request. It also verifies that when such a
+// Request is sent through the interface for which there is no subnet configured
+// the client's message is discarded.
+TEST_F(DirectClientTest, rebind) {
+    // Configure IfaceMgr with fake interfaces lo, eth0 and eth1.
+    IfaceMgrTestConfig iface_config(true);
+    // After creating interfaces we have to open sockets as it is required
+    // by the message processing code.
+    ASSERT_NO_THROW(IfaceMgr::instance().openSockets4());
+    // Add a subnet.
+    ASSERT_NO_FATAL_FAILURE(configureSubnet("10.0.0.0"));
+    // Make sure that the subnet has been really added. Also, the subnet
+    // will be needed to create a lease for a client.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("10.0.0.10"),
+                                                      classify_);
+    // Create a lease, which will be later renewed. By explicitly creating a
+    // lease we will know the lease parameters, such as leased address etc.
+    const uint8_t hwaddr[] = { 1, 2, 3, 4, 5, 6 };
+    Lease4Ptr lease(new Lease4(IOAddress("10.0.0.10"), hwaddr, sizeof(hwaddr),
+                               &generateClientId()->getData()[0],
+                               generateClientId()->getData().size(),
+                               100, 50, 75, time(NULL),
+                               subnet->getID()));
+    LeaseMgrFactory::instance().addLease(lease);
+
+    // Broadcast Request through an interface for which there is no subnet
+    // configured. This messag should be discarded by the server.
+    Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth1");
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Broadcast another Request through an interface for which there is
+    // a subnet configured. The server should generate a response.
+    req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 5678));
+    req->setCiaddr(IOAddress("10.0.0.10"));
+    req = createClientMessage(req, "eth0");
+    req->setRemoteAddr(req->getCiaddr());
+
+    srv_.fakeReceive(req);
+
+    // Process clients' messages.
+    srv_.run();
+
+    // Check that the server did send exactly one reposonse.
+    ASSERT_EQ(1, srv_.fake_sent_.size());
+    Pkt4Ptr response = srv_.fake_sent_.front();
+    ASSERT_TRUE(response);
+
+    // Make sure that the server responsed with ACK, not NAK.
+    ASSERT_EQ(DHCPACK, response->getType());
+    // Make sure that the response is generated for the second Request
+    // (transmitted over eth0).
+    EXPECT_EQ(5678, response->getTransid());
+    // Check that the offered address belongs to the suitable subnet.
+    subnet = CfgMgr::instance().getSubnet4(response->getYiaddr(), classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("10.0.0.0", subnet->get().first.toText());
+
+}
+
+}

+ 360 - 102
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -16,8 +16,10 @@
 #include <asiolink/io_address.h>
 #include <dhcp/option4_client_fqdn.h>
 #include <dhcp/option_int_array.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp_ddns/ncr_msg.h>
+#include <dhcpsrv/cfgmgr.h>
 
 #include <gtest/gtest.h>
 #include <boost/scoped_ptr.hpp>
@@ -30,13 +32,59 @@ using namespace isc::dhcp_ddns;
 
 namespace {
 
-class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
+class NameDhcpv4SrvTest : public Dhcpv4SrvTest {
 public:
-    NameDhcpv4SrvTest() : Dhcpv4SrvFakeIfaceTest() {
+    // Reference to D2ClientMgr singleton
+    D2ClientMgr& d2_mgr_;
+
+    // Bit Constants for turning on and off DDNS configuration options.
+    static const uint16_t ALWAYS_INCLUDE_FQDN = 1;
+    static const uint16_t OVERRIDE_NO_UPDATE = 2;
+    static const uint16_t OVERRIDE_CLIENT_UPDATE = 4;
+    static const uint16_t REPLACE_CLIENT_NAME = 8;
+
+    NameDhcpv4SrvTest() : Dhcpv4SrvTest(),
+        d2_mgr_(CfgMgr::instance().getD2ClientMgr()) {
         srv_ = new NakedDhcpv4Srv(0);
+        // Config DDNS to be enabled, all controls off
+        enableD2();
     }
+
     virtual ~NameDhcpv4SrvTest() {
         delete srv_;
+        // CfgMgr singleton doesn't get wiped between tests, so  we'll
+        // disable D2 explictly between tests.
+        disableD2();
+    }
+
+    /// @brief Sets the server's DDNS configuration to ddns updates disabled.
+    void disableD2() {
+        // Default constructor creates a config with DHCP-DDNS updates
+        // disabled.
+        D2ClientConfigPtr cfg(new D2ClientConfig());
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
+    /// @brief Enables DHCP-DDNS updates with the given options enabled.
+    ///
+    /// Replaces the current D2ClientConfiguration with a configuration
+    /// which as updates enabled and the control options set based upon
+    /// the bit mask of options.
+    ///
+    /// @param mask Bit mask of configuration options that should be enabled.
+    void enableD2(const uint16_t mask = 0) {
+        D2ClientConfigPtr cfg;
+
+        ASSERT_NO_THROW(cfg.reset(new D2ClientConfig(true,
+                                  isc::asiolink::IOAddress("127.0.0.1"), 53001,
+                                  dhcp_ddns::NCR_UDP, dhcp_ddns::FMT_JSON,
+                                  (mask & ALWAYS_INCLUDE_FQDN),
+                                  (mask & OVERRIDE_NO_UPDATE),
+                                  (mask & OVERRIDE_CLIENT_UPDATE),
+                                  (mask & REPLACE_CLIENT_NAME),
+                                  "myhost", "example.com")));
+        ASSERT_NO_THROW(CfgMgr::instance().setD2ClientConfig(cfg));
+        ASSERT_NO_THROW(srv_->startD2());
     }
 
     // Create a lease to be used by various tests.
@@ -78,15 +126,18 @@ public:
         return (opt_hostname);
     }
 
-    // Generates partial hostname from the address. The format of the
-    // generated address is: host-A-B-C-D, where A.B.C.D is an IP
-    // address.
+    /// @brief Convenience method for generating an FQDN from an IP address.
+    ///
+    /// This is just a wrapper method around the D2ClientMgr's method for
+    /// generating domain names from the configured prefix, suffix, and a
+    /// given IP address.  This is useful for verifying that fully generated
+    /// names are correct.
+    ///
+    /// @param addr IP address used in the lease.
+    ///
+    /// @return An std::string contained the generated FQDN.
     std::string generatedNameFromAddress(const IOAddress& addr) {
-        std::string gen_name = addr.toText();
-        std::replace(gen_name.begin(), gen_name.end(), '.', '-');
-        std::ostringstream hostname;
-        hostname << "host-" << gen_name;
-        return (hostname.str());
+        return(CfgMgr::instance().getD2ClientMgr().generateFqdn(addr));
     }
 
     // Get the Client FQDN Option from the given message.
@@ -110,7 +161,7 @@ public:
                                 const bool include_clientid = true) {
         Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(msg_type, 1234));
         pkt->setRemoteAddr(IOAddress("192.0.2.3"));
-        pkt->setIface("eth0");
+        pkt->setIface("eth1");
         // For DISCOVER we don't include server id, because client broadcasts
         // the message to all servers.
         if (msg_type != DHCPDISCOVER) {
@@ -182,6 +233,21 @@ public:
         Option4ClientFqdnPtr fqdn = getClientFqdnOption(answer);
         ASSERT_TRUE(fqdn);
 
+        checkFqdnFlags(answer, exp_flags);
+
+        EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
+        EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
+
+    }
+
+    /// @brief Checks the packet's FQDN option flags against a given mask
+    ///
+    /// @param pkt IPv4 packet whose FQDN flags should be checked.
+    /// @param exp_flags Bit mask of flags that are expected to be true.
+    void checkFqdnFlags(const Pkt4Ptr& pkt, const uint8_t exp_flags) {
+        Option4ClientFqdnPtr fqdn = getClientFqdnOption(pkt);
+        ASSERT_TRUE(fqdn);
+
         const bool flag_n = (exp_flags & Option4ClientFqdn::FLAG_N) != 0;
         const bool flag_s = (exp_flags & Option4ClientFqdn::FLAG_S) != 0;
         const bool flag_o = (exp_flags & Option4ClientFqdn::FLAG_O) != 0;
@@ -191,12 +257,9 @@ public:
         EXPECT_EQ(flag_s, fqdn->getFlag(Option4ClientFqdn::FLAG_S));
         EXPECT_EQ(flag_o, fqdn->getFlag(Option4ClientFqdn::FLAG_O));
         EXPECT_EQ(flag_e, fqdn->getFlag(Option4ClientFqdn::FLAG_E));
-
-        EXPECT_EQ(exp_domain_name, fqdn->getDomainName());
-        EXPECT_EQ(exp_domain_type, fqdn->getDomainNameType());
-
     }
 
+
     // Processes the Hostname option in the client's message and returns
     // the hostname option which would be sent to the client. It will
     // throw NULL pointer if the hostname option is not to be included
@@ -221,8 +284,8 @@ public:
 
     }
 
-    // Test that the client message holding an FQDN is processed and the
-    // NameChangeRequests are generated.
+    // Test that the client message holding an FQDN is processed and
+    // that the response packet is as expected.
     void testProcessMessageWithFqdn(const uint8_t msg_type,
                             const std::string& hostname) {
         Pkt4Ptr req = generatePktWithFqdn(msg_type, Option4ClientFqdn::FLAG_S |
@@ -261,16 +324,19 @@ public:
                                  const time_t cltt,
                                  const uint16_t len,
                                  const bool not_strict_expire_check = false) {
-        NameChangeRequest ncr = srv_->name_change_reqs_.front();
-        EXPECT_EQ(type, ncr.getChangeType());
-        EXPECT_EQ(forward, ncr.isForwardChange());
-        EXPECT_EQ(reverse, ncr.isReverseChange());
-        EXPECT_EQ(addr, ncr.getIpAddress());
-        EXPECT_EQ(fqdn, ncr.getFqdn());
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = d2_mgr_.peekAt(0));
+        ASSERT_TRUE(ncr);
+
+        EXPECT_EQ(type, ncr->getChangeType());
+        EXPECT_EQ(forward, ncr->isForwardChange());
+        EXPECT_EQ(reverse, ncr->isReverseChange());
+        EXPECT_EQ(addr, ncr->getIpAddress());
+        EXPECT_EQ(fqdn, ncr->getFqdn());
         // Compare dhcid if it is not empty. In some cases, the DHCID is
         // not known in advance and can't be compared.
         if (!dhcid.empty()) {
-            EXPECT_EQ(dhcid, ncr.getDhcid().toStr());
+            EXPECT_EQ(dhcid, ncr->getDhcid().toStr());
         }
         // In some cases, the test doesn't have access to the last transmission
         // time for the particular client. In such cases, the test can use the
@@ -278,15 +344,68 @@ public:
         // for equality but rather check that the lease expiration time is not
         // greater than the current time + lease lifetime.
         if (not_strict_expire_check) {
-            EXPECT_GE(cltt + len, ncr.getLeaseExpiresOn());
+            EXPECT_GE(cltt + len, ncr->getLeaseExpiresOn());
         } else {
-            EXPECT_EQ(cltt + len, ncr.getLeaseExpiresOn());
+            EXPECT_EQ(cltt + len, ncr->getLeaseExpiresOn());
         }
-        EXPECT_EQ(len, ncr.getLeaseLength());
-        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr.getStatus());
-        srv_->name_change_reqs_.pop();
+        EXPECT_EQ(len, ncr->getLeaseLength());
+        EXPECT_EQ(isc::dhcp_ddns::ST_NEW, ncr->getStatus());
+
+        // Process the message off the queue
+        ASSERT_NO_THROW(d2_mgr_.runReadyIO());
     }
 
+
+    /// @brief Tests processing a request with the given client flags
+    ///
+    /// This method creates a request with its FQDN flags set to the given
+    /// value and submits it to the server for processing.  It then checks
+    /// the following:
+    /// 1. Did the server generate an ACK with the correct FQDN flags
+    /// 2. If the server should have generated an NCR, did it? and If
+    /// so was it correct?
+    ///
+    /// @param client_flags Mask of client FQDN flags which are true
+    /// @param response_flags Mask of expected FQDN flags in the response
+    void flagVsConfigScenario(const uint8_t client_flags,
+                       const uint8_t response_flags) {
+        // Create fake interfaces and open fake sockets.
+        IfaceMgrTestConfig iface_config(true);
+        IfaceMgr::instance().openSockets4();
+
+        Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, client_flags,
+                                          "myhost.example.com.",
+                                          Option4ClientFqdn::FULL, true);
+
+        // Process the request.
+        Pkt4Ptr reply;
+        ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+        // Verify the response and flags.
+        checkResponse(reply, DHCPACK, 1234);
+        checkFqdnFlags(reply, response_flags);
+
+        // NCRs cannot be sent to the d2_mgr unless updates are enabled.
+        if (d2_mgr_.ddnsEnabled()) {
+            // There should be an NCR only if response S flag is 1.
+            /// @todo This logic will need to change if forward and reverse
+            /// updates are ever controlled independently.
+            if ((response_flags & Option4ClientFqdn::FLAG_S) == 0) {
+                ASSERT_EQ(0, d2_mgr_.getQueueSize());
+            } else {
+                // Verify that there is one NameChangeRequest as expected.
+                ASSERT_EQ(1, d2_mgr_.getQueueSize());
+                verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
+                                        reply->getYiaddr().toText(),
+                                        "myhost.example.com.",
+                                        "", // empty DHCID means don't check it
+                                        time(NULL) + subnet_->getValid(),
+                                        subnet_->getValid(), true);
+            }
+        }
+    }
+
+
     NakedDhcpv4Srv* srv_;
 
 };
@@ -348,21 +467,101 @@ TEST_F(NameDhcpv4SrvTest, dhcidComputeFromHWAddr) {
     EXPECT_EQ(dhcid_ref, dhcid.toStr());
 }
 
+// Tests the following scenario:
+//  - Updates are enabled
+//  - All overrides are off
+//  - Client requests forward update  (N = 0, S = 1)
+//
+//  Server should perform the update:
+//  - Reponse flags should N = 0, S = 1, O = 0
+//  - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, updatesEnabled) {
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_S),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_S));
+}
 
-// Test that server confirms to perform the forward and reverse DNS update,
-// when client asks for it.
-TEST_F(NameDhcpv4SrvTest, serverUpdateForwardFqdn) {
-    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
-                                        Option4ClientFqdn::FLAG_E |
-                                        Option4ClientFqdn::FLAG_S,
-                                        "myhost.example.com.",
-                                        Option4ClientFqdn::FULL,
-                                        true);
+// Tests the following scenario
+//  - Updates are disabled
+//  - Client requests forward update  (N = 0, S = 1)
+//
+//  Server should NOT perform updates:
+//   - Response flags should N = 1, S = 0, O = 1
+//   - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, updatesDisabled) {
+    disableD2();
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_S),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_N |
+                           Option4ClientFqdn::FLAG_O));
+}
 
-    testProcessFqdn(query,
-                    Option4ClientFqdn::FLAG_E | Option4ClientFqdn::FLAG_S,
-                    "myhost.example.com.");
+// Tests the following scenario:
+//  - Updates are enabled
+//  - All overrides are off.
+//  - Client requests no updates  (N = 1, S = 0)
+//
+//  Server should NOT perform updates:
+//  - Response flags should N = 1, S = 0, O = 0
+//  - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectNoUpdate) {
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_N),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_N));
+}
+
+// Tests the following scenario:
+//  - Updates are enabled
+//  - override-no-update is on
+//  - Client requests no updates  (N = 1, S = 0)
+//
+// Server should override "no update" request and perform updates:
+// - Response flags should be  N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideNoUpdate) {
+    enableD2(OVERRIDE_NO_UPDATE);
+    flagVsConfigScenario((Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_N),
+                          (Option4ClientFqdn::FLAG_E |
+                           Option4ClientFqdn::FLAG_S |
+                           Option4ClientFqdn::FLAG_O));
+}
+
+// Tests the following scenario:
+//  - Updates are enabled
+//  - All overrides are off.
+//  - Client requests delegation  (N = 0, S = 0)
+//
+// Server should respect client's delegation request and NOT do updates:
 
+// - Response flags should be  N = 1, S = 0, O = 0
+// - Should not queue any NCRs
+TEST_F(NameDhcpv4SrvTest, respectClientDelegation) {
+
+    flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+                         (Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_N));
+}
+
+// Tests the following scenario:
+//  - Updates are enabled
+//  - override-client-update is on.
+//  - Client requests delegation  (N = 0, S = 0)
+//
+// Server should override client's delegation request and do updates:
+// - Response flags should be  N = 0, S = 1, O = 1
+// - Should queue an NCR
+TEST_F(NameDhcpv4SrvTest, overrideClientDelegation) {
+    // Turn on override-client-update.
+    enableD2(OVERRIDE_CLIENT_UPDATE);
+
+    flagVsConfigScenario(Option4ClientFqdn::FLAG_E,
+                         (Option4ClientFqdn::FLAG_E |
+                          Option4ClientFqdn::FLAG_S |
+                          Option4ClientFqdn::FLAG_O));
 }
 
 // Test that server processes the Hostname option sent by a client and
@@ -447,34 +646,6 @@ TEST_F(NameDhcpv4SrvTest, serverUpdateForwardNoNameFqdn) {
 
 }
 
-// Test server's response when client requests no DNS update.
-TEST_F(NameDhcpv4SrvTest, noUpdateFqdn) {
-    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
-                                        Option4ClientFqdn::FLAG_E |
-                                        Option4ClientFqdn::FLAG_N,
-                                        "myhost.example.com.",
-                                        Option4ClientFqdn::FULL,
-                                        true);
-    testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
-                    Option4ClientFqdn::FLAG_N,
-                    "myhost.example.com.");
-}
-
-// Test that server does not accept delegation of the forward DNS update
-// to a client.
-TEST_F(NameDhcpv4SrvTest, clientUpdateNotAllowedFqdn) {
-    Pkt4Ptr query = generatePktWithFqdn(DHCPREQUEST,
-                                        Option4ClientFqdn::FLAG_E,
-                                        "myhost.example.com.",
-                                        Option4ClientFqdn::FULL,
-                                        true);
-
-    testProcessFqdn(query, Option4ClientFqdn::FLAG_E |
-                    Option4ClientFqdn::FLAG_S | Option4ClientFqdn::FLAG_O,
-                    "myhost.example.com.");
-
-}
-
 // Test that exactly one NameChangeRequest is generated when the new lease
 // has been acquired (old lease is NULL).
 TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
@@ -483,7 +654,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNewLease) {
     Lease4Ptr old_lease;
 
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
 
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             "192.0.2.3", "myhost.example.com.",
@@ -502,7 +673,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsRenewNoChange) {
     old_lease->valid_lft_ += 100;
 
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease, old_lease));
-    EXPECT_TRUE(srv_->name_change_reqs_.empty());
+    ASSERT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that no NameChangeRequest is generated when forward and reverse
@@ -515,7 +686,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNoUpdate) {
                                    "lease2.example.com.",
                                    false, false);
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
-    EXPECT_EQ(1, srv_->name_change_reqs_.size());
+    EXPECT_EQ(1, d2_mgr_.getQueueSize());
 
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "192.0.2.3", "lease1.example.com.",
@@ -527,7 +698,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsNoUpdate) {
     lease2->fqdn_rev_ = true;
     lease2->fqdn_fwd_ = true;
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
-    EXPECT_EQ(1, srv_->name_change_reqs_.size());
+    EXPECT_EQ(1, d2_mgr_.getQueueSize());
 
 }
 
@@ -541,7 +712,7 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsRenew) {
                                    "lease2.example.com.",
                                    true, true);
     ASSERT_NO_THROW(srv_->createNameChangeRequests(lease2, lease1));
-    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
 
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "192.0.2.3", "lease1.example.com.",
@@ -574,6 +745,9 @@ TEST_F(NameDhcpv4SrvTest, createNameChangeRequestsLeaseMismatch) {
 // Test that the OFFER message generated as a result of the DISCOVER message
 // processing will not result in generation of the NameChangeRequests.
 TEST_F(NameDhcpv4SrvTest, processDiscover) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPDISCOVER, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
@@ -583,12 +757,15 @@ TEST_F(NameDhcpv4SrvTest, processDiscover) {
     ASSERT_NO_THROW(reply = srv_->processDiscover(req));
     checkResponse(reply, DHCPOFFER, 1234);
 
-    EXPECT_TRUE(srv_->name_change_reqs_.empty());
+    EXPECT_EQ(0, d2_mgr_.getQueueSize());
 }
 
 // Test that server generates client's hostname from the IP address assigned
 // to it when DHCPv4 Client FQDN option specifies an empty domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "", Option4ClientFqdn::PARTIAL, true);
@@ -599,25 +776,56 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
     // The hostname is generated from the IP address acquired (yiaddr).
-    std::ostringstream hostname;
-    hostname << generatedNameFromAddress(reply->getYiaddr())
-             << ".example.com.";
+    std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
-                            reply->getYiaddr().toText(), hostname.str(),
+                            reply->getYiaddr().toText(), hostname,
                             "", // empty DHCID forces that it is not checked
                             time(NULL) + subnet_->getValid(),
                             subnet_->getValid(), true);
 }
 
 // Test that server generates client's hostname from the IP address assigned
+// to it when DHCPv4 Client FQDN option specifies an empty domain-name  AND
+// ddns updates are disabled.
+TEST_F(NameDhcpv4SrvTest, processRequestEmptyDomainNameDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    disableD2();
+    Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+                                      Option4ClientFqdn::FLAG_E,
+                                      "", Option4ClientFqdn::PARTIAL, true);
+    Pkt4Ptr reply;
+    ASSERT_NO_THROW(reply = srv_->processRequest(req));
+
+    checkResponse(reply, DHCPACK, 1234);
+
+    Option4ClientFqdnPtr fqdn = getClientFqdnOption(reply);
+    ASSERT_TRUE(fqdn);
+
+    // The hostname is generated from the IP address acquired (yiaddr).
+    std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
+    EXPECT_EQ(hostname, fqdn->getDomainName());
+    EXPECT_EQ(Option4ClientFqdn::FULL, fqdn->getDomainNameType());
+}
+
+
+// Test that server generates client's hostname from the IP address assigned
 // to it when Hostname option carries the top level domain-name.
 TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req->setIface("eth0");
+    req->setIface("eth1");
 
     Pkt4Ptr reply;
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
@@ -625,12 +833,13 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+
     // The hostname is generated from the IP address acquired (yiaddr).
-    std::ostringstream hostname;
-    hostname << generatedNameFromAddress(reply->getYiaddr()) << ".example.com.";
+    std::string hostname = generatedNameFromAddress(reply->getYiaddr());
+
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
-                            reply->getYiaddr().toText(), hostname.str(),
+                            reply->getYiaddr().toText(), hostname,
                             "", // empty DHCID forces that it is not checked
                             time(NULL), subnet_->getValid(), true);
 }
@@ -640,6 +849,9 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 // request but modify the DNS entries for the lease according to the contents
 // of the FQDN sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                        Option4ClientFqdn::FLAG_E,
                                        "myhost.example.com.",
@@ -651,7 +863,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
@@ -671,7 +883,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
     checkResponse(reply, DHCPACK, 1234);
 
     // There should be two NameChangeRequests. Verify that they are valid.
-    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             reply->getYiaddr().toText(),
                             "myhost.example.com.",
@@ -692,11 +904,14 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsFqdn) {
 // but modify the DNS entries for the lease according to the contents of the
 // Hostname sent in the second request.
 TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
     Pkt4Ptr req1 = generatePktWithHostname(DHCPREQUEST, "myhost.example.com.");
 
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req1->setIface("eth0");
+    req1->setIface("eth1");
 
 
     Pkt4Ptr reply;
@@ -705,7 +920,7 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
     checkResponse(reply, DHCPACK, 1234);
 
     // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
@@ -719,14 +934,14 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
 
     // Set interface for the incoming packet. The server requires it to
     // generate client id.
-    req2->setIface("eth0");
+    req2->setIface("eth1");
 
     ASSERT_NO_THROW(reply = srv_->processRequest(req2));
 
     checkResponse(reply, DHCPACK, 1234);
 
     // There should be two NameChangeRequests. Verify that they are valid.
-    ASSERT_EQ(2, srv_->name_change_reqs_.size());
+    ASSERT_EQ(2, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
                             reply->getYiaddr().toText(),
                             "myhost.example.com.",
@@ -742,40 +957,83 @@ TEST_F(NameDhcpv4SrvTest, processTwoRequestsHostname) {
                             time(NULL), subnet_->getValid(), true);
 }
 
-// Test that when the Release message is sent for the previously acquired
-// lease, then server genenerates a NameChangeRequest to remove the entries
-// corresponding to the lease being released.
+// Test that when a release message is sent for a previously acquired lease,
+// DDNS updates are enabled that the server genenerates a NameChangeRequest
+// to remove entries corresponding to the released lease.
 TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    // Verify the updates are enabled.
+    ASSERT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+    // Create and process a lease request so we have a lease to release.
     Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
                                       Option4ClientFqdn::FLAG_E,
                                       "myhost.example.com.",
                                       Option4ClientFqdn::FULL, true);
-
     Pkt4Ptr reply;
     ASSERT_NO_THROW(reply = srv_->processRequest(req));
-
     checkResponse(reply, DHCPACK, 1234);
 
-    // Verify that there is one NameChangeRequest generated.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    // Verify that there is one NameChangeRequest generated for lease.
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
                             "00010132E91AA355CFBB753C0F0497A5A940436"
                             "965B68B6D438D98E680BF10B09F3BCF",
                             time(NULL), subnet_->getValid(), true);
 
-    // Create a Release message.
+    // Create and process the Release message.
     Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
     rel->setCiaddr(reply->getYiaddr());
     rel->setRemoteAddr(IOAddress("192.0.2.3"));
     rel->addOption(generateClientId());
     rel->addOption(srv_->getServerID());
-
     ASSERT_NO_THROW(srv_->processRelease(rel));
 
     // The lease has been removed, so there should be a NameChangeRequest to
     // remove corresponding DNS entries.
-    ASSERT_EQ(1, srv_->name_change_reqs_.size());
+    ASSERT_EQ(1, d2_mgr_.getQueueSize());
+    verifyNameChangeRequest(isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            reply->getYiaddr().toText(), "myhost.example.com.",
+                            "00010132E91AA355CFBB753C0F0497A5A940436"
+                            "965B68B6D438D98E680BF10B09F3BCF",
+                            time(NULL), subnet_->getValid(), true);
+}
+
+// Test that when the Release message is sent for a previously acquired lease
+// and DDNS updates are disabled that server does NOT generate a
+// NameChangeRequest to remove entries corresponding to the released lease.
+// Queue size is not available when updates are not enabled, however,
+// attempting to send a NCR when updates disabled will result in a throw.
+// If no throws are experienced then no attempt was made to send a NCR.
+TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+    // Create fake interfaces and open fake sockets.
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    // Disable DDNS.
+    disableD2();
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+    // Create and process a lease request so we have a lease to release.
+    Pkt4Ptr req = generatePktWithFqdn(DHCPREQUEST, Option4ClientFqdn::FLAG_S |
+                                      Option4ClientFqdn::FLAG_E,
+                                      "myhost.example.com.",
+                                      Option4ClientFqdn::FULL, true);
+    Pkt4Ptr reply;
+    ASSERT_NO_THROW(reply = srv_->processRequest(req));
+    checkResponse(reply, DHCPACK, 1234);
+
+    // Create and process the Release message.
+    Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+    rel->setCiaddr(reply->getYiaddr());
+    rel->setRemoteAddr(IOAddress("192.0.2.3"));
+    rel->addOption(generateClientId());
+    rel->addOption(srv_->getServerID());
+    ASSERT_NO_THROW(srv_->processRelease(rel));
 }
 
+
 } // end of anonymous namespace

+ 18 - 1
src/bin/dhcp6/config_parser.cc

@@ -368,7 +368,7 @@ public:
     /// @param ignored first parameter
     /// stores global scope parameters, options, option defintions.
     Subnet6ConfigParser(const std::string&)
-        :SubnetConfigParser("", globalContext()) {
+        :SubnetConfigParser("", globalContext(), IOAddress("::")) {
     }
 
     /// @brief Adds the created subnet to a server's configuration.
@@ -381,6 +381,12 @@ public:
                 isc_throw(Unexpected,
                           "Invalid cast in Subnet4ConfigParser::commit");
             }
+
+            // Set relay infomation if it was provided
+            if (relay_info_) {
+                sub6ptr->setRelayInfo(*relay_info_);
+            }
+
             isc::dhcp::CfgMgr::instance().addSubnet6(sub6ptr);
         }
     }
@@ -404,10 +410,13 @@ protected:
             parser = new Uint32Parser(config_id, uint32_values_);
         } else if ((config_id.compare("subnet") == 0) ||
                    (config_id.compare("interface") == 0) ||
+                   (config_id.compare("client-class") == 0) ||
                    (config_id.compare("interface-id") == 0)) {
             parser = new StringParser(config_id, string_values_);
         } else if (config_id.compare("pool") == 0) {
             parser = new Pool6Parser(config_id, pools_);
+        } else if (config_id.compare("relay") == 0) {
+            parser = new RelayInfoParser(config_id, relay_info_, Option::V6);
         } else if (config_id.compare("pd-pools") == 0) {
             parser = new PdPoolListParser(config_id, pools_);
         } else if (config_id.compare("option-data") == 0) {
@@ -518,6 +527,14 @@ protected:
             subnet6->setInterfaceId(opt);
         }
 
+        // Try setting up client class (if specified)
+        try {
+            string client_class = string_values_->getParam("client-class");
+            subnet6->allowClientClass(client_class);
+        } catch (const DhcpConfigError&) {
+            // That's ok if it fails. client-class is optional.
+        }
+
         subnet_.reset(subnet6);
     }
 

+ 7 - 4
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -227,7 +227,7 @@ void ControlledDhcpv6Srv::establishSession() {
     int ctrl_socket = cc_session_->getSocketDesc();
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
               .arg(ctrl_socket);
-    IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
+    IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
 }
 
 void ControlledDhcpv6Srv::disconnectSession() {
@@ -236,13 +236,16 @@ void ControlledDhcpv6Srv::disconnectSession() {
         config_session_ = NULL;
     }
     if (cc_session_) {
+
+        int ctrl_socket = cc_session_->getSocketDesc();
         cc_session_->disconnect();
+
+        // deregister session socket
+        IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
+
         delete cc_session_;
         cc_session_ = NULL;
     }
-
-    // deregister session socket
-    IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL);
 }
 
 ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)

+ 2 - 1
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -105,7 +105,8 @@ protected:
     /// various configuration values. Installing the dummy handler
     /// that guarantees to return success causes initial configuration
     /// to be stored for the session being created and that it can
-    /// be later accessed with \ref isc::config::ConfigData::getFullConfig.
+    /// be later accessed with
+    /// \ref isc::config::ConfigData::getFullConfig().
     ///
     /// @param new_config new configuration.
     ///

+ 7 - 1
src/bin/dhcp6/dhcp6.dox

@@ -32,7 +32,7 @@
 
  @section dhcpv6Session BIND10 message queue integration
 
- DHCPv4 server component is now integrated with BIND10 message queue.
+ DHCPv6 server component is now integrated with BIND10 message queue.
  It follows the same principle as DHCPv4. See \ref dhcpv4Session for
  details.
 
@@ -217,6 +217,12 @@ Currently there is no class behaviour coded in DHCPv6, hence no v6 equivalent of
 @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing. Should any need for such a code arise,
 it will be conducted in an external hooks library.
 
+It is possible to define class restrictions in subnet, so a given subnet is only
+accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt6::classes_
+being passed in isc::dhcp::Dhcpv6Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet6().
+Currently this capability is usable, but the number of scenarios it supports is
+limited.
+
  @section dhcpv6Other Other DHCPv6 topics
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

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

@@ -254,6 +254,30 @@
                         "item_default": ""
                     }
                 },
+
+                { "item_name": "client-class",
+                  "item_type": "string",
+                  "item_optional": false,
+                  "item_default": "",
+                  "item_description" : "Restricts access to this subnet to specified client class (if defined)"
+                },
+
+                { "item_name": "relay",
+                  "item_type": "map",
+                  "item_optional": false,
+                  "item_default": {},
+                  "item_description" : "Structure holding relay information.",
+                  "map_item_spec": [
+                      {
+                          "item_name": "ip-address",
+                          "item_type": "string",
+                          "item_optional": false,
+                          "item_default": "::",
+                          "item_description" : "IPv6 address of the relay (defaults to :: if not specified)."
+                      }
+                   ]
+                },
+
                 {
                   "item_name": "pd-pools",
                   "item_type": "list",

+ 11 - 8
src/bin/dhcp6/dhcp6_messages.mes

@@ -1,4 +1,4 @@
-# Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2012-2014  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
@@ -104,13 +104,7 @@ in its response to the client.
 This debug message is logged when server has found the DHCPv6 Client FQDN Option
 sent by a client and started processing it.
 
-% DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME FQDN for the lease being deleted is empty: %1
-This error message is issued when a lease being deleted contains an indication
-that the DNS Update has been performed for it, but the FQDN is missing for this
-lease. This is an indication that someone may have messed up in the lease
-database.
-
-% DHCP6_DDNS_REMOVE_INVALID_HOSTNAME FQDN for the lease being deleted has invalid format: %1
+% DHCP6_DDNS_REMOVE_INVALID_HOSTNAME invalid FQDN: %1 for the lease: %2 when removing DNS bindings
 This error message is issued when a lease being deleted contains an indication
 that the DNS Update has been performed for it, but the FQDN held in the lease
 database has invalid format and can't be transformed to the canonical on-wire
@@ -237,6 +231,11 @@ but the lease does not contain any client identification. This is most
 likely due to a software error: please raise a bug report. As a temporary
 workaround, manually remove the lease entry from the database.
 
+% DHCP6_NAME_GEN_UPDATE_FAIL failed to update the lease using address %1, after generating FQDN for a client, reason: %2
+This message indicates the failure when trying to update the lease and/or
+options in the server's response with the hostname generated by the server
+from the acquired address.
+
 % DHCP6_NOT_RUNNING IPv6 DHCP server is not running
 A warning message is issued when an attempt is made to shut down the
 IPv6 DHCP server but it is not running.
@@ -272,6 +271,10 @@ of packet.  Note that a packet marked as UNKNOWN may well be a valid
 DHCP packet, just a type not expected by the server (e.g. it will report
 a received OFFER packet as UNKNOWN).
 
+% DHCP6_PACKET_MISMATCH_SERVERID_DROP dropping packet %1 (transid=%2, interface=%3) having mismatched server identifier
+A debug message noting that server has received message with server identifier
+option that not matching server identifier that server is using.
+
 % DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
 The IPv6 DHCP server tried to receive a packet but an error
 occurred during this attempt. The reason for the error is included in

+ 223 - 137
src/bin/dhcp6/dhcp6_srv.cc

@@ -104,9 +104,6 @@ namespace {
 // DHCPv6 Client FQDN Option sent by a client. They will be removed
 // when DDNS parameters for DHCPv6 are implemented with the ticket #3034.
 
-// Should server always include the FQDN option in its response, regardless
-// if it has been requested in ORO (Disabled).
-const bool FQDN_ALWAYS_INCLUDE = false;
 // Enable AAAA RR update delegation to the client (Disabled).
 const bool FQDN_ALLOW_CLIENT_UPDATE = false;
 // Globally enable updates (Enabled).
@@ -211,6 +208,33 @@ void Dhcpv6Srv::sendPacket(const Pkt6Ptr& packet) {
     IfaceMgr::instance().send(packet);
 }
 
+bool
+Dhcpv6Srv::testServerID(const Pkt6Ptr& pkt){
+	/// @todo Currently we always check server identifier regardless if
+	/// it is allowed in the received message or not (per RFC3315).
+	/// If the server identifier is not allowed in the message, the
+	/// sanityCheck function should deal with it. We may rethink this
+	/// design if we decide that it is appropriate to check at this stage
+	/// of message processing that the server identifier must or must not
+	/// be present. In such case however, the logic checking server id
+	/// will have to be removed from sanityCheck and placed here instead,
+	/// to avoid duplicate checks.
+	OptionPtr server_id = pkt->getOption(D6O_SERVERID);
+	if (server_id){
+		// Let us test received ServerID if it is same as ServerID
+		// which is beeing used by server
+		if (getServerID()->getData() != server_id->getData()){
+			LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_PACKET_MISMATCH_SERVERID_DROP)
+				.arg(pkt->getName())
+				.arg(pkt->getTransid())
+				.arg(pkt->getIface());
+			return (false);
+		}
+	}
+	// retun True if: no serverid received or ServerIDs matching
+	return (true);
+}
+
 bool Dhcpv6Srv::run() {
     while (!shutdown_) {
         /// @todo Calculate actual timeout to the next event (e.g. lease
@@ -283,6 +307,12 @@ bool Dhcpv6Srv::run() {
                 continue;
             }
         }
+        // Check if received query carries server identifier matching
+        // server identifier being used by the server.
+        if (!testServerID(query)){
+        	continue;
+        }
+
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
             .arg(query->getName());
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
@@ -613,10 +643,11 @@ Dhcpv6Srv::generateServerID() {
         seconds -= DUID_TIME_EPOCH;
 
         OptionBuffer srvid(8 + iface->getMacLen());
-        writeUint16(DUID::DUID_LLT, &srvid[0]);
-        writeUint16(HWTYPE_ETHERNET, &srvid[2]);
-        writeUint32(static_cast<uint32_t>(seconds), &srvid[4]);
-        memcpy(&srvid[0] + 8, iface->getMac(), iface->getMacLen());
+        // We know that the buffer is more than 8 bytes long at this point.
+        writeUint16(DUID::DUID_LLT, &srvid[0], 2);
+        writeUint16(HWTYPE_ETHERNET, &srvid[2], 2);
+        writeUint32(static_cast<uint32_t>(seconds), &srvid[4], 4);
+        memcpy(&srvid[8], iface->getMac(), iface->getMacLen());
 
         serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
                                          srvid.begin(), srvid.end()));
@@ -629,8 +660,8 @@ Dhcpv6Srv::generateServerID() {
     // See Section 9.3 of RFC3315 for details.
 
     OptionBuffer srvid(12);
-    writeUint16(DUID::DUID_EN, &srvid[0]);
-    writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);
+    writeUint16(DUID::DUID_EN, &srvid[0], srvid.size());
+    writeUint32(ENTERPRISE_ID_ISC, &srvid[2], srvid.size() - 2);
 
     // Length of the identifier is company specific. I hereby declare
     // ISC "standard" of 6 bytes long pseudo-random numbers.
@@ -829,10 +860,12 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         // This is a direct (non-relayed) message
 
         // Try to find a subnet if received packet from a directly connected client
-        subnet = CfgMgr::instance().getSubnet6(question->getIface());
+        subnet = CfgMgr::instance().getSubnet6(question->getIface(),
+                                               question->classes_);
         if (!subnet) {
             // If no subnet was found, try to find it based on remote address
-            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+            subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr(),
+                                                   question->classes_);
         }
     } else {
 
@@ -840,17 +873,19 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
         OptionPtr interface_id = question->getAnyRelayOption(D6O_INTERFACE_ID,
                                                              Pkt6::RELAY_GET_FIRST);
         if (interface_id) {
-            subnet = CfgMgr::instance().getSubnet6(interface_id);
+            subnet = CfgMgr::instance().getSubnet6(interface_id,
+                                                   question->classes_);
         }
 
         if (!subnet) {
-            // If no interface-id was specified (or not configured on server), let's
-            // try address matching
+            // If no interface-id was specified (or not configured on server),
+            // let's try address matching
             IOAddress link_addr = question->relay_info_.back().linkaddr_;
 
             // if relay filled in link_addr field, then let's use it
             if (link_addr != IOAddress("::")) {
-                subnet = CfgMgr::instance().getSubnet6(link_addr);
+                subnet = CfgMgr::instance().getSubnet6(link_addr,
+                                                       question->classes_);
             }
         }
     }
@@ -890,8 +925,7 @@ Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
 }
 
 void
-Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
-                        const Option6ClientFqdnPtr& fqdn) {
+Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
 
     // We need to allocate addresses for all IA_NA options in the client's
     // question (i.e. SOLICIT or REQUEST) message.
@@ -944,10 +978,9 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
          opt != question->options_.end(); ++opt) {
         switch (opt->second->getType()) {
         case D6O_IA_NA: {
-            OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
+            OptionPtr answer_opt = assignIA_NA(subnet, duid, question, answer,
                                                boost::dynamic_pointer_cast<
-                                               Option6IA>(opt->second),
-                                               fqdn);
+                                               Option6IA>(opt->second));
             if (answer_opt) {
                 answer->addOption(answer_opt);
             }
@@ -967,14 +1000,14 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
     }
 }
 
-Option6ClientFqdnPtr
-Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
+void
+Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer) {
     // Get Client FQDN Option from the client's message. If this option hasn't
     // been included, do nothing.
     Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
         Option6ClientFqdn>(question->getOption(D6O_CLIENT_FQDN));
     if (!fqdn) {
-        return (fqdn);
+        return;
     }
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
@@ -1025,13 +1058,14 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
     // generate one.
     if (fqdn->getDomainNameType() == Option6ClientFqdn::PARTIAL) {
         std::ostringstream name;
-        if (fqdn->getDomainName().empty()) {
-            name << FQDN_GENERATED_PARTIAL_NAME;
+        if (fqdn->getDomainName().empty() || FQDN_REPLACE_CLIENT_NAME) {
+            fqdn->setDomainName("", Option6ClientFqdn::PARTIAL);
+
         } else {
             name << fqdn->getDomainName();
+            name << "." << FQDN_PARTIAL_SUFFIX;
+            fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
         }
-        name << "." << FQDN_PARTIAL_SUFFIX;
-        fqdn_resp->setDomainName(name.str(), Option6ClientFqdn::FULL);
 
     // Server may be configured to replace a name supplied by a client,
     // even if client supplied fully qualified domain-name.
@@ -1042,58 +1076,17 @@ Dhcpv6Srv::processClientFqdn(const Pkt6Ptr& question) {
 
     }
 
-    // Return the FQDN option which can be included in the server's response.
-    // Note that it doesn't have to be included, if client didn't request
-    // it using ORO and server is not configured to always include it.
-    return (fqdn_resp);
+    // The FQDN has been processed successfully. Let's append it to the
+    // response to be sent to a client. Note that the Client FQDN option is
+    // always sent back to the client if Client FQDN was included in the
+    // client's message.
+    answer->addOption(fqdn_resp);
 }
 
-
 void
-Dhcpv6Srv::appendClientFqdn(const Pkt6Ptr& question,
-                            Pkt6Ptr& answer,
-                            const Option6ClientFqdnPtr& fqdn) {
-
-    // If FQDN is NULL, it means that client did not request DNS Update, plus
-    // server doesn't force updates.
-    if (!fqdn) {
-        return;
-    }
-
-    // Server sends back the FQDN option to the client if client has requested
-    // it using Option Request Option. However, server may be configured to
-    // send the FQDN option in its response, regardless whether client requested
-    // it or not.
-    bool include_fqdn = FQDN_ALWAYS_INCLUDE;
-    if (!include_fqdn) {
-        OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<
-            OptionUint16Array>(question->getOption(D6O_ORO));
-        if (oro) {
-            const std::vector<uint16_t>& values = oro->getValues();
-            for (int i = 0; i < values.size(); ++i) {
-                if (values[i] == D6O_CLIENT_FQDN) {
-                    include_fqdn = true;
-                }
-            }
-        }
-    }
-
-    if (include_fqdn) {
-        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
-                  DHCP6_DDNS_SEND_FQDN).arg(fqdn->toText());
-        answer->addOption(fqdn);
-    }
-
-}
-
-void
-Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
-                                    const Option6ClientFqdnPtr& opt_fqdn) {
-
-    // It is likely that client haven't included the FQDN option in the message
-    // and server is not configured to always update DNS. In such cases,
-    // FQDN option will be NULL. This is valid state, so we simply return.
-    if (!opt_fqdn) {
+Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer) {
+    // Don't create NameChangeRequests if DNS updates are disabled.
+    if (!FQDN_ENABLE_UPDATE) {
         return;
     }
 
@@ -1106,6 +1099,14 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
                   << " NULL when creating DNS NameChangeRequest");
     }
 
+    // It is likely that client haven't included the FQDN option. In such case,
+    // FQDN option will be NULL. This is valid state, so we simply return.
+    Option6ClientFqdnPtr opt_fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+    if (!opt_fqdn) {
+        return;
+    }
+
     // Get the Client Id. It is mandatory and a function creating a response
     // would have thrown an exception if it was missing. Thus throwning
     // Unexpected if it is missing as it is a programming error.
@@ -1133,8 +1134,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
     OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
     for (OptionCollection::const_iterator answer_ia =
              answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
-        // @todo IA_NA may contain multiple addresses. We should process
-        // each address individually. Currently we get only one.
+        /// @todo IA_NA may contain multiple addresses. We should process
+        /// each address individually. Currently we get only one.
         Option6IAAddrPtr iaaddr = boost::static_pointer_cast<
             Option6IAAddr>(answer_ia->second->getOption(D6O_IAADDR));
         // We need an address to create a name-to-address mapping.
@@ -1160,31 +1161,33 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
 
         LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                   DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST).arg(ncr.toText());
+
+        /// @todo Currently we create NCR with the first IPv6 address that
+        /// is carried in one of the IA_NAs. In the future, the NCR API should
+        /// be extended to map multiple IPv6 addresses to a single FQDN.
+        /// In such case, this return statement will be removed.
+        return;
     }
 }
 
 void
 Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
-    // If we haven't performed a DNS Update when lease was acquired,
-    // there is nothing to do here.
-    if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) {
+    // Don't create NameChangeRequests if DNS updates are disabled.
+    if (!FQDN_ENABLE_UPDATE) {
         return;
     }
 
-    // When lease was added into a database the host name should have
-    // been added. The hostname can be empty if someone messed up in the
-    // lease data base and removed the hostname.
-    if (lease->hostname_.empty()) {
-        LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_EMPTY_HOSTNAME)
-            .arg(lease->addr_.toText());
+    // If we haven't performed a DNS Update when lease was acquired,
+    // there is nothing to do here.
+    if (!lease->fqdn_fwd_ && !lease->fqdn_rev_) {
         return;
     }
 
     // If hostname is non-empty, try to convert it to wire format so as
     // DHCID can be computed from it. This may throw an exception if hostname
-    // has invalid format. Again, this should be only possible in case of
-    // manual intervention in the database. Note that the last parameter
-    // passed to the writeFqdn function forces conversion of the FQDN
+    // has invalid format or is empty. Again, this should be only possible
+    // in case of manual intervention in the database. Note that the last
+    // parameter passed to the writeFqdn function forces conversion of the FQDN
     // to lower case. This is required by the RFC4701, section 3.5.
     // The DHCID computation is further in this function.
     std::vector<uint8_t> hostname_wire;
@@ -1192,7 +1195,8 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
         OptionDataTypeUtil::writeFqdn(lease->hostname_, hostname_wire, true);
     } catch (const Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_DDNS_REMOVE_INVALID_HOSTNAME)
-            .arg(lease->hostname_);
+            .arg(lease->hostname_.empty() ? "(empty)" : lease->hostname_)
+            .arg(lease->addr_.toText());
         return;
     }
 
@@ -1233,14 +1237,14 @@ Dhcpv6Srv::sendNameChangeRequests() {
 
 OptionPtr
 Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                       const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
-                       const Option6ClientFqdnPtr& fqdn) {
+                       const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                       boost::shared_ptr<Option6IA> ia) {
     // If there is no subnet selected for handling this IA_NA, the only thing to do left is
     // to say that we are sorry, but the user won't get an address. As a convenience, we
     // use a different status text to indicate that (compare to the same status code,
     // but different wording below)
     if (!subnet) {
-        // Create empty IA_NA option with IAID matching the request.
+        // Creatasse empty IA_NA option with IAID matching the request.
         // Note that we don't use OptionDefinition class to create this option.
         // This is because we prefer using a constructor of Option6IA that
         // initializes IAID. Otherwise we would have to use setIAID() after
@@ -1255,16 +1259,16 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // Check if the client sent us a hint in his IA_NA. Clients may send an
     // address in their IA_NA options as a suggestion (e.g. the last address
     // they used before).
-    boost::shared_ptr<Option6IAAddr> hintOpt = boost::dynamic_pointer_cast<Option6IAAddr>
-                                        (ia->getOption(D6O_IAADDR));
+    boost::shared_ptr<Option6IAAddr> hint_opt =
+        boost::dynamic_pointer_cast<Option6IAAddr>(ia->getOption(D6O_IAADDR));
     IOAddress hint("::");
-    if (hintOpt) {
-        hint = hintOpt->getAddress();
+    if (hint_opt) {
+        hint = hint_opt->getAddress();
     }
 
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_NA_REQUEST)
-        .arg(duid?duid->toText():"(no-duid)").arg(ia->getIAID())
-        .arg(hintOpt?hint.toText():"(no hint)");
+        .arg(duid ? duid->toText() : "(no-duid)").arg(ia->getIAID())
+        .arg(hint_opt ? hint.toText() : "(no hint)");
 
     // "Fake" allocation is processing of SOLICIT message. We pretend to do an
     // allocation, but we do not put the lease in the database. That is ok,
@@ -1288,6 +1292,8 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // the update.
     bool do_fwd = false;
     bool do_rev = false;
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
     if (fqdn) {
         // Flag S must not coexist with flag N being set to 1, so if S=1
         // server takes responsibility for both reverse and forward updates.
@@ -1309,13 +1315,15 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
+    Lease6Collection old_leases;
     Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
                                                              ia->getIAID(),
                                                              hint, Lease::TYPE_NA,
                                                              do_fwd, do_rev,
                                                              hostname,
                                                              fake_allocation,
-                                                             callout_handle);
+                                                             callout_handle,
+                                                             old_leases);
     /// @todo: Handle more than one lease
     Lease6Ptr lease;
     if (!leases.empty()) {
@@ -1350,26 +1358,25 @@ Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
         // but this is considered waste of bandwidth as absence of status
         // code is considered a success.
 
+        Lease6Ptr old_lease;
+        if (!old_leases.empty()) {
+            old_lease = *old_leases.begin();
+        }
         // Allocation engine may have returned an existing lease. If so, we
         // have to check that the FQDN settings we provided are the same
         // that were set. If they aren't, we will have to remove existing
         // DNS records and update the lease with the new settings.
-        if ((lease->hostname_ != hostname) || (lease->fqdn_fwd_ != do_fwd) ||
-            (lease->fqdn_rev_ != do_rev)) {
+        if (!fake_allocation && old_lease &&
+            !lease->hasIdenticalFqdn(*old_lease)) {
             LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL,
                       DHCP6_DDNS_LEASE_ASSIGN_FQDN_CHANGE)
-                .arg(lease->toText())
+                .arg(old_lease->toText())
                 .arg(hostname)
                 .arg(do_rev ? "true" : "false")
                 .arg(do_fwd ? "true" : "false");
 
             // Schedule removal of the existing lease.
-            createRemovalNameChangeRequest(lease);
-            // Set the new lease properties and update.
-            lease->hostname_ = hostname;
-            lease->fqdn_fwd_ = do_fwd;
-            lease->fqdn_rev_ = do_rev;
-            LeaseMgrFactory::instance().updateLease6(lease);
+            createRemovalNameChangeRequest(old_lease);
         }
 
     } else {
@@ -1437,13 +1444,15 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // will try to honour the hint, but it is just a hint - some other address
     // may be used instead. If fake_allocation is set to false, the lease will
     // be inserted into the LeaseMgr as well.
+    Lease6Collection old_leases;
     Lease6Collection leases = alloc_engine_->allocateLeases6(subnet, duid,
-                                                            ia->getIAID(),
-                                                            hint, Lease::TYPE_PD,
-                                                            false, false,
-                                                            string(),
-                                                            fake_allocation,
-                                                            callout_handle);
+                                                             ia->getIAID(),
+                                                             hint, Lease::TYPE_PD,
+                                                             false, false,
+                                                             string(),
+                                                             fake_allocation,
+                                                             callout_handle,
+                                                             old_leases);
 
     if (!leases.empty()) {
 
@@ -1491,8 +1500,8 @@ Dhcpv6Srv::assignIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 
 OptionPtr
 Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                      const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
-                      const Option6ClientFqdnPtr& fqdn) {
+                      const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                      boost::shared_ptr<Option6IA> ia) {
     if (!subnet) {
         // There's no subnet select for this client. There's nothing to renew.
         boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
@@ -1541,6 +1550,8 @@ Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
     // the update.
     bool do_fwd = false;
     bool do_rev = false;
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
     if (fqdn) {
         if (fqdn->getFlag(Option6ClientFqdn::FLAG_S)) {
             do_fwd = true;
@@ -1733,8 +1744,7 @@ Dhcpv6Srv::renewIA_PD(const Subnet6Ptr& subnet, const DuidPtr& duid,
 }
 
 void
-Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
-                       const Option6ClientFqdnPtr& fqdn) {
+Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
 
     // We need to renew addresses for all IA_NA options in the client's
     // RENEW message.
@@ -1778,10 +1788,9 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
         switch (opt->second->getType()) {
 
         case D6O_IA_NA: {
-            OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
+            OptionPtr answer_opt = renewIA_NA(subnet, duid, renew, reply,
                                               boost::dynamic_pointer_cast<
-                                              Option6IA>(opt->second),
-                                              fqdn);
+                                              Option6IA>(opt->second));
             if (answer_opt) {
                 reply->addOption(answer_opt);
             }
@@ -2178,13 +2187,14 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
     appendRequestedOptions(solicit, advertise);
     appendRequestedVendorOptions(solicit, advertise);
 
-    Option6ClientFqdnPtr fqdn = processClientFqdn(solicit);
-    assignLeases(solicit, advertise, fqdn);
-    appendClientFqdn(solicit, advertise, fqdn);
+    processClientFqdn(solicit, advertise);
+    assignLeases(solicit, advertise);
     // Note, that we don't create NameChangeRequests here because we don't
     // perform DNS Updates for Solicit. Client must send Request to update
     // DNS.
 
+    generateFqdn(advertise);
+
     return (advertise);
 }
 
@@ -2200,10 +2210,10 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
     appendRequestedOptions(request, reply);
     appendRequestedVendorOptions(request, reply);
 
-    Option6ClientFqdnPtr fqdn = processClientFqdn(request);
-    assignLeases(request, reply, fqdn);
-    appendClientFqdn(request, reply, fqdn);
-    createNameChangeRequests(reply, fqdn);
+    processClientFqdn(request, reply);
+    assignLeases(request, reply);
+    generateFqdn(reply);
+    createNameChangeRequests(reply);
 
     return (reply);
 }
@@ -2219,12 +2229,12 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
     appendDefaultOptions(renew, reply);
     appendRequestedOptions(renew, reply);
 
-    Option6ClientFqdnPtr fqdn = processClientFqdn(renew);
-    renewLeases(renew, reply, fqdn);
-    appendClientFqdn(renew, reply, fqdn);
-    createNameChangeRequests(reply, fqdn);
+    processClientFqdn(renew, reply);
+    renewLeases(renew, reply);
+    generateFqdn(reply);
+    createNameChangeRequests(reply);
 
-    return reply;
+    return (reply);
 }
 
 Pkt6Ptr
@@ -2355,10 +2365,13 @@ Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
     // The buffer being read comprises a set of options, each starting with
     // a two-byte type code and a two-byte length field.
     while (offset + 4 <= length) {
-        uint16_t opt_type = isc::util::readUint16(&buf[offset]);
+        // At this point, from the while condition, we know that there
+        // are at least 4 bytes available following offset in the
+        // buffer.
+        uint16_t opt_type = isc::util::readUint16(&buf[offset], 2);
         offset += 2;
 
-        uint16_t opt_len = isc::util::readUint16(&buf[offset]);
+        uint16_t opt_len = isc::util::readUint16(&buf[offset], 2);
         offset += 2;
 
         if (offset + opt_len > length) {
@@ -2460,5 +2473,78 @@ void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
     }
 }
 
+void
+Dhcpv6Srv::generateFqdn(const Pkt6Ptr& answer) {
+    if (!answer) {
+        isc_throw(isc::Unexpected, "an instance of the object encapsulating"
+                  " a message must not be NULL when generating FQDN");
+    }
+
+    // It is likely that client haven't included the FQDN option. In such case,
+    // FQDN option will be NULL. Also, there is nothing to do if the option
+    // is present and conveys the non-empty FQDN.
+    Option6ClientFqdnPtr fqdn = boost::dynamic_pointer_cast<
+        Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
+    if (!fqdn || !fqdn->getDomainName().empty()) {
+        return;
+    }
+
+    // Get the first IA_NA acquired for the client.
+    OptionPtr ia = answer->getOption(D6O_IA_NA);
+    if (!ia) {
+        return;
+    }
+
+    // If it has any IAAddr, use the first one to generate unique FQDN.
+    Option6IAAddrPtr iaaddr = boost::dynamic_pointer_cast<
+        Option6IAAddr>(ia->getOption(D6O_IAADDR));
+    if (!iaaddr) {
+        return;
+    }
+    // Get the IPv6 address acquired by the client.
+    IOAddress addr = iaaddr->getAddress();
+    std::string hostname = addr.toText();
+    // Colons may not be ok for FQDNs so let's replace them with hyphens.
+    std::replace(hostname.begin(), hostname.end(), ':', '-');
+    std::ostringstream stream;
+    // The final FQDN consists of the partial domain name and the suffix.
+    // For example, if the acquired address is 2001:db8:1::2, the generated
+    // FQDN may be:
+    //     host-2001-db8:1--2.example.com.
+    // where prefix 'host' should be configurable. The domain name suffix
+    // should also be configurable.
+    stream << "host-" << hostname << "." << FQDN_PARTIAL_SUFFIX << ".";
+    try {
+        // The lease has been acquired but the FQDN for this lease hasn't
+        // been updated in the lease database. We now have new FQDN
+        // generated, so the lease database has to be updated here.
+        // However, never update lease database for Advertise, just send
+        // our notion of client's FQDN in the Client FQDN option.
+        if (answer->getType() != DHCPV6_ADVERTISE) {
+            Lease6Ptr lease =
+                LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+            if (lease) {
+                lease->hostname_ = stream.str();
+                LeaseMgrFactory::instance().updateLease6(lease);
+
+            } else {
+                isc_throw(isc::Unexpected, "there is no lease in the database "
+                          " for address " << addr << ", so as it is impossible"
+                          " to update FQDN data. This is a programmatic error"
+                          " as the given address is now being handed to the"
+                          " client");
+            }
+        }
+
+        // Set the generated FQDN in the Client FQDN option.
+        fqdn->setDomainName(stream.str(), Option6ClientFqdn::FULL);
+
+    } catch (const Exception& ex) {
+        LOG_ERROR(dhcp6_logger, DHCP6_NAME_GEN_UPDATE_FAIL)
+            .arg(hostname)
+            .arg(ex.what());
+    }
+}
+
 };
 };

+ 76 - 41
src/bin/dhcp6/dhcp6_srv.h

@@ -119,6 +119,16 @@ public:
 
 protected:
 
+    /// @brief Compare received server id with our server id
+    ///
+    /// Checks if the server id carried in a query from a client matches
+    /// server identifier being used by the server.
+    ///
+    /// @param pkt DHCPv6 packet carrying server identifier to be checked.
+    /// @return true if server id carried in the query matches server id
+    /// used by the server; false otherwise.
+    bool testServerID(const Pkt6Ptr& pkt);
+
     /// @brief verifies if specified packet meets RFC requirements
     ///
     /// Checks if mandatory option is really there, that forbidden option
@@ -214,15 +224,17 @@ protected:
     /// @param subnet subnet the client is connected to
     /// @param duid client's duid
     /// @param query client's message (typically SOLICIT or REQUEST)
+    /// @param answer server's response to the client's message. This
+    /// message should contain Client FQDN option being sent by the server
+    /// to the client (if the client sent this option to the server).
     /// @param ia pointer to client's IA_NA option (client's request)
-    /// @param fqdn A DHCPv6 Client FQDN %Option generated in a response to the
-    /// FQDN option sent by a client.
+    ///
     /// @return IA_NA option (server's response)
     OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
                           const isc::dhcp::DuidPtr& duid,
                           const isc::dhcp::Pkt6Ptr& query,
-                          Option6IAPtr ia,
-                          const Option6ClientFqdnPtr& fqdn);
+                          const isc::dhcp::Pkt6Ptr& answer,
+                          Option6IAPtr ia);
 
     /// @brief Processes IA_PD option (and assigns prefixes if necessary).
     ///
@@ -250,12 +262,14 @@ protected:
     /// @param subnet subnet the sender belongs to
     /// @param duid client's duid
     /// @param query client's message
+    /// @param answer server's response to the client's message. This
+    /// message should contain Client FQDN option being sent by the server
+    /// to the client (if the client sent this option to the server).
     /// @param ia IA_NA option that is being renewed
-    /// @param fqdn DHCPv6 Client FQDN Option included in the server's response
     /// @return IA_NA option (server's response)
     OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
-                         const Pkt6Ptr& query, boost::shared_ptr<Option6IA> ia,
-                         const Option6ClientFqdnPtr& fqdn);
+                         const Pkt6Ptr& query, const Pkt6Ptr& answer,
+                         boost::shared_ptr<Option6IA> ia);
 
     /// @brief Renews specific IA_PD option
     ///
@@ -352,11 +366,10 @@ protected:
     /// @todo: Extend this method once TA and PD becomes supported
     ///
     /// @param question client's message (with requested IA_NA)
-    /// @param answer server's message (IA_NA options will be added here)
-    /// @param fqdn an FQDN option generated in a response to the client's
-    /// FQDN option.
-    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
-                      const Option6ClientFqdnPtr& fqdn);
+    /// @param answer server's message (IA_NA options will be added here).
+    /// This message should contain Client FQDN option being sent by the server
+    /// to the client (if the client sent this option to the server).
+    void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
 
     /// @brief Processes Client FQDN Option.
     ///
@@ -365,7 +378,7 @@ protected:
     /// Received option comprises flags field which controls what DNS updates
     /// server should do. Server may override client's preference based on
     /// the current configuration. Server indicates that it has overridden
-    /// the preference by storing DHCPv6 Client Fqdn %Option with the
+    /// the preference by storing DHCPv6 Client FQDN option with the
     /// appropriate flags in the response to a client. This option is also
     /// used to communicate the client's domain-name which should be sent
     /// to the DNS in the update. Again, server may act upon the received
@@ -376,25 +389,10 @@ protected:
     /// held in this function.
     ///
     /// @param question Client's message.
-    ///
-    /// @return FQDN option produced in the response to the client's message.
-    Option6ClientFqdnPtr processClientFqdn(const Pkt6Ptr& question);
-
-    /// @brief Adds DHCPv6 Client FQDN %Option to the server response.
-    ///
-    /// This function will add the specified FQDN option into the server's
-    /// response when FQDN is not NULL and server is either configured to
-    /// always include the FQDN in the response or client requested it using
-    /// %Option Request %Option.
-    /// This function is exception safe.
-    ///
-    /// @param question A message received from the client.
-    /// @param [out] answer A server's response where FQDN option will be added.
-    /// @param fqdn A DHCPv6 Client FQDN %Option to be added to the server's
-    /// response to a client.
-    void appendClientFqdn(const Pkt6Ptr& question,
-                          Pkt6Ptr& answer,
-                          const Option6ClientFqdnPtr& fqdn);
+    /// @param answer Server's response to a client. If server generated
+    /// Client FQDN option for the client, this option is stored in this
+    /// object.
+    void processClientFqdn(const Pkt6Ptr& question, const Pkt6Ptr& answer);
 
     /// @brief Creates a number of @c isc::dhcp_ddns::NameChangeRequest objects
     /// based on the DHCPv6 Client FQDN %Option.
@@ -410,11 +408,9 @@ protected:
     ///
     /// @todo Add support for multiple IAADDR options in the IA_NA.
     ///
-    /// @param answer A message beging sent to the Client.
-    /// @param fqdn_answer A DHCPv6 Client FQDN %Option which is included in the
-    /// response message sent to a client.
-    void createNameChangeRequests(const Pkt6Ptr& answer,
-                                  const Option6ClientFqdnPtr& fqdn_answer);
+    /// @param answer A message beging sent to the Client. If it holds the
+    /// Client FQDN option, this option is used to create NameChangeRequests.
+    void createNameChangeRequests(const Pkt6Ptr& answer);
 
     /// @brief Creates a @c isc::dhcp_ddns::NameChangeRequest which requests
     /// removal of DNS entries for a particular lease.
@@ -450,10 +446,7 @@ protected:
     /// as IA_NA/IAADDR to reply packet.
     /// @param renew client's message asking for renew
     /// @param reply server's response
-    /// @param fqdn A DHCPv6 Client FQDN %Option generated in the response to the
-    /// client's FQDN option.
-    void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
-                     const Option6ClientFqdnPtr& fqdn);
+    void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
 
     /// @brief Attempts to release received addresses
     ///
@@ -554,6 +547,48 @@ private:
     /// @param errmsg An error message containing a cause of the failure.
     static void ifaceMgrSocket6ErrorHandler(const std::string& errmsg);
 
+    /// @brief Generate FQDN to be sent to a client if none exists.
+    ///
+    /// This function is meant to be called by the functions which process
+    /// client's messages. The function should be called after a function
+    /// which creates FQDN option for the client. This option must exist
+    /// in the answer message specified as an argument. It must also be
+    /// called after functions which assign leases for a client. The
+    /// IA options being a result of lease acquisition must be appended
+    /// to the message specified as a parameter.
+    ///
+    /// If the Client FQDN option being present in the message carries empty
+    /// hostname, this function will attempt to generate hostname from the
+    /// IPv6 address being acquired by the client. The IPv6 address is retrieved
+    /// from the IA_NA option carried in the specified message. If multiple
+    /// addresses are present in the particular IA_NA option or multiple IA_NA
+    /// options exist, the first address found is selected.
+    ///
+    /// The IPv6 address is converted to the hostname using the following
+    /// pattern:
+    /// @code
+    ///     prefix-converted-ip-address.domain-name-suffix.
+    /// @endcode
+    /// where:
+    /// - prefix is a configurable prefix string appended to all auto-generated
+    /// hostnames.
+    /// - converted-ip-address is created by replacing all colons from the IPv6
+    /// address with hyphens.
+    /// - domain-name-suffix is a suffix for a domain name that, together with
+    /// the other parts, constitute the fully qualified domain name.
+    ///
+    /// When hostname is successfully generated, it is either used to update
+    /// FQDN-related fields in a lease database or to update the Client FQDN
+    /// option being sent back to the client. The lease database update is
+    /// NOT performed if Advertise message is being processed.
+    ///
+    /// @param answer Message being sent to a client, which may hold IA_NA
+    /// and Client FQDN options to be used to generate name for a client.
+    ///
+    /// @throw isc::Unexpected if specified message is NULL. This is treated
+    /// as a programmatic error.
+    void generateFqdn(const Pkt6Ptr& answer);
+
     /// @brief Allocation Engine.
     /// Pointer to the allocation engine that we are currently using
     /// It must be a pointer, because we will support changing engines

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

@@ -54,7 +54,8 @@ if HAVE_GTEST
 # to unexpected errors. For this reason, the --enable-static-link option is
 # ignored for unit tests built here.
 
-lib_LTLIBRARIES = libco1.la libco2.la
+nodistdir=$(abs_top_builddir)/src/bin/dhcp6/tests
+nodist_LTLIBRARIES = libco1.la libco2.la
 
 libco1_la_SOURCES  = callout_library_1.cc callout_library_common.h
 libco1_la_CXXFLAGS = $(AM_CXXFLAGS)

+ 2 - 5
src/bin/dhcp6/tests/callout_library_common.h

@@ -34,9 +34,6 @@
 
 #include <fstream>
 
-using namespace isc::hooks;
-using namespace std;
-
 extern "C" {
 
 /// @brief Append digit to marker file
@@ -51,7 +48,7 @@ extern "C" {
 int
 appendDigit(const char* name) {
     // Open the file and check if successful.
-    fstream file(name, fstream::out | fstream::app);
+    std::fstream file(name, std::fstream::out | std::fstream::app);
     if (!file.good()) {
         return (1);
     }
@@ -70,7 +67,7 @@ version() {
 }
 
 int
-load(LibraryHandle&) {
+load(isc::hooks::LibraryHandle&) {
     return (appendDigit(LOAD_MARKER_FILE));
 }
 

+ 413 - 46
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -138,19 +138,19 @@ public:
             params["name"] = param_value;
             params["space"] = "dhcp6";
             params["code"] = "38";
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "space") {
             params["name"] = "subscriber-id";
             params["space"] = param_value;
             params["code"] = "38";
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "code") {
             params["name"] = "subscriber-id";
             params["space"] = "dhcp6";
             params["code"] = param_value;
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = "False";
         } else if (parameter == "data") {
             params["name"] = "subscriber-id";
@@ -162,12 +162,20 @@ public:
             params["name"] = "subscriber-id";
             params["space"] = "dhcp6";
             params["code"] = "38";
-            params["data"] = "AB CDEF0105";
+            params["data"] = "ABCDEF0105";
             params["csv-format"] = param_value;
         }
         return (createConfigWithOption(params));
     }
 
+    /// @brief Create simple configuration with single option.
+    ///
+    /// This function creates a configuration for a single option with
+    /// custom values for all parameters that describe the option.
+    ///
+    /// @params params map holding parameters and their values.
+    /// @return configuration string containing custom values of parameters
+    /// describing an option.
     std::string createConfigWithOption(const std::map<std::string,
                                        std::string>& params)
     {
@@ -176,6 +184,15 @@ public:
             "\"preferred-lifetime\": 3000,"
             "\"rebind-timer\": 2000, "
             "\"renew-timer\": 1000, "
+            "\"option-def\": [ {"
+            "  \"name\": \"bool-option\","
+            "  \"code\": 1000,"
+            "  \"type\": \"boolean\","
+            "  \"array\": False,"
+            "  \"record-types\": \"\","
+            "  \"space\": \"dhcp6\","
+            "  \"encapsulate\": \"\""
+            "} ],"
             "\"subnet6\": [ { "
             "    \"pool\": [ \"2001:db8:1::/80\" ],"
             "    \"subnet\": \"2001:db8:1::/64\", "
@@ -208,6 +225,63 @@ public:
         return (stream.str());
     }
 
+    /// @brief Returns an option from the subnet.
+    ///
+    /// This function returns an option from a subnet to which the
+    /// specified subnet address belongs. The option is identified
+    /// by its code.
+    ///
+    /// @param subnet_address Address which belongs to the subnet from
+    /// which the option is to be returned.
+    /// @param option_code Code of the option to be returned.
+    /// @param expected_options_count Expected number of options in
+    /// the particular subnet.
+    ///
+    /// @return Descriptor of the option. If the descriptor holds a
+    /// NULL option pointer, it means that there was no such option
+    /// in the subnet.
+    Subnet::OptionDescriptor
+    getOptionFromSubnet(const IOAddress& subnet_address,
+                        const uint16_t option_code,
+                        const uint16_t expected_options_count = 1) {
+        Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(subnet_address,
+                                                          classify_);
+        if (!subnet) {
+            /// @todo replace toText() with the use of operator <<.
+            ADD_FAILURE() << "A subnet for the specified address "
+                          << subnet_address.toText()
+                          << "does not exist in Config Manager";
+        }
+        Subnet::OptionContainerPtr options =
+            subnet->getOptionDescriptors("dhcp6");
+        if (expected_options_count != options->size()) {
+            ADD_FAILURE() << "The number of options in the subnet '"
+                          << subnet_address.toText() << "' is different "
+                " than expected number of options '"
+                          << expected_options_count << "'";
+        }
+
+        // Get the search index. Index #1 is to search using option code.
+        const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+        // Get the options for specified index. Expecting one option to be
+        // returned but in theory we may have multiple options with the same
+        // code so we get the range.
+        std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+                  Subnet::OptionContainerTypeIndex::const_iterator> range =
+            idx.equal_range(option_code);
+        if (std::distance(range.first, range.second) > 1) {
+            ADD_FAILURE() << "There is more than one option having the"
+                " option code '" << option_code << "' in a subnet '"
+                          << subnet_address.toText() << "'. Expected "
+                " at most one option";
+        } else if (std::distance(range.first, range.second) == 0) {
+            return (Subnet::OptionDescriptor(OptionPtr(), false));
+        }
+
+        return (*range.first);
+    }
+
     /// @brief Parse and Execute configuration
     ///
     /// Parses a configuration and executes a configuration of the server.
@@ -305,6 +379,24 @@ public:
         ASSERT_EQ(1, rcode_);
     }
 
+    /// @brief Test invalid option paramater value.
+    ///
+    /// This test function constructs the simple configuration
+    /// string and injects invalid option configuration into it.
+    /// It expects that parser will fail with provided option code.
+    ///
+    /// @param params Map of parameters defining an option.
+    void
+    testInvalidOptionParam(const std::map<std::string, std::string>& params) {
+        ConstElementPtr x;
+        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_);
+    }
+
     /// @brief Test option against given code and data.
     ///
     /// @param option_desc option descriptor that carries the option to
@@ -346,11 +438,45 @@ public:
                             expected_data_len));
     }
 
+    /// @brief Test option configuration.
+    ///
+    /// This function creates a configuration for a specified option using
+    /// a map of parameters specified as the argument. The map holds
+    /// name/value pairs which identifies option's configuration parameters:
+    /// - name
+    /// - space
+    /// - code
+    /// - data
+    /// - csv-format.
+    /// This function applies a new server configuration and checks that the
+    /// option being configured is inserted into CfgMgr. The raw contents of
+    /// this option are compared with the binary data specified as expected
+    /// data passed to this function.
+    ///
+    /// @param params Map of parameters defining an option.
+    /// @param option_code Option code.
+    /// @param expected_data Array containing binary data expected to be stored
+    /// in the configured option.
+    /// @param expected_data_len Length of the array holding reference data.
+    void testConfiguration(const std::map<std::string, std::string>& params,
+                           const uint16_t option_code,
+                           const uint8_t* expected_data,
+                           const size_t expected_data_len) {
+        std::string config = createConfigWithOption(params);
+        ASSERT_TRUE(executeConfiguration(config, "parse option configuration"));
+        // The subnet should now hold one option with the specified code.
+        Subnet::OptionDescriptor desc =
+            getOptionFromSubnet(IOAddress("2001:db8:1::5"), option_code);
+        ASSERT_TRUE(desc.option);
+        testOption(desc, option_code, expected_data, expected_data_len);
+    }
+
     int rcode_;          ///< Return code (see @ref isc::config::parseAnswer)
     Dhcpv6Srv srv_;      ///< Instance of the Dhcp6Srv used during tests
     ConstElementPtr comment_; ///< Comment (see @ref isc::config::parseAnswer)
     string valid_iface_; ///< Valid network interface name (present in system)
     string bogus_iface_; ///< invalid network interface name (not in system)
+    isc::dhcp::ClientClasses classify_; ///< used in client classification
 };
 
 // Goal of this test is a verification if a very simple config update
@@ -430,7 +556,8 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
     // Now check if the configuration was indeed handled and we have
     // expected pool configured.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+        classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -648,7 +775,8 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1, subnet->getT1());
     EXPECT_EQ(2, subnet->getT2());
@@ -684,7 +812,8 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(valid_iface_, subnet->getIface());
 }
@@ -717,7 +846,8 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
     comment_ = parseAnswer(rcode_, status);
     EXPECT_EQ(1, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     EXPECT_FALSE(subnet);
 }
 
@@ -782,13 +912,13 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
     // Try to get a subnet based on bogus interface-id option
     OptionBuffer tmp(bogus_interface_id.begin(), bogus_interface_id.end());
     OptionPtr ifaceid(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     EXPECT_FALSE(subnet);
 
     // Now try to get subnet for valid interface-id value
     tmp = OptionBuffer(valid_interface_id.begin(), valid_interface_id.end());
     ifaceid.reset(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
-    subnet = CfgMgr::instance().getSubnet6(ifaceid);
+    subnet = CfgMgr::instance().getSubnet6(ifaceid, classify_);
     ASSERT_TRUE(subnet);
     EXPECT_TRUE(ifaceid->equal(subnet->getInterfaceId()));
 }
@@ -901,7 +1031,8 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
     comment_ = parseAnswer(rcode_, x);
     EXPECT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
@@ -944,9 +1075,8 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     EXPECT_EQ(0, rcode_);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
-
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Fetch the collection of PD pools.  It should have 1 entry.
@@ -1019,8 +1149,8 @@ TEST_F(Dhcp6ParserTest, pdPoolList) {
     EXPECT_EQ(0, rcode_);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Fetch the collection of NA pools.  It should have 1 entry.
@@ -1077,8 +1207,8 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     EXPECT_EQ(0, rcode_);
 
     // Test that we can retrieve the subnet.
-    Subnet6Ptr subnet = CfgMgr::
-                        instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
 
     ASSERT_TRUE(subnet);
 
@@ -1622,9 +1752,9 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulateOwnSpace) {
 
 /// The purpose of this test is to verify that it is not allowed
 /// to override the standard option (that belongs to dhcp6 option
-/// space) and that it is allowed to define option in the dhcp6
-/// option space that has a code which is not used by any of the
-/// standard options.
+/// space and has its definition) and that it is allowed to define
+/// option in the dhcp6 option space that has a code which is not
+/// used by any of the standard options.
 TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
 
     // Configuration string. The option code 100 is unassigned
@@ -1662,9 +1792,8 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     EXPECT_EQ(OPT_STRING_TYPE, def->getType());
     EXPECT_FALSE(def->getArrayType());
 
-    // The combination of option space and code is
-    // invalid. The 'dhcp6' option space groups
-    // standard options and the code 3 is reserved
+    // The combination of option space and code is invalid. The 'dhcp6'
+    // option space groups standard options and the code 3 is reserved
     // for one of them.
     config =
         "{ \"option-def\": [ {"
@@ -1684,6 +1813,39 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
     ASSERT_TRUE(status);
     // Expecting parsing error (error code 1).
     checkResult(status, 1);
+
+    /// @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
+    /// allowed to define it from the configuration interface. This test will
+    /// have to be removed once definitions for remaining standard options are
+    /// created.
+    config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"boot-file-name\","
+        "      \"code\": 59,"
+        "      \"type\": \"string\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"dhcp6\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+    json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting success.
+    checkResult(status, 0);
+
+    def = CfgMgr::instance().getOptionDef("dhcp6", 59);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("boot-file-name", def->getName());
+    EXPECT_EQ(59, def->getCode());
+    EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
 }
 
 // Goal of this test is to verify that global option
@@ -1699,7 +1861,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
         "    \"name\": \"subscriber-id\","
         "    \"space\": \"dhcp6\","
         "    \"code\": 38,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1722,7 +1884,8 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(2, options->size());
@@ -1780,7 +1943,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
         "    \"name\": \"subscriber-id\","
         "    \"space\": \"dhcp6\","
         "    \"code\": 38,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1814,7 +1977,8 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     // Try to get the option from the space dhcp6.
     Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
@@ -1965,7 +2129,8 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2029,7 +2194,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                       classify_);
     ASSERT_TRUE(subnet1);
     Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options1->size());
@@ -2054,7 +2220,8 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
                sizeof(subid_expected));
 
     // Test another subnet in the same way.
-    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
+    Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"),
+                                                       classify_);
     ASSERT_TRUE(subnet2);
     Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options2->size());
@@ -2072,6 +2239,89 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
                sizeof(user_class_expected));
 }
 
+// The goal of this test is to check that the option carrying a boolean
+// value can be configured using one of the values: "true", "false", "0"
+// or "1".
+TEST_F(Dhcp6ParserTest, optionDataBoolean) {
+    // Create configuration. Use standard option 1000.
+    std::map<std::string, std::string> params;
+    params["name"] = "bool-option";
+    params["space"] = "dhcp6";
+    params["code"] = "1000";
+    params["data"] = "true";
+    params["csv-format"] = "true";
+
+    std::string config = createConfigWithOption(params);
+    ASSERT_TRUE(executeConfiguration(config, "parse configuration with a"
+                                     " boolean value"));
+
+    // The subnet should now hold one option with the code 1000.
+    Subnet::OptionDescriptor desc =
+        getOptionFromSubnet(IOAddress("2001:db8:1::5"), 1000);
+    ASSERT_TRUE(desc.option);
+
+    // This option should be set to "true", represented as 0x1 in the option
+    // buffer.
+    uint8_t expected_option_data[] = {
+        0x1
+    };
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Configure the option with the "1" value. This should have the same
+    // effect as if "true" was specified.
+    params["data"] = "1";
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // The value of "1" with a few leading zeros should work too.
+    params["data"] = "00001";
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Configure the option with the "false" value.
+    params["data"] = "false";
+    // The option buffer should now hold the value of 0.
+    expected_option_data[0] = 0;
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Specifying "0" should have the same effect as "false".
+    params["data"] = "0";
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // The same effect should be for multiple 0 chars.
+    params["data"] = "00000";
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // Bogus values should not be accepted.
+    params["data"] = "bugus";
+    testInvalidOptionParam(params);
+
+    params["data"] = "2";
+    testInvalidOptionParam(params);
+
+    // Now let's test that it is possible to use binary format.
+    params["data"] = "0";
+    params["csv-format"] = "false";
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // The binary 1 should work as well.
+    params["data"] = "1";
+    expected_option_data[0] = 1;
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+    // As well as an even number of digits.
+    params["data"] = "01";
+    testConfiguration(params, 1000, expected_option_data,
+                      sizeof(expected_option_data));
+
+}
+
 // Verify that empty option name is rejected in the configuration.
 TEST_F(Dhcp6ParserTest, optionNameEmpty) {
     // Empty option names not allowed.
@@ -2129,14 +2379,6 @@ TEST_F(Dhcp6ParserTest, optionDataUnexpectedPrefix) {
     testInvalidOptionParam("0x0102", "data");
 }
 
-// Verify that option data consisting od an odd number of
-// hexadecimal digits is rejected in the configuration.
-TEST_F(Dhcp6ParserTest, optionDataOddLength) {
-    // Option code 0 is reserved and should not be accepted
-    // by configuration parser.
-    testInvalidOptionParam("123", "data");
-}
-
 // Verify that either lower or upper case characters are allowed
 // to specify the option data.
 TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
@@ -2149,7 +2391,8 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options->size());
@@ -2193,7 +2436,8 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
     comment_ = parseAnswer(rcode_, x);
     ASSERT_EQ(0, rcode_);
 
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
     Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
     ASSERT_EQ(1, options->size());
@@ -2243,7 +2487,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
         "    \"name\": \"option-one\","
         "    \"space\": \"vendor-4491\","
         "    \"code\": 100,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -2268,7 +2512,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2327,7 +2572,8 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
     checkResult(status, 0);
 
     // Options should be now available for the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // Try to get the option from the vendor space 4491
@@ -2461,7 +2707,8 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
     checkResult(status, 0);
 
     // Get the subnet.
-    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"),
+                                                      classify_);
     ASSERT_TRUE(subnet);
 
     // We should have one option available.
@@ -2725,4 +2972,124 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
 }
 
 
+// This test checks if it is possible to specify relay information
+TEST_F(Dhcp6ParserTest, subnetRelayInfo) {
+
+    ConstElementPtr status;
+
+    // A config with relay information.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+        "    \"relay\": { "
+        "        \"ip-address\": \"2001:db8:1::abcd\""
+        "    },"
+        "    \"subnet\": \"2001:db8:1::/64\" } ],"
+        "\"preferred-lifetime\": 3000, "
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+    // returned value should be 0 (configuration success)
+    checkResult(status, 0);
+
+    Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::1"),
+                                                      classify_);
+    ASSERT_TRUE(subnet);
+    EXPECT_EQ("2001:db8:1::abcd", subnet->getRelayInfo().addr_.toText());
+}
+
+// Goal of this test is to verify that multiple subnets can be configured
+// with defined client classes.
+TEST_F(Dhcp6ParserTest, classifySubnets) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\", "
+        "    \"client-class\": \"alpha\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"client-class\": \"beta\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"client-class\": \"gamma\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/80\" ],"
+        "    \"subnet\": \"2001:db8:4::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    const Subnet6Collection* subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(4, subnets->size()); // We expect 4 subnets
+
+    // Let's check if client belonging to alpha class is supported in subnet[0]
+    // and not supported in any other subnet (except subnet[3], which allows
+    // everyone).
+    ClientClasses classes;
+    classes.insert("alpha");
+    EXPECT_TRUE (subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to beta class is supported in subnet[1]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("beta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to gamma class is supported in subnet[2]
+    // and not supported in any other subnet  (except subnet[3], which allows
+    // everyone).
+    classes.clear();
+    classes.insert("gamma");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Let's check if client belonging to some other class (not mentioned in
+    // the config) is supported only in subnet[3], which allows everyone.
+    classes.clear();
+    classes.insert("delta");
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+
+    // Finally, let's check class-less client. He should be allowed only in
+    // the last subnet, which does not have any class restrictions.
+    classes.clear();
+    EXPECT_FALSE(subnets->at(0)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(1)->clientSupported(classes));
+    EXPECT_FALSE(subnets->at(2)->clientSupported(classes));
+    EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
+}
+
+
 };

+ 105 - 1
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2014  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -47,6 +47,8 @@
 #include <sstream>
 
 using namespace isc;
+using namespace isc::data;
+using namespace isc::config;
 using namespace isc::test;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
@@ -1073,6 +1075,42 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
     EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
                  RFCViolation);
 }
+// Check that the server is testing if server identifier received in the
+// query, matches server identifier used by the server.
+TEST_F(Dhcpv6SrvTest, testServerID) {
+	NakedDhcpv6Srv srv(0);
+
+	Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+    std::vector<uint8_t> bin;
+
+    // diud_llt constructed with: time = 0, macaddress = 00:00:00:00:00:00
+    // it's necessary to generate server identifier option
+    isc::util::encode::decodeHex("0001000100000000000000000000", bin);
+    // Now create server identifier option
+    OptionPtr serverid = OptionPtr(new Option(Option::V6, D6O_SERVERID, bin));
+
+    // Server identifier option is MANDATORY in Request message.
+    // Add server identifier option with different value from one that
+    // server is using.
+    req->addOption(serverid);
+
+    // Message shoud be dropped
+    EXPECT_FALSE(srv.testServerID(req));
+
+    // Delete server identifier option and add new one, with same value as
+    // server's server identifier.
+    req->delOption(D6O_SERVERID);
+    req->addOption(srv.getServerID());
+
+    // With proper server identifier we expect true
+    EXPECT_TRUE(srv.testServerID(req));
+
+    // server-id MUST NOT appear in Solicit, so check if server is
+    // not dropping a message without server id.
+    Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+    EXPECT_TRUE(srv.testServerID(req));
+}
 
 // This test verifies if selectSubnet() selects proper subnet for a given
 // source address.
@@ -1702,6 +1740,72 @@ TEST_F(Dhcpv6SrvTest, clientClassification) {
     EXPECT_FALSE(sol2->inClass("docsis3.0"));
 }
 
+// Checks if the client-class field is indeed used for subnet selection.
+// Note that packet classification is already checked in Dhcpv6SrvTest
+// .clientClassification above.
+TEST_F(Dhcpv6SrvTest, clientClassify2) {
+
+    NakedDhcpv6Srv srv(0);
+
+    ConstElementPtr status;
+
+    // This test configures 2 subnets. We actually only need the
+    // first one, but since there's still this ugly hack that picks
+    // the pool if there is only one, we must use more than one
+    // subnet. That ugly hack will be removed in #3242, currently
+    // under review.
+
+    // The second subnet does not play any role here. The client's
+    // IP address belongs to the first subnet, so only that first
+    // subnet it being tested.
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " {  \"pool\": [ \"2001:db8:1::/64\" ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"client-class\": \"foo\" "
+        " }, "
+        " {  \"pool\": [ \"2001:db8:2::/64\" ],"
+        "    \"subnet\": \"2001:db8:2::/48\", "
+        "    \"client-class\": \"xyzzy\" "
+        " } "
+        "],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp6Server(srv, json));
+
+    // check if returned status is OK
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // This discover does not belong to foo class, so it will not
+    // be serviced
+    EXPECT_FALSE(srv.selectSubnet(sol));
+
+    // Let's add the packet to bar class and try again.
+    sol->addClass("bar");
+
+    // Still not supported, because it belongs to wrong class.
+    EXPECT_FALSE(srv.selectSubnet(sol));
+
+    // Let's add it to maching class.
+    sol->addClass("foo");
+
+    // This time it should work
+    EXPECT_TRUE(srv.selectSubnet(sol));
+}
+
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.

+ 3 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -15,6 +15,9 @@
 #include <gtest/gtest.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
 namespace isc {
 namespace test {
 

+ 146 - 117
src/bin/dhcp6/tests/dhcp6_test_utils.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -37,24 +37,16 @@
 
 #include <list>
 
-using namespace isc::dhcp;
-using namespace isc::config;
-using namespace isc::data;
-using namespace isc::hooks;
-using namespace isc::asiolink;
-using namespace isc::util;
-using namespace isc::hooks;
-
 namespace isc {
 namespace test {
 
 /// @brief "naked" Dhcpv6Srv class that exposes internal members
 class NakedDhcpv6Srv: public isc::dhcp::Dhcpv6Srv {
 public:
-    NakedDhcpv6Srv(uint16_t port) : Dhcpv6Srv(port) {
+    NakedDhcpv6Srv(uint16_t port) : isc::dhcp::Dhcpv6Srv(port) {
         // Open the "memfile" database for leases
         std::string memfile = "type=memfile";
-        LeaseMgrFactory::create(memfile);
+        isc::dhcp::LeaseMgrFactory::create(memfile);
     }
 
     /// @brief fakes packet reception
@@ -70,7 +62,7 @@ public:
         // If there is anything prepared as fake incoming
         // traffic, use it
         if (!fake_received_.empty()) {
-            Pkt6Ptr pkt = fake_received_.front();
+            isc::dhcp::Pkt6Ptr pkt = fake_received_.front();
             fake_received_.pop_front();
             return (pkt);
         }
@@ -78,7 +70,7 @@ public:
         // If not, just trigger shutdown and
         // return immediately
         shutdown();
-        return (Pkt6Ptr());
+        return (isc::dhcp::Pkt6Ptr());
     }
 
     /// @brief fake packet sending
@@ -86,20 +78,20 @@ public:
     /// Pretend to send a packet, but instead just store
     /// it in fake_send_ list where test can later inspect
     /// server's response.
-    virtual void sendPacket(const Pkt6Ptr& pkt) {
+    virtual void sendPacket(const isc::dhcp::Pkt6Ptr& pkt) {
         fake_sent_.push_back(pkt);
     }
 
     /// @brief adds a packet to fake receive queue
     ///
     /// See fake_received_ field for description
-    void fakeReceive(const Pkt6Ptr& pkt) {
+    void fakeReceive(const isc::dhcp::Pkt6Ptr& pkt) {
         fake_received_.push_back(pkt);
     }
 
     virtual ~NakedDhcpv6Srv() {
         // Close the lease database
-        LeaseMgrFactory::destroy();
+        isc::dhcp::LeaseMgrFactory::destroy();
     }
 
     using Dhcpv6Srv::processSolicit;
@@ -111,6 +103,7 @@ public:
     using Dhcpv6Srv::createRemovalNameChangeRequest;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
+    using Dhcpv6Srv::testServerID;
     using Dhcpv6Srv::sanityCheck;
     using Dhcpv6Srv::classifyPacket;
     using Dhcpv6Srv::loadServerID;
@@ -120,13 +113,14 @@ public:
 
     /// @brief packets we pretend to receive
     ///
-    /// Instead of setting up sockets on interfaces that change between OSes, it
-    /// is much easier to fake packet reception. This is a list of packets that
-    /// we pretend to have received. You can schedule new packets to be received
-    /// using fakeReceive() and NakedDhcpv6Srv::receivePacket() methods.
-    std::list<Pkt6Ptr> fake_received_;
-
-    std::list<Pkt6Ptr> fake_sent_;
+    /// Instead of setting up sockets on interfaces that change between
+    /// OSes, it is much easier to fake packet reception. This is a list
+    /// of packets that we pretend to have received. You can schedule
+    /// new packets to be received using fakeReceive() and
+    /// NakedDhcpv6Srv::receivePacket() methods.
+    std::list<isc::dhcp::Pkt6Ptr> fake_received_;
+
+    std::list<isc::dhcp::Pkt6Ptr> fake_sent_;
 };
 
 static const char* DUID_FILE = "server-id-test.txt";
@@ -140,7 +134,8 @@ public:
         // it's ok if that fails. There should not be such a file anyway
         unlink(DUID_FILE);
 
-        const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+        const isc::dhcp::IfaceMgr::IfaceCollection& ifaces =
+            isc::dhcp::IfaceMgr::instance().getIfaces();
 
         // There must be some interface detected
         if (ifaces.empty()) {
@@ -152,47 +147,56 @@ public:
     }
 
     // Generate IA_NA or IA_PD option with specified parameters
-    boost::shared_ptr<Option6IA> generateIA(uint16_t type, uint32_t iaid,
-                                            uint32_t t1, uint32_t t2);
+    boost::shared_ptr<isc::dhcp::Option6IA> generateIA
+        (uint16_t type, uint32_t iaid, uint32_t t1, uint32_t t2);
 
     /// @brief generates interface-id option, based on text
     ///
     /// @param iface_id textual representation of the interface-id content
     ///
     /// @return pointer to the option object
-    OptionPtr generateInterfaceId(const std::string& iface_id) {
-        OptionBuffer tmp(iface_id.begin(), iface_id.end());
-        return OptionPtr(new Option(Option::V6, D6O_INTERFACE_ID, tmp));
+    isc::dhcp::OptionPtr generateInterfaceId(const std::string& iface_id) {
+        isc::dhcp::OptionBuffer tmp(iface_id.begin(), iface_id.end());
+        return (isc::dhcp::OptionPtr
+                (new isc::dhcp::Option(isc::dhcp::Option::V6,
+                                       D6O_INTERFACE_ID, tmp)));
     }
 
     // Generate client-id option
-    OptionPtr generateClientId(size_t duid_size = 32) {
+    isc::dhcp::OptionPtr generateClientId(size_t duid_size = 32) {
 
-        OptionBuffer clnt_duid(duid_size);
+        isc::dhcp::OptionBuffer clnt_duid(duid_size);
         for (int i = 0; i < duid_size; i++) {
             clnt_duid[i] = 100 + i;
         }
 
-        duid_ = DuidPtr(new DUID(clnt_duid));
+        duid_ = isc::dhcp::DuidPtr(new isc::dhcp::DUID(clnt_duid));
 
-        return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
-                                     clnt_duid.begin(),
-                                     clnt_duid.begin() + duid_size)));
+        return (isc::dhcp::OptionPtr
+                (new isc::dhcp::Option(isc::dhcp::Option::V6, D6O_CLIENTID,
+                                       clnt_duid.begin(),
+                                       clnt_duid.begin() + duid_size)));
     }
 
-    // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
-    void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+    // Checks if server response (ADVERTISE or REPLY) includes proper
+    // server-id.
+    void checkServerId(const isc::dhcp::Pkt6Ptr& rsp,
+                       const isc::dhcp::OptionPtr& expected_srvid)
+    {
         // check that server included its server-id
-        OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+        isc::dhcp::OptionPtr tmp = rsp->getOption(D6O_SERVERID);
         EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
         ASSERT_EQ(tmp->len(), expected_srvid->len() );
         EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
     }
 
-    // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
-    void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+    // Checks if server response (ADVERTISE or REPLY) includes proper
+    // client-id.
+    void checkClientId(const isc::dhcp::Pkt6Ptr& rsp,
+                       const isc::dhcp::OptionPtr& expected_clientid)
+    {
         // check that server included our own client-id
-        OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+        isc::dhcp::OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
         ASSERT_TRUE(tmp);
         EXPECT_EQ(expected_clientid->getType(), tmp->getType());
         ASSERT_EQ(expected_clientid->len(), tmp->len());
@@ -202,18 +206,21 @@ public:
     }
 
     // Checks if server response is a NAK
-    void checkNakResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+    void checkNakResponse(const isc::dhcp::Pkt6Ptr& rsp,
+                          uint8_t expected_message_type,
                           uint32_t expected_transid,
-                          uint16_t expected_status_code) {
+                          uint16_t expected_status_code)
+    {
         // Check if we get response at all
         checkResponse(rsp, expected_message_type, expected_transid);
 
         // Check that IA_NA was returned
-        OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
+        isc::dhcp::OptionPtr option_ia_na = rsp->getOption(D6O_IA_NA);
         ASSERT_TRUE(option_ia_na);
 
         // check that the status is no address available
-        boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(option_ia_na);
+        boost::shared_ptr<isc::dhcp::Option6IA> ia =
+            boost::dynamic_pointer_cast<isc::dhcp::Option6IA>(option_ia_na);
         ASSERT_TRUE(ia);
 
         checkIA_NAStatusCode(ia, expected_status_code);
@@ -227,8 +234,10 @@ public:
     // Status code indicates type of error encountered (in theory it can also
     // indicate success, but servers typically don't send success status
     // as this is the default result and it saves bandwidth)
-    void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
-                            uint16_t expected_status_code) {
+    void checkIA_NAStatusCode
+        (const boost::shared_ptr<isc::dhcp::Option6IA>& ia,
+         uint16_t expected_status_code)
+    {
         // Make sure there is no address assigned.
         EXPECT_FALSE(ia->getOption(D6O_IAADDR));
 
@@ -236,10 +245,12 @@ public:
         EXPECT_EQ(0, ia->getT1());
         EXPECT_EQ(0, ia->getT2());
 
-        OptionCustomPtr status =
-            boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+        isc::dhcp::OptionCustomPtr status =
+            boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
+                (ia->getOption(D6O_STATUS_CODE));
 
-        // It is ok to not include status success as this is the default behavior
+        // It is ok to not include status success as this is the default
+        // behavior
         if (expected_status_code == STATUS_Success && !status) {
             return;
         }
@@ -247,35 +258,42 @@ public:
         EXPECT_TRUE(status);
 
         if (status) {
-            // We don't have dedicated class for status code, so let's just interpret
-            // first 2 bytes as status. Remainder of the status code option content is
-            // just a text explanation what went wrong.
+            // We don't have dedicated class for status code, so let's
+            // just interpret first 2 bytes as status. Remainder of the
+            // status code option content is just a text explanation
+            // what went wrong.
             EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
                       status->readInteger<uint16_t>(0));
         }
     }
 
-    void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
-        OptionCustomPtr status =
-            boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+    void checkMsgStatusCode(const isc::dhcp::Pkt6Ptr& msg,
+                            uint16_t expected_status)
+    {
+        isc::dhcp::OptionCustomPtr status =
+            boost::dynamic_pointer_cast<isc::dhcp::OptionCustom>
+                (msg->getOption(D6O_STATUS_CODE));
 
-        // It is ok to not include status success as this is the default behavior
+        // It is ok to not include status success as this is the default
+        // behavior
         if (expected_status == STATUS_Success && !status) {
             return;
         }
 
         EXPECT_TRUE(status);
         if (status) {
-            // We don't have dedicated class for status code, so let's just interpret
-            // first 2 bytes as status. Remainder of the status code option content is
-            // just a text explanation what went wrong.
+            // We don't have dedicated class for status code, so let's
+            // just interpret first 2 bytes as status. Remainder of the
+            // status code option content is just a text explanation
+            // what went wrong.
             EXPECT_EQ(static_cast<uint16_t>(expected_status),
                       status->readInteger<uint16_t>(0));
         }
     }
 
     // Basic checks for generated response (message type and transaction-id).
-    void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+    void checkResponse(const isc::dhcp::Pkt6Ptr& rsp,
+                       uint8_t expected_message_type,
                        uint32_t expected_transid) {
         ASSERT_TRUE(rsp);
         EXPECT_EQ(expected_message_type, rsp->getType());
@@ -285,28 +303,27 @@ public:
     virtual ~NakedDhcpv6SrvTest() {
         // Let's clean up if there is such a file.
         unlink(DUID_FILE);
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "buffer6_receive");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "buffer6_send");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "lease6_renew");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "lease6_release");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "pkt6_receive");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "pkt6_send");
-        HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts(
-                                                 "subnet6_select");
-
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("buffer6_receive");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("buffer6_send");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("lease6_renew");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("lease6_release");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("pkt6_receive");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("pkt6_send");
+        isc::hooks::HooksManager::preCalloutsLibraryHandle()
+            .deregisterAllCallouts("subnet6_select");
     };
 
     // A DUID used in most tests (typically as client-id)
-    DuidPtr duid_;
+    isc::dhcp::DuidPtr duid_;
 
     int rcode_;
-    ConstElementPtr comment_;
+    isc::data::ConstElementPtr comment_;
 
     // Name of a valid network interface
     std::string valid_iface_;
@@ -323,18 +340,23 @@ public:
     /// Sets up a single subnet6 with one pool for addresses and second
     /// pool for prefixes.
     Dhcpv6SrvTest() {
-        subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
-                                         2000, 3000, 4000));
-        pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:1::"),
-                                   64));
+        subnet_ = isc::dhcp::Subnet6Ptr
+            (new isc::dhcp::Subnet6(isc::asiolink::IOAddress("2001:db8:1::"),
+                                    48, 1000, 2000, 3000, 4000));
+        pool_ = isc::dhcp::Pool6Ptr
+            (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_NA,
+                                  isc::asiolink::IOAddress("2001:db8:1:1::"),
+                                  64));
         subnet_->addPool(pool_);
 
-        CfgMgr::instance().deleteSubnets6();
-        CfgMgr::instance().addSubnet6(subnet_);
+        isc::dhcp::CfgMgr::instance().deleteSubnets6();
+        isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
 
         // configure PD pool
-        pd_pool_ = Pool6Ptr(new Pool6(Lease::TYPE_PD,
-                                      IOAddress("2001:db8:1:2::"), 64, 80));
+        pd_pool_ = isc::dhcp::Pool6Ptr
+            (new isc::dhcp::Pool6(isc::dhcp::Lease::TYPE_PD,
+                                  isc::asiolink::IOAddress("2001:db8:1:2::"),
+                                  64, 80));
         subnet_->addPool(pd_pool_);
     }
 
@@ -342,7 +364,7 @@ public:
     ///
     /// Removes existing configuration.
     ~Dhcpv6SrvTest() {
-        CfgMgr::instance().deleteSubnets6();
+        isc::dhcp::CfgMgr::instance().deleteSubnets6();
     };
 
     /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
@@ -353,8 +375,8 @@ public:
     /// @param expected_t1 expected T1 value
     /// @param expected_t2 expected T2 value
     /// @return IAADDR option for easy chaining with checkIAAddr method
-    boost::shared_ptr<Option6IAAddr>
-        checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+    boost::shared_ptr<isc::dhcp::Option6IAAddr>
+        checkIA_NA(const isc::dhcp::Pkt6Ptr& rsp, uint32_t expected_iaid,
                    uint32_t expected_t1, uint32_t expected_t2);
 
     /// @brief Checks that server response (ADVERTISE or REPLY) contains proper
@@ -365,15 +387,15 @@ public:
     /// @param expected_t1 expected T1 value
     /// @param expected_t2 expected T2 value
     /// @return IAPREFIX option for easy chaining with checkIAAddr method
-    boost::shared_ptr<Option6IAPrefix>
-    checkIA_PD(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+    boost::shared_ptr<isc::dhcp::Option6IAPrefix>
+    checkIA_PD(const isc::dhcp::Pkt6Ptr& rsp, uint32_t expected_iaid,
                uint32_t expected_t1, uint32_t expected_t2);
 
     // Check that generated IAADDR option contains expected address
     // and lifetime values match the configured subnet
-    void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
-                     const IOAddress& expected_addr,
-                     Lease::Type type) {
+    void checkIAAddr(const boost::shared_ptr<isc::dhcp::Option6IAAddr>& addr,
+                     const isc::asiolink::IOAddress& expected_addr,
+                     isc::dhcp::Lease::Type type) {
 
         // Check that the assigned address is indeed from the configured pool.
         // Note that when comparing addresses, we compare the textual
@@ -387,8 +409,9 @@ public:
 
     // Checks if the lease sent to client is present in the database
     // and is valid when checked agasint the configured subnet
-    Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
-                         boost::shared_ptr<Option6IAAddr> addr);
+    isc::dhcp::Lease6Ptr checkLease
+        (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_na,
+         boost::shared_ptr<isc::dhcp::Option6IAAddr> addr);
 
     /// @brief Verifies received IAPrefix option
     ///
@@ -399,8 +422,9 @@ public:
     /// @param ia_pd IA_PD option that contains the IAPRefix option
     /// @param prefix pointer to the IAPREFIX option
     /// @return corresponding IPv6 lease (if found)
-    Lease6Ptr checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
-                           boost::shared_ptr<Option6IAPrefix> prefix);
+    isc::dhcp::Lease6Ptr checkPdLease
+      (const isc::dhcp::DuidPtr& duid, const isc::dhcp::OptionPtr& ia_pd,
+       boost::shared_ptr<isc::dhcp::Option6IAPrefix> prefix);
 
     /// @brief Creates a message with specified IA
     ///
@@ -414,10 +438,10 @@ public:
     /// @param prefix_len length of the prefix (used for prefixes only)
     /// @param iaid IA identifier (used in IA_XX option)
     /// @return created message
-    Pkt6Ptr
-    createMessage(uint8_t message_type, Lease::Type lease_type,
-                  const IOAddress& addr, const uint8_t prefix_len,
-                  uint32_t iaid);
+    isc::dhcp::Pkt6Ptr
+    createMessage(uint8_t message_type, isc::dhcp::Lease::Type lease_type,
+                  const isc::asiolink::IOAddress& addr,
+                  const uint8_t prefix_len, uint32_t iaid);
 
     /// @brief Performs basic (positive) RENEW test
     ///
@@ -431,7 +455,8 @@ public:
     /// @param renew_addr address being sent in RENEW
     /// @param prefix_len length of the prefix (128 for addresses)
     void
-    testRenewBasic(Lease::Type type, const std::string& existing_addr,
+    testRenewBasic(isc::dhcp::Lease::Type type,
+                   const std::string& existing_addr,
                    const std::string& renew_addr, const uint8_t prefix_len);
 
     /// @brief Performs negative RENEW test
@@ -444,7 +469,8 @@ public:
     /// @param type type (TYPE_NA or TYPE_PD)
     /// @param addr address being sent in RENEW
     void
-    testRenewReject(Lease::Type type, const IOAddress& addr);
+    testRenewReject(isc::dhcp::Lease::Type type,
+                    const isc::asiolink::IOAddress& addr);
 
     /// @brief Performs basic (positive) RELEASE test
     ///
@@ -457,44 +483,47 @@ public:
     /// @param existing address to be preinserted into the database
     /// @param release_addr address being sent in RELEASE
     void
-    testReleaseBasic(Lease::Type type, const IOAddress& existing,
-                     const IOAddress& release_addr);
+    testReleaseBasic(isc::dhcp::Lease::Type type,
+                     const isc::asiolink::IOAddress& existing,
+                     const isc::asiolink::IOAddress& release_addr);
 
     /// @brief Performs negative RELEASE test
     ///
-    /// See releaseReject and pdReleaseReject tests for detailed explanation.
-    /// In essence the test attempts to perform couple failed RELEASE scenarios.
+    /// See releaseReject and pdReleaseReject tests for detailed
+    /// explanation.  In essence the test attempts to perform couple
+    /// failed RELEASE scenarios.
     ///
     /// This method does not throw, but uses gtest macros to signify failures.
     ///
     /// @param type type (TYPE_NA or TYPE_PD)
     /// @param addr address being sent in RELEASE
     void
-    testReleaseReject(Lease::Type type, const IOAddress& addr);
+    testReleaseReject(isc::dhcp::Lease::Type type,
+                      const isc::asiolink::IOAddress& addr);
 
     // see wireshark.cc for descriptions
     // The descriptions are too large and too closely related to the
     // code, so it is kept in .cc rather than traditionally in .h
-    Pkt6Ptr captureSimpleSolicit();
-    Pkt6Ptr captureRelayedSolicit();
-    Pkt6Ptr captureDocsisRelayedSolicit();
-    Pkt6Ptr captureeRouterRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureSimpleSolicit();
+    isc::dhcp::Pkt6Ptr captureRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureDocsisRelayedSolicit();
+    isc::dhcp::Pkt6Ptr captureeRouterRelayedSolicit();
 
     /// @brief Auxiliary method that sets Pkt6 fields
     ///
     /// Used to reconstruct captured packets. Sets UDP ports, interface names,
     /// and other fields to some believable values.
     /// @param pkt packet that will have its fields set
-    void captureSetDefaultFields(const Pkt6Ptr& pkt);
+    void captureSetDefaultFields(const isc::dhcp::Pkt6Ptr& pkt);
 
     /// A subnet used in most tests
-    Subnet6Ptr subnet_;
+    isc::dhcp::Subnet6Ptr subnet_;
 
     /// A normal, non-temporary pool used in most tests
-    Pool6Ptr pool_;
+    isc::dhcp::Pool6Ptr pool_;
 
     /// A prefix pool used in most tests
-    Pool6Ptr pd_pool_;
+    isc::dhcp::Pool6Ptr pd_pool_;
 };
 
 }; // end of isc::test namespace

+ 195 - 67
src/bin/dhcp6/tests/fqdn_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-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
@@ -227,9 +227,6 @@ public:
     /// This function verifies that the FQDN option returned is correct.
     ///
     /// @param msg_type A type of the client's message.
-    /// @param use_oro A boolean value which indicates whether the DHCPv6 ORO
-    /// option (requesting return of the FQDN option by the server) should be
-    /// included in the client's message (if true), or not included (if false).
     /// @param in_flags A value of flags field to be set for the FQDN carried
     /// in the client's message.
     /// @param in_domain_name A domain name to be carried in the client's FQDN
@@ -240,7 +237,6 @@ public:
     /// @param exp_domain_name A domain name expected in the FQDN sent by a
     /// server.
     void testFqdn(const uint16_t msg_type,
-                  const bool use_oro,
                   const uint8_t in_flags,
                   const std::string& in_domain_name,
                   const Option6ClientFqdn::DomainNameType in_domain_type,
@@ -251,11 +247,14 @@ public:
                                            in_flags,
                                            in_domain_name,
                                            in_domain_type,
-                                           use_oro);
+                                           true);
         ASSERT_TRUE(getClientFqdnOption(question));
 
-        Option6ClientFqdnPtr answ_fqdn;
-        ASSERT_NO_THROW(answ_fqdn = srv.processClientFqdn(question));
+        Pkt6Ptr answer(new Pkt6(msg_type == DHCPV6_SOLICIT ? DHCPV6_ADVERTISE :
+                                DHCPV6_REPLY, question->getTransid()));
+        ASSERT_NO_THROW(srv.processClientFqdn(question, answer));
+        Option6ClientFqdnPtr answ_fqdn = boost::dynamic_pointer_cast<
+            Option6ClientFqdn>(answer->getOption(D6O_CLIENT_FQDN));
         ASSERT_TRUE(answ_fqdn);
 
         const bool flag_n = (exp_flags & Option6ClientFqdn::FLAG_N) != 0;
@@ -267,7 +266,19 @@ public:
         EXPECT_EQ(flag_o, answ_fqdn->getFlag(Option6ClientFqdn::FLAG_O));
 
         EXPECT_EQ(exp_domain_name, answ_fqdn->getDomainName());
-        EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+        // If server is configured to generate full FQDN for a client, and/or
+        // client sent empty FQDN the expected result of the processing by
+        // processClientFqdn is an empty, partial FQDN. This is an indication
+        // for the code which performs lease allocation that the FQDN has to
+        // be generated from the lease address.
+        if (exp_domain_name.empty()) {
+            EXPECT_EQ(Option6ClientFqdn::PARTIAL,
+                      answ_fqdn->getDomainNameType());
+
+        } else {
+            EXPECT_EQ(Option6ClientFqdn::FULL, answ_fqdn->getDomainNameType());
+
+        }
     }
 
     /// @brief Tests that the client's message holding an FQDN is processed
@@ -283,15 +294,19 @@ public:
     /// that the server doesn't respond with the FQDN.
     void testProcessMessage(const uint8_t msg_type,
                             const std::string& hostname,
+                            const std::string& exp_hostname,
                             NakedDhcpv6Srv& srv,
                             const bool include_oro = true) {
         // Create a message of a specified type, add server id and
         // FQDN option.
         OptionPtr srvid = srv.getServerID();
+        // Set the appropriate FQDN type. It must be partial if hostname is
+        // empty.
+        Option6ClientFqdn::DomainNameType fqdn_type = (hostname.empty() ?
+            Option6ClientFqdn::PARTIAL : Option6ClientFqdn::FULL);
+
         Pkt6Ptr req = generateMessage(msg_type, Option6ClientFqdn::FLAG_S,
-                                      hostname,
-                                      Option6ClientFqdn::FULL,
-                                      include_oro, srvid);
+                                      hostname, fqdn_type, include_oro, srvid);
 
         // For different client's message types we have to invoke different
         // functions to generate response.
@@ -337,13 +352,15 @@ public:
             Lease6Ptr lease =
                 checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
             ASSERT_TRUE(lease);
+            EXPECT_EQ(exp_hostname, lease->hostname_);
         }
 
-        if (include_oro) {
-            ASSERT_TRUE(reply->getOption(D6O_CLIENT_FQDN));
-        } else {
-            ASSERT_FALSE(reply->getOption(D6O_CLIENT_FQDN));
-        }
+        // The Client FQDN option should be always present in the server's
+        // response, regardless if requested using ORO or not.
+        Option6ClientFqdnPtr fqdn;
+        ASSERT_TRUE(fqdn = boost::dynamic_pointer_cast<
+                        Option6ClientFqdn>(reply->getOption(D6O_CLIENT_FQDN)));
+        EXPECT_EQ(exp_hostname, fqdn->getDomainName());
     }
 
     /// @brief Verify that NameChangeRequest holds valid values.
@@ -394,7 +411,7 @@ public:
 
 // Test server's response when client requests that server performs AAAA update.
 TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
-    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S,
+    testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S,
              "myhost.example.com",
              Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_S,
              "myhost.example.com.");
@@ -403,7 +420,7 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdate) {
 // Test server's response when client provides partial domain-name and requests
 // that server performs AAAA update.
 TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
-    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "myhost",
+    testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "myhost",
              Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
              "myhost.example.com.");
 }
@@ -411,14 +428,13 @@ TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdatePartialName) {
 // Test server's response when client provides empty domain-name and requests
 // that server performs AAAA update.
 TEST_F(FqdnDhcpv6SrvTest, serverAAAAUpdateNoName) {
-    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_S, "",
-             Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S,
-             "myhost.example.com.");
+    testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_S, "",
+             Option6ClientFqdn::PARTIAL, Option6ClientFqdn::FLAG_S, "");
 }
 
 // Test server's response when client requests no DNS update.
 TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
-    testFqdn(DHCPV6_SOLICIT, true, Option6ClientFqdn::FLAG_N,
+    testFqdn(DHCPV6_SOLICIT, Option6ClientFqdn::FLAG_N,
              "myhost.example.com",
              Option6ClientFqdn::FULL, Option6ClientFqdn::FLAG_N,
              "myhost.example.com.");
@@ -427,7 +443,7 @@ TEST_F(FqdnDhcpv6SrvTest, noUpdate) {
 // Test server's response when client requests that server delegates the AAAA
 // update to the client and this delegation is not allowed.
 TEST_F(FqdnDhcpv6SrvTest, clientAAAAUpdateNotAllowed) {
-    testFqdn(DHCPV6_SOLICIT, true, 0, "myhost.example.com.",
+    testFqdn(DHCPV6_SOLICIT, 0, "myhost.example.com.",
              Option6ClientFqdn::FULL,
              Option6ClientFqdn::FLAG_S | Option6ClientFqdn::FLAG_O,
              "myhost.example.com.");
@@ -439,10 +455,8 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAnswer) {
     NakedDhcpv6Srv srv(0);
 
     Pkt6Ptr answer;
-    Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
-                                                 "myhost.example.com",
-                                                 Option6ClientFqdn::FULL);
-    EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
+
+    EXPECT_THROW(srv.createNameChangeRequests(answer),
                  isc::Unexpected);
 
 }
@@ -456,22 +470,21 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoDUID) {
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
                                                  "myhost.example.com",
                                                  Option6ClientFqdn::FULL);
+    answer->addOption(fqdn);
 
-    EXPECT_THROW(srv.createNameChangeRequests(answer, fqdn),
-                 isc::Unexpected);
+    EXPECT_THROW(srv.createNameChangeRequests(answer), isc::Unexpected);
 
 }
 
-// Test no NameChangeRequests are added if FQDN option is NULL.
+// Test no NameChangeRequests if Client FQDN is not added to the server's
+// response.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoFQDN) {
     NakedDhcpv6Srv srv(0);
 
     // Create Reply message with Client Id and Server id.
     Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
 
-    // Pass NULL FQDN option. No NameChangeRequests should be created.
-    Option6ClientFqdnPtr fqdn;
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
 
     // There should be no new NameChangeRequests.
     EXPECT_TRUE(srv.name_change_reqs_.empty());
@@ -485,20 +498,21 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequestsNoAddr) {
     // Create Reply message with Client Id and Server id.
     Pkt6Ptr answer = generateMessageWithIds(DHCPV6_REPLY, srv);
 
+    // Add Client FQDN option.
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
                                                  "myhost.example.com",
                                                  Option6ClientFqdn::FULL);
+    answer->addOption(fqdn);
 
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
+    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
 
     // We didn't add any IAs, so there should be no NameChangeRequests in th
     // queue.
     ASSERT_TRUE(srv.name_change_reqs_.empty());
 }
 
-// Test that a number of NameChangeRequests is created as a result of
-// processing the answer message which holds 3 IAs and when FQDN is
-// specified.
+// Test that exactly one NameChangeRequest is created as a result of processing
+// the answer message which holds 3 IAs and when FQDN is specified.
 TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
     NakedDhcpv6Srv srv(0);
 
@@ -516,33 +530,19 @@ TEST_F(FqdnDhcpv6SrvTest, createNameChangeRequests) {
     Option6ClientFqdnPtr fqdn = createClientFqdn(Option6ClientFqdn::FLAG_S,
                                                  "MYHOST.EXAMPLE.COM",
                                                  Option6ClientFqdn::FULL);
+    answer->addOption(fqdn);
 
-    // Create NameChangeRequests. Since we have added 3 IAs, it should
-    // result in generation of 3 distinct NameChangeRequests.
-    ASSERT_NO_THROW(srv.createNameChangeRequests(answer, fqdn));
-    ASSERT_EQ(3, srv.name_change_reqs_.size());
-
-    // Verify that NameChangeRequests are correct. Each call to the
-    // verifyNameChangeRequest will pop verified request from the queue.
+    // Create NameChangeRequest for the first allocated address.
+    ASSERT_NO_THROW(srv.createNameChangeRequests(answer));
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
 
+    // Verify that NameChangeRequest is correct.
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1::1",
                             "000201415AA33D1187D148275136FA30300478"
                             "FAAAA3EBD29826B5C907B2C9268A6F52",
                             0, 500);
 
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
-                            "2001:db8:1::2",
-                            "000201415AA33D1187D148275136FA30300478"
-                            "FAAAA3EBD29826B5C907B2C9268A6F52",
-                            0, 500);
-
-    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
-                            "2001:db8:1::3",
-                            "000201415AA33D1187D148275136FA30300478"
-                            "FAAAA3EBD29826B5C907B2C9268A6F52",
-                            0, 500);
-
 }
 
 // Test creation of the NameChangeRequest to remove both forward and reverse
@@ -642,7 +642,8 @@ TEST_F(FqdnDhcpv6SrvTest, processSolicit) {
 
     // Create a Solicit message with FQDN option and generate server's
     // response using processSolicit function.
-    testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_SOLICIT, "myhost.example.com",
+                       "myhost.example.com.", srv);
     EXPECT_TRUE(srv.name_change_reqs_.empty());
 }
 
@@ -657,7 +658,8 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -672,7 +674,8 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
     // entries should be removed and the entries for the new domain-name
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
-    testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -687,6 +690,38 @@ TEST_F(FqdnDhcpv6SrvTest, processTwoRequests) {
 
 }
 
+// Test that NameChangeRequest is not generated when Solicit message is sent.
+// The Solicit is here sent after a lease has been allocated for a client.
+// The Solicit conveys a different hostname which would trigger updates to
+// DNS if the Request was sent instead of Soicit. The code should differentiate
+// behavior depending whether Solicit or Request is sent.
+TEST_F(FqdnDhcpv6SrvTest, processRequestSolicit) {
+    NakedDhcpv6Srv srv(0);
+
+    // Create a Request message with FQDN option and generate server's
+    // response using processRequest function. This will result in the
+    // creation of a new lease and the appropriate NameChangeRequest
+    // to add both reverse and forward mapping to DNS.
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4000);
+
+    // When the returning client sends Solicit the code should never generate
+    // NameChangeRequest and preserve existing DNS entries for the client.
+    // The NameChangeRequest should only be generated when a client sends
+    // Request or Renew.
+    testProcessMessage(DHCPV6_SOLICIT, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
+    ASSERT_TRUE(srv.name_change_reqs_.empty());
+
+}
+
+
 // Test that client may send Request followed by the Renew, both holding
 // FQDN options, but each option holding different domain-name. The Renew
 // should result in generation of the two NameChangeRequests, one to remove
@@ -699,7 +734,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -714,7 +750,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRenew) {
     // entries should be removed and the entries for the new domain-name
     // should be added. Therefore, we expect two NameChangeRequests. One to
     // remove the existing entries, one to add new entries.
-    testProcessMessage(DHCPV6_RENEW, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_RENEW, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_EQ(2, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -736,7 +773,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // response using processRequest function. This will result in the
     // creation of a new lease and the appropriate NameChangeRequest
     // to add both reverse and forward mapping to DNS.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -748,7 +786,8 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
     // removed and all existing DNS entries for this lease should be
     // also removed. Therefore, we expect that single NameChangeRequest to
     // remove DNS entries is generated.
-    testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com", srv);
+    testProcessMessage(DHCPV6_RELEASE, "otherhost.example.com",
+                       "otherhost.example.com.", srv);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -758,15 +797,16 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestRelease) {
 
 }
 
-// Checks that the server does not include DHCPv6 Client FQDN option in its
-// response when client doesn't include ORO option in the Request.
+// Checks that the server include DHCPv6 Client FQDN option in its
+// response even when client doesn't request this option using ORO.
 TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
     NakedDhcpv6Srv srv(0);
 
     // The last parameter disables use of the ORO to request FQDN option
     // In this case, we expect that the FQDN option will not be included
     // in the server's response. The testProcessMessage will check that.
-    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com", srv, false);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv, false);
     ASSERT_EQ(1, srv.name_change_reqs_.size());
     verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
                             "2001:db8:1:1::dead:beef",
@@ -775,4 +815,92 @@ TEST_F(FqdnDhcpv6SrvTest, processRequestWithoutFqdn) {
                             0, 4000);
 }
 
+// Checks that FQDN is generated from an ip address, when client sends an empty
+// FQDN.
+TEST_F(FqdnDhcpv6SrvTest, processRequestEmptyFqdn) {
+    NakedDhcpv6Srv srv(0);
+
+    testProcessMessage(DHCPV6_REQUEST, "",
+                       "host-2001-db8-1-1--dead-beef.example.com.",
+                       srv, false);
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "0002018D6874B105A5C92DBBD6E4F6C80A93161"
+                            "BC03996F0CD0EB75800DEF997C29961",
+                            0, 4000);
+
+}
+
+// Checks that when the server reuses expired lease, the NameChangeRequest
+// is generated to remove the DNS mapping for the expired lease and second
+// NameChangeRequest to add a DNS mapping for a new lease.
+TEST_F(FqdnDhcpv6SrvTest, processRequestReuseExpiredLease) {
+    // This address will be used throughout the test.
+    IOAddress addr("2001:db8:1:1::dead:beef");
+    // We are going to configure a subnet with a pool that consists of
+    // exactly one address. This address will be handed out to the
+    // client, will get expired and then be reused.
+    CfgMgr::instance().deleteSubnets6();
+    subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1:1::"), 56, 1, 2,
+                                     3, 4));
+    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr));
+    subnet_->addPool(pool_);
+    CfgMgr::instance().addSubnet6(subnet_);
+
+    // Allocate a lease.
+    NakedDhcpv6Srv srv(0);
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com",
+                       "myhost.example.com.", srv);
+    // Test that the appropriate NameChangeRequest has been generated.
+    ASSERT_EQ(1, srv.name_change_reqs_.size());
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4);
+    // Get the lease acquired and modify it. In particular, expire it.
+    Lease6Ptr lease =
+        LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+    ASSERT_TRUE(lease);
+    // One of the following: IAID, DUID or subnet identifier has to be changed
+    // because otherwise the allocation engine will treat the lease as
+    // being renewed by the same client. If we at least change subnet identifier
+    // the lease will be treated as expired lease to be reused.
+    ++lease->subnet_id_;
+
+    // Move the cllt back in time and make sure that the lease got expired.
+    lease->cltt_ = time(NULL) - 10;
+    lease->valid_lft_ = 5;
+    ASSERT_TRUE(lease->expired());
+    // Change the hostname so as the name change request for removing existing
+    // DNS mapping is generated.
+    lease->hostname_ = "otherhost.example.com.";
+    // Update the lease in the lease database.
+    LeaseMgrFactory::instance().updateLease6(lease);
+
+    // Simulate another lease acquisition. Since, our pool consists of
+    // exactly one address and this address is used by the lease in the
+    // lease database, it is guaranteed that the allocation engine will
+    // reuse this lease.
+    testProcessMessage(DHCPV6_REQUEST, "myhost.example.com.",
+                       "myhost.example.com.", srv);
+    ASSERT_EQ(2, srv.name_change_reqs_.size());
+    // The first name change request generated, should remove a DNS
+    // mapping for an expired lease.
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_REMOVE, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201D422AA463306223D269B6CB7AFE7AAD2"
+                            "65FCEA97F93623019B2E0D14E5323D5A",
+                            0, 5);
+    // The second name change request should add a DNS mapping for
+    // a new lease.
+    verifyNameChangeRequest(srv, isc::dhcp_ddns::CHG_ADD, true, true,
+                            "2001:db8:1:1::dead:beef",
+                            "000201415AA33D1187D148275136FA30300478"
+                            "FAAAA3EBD29826B5C907B2C9268A6F52",
+                            0, 4);
+
+}
+
 }   // end of anonymous namespace

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

@@ -36,6 +36,8 @@
 #include <sstream>
 
 using namespace isc;
+using namespace isc::data;
+using namespace isc::config;
 using namespace isc::test;
 using namespace isc::asiolink;
 using namespace isc::dhcp;

+ 2 - 0
src/bin/dhcp6/tests/wireshark.cc

@@ -38,6 +38,8 @@
 ///    (Make sure that the packet is expanded in the view. The text file will
 ///    contain whatever expansion level you have in the graphical tree.)
 
+using namespace isc::dhcp;
+using namespace isc::asiolink;
 using namespace std;
 
 namespace isc {

+ 2 - 2
src/bin/resolver/resolver.cc

@@ -418,7 +418,7 @@ Resolver::processMessage(const IOMessage& io_message,
             server->resume(false);
             return;
         }
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO,
                   RESOLVER_HEADER_PROCESSING_FAILED).arg(ex.what());
         server->resume(false);
@@ -436,7 +436,7 @@ Resolver::processMessage(const IOMessage& io_message,
                          buffer, error.getRcode());
         server->resume(true);
         return;
-    } catch (const Exception& ex) {
+    } catch (const isc::Exception& ex) {
         LOG_DEBUG(resolver_logger, RESOLVER_DBG_IO,
                   RESOLVER_MESSAGE_PROCESSING_FAILED)
                   .arg(ex.what()).arg(Rcode::SERVFAIL());

+ 1 - 4
src/bin/xfrin/tests/xfrin_test.py

@@ -373,10 +373,7 @@ class MockXfrinConnection(XfrinConnection):
         [resp.add_rrset(Message.SECTION_AUTHORITY, a) for a in authorities]
 
         renderer = MessageRenderer()
-        if tsig_ctx is not None:
-            resp.to_wire(renderer, tsig_ctx)
-        else:
-            resp.to_wire(renderer)
+        resp.to_wire(renderer, tsig_ctx)
         reply_data = struct.pack('H', socket.htons(renderer.get_length()))
         reply_data += renderer.get_data()
 

+ 2 - 1
src/bin/xfrin/xfrin.py.in

@@ -727,8 +727,9 @@ class XfrinConnection(asyncore.dispatcher):
             self.connect(self._master_addrinfo[2])
             return True
         except socket.error as e:
-            logger.error(XFRIN_CONNECT_MASTER, self.tsig_key_name,
+            logger.error(XFRIN_CONNECT_MASTER,
                          self._master_addrinfo[2],
+                         self._zone_name.to_text(),
                          str(e))
             return False
 

+ 3 - 3
src/bin/xfrin/xfrin_messages.mes

@@ -56,9 +56,9 @@ most likely cause is that xfrin the msgq daemon is not running.
 There was an error while the given command was being processed. The
 error is given in the log message.
 
-% XFRIN_CONNECT_MASTER (with TSIG %1) error connecting to master at %2: %3
-There was an error opening a connection to the master. The error is
-shown in the log message.
+% XFRIN_CONNECT_MASTER error connecting to master at %1 for %2: %3
+There was an error opening a connection to the master for the specified
+zone. The error is shown in the log message.
 
 % XFRIN_DATASRC_CONFIG_ERROR failed to update data source configuration: %1
 Configuration for the global data sources is updated, but the update

+ 4 - 9
src/bin/xfrout/xfrout.py.in

@@ -143,11 +143,11 @@ def format_addrinfo(addrinfo):
         raise TypeError("addrinfo argument to format_addrinfo() does not "
                         "appear to be consisting of (family, socktype, (addr, port))")
 
+# This function is not inlined as it is replaced with a mock function
+# during testing.
 def get_rrset_len(rrset):
     """Returns the wire length of the given RRset"""
-    bytes = bytearray()
-    rrset.to_wire(bytes)
-    return len(bytes)
+    return rrset.get_length()
 
 def get_soa_serial(soa_rdata):
     '''Extract the serial field of an SOA RDATA and returns it as an Serial object.
@@ -345,12 +345,7 @@ class XfroutSession():
         render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
         render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
 
-        # XXX Currently, python wrapper doesn't accept 'None' parameter in this case,
-        # we should remove the if statement and use a universal interface later.
-        if tsig_ctx is not None:
-            msg.to_wire(render, tsig_ctx)
-        else:
-            msg.to_wire(render)
+        msg.to_wire(render, tsig_ctx)
 
         header_len = struct.pack('H', socket.htons(render.get_length()))
         self._send_data(sock_fd, header_len)

+ 2 - 1
src/hooks/dhcp/user_chk/Makefile.am

@@ -31,7 +31,8 @@ EXTRA_DIST = libdhcp_user_chk.dox
 #CLEANFILES = *.gcno *.gcda user_chk_messages.h user_chk_messages.cc s-messages
 CLEANFILES = *.gcno *.gcda
 
-lib_LTLIBRARIES = libdhcp_user_chk.la
+nodistdir=$(abs_top_builddir)/src/hooks/dhcp/user_chk
+nodist_LTLIBRARIES = libdhcp_user_chk.la
 libdhcp_user_chk_la_SOURCES  =
 libdhcp_user_chk_la_SOURCES += load_unload.cc
 libdhcp_user_chk_la_SOURCES += pkt_receive_co.cc

+ 1 - 4
src/hooks/dhcp/user_chk/user_chk.h

@@ -18,14 +18,11 @@
 #include <fstream>
 #include <string>
 
-using namespace std;
-using namespace user_chk;
-
 // The following constants are used throughout the library.  They are defined
 // in load_unload.cc
 
 /// @brief Pointer to the registry instance.
-extern UserRegistryPtr user_registry;
+extern user_chk::UserRegistryPtr user_registry;
 
 /// @brief Output filestream for recording user check outcomes.
 extern std::fstream user_chk_output;

+ 1 - 1
src/hooks/dhcp/user_chk/user_file.cc

@@ -35,7 +35,7 @@ UserFile::~UserFile(){
 void
 UserFile::open() {
     if (isOpen()) {
-        isc_throw (UserFileError, "file is already open");
+        isc_throw(UserFileError, "file is already open");
     }
 
     file_.open(fname_.c_str(), std::ifstream::in);

+ 4 - 6
src/hooks/dhcp/user_chk/user_file.h

@@ -23,17 +23,15 @@
 #include <fstream>
 #include <string>
 
-using namespace std;
-
 namespace user_chk {
 
 /// @brief Thrown a UserFile encounters an error.
 /// Note that it derives from UserDataSourceError to comply with the interface.
 class UserFileError : public UserDataSourceError {
 public:
-    UserFileError(const char* file, size_t line,
-                               const char* what) :
-        UserDataSourceError(file, line, what) { };
+    UserFileError(const char* file, size_t line, const char* what) :
+        UserDataSourceError(file, line, what)
+    {}
 };
 
 /// @brief Provides a UserDataSource implementation for JSON text files.
@@ -125,7 +123,7 @@ public:
 
 private:
     /// @brief Pathname of the input text file.
-    string fname_;
+    std::string fname_;
 
     /// @brief Input file stream.
     std::ifstream file_;

+ 3 - 5
src/hooks/dhcp/user_chk/user_registry.h

@@ -24,16 +24,14 @@
 
 #include <string>
 
-using namespace std;
-
 namespace user_chk {
 
 /// @brief Thrown UserRegistry encounters an error
 class UserRegistryError : public isc::Exception {
 public:
-    UserRegistryError(const char* file, size_t line,
-                               const char* what) :
-        isc::Exception(file, line, what) { };
+    UserRegistryError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what)
+    {}
 };
 
 /// @brief Defines a map of unique Users keyed by UserId.

+ 1 - 1
src/lib/acl/ip_check.cc

@@ -115,7 +115,7 @@ namespace {
 const uint8_t*
 getSockAddrData(const struct sockaddr& sa) {
     const void* sa_ptr = &sa;
-    const void* data_ptr;
+    const void* data_ptr = NULL;
     if (sa.sa_family == AF_INET) {
         const struct sockaddr_in* sin =
             static_cast<const struct sockaddr_in*>(sa_ptr);

+ 1 - 1
src/lib/asiodns/io_fetch.cc

@@ -162,7 +162,7 @@ struct IOFetchData {
     // we sent.
     bool responseOK() {
         return (*remote_snd == *remote_rcv && cumulative >= 2 &&
-                readUint16(received->getData()) == qid);
+                readUint16(received->getData(), received->getLength()) == qid);
     }
 };
 

+ 6 - 2
src/lib/asiodns/tests/io_fetch_unittest.cc

@@ -280,7 +280,8 @@ public:
         cumulative_ += length;
         bool complete = false;
         if (cumulative_ > 2) {
-            uint16_t dns_length = readUint16(receive_buffer_);
+            uint16_t dns_length = readUint16(receive_buffer_,
+                                             sizeof(receive_buffer_));
             complete = ((dns_length + 2) == cumulative_);
         }
 
@@ -310,7 +311,10 @@ public:
         send_buffer_.clear();
         send_buffer_.push_back(0);
         send_buffer_.push_back(0);
-        writeUint16(return_data_.size(), &send_buffer_[0]);
+        // send_buffer_.capacity() seems more logical below, but the
+        // code above fills in the first two bytes and size() becomes 2
+        // (sizeof uint16_t).
+        writeUint16(return_data_.size(), &send_buffer_[0], send_buffer_.size());
         copy(return_data_.begin(), return_data_.end(), back_inserter(send_buffer_));
         if (return_data_.size() >= 2) {
             send_buffer_[2] = qid_0;

+ 22 - 10
src/lib/asiolink/dummy_io_cb.h

@@ -17,6 +17,8 @@
 
 #include <iostream>
 
+#include <exceptions/exceptions.h>
+
 #include <asio/error.hpp>
 #include <asio/error_code.hpp>
 
@@ -39,20 +41,30 @@ public:
 
     /// \brief Asynchronous I/O callback method
     ///
-    /// TODO: explain why this method should never be called.
-    /// This should be unused.
-    void operator()(asio::error_code)
-    {
-        // TODO: log an error if this method ever gets called.
+    /// Should never be called, as this class is a convenience class provided
+    /// for instances where a socket is required but it is known that no
+    /// asynchronous operations will be carried out.
+    void operator()(asio::error_code) {
+        // If the function is called, there is a serious logic error in
+        // the program (this class should not be used as the callback
+        // class).  As the asiolink module is too low-level for logging
+        // errors, throw an exception.
+        isc_throw(isc::Unexpected,
+                  "DummyIOCallback::operator() must not be called");
     }
 
     /// \brief Asynchronous I/O callback method
     ///
-    /// TODO: explain why this method should never be called.
-    /// This should be unused.
-    void operator()(asio::error_code, size_t)
-    {
-        // TODO: log an error if this method ever gets called.
+    /// Should never be called, as this class is a convenience class provided
+    /// for instances where a socket is required but it is known that no
+    /// asynchronous operations will be carried out.
+    void operator()(asio::error_code, size_t) {
+        // If the function is called, there is a serious logic error in
+        // the program (this class should not be used as the callback
+        // class).  As the asiolink module is too low-level for logging
+        // errors, throw an exception.
+        isc_throw(isc::Unexpected,
+                  "DummyIOCallback::operator() must not be called");
     }
 };
 

+ 16 - 5
src/lib/asiolink/io_address.cc

@@ -100,14 +100,25 @@ IOAddress::getFamily() const {
     }
 }
 
-const asio::ip::address&
-IOAddress::getAddress() const {
-    return asio_address_;
+bool
+IOAddress::isV6LinkLocal() const {
+    if (!asio_address_.is_v6()) {
+        return (false);
+    }
+    return (asio_address_.to_v6().is_link_local());
+}
+
+bool
+IOAddress::isV6Multicast() const {
+    if (!asio_address_.is_v6()) {
+        return (false);
+    }
+    return (asio_address_.to_v6().is_multicast());
 }
 
 IOAddress::operator uint32_t() const {
-    if (getAddress().is_v4()) {
-        return (getAddress().to_v4().to_ulong());
+    if (asio_address_.is_v4()) {
+        return (asio_address_.to_v4().to_ulong());
     } else {
         isc_throw(BadValue, "Can't convert " << toText()
                   << " address to IPv4.");

+ 12 - 10
src/lib/asiolink/io_address.h

@@ -91,14 +91,6 @@ public:
     /// \return A string representation of the address.
     std::string toText() const;
 
-    /// \brief Returns const reference to the underlying address object.
-    ///
-    /// This is useful, when access to interface offerted by
-    //  asio::ip::address_v4 and asio::ip::address_v6 is beneficial.
-    /// 
-    /// \return A const reference to asio::ip::address object
-    const asio::ip::address& getAddress() const;
-
     /// \brief Returns the address family
     ///
     /// \return AF_INET for IPv4 or AF_INET6 for IPv6.
@@ -118,6 +110,16 @@ public:
         return (asio_address_.is_v6());
     }
 
+    /// \brief checks whether and address is IPv6 and is link-local
+    ///
+    /// \return true if the address is IPv6 link-local, false otherwise
+    bool isV6LinkLocal() const;
+
+    /// \brief checks whether and address is IPv6 and is multicast
+    ///
+    /// \return true if the address is IPv6 multicast, false otherwise
+    bool isV6Multicast() const;
+
     /// \brief Creates an address from over wire data.
     ///
     /// \param family AF_NET for IPv4 or AF_NET6 for IPv6.
@@ -196,7 +198,7 @@ public:
     ///
     /// \param other Address to compare against.
     ///
-    /// See \ref smaller_than method for details.
+    /// See \ref lessThan method for details.
     bool operator<(const IOAddress& other) const {
         return (lessThan(other));
     }
@@ -205,7 +207,7 @@ public:
     ///
     /// \param other Address to compare against.
     ///
-    /// See \ref smaller_equal method for details.
+    /// See \ref smallerEqual method for details.
     bool operator<=(const IOAddress& other) const {
         return (smallerEqual(other));
     }

+ 1 - 1
src/lib/asiolink/tcp_socket.h

@@ -358,7 +358,7 @@ TCPSocket<C>::processReceivedData(const void* staging, size_t length,
         }
 
         // Have enough data to interpret the packet count, so do so now.
-        expected = isc::util::readUint16(data);
+        expected = isc::util::readUint16(data, cumulative);
 
         // We have two bytes less of data to process.  Point to the start of the
         // data and adjust the packet size.  Note that at this point,

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

@@ -35,6 +35,7 @@ run_unittests_SOURCES += udp_endpoint_unittest.cc
 run_unittests_SOURCES += udp_socket_unittest.cc
 run_unittests_SOURCES += io_service_unittest.cc
 run_unittests_SOURCES += local_socket_unittest.cc
+run_unittests_SOURCES += dummy_io_callback_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 

+ 36 - 0
src/lib/asiolink/tests/dummy_io_callback_unittest.cc

@@ -0,0 +1,36 @@
+// Copyright (C) 2014  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+
+#include <asiolink/dummy_io_cb.h>
+
+using namespace isc::asiolink;
+using namespace asio;
+
+namespace { // begin unnamed namespace
+
+TEST(DummyIOCallbackTest, throws) {
+    DummyIOCallback cb;
+    asio::error_code error_code;
+
+    // All methods should throw isc::Unexpected.
+    EXPECT_THROW(cb(error_code), isc::Unexpected);
+    EXPECT_THROW(cb(error_code, 42), isc::Unexpected);
+}
+
+} // end of unnamed namespace

+ 35 - 0
src/lib/asiolink/tests/io_address_unittest.cc

@@ -182,3 +182,38 @@ TEST(IOAddressTest, LeftShiftOperator) {
     oss << addr;
     EXPECT_EQ(addr.toText(), oss.str());
 }
+
+// Tests address classification methods (which were previously used by accessing
+// underlying asio objects directly)
+TEST(IOAddressTest, accessClassificationMethods) {
+    IOAddress addr1("192.0.2.5"); // IPv4
+    IOAddress addr2("::");  // IPv6
+    IOAddress addr3("2001:db8::1"); // global IPv6
+    IOAddress addr4("fe80::1234");  // link-local
+    IOAddress addr5("ff02::1:2");   // multicast
+
+    EXPECT_TRUE (addr1.isV4());
+    EXPECT_FALSE(addr1.isV6());
+    EXPECT_FALSE(addr1.isV6LinkLocal());
+    EXPECT_FALSE(addr1.isV6Multicast());
+
+    EXPECT_FALSE(addr2.isV4());
+    EXPECT_TRUE (addr2.isV6());
+    EXPECT_FALSE(addr2.isV6LinkLocal());
+    EXPECT_FALSE(addr2.isV6Multicast());
+
+    EXPECT_FALSE(addr3.isV4());
+    EXPECT_TRUE (addr3.isV6());
+    EXPECT_FALSE(addr3.isV6LinkLocal());
+    EXPECT_FALSE(addr3.isV6Multicast());
+
+    EXPECT_FALSE(addr4.isV4());
+    EXPECT_TRUE (addr4.isV6());
+    EXPECT_TRUE (addr4.isV6LinkLocal());
+    EXPECT_FALSE(addr4.isV6Multicast());
+
+    EXPECT_FALSE(addr5.isV4());
+    EXPECT_TRUE (addr5.isV6());
+    EXPECT_FALSE(addr5.isV6LinkLocal());
+    EXPECT_TRUE (addr5.isV6Multicast());
+}

+ 4 - 4
src/lib/asiolink/tests/tcp_socket_unittest.cc

@@ -227,7 +227,7 @@ serverRead(tcp::socket& socket, TCPCallback& server_cb) {
         // If we have read at least two bytes, we can work out how much we
         // should be reading.
         if (server_cb.cumulative() >= 2) {
-           server_cb.expected() = readUint16(server_cb.data());
+            server_cb.expected() = readUint16(server_cb.data(), server_cb.length());
             if ((server_cb.expected() + 2) == server_cb.cumulative()) {
 
                 // Amount of data read from socket equals the size of the
@@ -261,7 +261,7 @@ TEST(TCPSocket, processReceivedData) {
     }
 
     // Check that the method will handle various receive sizes.
-    writeUint16(PACKET_SIZE, inbuff);
+    writeUint16(PACKET_SIZE, inbuff, sizeof(inbuff));
 
     cumulative = 0;
     offset = 0;
@@ -317,7 +317,7 @@ TEST(TCPSocket, processReceivedData) {
 // Tests the operation of a TCPSocket by opening it, sending an asynchronous
 // message to a server, receiving an asynchronous message from the server and
 // closing.
-TEST(TCPSocket, SequenceTest) {
+TEST(TCPSocket, sequenceTest) {
 
     // Common objects.
     IOService   service;                    // Service object for async control
@@ -408,7 +408,7 @@ TEST(TCPSocket, SequenceTest) {
     server_cb.length() = 0;
     server_cb.cumulative() = 0;
 
-    writeUint16(sizeof(INBOUND_DATA), server_cb.data());
+    writeUint16(sizeof(INBOUND_DATA), server_cb.data(), TCPCallback::MIN_SIZE);
     copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
         (server_cb.data() + 2));
     server_socket.async_send(asio::buffer(server_cb.data(),

+ 1 - 1
src/lib/asiolink/tests/udp_socket_unittest.cc

@@ -188,7 +188,7 @@ TEST(UDPSocket, processReceivedData) {
     // two bytes of the buffer.
     uint16_t count = 0;
     for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
-        writeUint16(count, inbuff);
+        writeUint16(count, inbuff, sizeof(inbuff));
 
         // Set some random values
         cumulative = 5;

+ 2 - 3
src/lib/cache/message_cache.h

@@ -73,8 +73,8 @@ protected:
     /// \param name query name of the message.
     /// \param type query type of the message.
     /// \return return the hash key.
-    HashKey getEntryHashKey(const isc::dns::Name& name,
-                            const isc::dns::RRType& type) const;
+    isc::nsas::HashKey getEntryHashKey(const isc::dns::Name& name,
+                                       const isc::dns::RRType& type) const;
 
     // Make these variants be protected for easy unittest.
 protected:
@@ -91,4 +91,3 @@ typedef boost::shared_ptr<MessageCache> MessageCachePtr;
 } // namespace isc
 
 #endif // MESSAGE_CACHE_H
-

+ 1 - 0
src/lib/cache/message_entry.cc

@@ -23,6 +23,7 @@
 #include "logger.h"
 
 using namespace isc::dns;
+using namespace isc::nsas;
 using namespace std;
 
 // Put file scope functions in unnamed namespace.

+ 3 - 5
src/lib/cache/message_entry.h

@@ -22,8 +22,6 @@
 #include "rrset_cache.h"
 #include "rrset_entry.h"
 
-using namespace isc::nsas;
-
 namespace isc {
 namespace cache {
 
@@ -33,7 +31,7 @@ class RRsetEntry;
 ///
 /// The object of MessageEntry represents one response message
 /// answered to the resolver client.
-class MessageEntry : public NsasEntry<MessageEntry> {
+class MessageEntry : public isc::nsas::NsasEntry<MessageEntry> {
 // Noncopyable
 private:
     MessageEntry(const MessageEntry& source);
@@ -92,7 +90,7 @@ public:
     /// \brief Get the hash key of the message entry.
     ///
     /// \return return hash key
-    virtual HashKey hashKey() const {
+    virtual isc::nsas::HashKey hashKey() const {
         return (*hash_key_ptr_);
     }
 
@@ -173,7 +171,7 @@ protected:
 
 private:
     std::string entry_name_; // The name for this entry(name + type)
-    HashKey* hash_key_ptr_;  // the key for messag entry in hash table.
+    isc::nsas::HashKey* hash_key_ptr_;  // the key for messag entry in hash table.
 
     std::vector<RRsetRef> rrsets_;
     RRsetCachePtr rrset_cache_; //Normal rrset cache

+ 0 - 2
src/lib/cache/rrset_cache.h

@@ -20,8 +20,6 @@
 
 #include <util/lru_list.h>
 
-using namespace isc::nsas;
-
 namespace isc {
 namespace cache {
 

+ 1 - 0
src/lib/cache/rrset_entry.cc

@@ -21,6 +21,7 @@
 #include "rrset_copy.h"
 
 using namespace isc::dns;
+using namespace isc::nsas;
 
 namespace isc {
 namespace cache {

+ 3 - 5
src/lib/cache/rrset_entry.h

@@ -22,8 +22,6 @@
 #include <nsas/fetchable.h>
 #include "cache_entry_key.h"
 
-using namespace isc::nsas;
-
 namespace isc {
 namespace cache {
 
@@ -60,7 +58,7 @@ enum RRsetTrustLevel {
 /// The object of RRsetEntry represents one cached RRset.
 /// Each RRset entry may be refered using shared_ptr by several message
 /// entries.
-class RRsetEntry : public NsasEntry<RRsetEntry>
+class RRsetEntry : public isc::nsas::NsasEntry<RRsetEntry>
 {
     ///
     /// \name Constructors and Destructor
@@ -105,7 +103,7 @@ public:
     /// \brief Get the hash key
     ///
     /// \return return hash key
-    HashKey hashKey() const {
+    isc::nsas::HashKey hashKey() const {
         return (hash_key_);
     }
 
@@ -124,7 +122,7 @@ private:
     time_t expire_time_;     // Expiration time of rrset.
     RRsetTrustLevel trust_level_; // RRset trustworthiness.
     boost::shared_ptr<isc::dns::RRset> rrset_;
-    HashKey hash_key_;       // RRsetEntry hash key
+    isc::nsas::HashKey hash_key_; // RRsetEntry hash key
 };
 
 typedef boost::shared_ptr<RRsetEntry> RRsetEntryPtr;

+ 2 - 6
src/lib/cache/tests/cache_test_messagefromfile.h

@@ -17,9 +17,6 @@
 #include <util/buffer.h>
 #include <dns/message.h>
 
-using namespace isc;
-using namespace isc::dns;
-
 namespace {
 
 /// \brief Reads a Message from a data file
@@ -27,13 +24,12 @@ namespace {
 /// \param message Message to put the read data in
 /// \param datafile The file to read from
 void
-messageFromFile(Message& message, const char* datafile) {
+messageFromFile(isc::dns::Message& message, const char* datafile) {
     std::vector<unsigned char> data;
-    UnitTestUtil::readWireData(datafile, data);
+    isc::UnitTestUtil::readWireData(datafile, data);
 
     isc::util::InputBuffer buffer(&data[0], data.size());
     message.fromWire(buffer);
 }
 
 }   // namespace
-

+ 2 - 6
src/lib/cache/tests/cache_test_sectioncount.h

@@ -17,9 +17,6 @@
 #include <util/buffer.h>
 #include <dns/message.h>
 
-using namespace isc;
-using namespace isc::dns;
-
 namespace {
 
 /// \brief Counts the number of rrsets in the given section
@@ -29,9 +26,9 @@ namespace {
 ///
 /// \return The number of RRsets in the given section
 int
-sectionRRsetCount(Message& msg, Message::Section section) {
+sectionRRsetCount(isc::dns::Message& msg, isc::dns::Message::Section section) {
     int count = 0;
-    for (RRsetIterator rrset_iter = msg.beginSection(section);
+    for (isc::dns::RRsetIterator rrset_iter = msg.beginSection(section);
          rrset_iter != msg.endSection(section);
          ++rrset_iter) {
         ++count;
@@ -41,4 +38,3 @@ sectionRRsetCount(Message& msg, Message::Section section) {
 }
 
 }   // namespace
-

+ 1 - 0
src/lib/cache/tests/message_cache_unittest.cc

@@ -23,6 +23,7 @@
 #include "cache_test_messagefromfile.h"
 
 using namespace isc::cache;
+using namespace isc::nsas;
 using namespace isc;
 using namespace isc::dns;
 using namespace isc::util;

+ 1 - 1
src/lib/cc/session.h

@@ -163,7 +163,7 @@ namespace isc {
 
             /// @brief returns socket descriptor from underlying socket connection
             ///
-            /// @param returns socket descriptor used for session connection
+            /// @return socket descriptor used for session connection
             virtual int getSocketDesc() const;
     private:
             // The following two methods are virtual to allow tests steal and

+ 6 - 6
src/lib/cc/tests/data_unittests.cc

@@ -84,7 +84,7 @@ TEST(Element, from_and_to_json) {
     sv.push_back("\"asdf\"");
     sv.push_back("null");
     sv.push_back("[ 1, 2, 3, 4 ]");
-    sv.push_back("{ \"name\": \"foo\", \"value\": 47806 }");
+    sv.push_back("{ \"name\": \"foo\", \"value\": 56176 }");
     sv.push_back("[ { \"a\": 1, \"b\": \"c\" }, { \"a\": 2, \"b\": \"d\" } ]");
     sv.push_back("8.23");
     sv.push_back("123.456");
@@ -570,12 +570,12 @@ TEST(Element, ListElement) {
     el->set(0, Element::fromJSON("\"foo\""));
     EXPECT_EQ(el->get(0)->stringValue(), "foo");
 
-    el->add(Element::create(47806));
-    EXPECT_EQ(el->get(3)->intValue(), 47806);
+    el->add(Element::create(56176));
+    EXPECT_EQ(el->get(3)->intValue(), 56176);
 
     el->remove(1);
     el->remove(1);
-    EXPECT_EQ(el->str(), "[ \"foo\", 47806 ]");
+    EXPECT_EQ(el->str(), "[ \"foo\", 56176 ]");
 
     // hmm, it errors on EXPECT_THROW(el->get(3), std::out_of_range)
     EXPECT_ANY_THROW(el->get(3));
@@ -600,8 +600,8 @@ TEST(Element, MapElement) {
 
     EXPECT_TRUE(isNull(el->get("value3")));
 
-    el->set("value3", Element::create(47806));
-    EXPECT_EQ(el->get("value3")->intValue(), 47806);
+    el->set("value3", Element::create(56176));
+    EXPECT_EQ(el->get("value3")->intValue(), 56176);
 
     el->remove("value3");
     EXPECT_TRUE(isNull(el->get("value3")));

+ 2 - 2
src/lib/config/config_data.h

@@ -110,8 +110,8 @@ public:
     isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
                                            bool recurse = false) const;
 
-    /// Returns a map of the top-level configuration items, as currently
-    /// set or their defaults
+    /// \brief Returns a map of the top-level configuration items, as
+    /// currently set or their defaults.
     ///
     /// \return An ElementPtr pointing to a MapElement containing
     ///         the top-level configuration items

+ 1 - 1
src/lib/config/tests/testdata/spec22.spec

@@ -102,7 +102,7 @@
                 "item_name": "v92b",
                 "item_type": "integer",
                 "item_optional": false,
-                "item_default": 47806
+                "item_default": 56176
               }
             ]
           }

+ 1 - 1
src/lib/config/tests/testdata/spec27.spec

@@ -107,7 +107,7 @@
                     "item_name": "v92b",
                     "item_type": "integer",
                     "item_optional": false,
-                    "item_default": 47806
+                    "item_default": 56176
                   }
                 ]
               }

+ 4 - 2
src/lib/datasrc/cache_config.cc

@@ -75,7 +75,8 @@ CacheConfig::CacheConfig(const std::string& datasrc_type,
 
         if (!enabled_) {
             isc_throw(CacheConfigError,
-                      "The cache must be enabled for the MasterFiles type");
+                      "The cache must be enabled for the MasterFiles type: "
+                      << datasrc_conf);
         }
 
         typedef std::map<std::string, ConstElementPtr> ZoneToFile;
@@ -100,7 +101,8 @@ CacheConfig::CacheConfig(const std::string& datasrc_type,
         if (!datasrc_conf.contains("cache-zones")) {
             isc_throw(NotImplemented, "Auto-detection of zones "
                       "to cache is not yet implemented, supply "
-                      "cache-zones parameter");
+                      "cache-zones parameter: "
+                      << datasrc_conf);
             // TODO: Auto-detect list of all zones in the
             // data source.
         }

+ 1 - 1
src/lib/datasrc/cache_config.h

@@ -170,7 +170,7 @@ public:
     /// \param zone_name The origin name of the zone
     /// \return A \c LoadAction functor to load zone data or an empty functor
     /// (see above).
-    memory::LoadAction getLoadAction(const dns::RRClass& rrlcass,
+    memory::LoadAction getLoadAction(const dns::RRClass& rrclass,
                                      const dns::Name& zone_name) const;
 
     /// \brief Read only iterator type over configured cached zones.

+ 1 - 1
src/lib/datasrc/client_list.h

@@ -316,7 +316,7 @@ typedef boost::shared_ptr<ClientList> ClientListPtr;
 typedef boost::shared_ptr<const ClientList> ConstClientListPtr;
 
 /// \brief Concrete implementation of the ClientList, which is constructed
-/// based on configuration.
+///     based on configuration.
 ///
 /// This is the implementation which is expected to be used in the servers.
 /// However, it is expected most of the code will use it as the ClientList,

+ 12 - 9
src/lib/datasrc/database.cc

@@ -23,6 +23,7 @@
 
 #include <exceptions/exceptions.h>
 #include <dns/name.h>
+#include <dns/labelsequence.h>
 #include <dns/rrclass.h>
 #include <dns/rrttl.h>
 #include <dns/rrset.h>
@@ -103,7 +104,7 @@ DatabaseClient::findZone(const Name& name) const {
     }
     // Then super domains
     // Start from 1, as 0 is covered above
-    for (size_t i(1); i < name.getLabelCount(); ++i) {
+    for (size_t i = 1; i < name.getLabelCount(); ++i) {
         isc::dns::Name superdomain(name.split(i));
         zone = accessor_->getZone(superdomain.toText());
         if (zone.first) {
@@ -344,7 +345,7 @@ DatabaseClient::Finder::getRRsets(const string& name, const WantedTypes& types,
     }
     if (!sig_store.empty()) {
         // Add signatures to all found RRsets
-        for (std::map<RRType, RRsetPtr>::iterator i(result.begin());
+        for (std::map<RRType, RRsetPtr>::iterator i = result.begin();
              i != result.end(); ++ i) {
             sig_store.appendSignatures(i->second);
         }
@@ -646,7 +647,7 @@ DatabaseClient::Finder::findWildcardMatch(
 
         // Strip off the left-more label(s) in the name and replace with a "*".
         const Name superdomain(name.split(i));
-        const string wildcard("*." + superdomain.toText());
+        const string wildcard(Name("*", 1, &superdomain).toText());
         const string construct_name(name.toText());
 
         // TODO Add a check for DNAME, as DNAME wildcards are discouraged (see
@@ -760,7 +761,7 @@ DatabaseClient::Finder::FindDNSSECContext::probe() {
             const string origin = finder_.getOrigin().toText();
             const FoundRRsets nsec3_found =
                 finder_.getRRsets(origin, NSEC3PARAM_TYPES(), true);
-            const FoundIterator nfi=
+            const FoundIterator nfi =
                 nsec3_found.second.find(RRType::NSEC3PARAM());
             is_nsec3_ = (nfi != nsec3_found.second.end());
 
@@ -908,7 +909,7 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
         if (any) {
             // An ANY query, copy everything to the target instead of returning
             // directly.
-            for (FoundIterator it(found.second.begin());
+            for (FoundIterator it = found.second.begin();
                  it != found.second.end(); ++it) {
                 if (it->second) {
                     // Skip over the empty ANY
@@ -1108,12 +1109,14 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
     // This will be set to the one covering the query name
     ConstRRsetPtr covering_proof;
 
+    LabelSequence name_ls(name);
     // We keep stripping the leftmost label until we find something.
     // In case it is recursive, we'll exit the loop at the first iteration.
-    for (unsigned labels(qlabels); labels >= olabels; -- labels) {
-        const string hash(calculator->calculate(labels == qlabels ? name :
-                                                name.split(qlabels - labels,
-                                                           labels)));
+    for (unsigned int labels = qlabels; labels >= olabels;
+         --labels, name_ls.stripLeft(1))
+    {
+        const std::string hash = calculator->calculate(name_ls);
+
         // Get the exact match for the name.
         LOG_DEBUG(logger, DBG_TRACE_BASIC, DATASRC_DATABASE_FINDNSEC3_TRYHASH).
             arg(name).arg(labels).arg(hash);

+ 4 - 0
src/lib/datasrc/memory/domaintree.h

@@ -1206,6 +1206,10 @@ public:
     ///
     /// \param mem_sgmt A \c MemorySegment from which memory for the new
     /// \c DomainTree is allocated.
+    /// \param return_empty_node Whether \c find() on this tree should
+    ///                          return empty nodes (which contain no
+    ///                          data). If it's \c true, \c find() will
+    ///                          match and return empty nodes.
     static DomainTree* create(util::MemorySegment& mem_sgmt,
                               bool return_empty_node = false)
     {

+ 14 - 1
src/lib/datasrc/memory/memory_client.cc

@@ -90,6 +90,7 @@ private:
     ZoneChain chain_;
     const RdataSet* set_node_;
     const RRClass rrclass_;
+    ConstRRsetPtr soa_;
     const ZoneTree& tree_;
     const ZoneNode* node_;
     // Only used when separate_rrs_ is true
@@ -120,6 +121,18 @@ public:
             isc_throw(Unexpected,
                       "In-memory zone corrupted, missing origin node");
         }
+
+        if (node_) {
+            const RdataSet* origin_set = node_->getData();
+            if (origin_set) {
+                const RdataSet* soa_set = RdataSet::find(origin_set, RRType::SOA());
+                if (soa_set) {
+                    soa_ = ConstRRsetPtr (new TreeNodeRRset(rrclass_, node_,
+                                                            soa_set, true));
+                }
+            }
+        }
+
         // Initialize the iterator if there's somewhere to point to
         if (node_ != NULL && node_->getData() != NULL) {
             set_node_ = node_->getData();
@@ -231,7 +244,7 @@ public:
     }
 
     virtual ConstRRsetPtr getSOA() const {
-        isc_throw(NotImplemented, "Not implemented");
+        return (soa_);
     }
 };
 

+ 1 - 1
src/lib/datasrc/memory/rdataset.h

@@ -300,7 +300,7 @@ public:
     /// needs to be extended, unless there's a reason other than simply
     /// because it's already a member function.
     ///
-    /// \param rdata_head A pointer to \c RdataSet from which the search
+    /// \param rdataset_head A pointer to \c RdataSet from which the search
     /// starts.  It can be NULL.
     /// \param type The RRType of \c RdataSet to find.
     /// \param sigonly_ok Whether it should find an RdataSet that only has

+ 80 - 0
src/lib/datasrc/memory/treenode_rrset.cc

@@ -101,6 +101,19 @@ TreeNodeRRset::toText() const {
 
 namespace {
 void
+sizeupName(const LabelSequence& name_labels, RdataNameAttributes,
+           size_t* length)
+{
+    *length += name_labels.getDataLength();
+}
+
+void
+sizeupData(const void*, size_t data_len, size_t* length)
+{
+    *length += data_len;
+}
+
+void
 renderName(const LabelSequence& name_labels, RdataNameAttributes attr,
            AbstractMessageRenderer* renderer)
 {
@@ -114,6 +127,35 @@ renderData(const void* data, size_t data_len,
     renderer->writeData(data, data_len);
 }
 
+// Helper for calculating wire data length of a single (etiher main or
+// RRSIG) RRset.
+uint16_t
+getLengthHelper(size_t* rlength, size_t rr_count, uint16_t name_labels_size,
+                RdataReader& reader, bool (RdataReader::* rdata_iterate_fn)())
+{
+    uint16_t length = 0;
+
+    for (size_t i = 0; i < rr_count; ++i) {
+        size_t rrlen = 0;
+
+        rrlen += name_labels_size;
+        rrlen += 2; // TYPE field
+        rrlen += 2; // CLASS field
+        rrlen += 4; // TTL field
+        rrlen += 2; // RDLENGTH field
+
+        *rlength = 0;
+        const bool rendered = (reader.*rdata_iterate_fn)();
+        assert(rendered == true);
+
+        rrlen += *rlength;
+        assert(length + rrlen < 65536);
+        length += rrlen;
+    }
+
+    return (length);
+}
+
 // Common code logic for rendering a single (either main or RRSIG) RRset.
 size_t
 writeRRs(AbstractMessageRenderer& renderer, size_t rr_count,
@@ -149,6 +191,39 @@ writeRRs(AbstractMessageRenderer& renderer, size_t rr_count,
 }
 }
 
+uint16_t
+TreeNodeRRset::getLength() const {
+    size_t rlength = 0;
+    RdataReader reader(rrclass_, rdataset_->type, rdataset_->getDataBuf(),
+                       rdataset_->getRdataCount(), rrsig_count_,
+                       boost::bind(sizeupName, _1, _2, &rlength),
+                       boost::bind(sizeupData, _1, _2, &rlength));
+
+    // Get the owner name of the RRset in the form of LabelSequence.
+    uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+    const LabelSequence name_labels = getOwnerLabels(labels_buf);
+    const uint16_t name_labels_size = name_labels.getDataLength();
+
+    // Find the length of the main (non RRSIG) RRs
+    const uint16_t rrset_length =
+        getLengthHelper(&rlength, rdataset_->getRdataCount(), name_labels_size,
+                        reader, &RdataReader::iterateRdata);
+
+    rlength = 0;
+    const bool rendered = reader.iterateRdata();
+    assert(rendered == false); // we should've reached the end
+
+    // Find the length of any RRSIGs, if we supposed to do so
+    const uint16_t rrsig_length = dnssec_ok_ ?
+        getLengthHelper(&rlength, rrsig_count_, name_labels_size,
+                        reader, &RdataReader::iterateSingleSig) : 0;
+
+    // the uint16_ts are promoted to ints during addition below, so it
+    // won't overflow a 16-bit register.
+    assert(rrset_length + rrsig_length < 65536);
+    return (rrset_length + rrsig_length);
+}
+
 unsigned int
 TreeNodeRRset::toWire(AbstractMessageRenderer& renderer) const {
     RdataReader reader(rrclass_, rdataset_->type, rdataset_->getDataBuf(),
@@ -195,6 +270,11 @@ TreeNodeRRset::addRdata(const rdata::Rdata&) {
     isc_throw(Unexpected, "unexpected method called on TreeNodeRRset");
 }
 
+void
+TreeNodeRRset::addRdata(const std::string&) {
+    isc_throw(Unexpected, "unexpected method called on TreeNodeRRset");
+}
+
 namespace {
 // In this namespace we define a set of helper stuff to implement the
 // RdataIterator for the TreeNodeRRset.  We should eventually optimize

+ 0 - 0
src/lib/datasrc/memory/treenode_rrset.h


Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff