Browse Source

Merge branch 'master' into trac2168

Conflicts:
	src/lib/dns/tests/rrset_unittest.cc
Mukund Sivaraman 11 years ago
parent
commit
8b413f0b6f
100 changed files with 3339 additions and 983 deletions
  1. 116 0
      ChangeLog
  2. 3 2
      configure.ac
  3. 3 1
      doc/devel/contribute.dox
  4. 6 4
      doc/devel/mainpage.dox
  5. 86 20
      doc/guide/bind10-guide.xml
  6. 2 0
      src/bin/auth/.gitignore
  7. 3 3
      src/bin/auth/auth_messages.mes
  8. 12 19
      src/bin/auth/auth_srv.cc
  9. 2 0
      src/bin/auth/auth_srv.h
  10. 11 14
      src/bin/auth/query.cc
  11. 12 0
      src/bin/auth/query.h
  12. 7 0
      src/bin/auth/tests/query_unittest.cc
  13. 3 2
      src/bin/bindctl/bindcmd.py
  14. 1 0
      src/bin/d2/.gitignore
  15. 2 3
      src/bin/d2/d2_cfg_mgr.cc
  16. 5 5
      src/bin/d2/dns_client.h
  17. 4 4
      src/bin/d2/tests/d2_update_mgr_unittests.cc
  18. 17 7
      src/bin/d2/tests/dns_client_unittests.cc
  19. 18 99
      src/bin/d2/tests/nc_add_unittests.cc
  20. 9 4
      src/bin/d2/tests/nc_test_utils.cc
  21. 58 123
      src/bin/d2/tests/nc_trans_unittests.cc
  22. 1 1
      src/bin/d2/tests/test_data_files_config.h.in
  23. 1 4
      src/bin/ddns/ddns.py.in
  24. 1 4
      src/bin/ddns/tests/ddns_test.py
  25. 1 0
      src/bin/dhcp4/.gitignore
  26. 11 5
      src/bin/dhcp4/config_parser.cc
  27. 5 4
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  28. 2 1
      src/bin/dhcp4/ctrl_dhcp4_srv.h
  29. 30 0
      src/bin/dhcp4/dhcp4.dox
  30. 90 1
      src/bin/dhcp4/dhcp4.spec
  31. 16 1
      src/bin/dhcp4/dhcp4_messages.mes
  32. 222 151
      src/bin/dhcp4/dhcp4_srv.cc
  33. 35 1
      src/bin/dhcp4/dhcp4_srv.h
  34. 3 0
      src/bin/dhcp4/tests/.gitignore
  35. 2 1
      src/bin/dhcp4/tests/Makefile.am
  36. 515 19
      src/bin/dhcp4/tests/config_parser_unittest.cc
  37. 98 24
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  38. 4 4
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  39. 11 1
      src/bin/dhcp4/tests/dhcp4_test_utils.h
  40. 287 69
      src/bin/dhcp4/tests/fqdn_unittest.cc
  41. 17 0
      src/bin/dhcp4/tests/test_data_files_config.h.in
  42. 58 2
      src/bin/dhcp4/tests/wireshark.cc
  43. 1 0
      src/bin/dhcp6/.gitignore
  44. 6 2
      src/bin/dhcp6/config_parser.cc
  45. 7 4
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  46. 2 1
      src/bin/dhcp6/ctrl_dhcp6_srv.h
  47. 27 0
      src/bin/dhcp6/dhcp6.dox
  48. 15 8
      src/bin/dhcp6/dhcp6_messages.mes
  49. 251 132
      src/bin/dhcp6/dhcp6_srv.cc
  50. 86 41
      src/bin/dhcp6/dhcp6_srv.h
  51. 3 0
      src/bin/dhcp6/tests/.gitignore
  52. 2 1
      src/bin/dhcp6/tests/Makefile.am
  53. 399 20
      src/bin/dhcp6/tests/config_parser_unittest.cc
  54. 78 13
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  55. 4 4
      src/bin/dhcp6/tests/dhcp6_test_utils.cc
  56. 4 1
      src/bin/dhcp6/tests/dhcp6_test_utils.h
  57. 195 67
      src/bin/dhcp6/tests/fqdn_unittest.cc
  58. 145 5
      src/bin/dhcp6/tests/wireshark.cc
  59. 1 0
      src/bin/resolver/.gitignore
  60. 1 4
      src/bin/xfrin/tests/xfrin_test.py
  61. 2 1
      src/bin/xfrin/xfrin.py.in
  62. 3 3
      src/bin/xfrin/xfrin_messages.mes
  63. 4 9
      src/bin/xfrout/xfrout.py.in
  64. 2 1
      src/hooks/dhcp/user_chk/Makefile.am
  65. 2 0
      src/hooks/dhcp/user_chk/tests/.gitignore
  66. 1 1
      src/lib/acl/ip_check.cc
  67. 1 0
      src/lib/asiodns/.gitignore
  68. 1 1
      src/lib/asiodns/io_fetch.cc
  69. 6 2
      src/lib/asiodns/tests/io_fetch_unittest.cc
  70. 22 10
      src/lib/asiolink/dummy_io_cb.h
  71. 22 5
      src/lib/asiolink/io_address.cc
  72. 28 10
      src/lib/asiolink/io_address.h
  73. 2 2
      src/lib/asiolink/io_endpoint.cc
  74. 1 1
      src/lib/asiolink/tcp_socket.h
  75. 1 0
      src/lib/asiolink/tests/Makefile.am
  76. 36 0
      src/lib/asiolink/tests/dummy_io_callback_unittest.cc
  77. 46 1
      src/lib/asiolink/tests/io_address_unittest.cc
  78. 4 4
      src/lib/asiolink/tests/tcp_socket_unittest.cc
  79. 1 1
      src/lib/asiolink/tests/udp_socket_unittest.cc
  80. 1 0
      src/lib/cache/.gitignore
  81. 1 0
      src/lib/cc/.gitignore
  82. 1 1
      src/lib/cc/session.h
  83. 6 6
      src/lib/cc/tests/data_unittests.cc
  84. 1 0
      src/lib/config/.gitignore
  85. 2 2
      src/lib/config/config_data.h
  86. 1 1
      src/lib/config/tests/testdata/spec22.spec
  87. 1 1
      src/lib/config/tests/testdata/spec27.spec
  88. 2 0
      src/lib/datasrc/.gitignore
  89. 4 2
      src/lib/datasrc/cache_config.cc
  90. 1 1
      src/lib/datasrc/cache_config.h
  91. 1 1
      src/lib/datasrc/client_list.h
  92. 6 6
      src/lib/datasrc/database.cc
  93. 1 0
      src/lib/datasrc/memory/.gitignore
  94. 4 0
      src/lib/datasrc/memory/domaintree.h
  95. 14 1
      src/lib/datasrc/memory/memory_client.cc
  96. 1 1
      src/lib/datasrc/memory/rdataset.h
  97. 80 0
      src/lib/datasrc/memory/treenode_rrset.cc
  98. 8 1
      src/lib/datasrc/memory/treenode_rrset.h
  99. 1 1
      src/lib/datasrc/memory/zone_data_loader.cc
  100. 0 0
      src/lib/datasrc/memory/zone_data_updater.h

+ 116 - 0
ChangeLog

@@ -1,3 +1,119 @@
+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
+	the content of the packet's user class option (DHCPv4) or vendor class
+	option (DHCPv6). Two classes (docsis3.0 and eRouter1.0) have class
+	specific behavior in b10-dhcp4. See DHCPv4 Client Classification and
+	DHCPv6 Client Classification in BIND10 Developer's Guide for details.
+	This is a first ticket in a series of planned at least three tickets.
+	(Trac #3203, git afea612c23143f81a4201e39ba793bc837c5c9f1)
+
+731.	[func]		tmark
+	b10-dhcp4 now parses parameters which support DHCP-DDNS updates via
+	the DHCP-DDNS module, b10-dhcp-ddns.  These parameters are part of new
+	configuration element, dhcp-ddns, defined in dhcp4.spec. The parameters
+	parse, store and retrieve but do not yet govern behavior.  That will be
+	provided under separate ticket.
+	(Trac# 3033, git 0ba859834503f2b9b908cd7bc572e0286ca9201f)
+
+730.	[bug]		tomek
+	b10-dhcp4, b10-dhcp6: Both servers used to unnecessarily increase
+	subnet-id values after reconfiguration. The subnet-ids are now reset
+	to 1 every time a server is reconfigured.
+	(Trac #3234, git 31e416087685a6dadc3047fdbb0927bbf60095aa)
+
+729.	[bug]		marcin
+	b10-dhcp4 discards DHCPv4 messages carrying server identifiers
+	which don't match server identifiers used by the server.
+	(Trac #3279, git 805d2b269c6bf3e7be68c13f1da1709d8150a666)
+
 728.	[func]		marcin
 	b10-dhcp6: If server fails to open a socket on one interface it
 	will log a warning and continue to open sockets on other interfaces.

+ 3 - 2
configure.ac

@@ -76,7 +76,7 @@ AM_CONDITIONAL(USE_CLANGPP, test "X${CLANGPP}" = "Xyes")
 
 dnl Determine if weare using GNU sed
 GNU_SED=no
-$SED --version 2> /dev/null | grep -q GNU
+$SED --version 2> /dev/null | grep GNU > /dev/null 2>&1
 if test $? -eq 0; then
   GNU_SED=yes
 fi
@@ -445,7 +445,7 @@ fi
 # Python 3.2 has an unused parameter in one of its headers. This
 # has been reported, but not fixed as of yet, so we check if we need
 # to set -Wno-unused-parameter.
-if test "X$GXX" = "Xyes" -a $werror_ok = 1; then
+if test "X$GXX" = "Xyes" -a "$werror_ok" = 1; then
 	CPPFLAGS_SAVED="$CPPFLAGS"
 	CPPFLAGS=${PYTHON_INCLUDES}
 	CXXFLAGS_SAVED="$CXXFLAGS"
@@ -1389,6 +1389,7 @@ AC_CONFIG_FILES([compatcheck/Makefile
                  src/bin/dhcp4/spec_config.h.pre
                  src/bin/dhcp4/tests/Makefile
                  src/bin/dhcp4/tests/marker_file.h
+                 src/bin/dhcp4/tests/test_data_files_config.h
                  src/bin/dhcp4/tests/test_libraries.h
                  src/bin/dhcp6/Makefile
                  src/bin/dhcp6/spec_config.h.pre

+ 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

+ 6 - 4
doc/devel/mainpage.dox

@@ -60,6 +60,7 @@
  *   - @subpage dhcpv4ConfigInherit
  *   - @subpage dhcpv4OptionsParse
  *   - @subpage dhcpv4DDNSIntegration
+ *   - @subpage dhcpv4Classifier
  *   - @subpage dhcpv4Other
  * - @subpage dhcp6
  *   - @subpage dhcpv6Session
@@ -67,6 +68,7 @@
  *   - @subpage dhcpv6ConfigInherit
  *   - @subpage dhcpv6DDNSIntegration
  *   - @subpage dhcpv6OptionsParse
+ *   - @subpage dhcpv6Classifier
  *   - @subpage dhcpv6Other
  * - @subpage libdhcp
  *   - @subpage libdhcpIntro
@@ -84,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>
  *

+ 86 - 20
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
@@ -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>
@@ -3928,7 +3941,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>
@@ -4172,10 +4185,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 +4196,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 +4211,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">
@@ -4471,6 +4495,21 @@ Dhcp4/subnet4	[]	list	(default)
       development and should be treated as <quote>not implemented
       yet</quote>, rather than actual limitations.</para>
       <itemizedlist>
+          <listitem> <!-- see tickets #3234, #3281 -->
+            <para>
+              On-line configuration has some limitations. Adding new subnets or
+              modifying existing ones work, as is removing the last subnet from
+              the list. However, removing non-last (e.g. removing subnet 1,2 or 3 if
+              there are 4 subnets configured) will cause issues. The problem is
+              caused by simplistic subnet-id assignment. The subnets are always
+              numbered, starting from 1. That subnet-id is then used in leases
+              that are stored in the lease database. Removing non-last subnet will
+              cause the configuration information to mismatch data in the lease
+              database. It is possible to manually update subnet-id fields in
+              MySQL database, but it is awkward and error prone process. A better
+              reconfiguration support is planned.
+            </para>
+          </listitem>
           <listitem>
           <para>
             On startup, the DHCPv4 server does not get the full configuration from
@@ -5052,10 +5091,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>
@@ -5063,7 +5102,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
@@ -5078,12 +5117,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">
@@ -5384,6 +5434,22 @@ should include options from the isc option space:
       yet</quote>, rather than actual limitations.</para>
       <itemizedlist>
 
+          <listitem> <!-- see tickets #3234, #3281 -->
+            <para>
+              On-line configuration has some limitations. Adding new subnets or
+              modifying existing ones work, as is removing the last subnet from
+              the list. However, removing non-last (e.g. removing subnet 1,2 or 3 if
+              there are 4 subnets configured) will cause issues. The problem is
+              caused by simplistic subnet-id assignment. The subnets are always
+              numbered, starting from 1. That subnet-id is then used in leases
+              that are stored in the lease database. Removing non-last subnet will
+              cause the configuration information to mismatch data in the lease
+              database. It is possible to manually update subnet-id fields in
+              MySQL database, but it is awkward and error prone process. A better
+              reconfiguration support is planned.
+            </para>
+          </listitem>
+
         <listitem>
           <para>
             On startup, the DHCPv6 server does not get the full configuration from

+ 2 - 0
src/bin/auth/.gitignore

@@ -11,3 +11,5 @@
 /gen-statisticsitems.py.pre
 /statistics.cc
 /statistics_items.h
+/s-genstats
+/s-messages

+ 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

+ 12 - 19
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);
 }
@@ -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) {
@@ -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);
@@ -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_;
 };
 
 }

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

@@ -1215,6 +1215,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 - 0
src/bin/d2/.gitignore

@@ -4,3 +4,4 @@
 /d2_messages.h
 /spec_config.h
 /spec_config.h.pre
+/s-messages

+ 2 - 3
src/bin/d2/d2_cfg_mgr.cc

@@ -119,7 +119,7 @@ std::string
 D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
     if (!ioaddr.isV4()) {
         isc_throw(D2CfgError, "D2CfgMgr address is not IPv4 address :"
-                              << ioaddr.toText());
+                  << ioaddr);
     }
 
     // Get the address in byte vector form.
@@ -148,8 +148,7 @@ D2CfgMgr::reverseV4Address(const isc::asiolink::IOAddress& ioaddr) {
 std::string
 D2CfgMgr::reverseV6Address(const isc::asiolink::IOAddress& ioaddr) {
     if (!ioaddr.isV6()) {
-        isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: "
-                              << ioaddr.toText());
+        isc_throw(D2CfgError, "D2Cfg address is not IPv6 address: " << ioaddr);
     }
 
     // Turn the address into a string of digits.

+ 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,

+ 4 - 4
src/bin/d2/tests/d2_update_mgr_unittests.cc

@@ -213,7 +213,7 @@ public:
     /// vary.
     void processAll(unsigned int timeout_millisec =
                     NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT + 100,
-                    size_t max_passes = 20) {
+                    size_t max_passes = 100) {
         // Loop until all the transactions have been dequeued and run through to
         // completion.
         size_t passes = 0;
@@ -238,9 +238,9 @@ public:
             }
 
             // This is a last resort fail safe to ensure we don't go around
-            // forever. We cut it off the number of passes at 20.  This is
-            // roughly twice the number for the longest test (currently,
-            // multiTransactionTimeout).
+            // forever. We cut it off the number of passes at 100 (default
+            // value).  This is roughly ten times the number for the longest
+            // test (currently, multiTransactionTimeout).
             if (passes > max_passes) {
                 ADD_FAILURE() << "processALL failed, too many passes: "
                     << passes <<  ", total handlers executed: " << handlers;

+ 17 - 7
src/bin/d2/tests/dns_client_unittests.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
@@ -128,7 +128,7 @@ public:
                           response_->getRRCount(D2UpdateMessage::SECTION_ZONE));
                 D2ZonePtr zone = response_->getZone();
                 ASSERT_TRUE(zone);
-                EXPECT_EQ("response.example.com.", zone->getName().toText());
+                EXPECT_EQ("example.com.", zone->getName().toText());
                 EXPECT_EQ(RRClass::IN().getCode(), zone->getClass().getCode());
 
             } else {
@@ -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
@@ -288,7 +296,7 @@ public:
         // Create a request DNS Update message.
         D2UpdateMessage message(D2UpdateMessage::OUTBOUND);
         ASSERT_NO_THROW(message.setRcode(Rcode(Rcode::NOERROR_CODE)));
-        ASSERT_NO_THROW(message.setZone(Name("response.example.com"), RRClass::IN()));
+        ASSERT_NO_THROW(message.setZone(Name("example.com"), RRClass::IN()));
 
         // In order to perform the full test, when the client sends the request
         // and receives a response from the server, we have to emulate 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);

+ 18 - 99
src/bin/d2/tests/nc_add_unittests.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
@@ -187,19 +187,12 @@ typedef boost::shared_ptr<NameAddStub> NameAddStubPtr;
 ///
 /// Note this class uses NameAddStub class to exercise non-public
 /// aspects of NameAddTransaction.
-class NameAddTransactionTest : public ::testing::Test {
+class NameAddTransactionTest : public TransactionTest {
 public:
-    IOServicePtr io_service_;
-    DdnsDomainPtr forward_domain_;
-    DdnsDomainPtr reverse_domain_;
 
-    NameAddTransactionTest() : io_service_(new isc::asiolink::IOService()) {
+    NameAddTransactionTest() {
     }
 
-    static const unsigned int FORWARD_CHG = 0x01;
-    static const unsigned int REVERSE_CHG = 0x02;
-    static const unsigned int FWD_AND_REV_CHG = REVERSE_CHG | FORWARD_CHG;
-
     virtual ~NameAddTransactionTest() {
     }
 
@@ -211,53 +204,12 @@ public:
     /// will have either the forward, reverse, or both domains populated.
     ///
     /// @param change_mask determines which change directions are requested
-    NameAddStubPtr makeTransaction4(int change_mask=FWD_AND_REV_CHG) {
-        const char* msg_str =
-            "{"
-            " \"change_type\" : 0 , "
-            " \"forward_change\" : true , "
-            " \"reverse_change\" : true , "
-            " \"fqdn\" : \"my.forward.example.com.\" , "
-            " \"ip_address\" : \"192.168.2.1\" , "
-            " \"dhcid\" : \"0102030405060708\" , "
-            " \"lease_expires_on\" : \"20130121132405\" , "
-            " \"lease_length\" : 1300 "
-            "}";
-
-        // Create NameChangeRequest from JSON string.
-        dhcp_ddns::NameChangeRequestPtr ncr = dhcp_ddns::NameChangeRequest::
-                                              fromJSON(msg_str);
-
-        // If the change mask does not include a forward change clear the
-        // forward domain; otherwise create the domain and its servers.
-        if (!(change_mask & FORWARD_CHG)) {
-            ncr->setForwardChange(false);
-            forward_domain_.reset();
-        } else {
-            // Create the forward domain and then its servers.
-            forward_domain_ = makeDomain("example.com.");
-            addDomainServer(forward_domain_, "forward.example.com",
-                            "1.1.1.1");
-            addDomainServer(forward_domain_, "forward2.example.com",
-                            "1.1.1.2");
-        }
-
-        // If the change mask does not include a reverse change clear the
-        // reverse domain; otherwise create the domain and its servers.
-        if (!(change_mask & REVERSE_CHG)) {
-            ncr->setReverseChange(false);
-            reverse_domain_.reset();
-        } else {
-            // Create the reverse domain and its server.
-            reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.");
-            addDomainServer(reverse_domain_, "reverse.example.com",
-                            "2.2.2.2");
-            addDomainServer(reverse_domain_, "reverse2.example.com",
-                            "2.2.2.3");
-        }
+    NameAddStubPtr makeTransaction4(int change_mask = FWD_AND_REV_CHG) {
+        // Creates IPv4 remove request, forward, and reverse domains.
+        setupForIPv4Transaction(dhcp_ddns::CHG_ADD, change_mask);
 
         // Now create the test transaction as would occur in update manager.
-        return (NameAddStubPtr(new NameAddStub(io_service_, ncr,
+        return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
                                                forward_domain_,
                                                reverse_domain_)));
     }
@@ -270,53 +222,16 @@ public:
     /// will have either the forward, reverse, or both domains populated.
     ///
     /// @param change_mask determines which change directions are requested
-    NameAddStubPtr makeTransaction6(int change_mask=FWD_AND_REV_CHG) {
-        const char* msg_str =
-            "{"
-            " \"change_type\" : 0 , "
-            " \"forward_change\" : true , "
-            " \"reverse_change\" : true , "
-            " \"fqdn\" : \"my6.forward.example.com.\" , "
-            " \"ip_address\" : \"2001:1::100\" , "
-            " \"dhcid\" : \"0102030405060708\" , "
-            " \"lease_expires_on\" : \"20130121132405\" , "
-            " \"lease_length\" : 1300 "
-            "}";
-
-        // Create NameChangeRequest from JSON string.
-        dhcp_ddns::NameChangeRequestPtr ncr = makeNcrFromString(msg_str);
-
-        // If the change mask does not include a forward change clear the
-        // forward domain; otherwise create the domain and its servers.
-        if (!(change_mask & FORWARD_CHG)) {
-            ncr->setForwardChange(false);
-            forward_domain_.reset();
-        } else {
-            // Create the forward domain and then its servers.
-            forward_domain_ = makeDomain("example.com.");
-            addDomainServer(forward_domain_, "fwd6-server.example.com",
-                            "2001:1::5");
-        }
-
-        // If the change mask does not include a reverse change clear the
-        // reverse domain; otherwise create the domain and its servers.
-        if (!(change_mask & REVERSE_CHG)) {
-            ncr->setReverseChange(false);
-            reverse_domain_.reset();
-        } else {
-            // Create the reverse domain and its server.
-            reverse_domain_ = makeDomain("1.2001.ip6.arpa.");
-            addDomainServer(reverse_domain_, "rev6-server.example.com",
-                            "2001:1::6");
-        }
+    NameAddStubPtr makeTransaction6(int change_mask = FWD_AND_REV_CHG) {
+        // Creates IPv6 remove request, forward, and reverse domains.
+        setupForIPv6Transaction(dhcp_ddns::CHG_ADD, change_mask);
 
         // Now create the test transaction as would occur in update manager.
-        return (NameAddStubPtr(new NameAddStub(io_service_, ncr,
+        return (NameAddStubPtr(new NameAddStub(io_service_, ncr_,
                                                forward_domain_,
                                                reverse_domain_)));
     }
 
-
     /// @brief Create a test transaction at a known point in the state model.
     ///
     /// Method prepares a new test transaction and sets its state and next
@@ -329,15 +244,19 @@ public:
     /// @param state value to set as the current state
     /// @param event value to post as the next event
     /// @param change_mask determines which change directions are requested
+    /// @param family selects between an IPv4 (AF_INET) and IPv6 (AF_INET6)
+    /// transaction.
     NameAddStubPtr prepHandlerTest(unsigned int state, unsigned int event,
-                                   unsigned int change_mask = FWD_AND_REV_CHG) {
-        NameAddStubPtr name_add = makeTransaction4(change_mask);
+                                   unsigned int change_mask = FWD_AND_REV_CHG,
+                                   short family = AF_INET) {
+        NameAddStubPtr name_add =  (family == AF_INET ?
+                                    makeTransaction4(change_mask) :
+                                    makeTransaction4(change_mask));
         name_add->initDictionaries();
         name_add->postNextEvent(event);
         name_add->setState(state);
         return (name_add);
     }
-
 };
 
 /// @brief Tests NameAddTransaction construction.

+ 9 - 4
src/bin/d2/tests/nc_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
@@ -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());
 }
 
 
@@ -172,7 +177,7 @@ TimedIO::runTimedIO(int run_time) {
     run_time_ = run_time;
     int cnt = io_service_->get_io_service().poll();
     if (cnt == 0) {
-        timer_.setup(boost::bind(&TransactionTest::timesUp, this), run_time_);
+        timer_.setup(boost::bind(&TimedIO::timesUp, this), run_time_);
         cnt = io_service_->get_io_service().run_one();
         timer_.cancel();
     }

+ 58 - 123
src/bin/d2/tests/nc_trans_unittests.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
@@ -275,83 +275,65 @@ typedef boost::shared_ptr<NameChangeStub> NameChangeStubPtr;
 ///
 /// Note this class uses NameChangeStub class to exercise non-public
 /// aspects of NameChangeTransaction.
-class NameChangeTransactionTest : public ::testing::Test {
+class NameChangeTransactionTest : public TransactionTest {
 public:
-    IOServicePtr io_service_;
-    DdnsDomainPtr forward_domain_;
-    DdnsDomainPtr reverse_domain_;
-    asiolink::IntervalTimer timer_;
-    int run_time_;
-
-    NameChangeTransactionTest()
-        : io_service_(new isc::asiolink::IOService()), timer_(*io_service_),
-         run_time_(0) {
+    NameChangeTransactionTest() {
     }
 
     virtual ~NameChangeTransactionTest() {
     }
 
-    /// @brief Run the IO service for no more than a given amount of time.
-    ///
-    /// Uses an IntervalTimer to interrupt the invocation of IOService run(),
-    /// after the given number of milliseconds elapse.  The timer executes
-    /// the timesUp() method if it expires.
-    ///
-    /// @param run_time amount of time in milliseconds to allow run to execute.
-    void runTimedIO(int run_time) {
-        run_time_ = run_time;
-        timer_.setup(boost::bind(&NameChangeTransactionTest::timesUp, this),
-                     run_time_);
-        io_service_->run();
-    }
-
-    /// @brief IO Timer expiration handler
-    ///
-    /// Stops the IOSerivce and fails the current test.
-    void timesUp() {
-        io_service_->stop();
-        FAIL() << "Test Time: " << run_time_ << " expired";
-    }
-
     /// @brief  Instantiates a NameChangeStub test transaction
     /// The transaction is constructed around a predefined (i.e "canned")
     /// NameChangeRequest. The request has both forward and reverse DNS
     /// changes requested, and both forward and reverse domains are populated.
     NameChangeStubPtr makeCannedTransaction() {
-        // NCR in JSON form.
-        const char* msg_str =
-            "{"
-            " \"change_type\" : 0 , "
-            " \"forward_change\" : true , "
-            " \"reverse_change\" : true , "
-            " \"fqdn\" : \"my.example.com.\" , "
-            " \"ip_address\" : \"192.168.2.1\" , "
-            " \"dhcid\" : \"0102030405060708\" , "
-            " \"lease_expires_on\" : \"20130121132405\" , "
-            " \"lease_length\" : 1300 "
-            "}";
-
-        // Create the request from JSON.
-        dhcp_ddns::NameChangeRequestPtr ncr = dhcp_ddns::NameChangeRequest::
-                                              fromJSON(msg_str);
-
-        // Make forward DdnsDomain with 2 forward servers.
-        forward_domain_ = makeDomain("example.com.");
-        addDomainServer(forward_domain_, "forward.example.com",
-                        "127.0.0.1", 5301);
-        addDomainServer(forward_domain_, "forward2.example.com",
-                        "127.0.0.1", 5302);
-
-        // Make reverse DdnsDomain with one reverse server.
-        reverse_domain_ = makeDomain("2.168.192.in.addr.arpa.");
-        addDomainServer(reverse_domain_, "reverse.example.com",
-                        "127.0.0.1", 5301);
+        // Creates IPv4 remove request, forward, and reverse domains.
+        setupForIPv4Transaction(dhcp_ddns::CHG_ADD, FWD_AND_REV_CHG);
 
+        // Now create the test transaction as would occur in update manager.
         // Instantiate the transaction as would be done by update manager.
-        return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr,
+        return (NameChangeStubPtr(new NameChangeStub(io_service_, ncr_,
                                   forward_domain_, reverse_domain_)));
     }
 
+    /// @brief Builds and then sends an update request
+    ///
+    /// This method is used to build and send and update request. It is used
+    /// in conjuction with FauxServer to test various message response
+    /// scenarios.
+    /// @param name_change Transaction under test
+    /// @param run_time Maximum time to permit IO processing to run before
+    /// timing out (in milliseconds)
+    void doOneExchange(NameChangeStubPtr name_change,
+                       unsigned int run_time = 500) {
+        // Create a valid request for the transaction.
+        D2UpdateMessagePtr req;
+        ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::
+                                                      OUTBOUND)));
+        ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
+        req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
+        req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
+
+        // Set the flag to use the NameChangeStub's DNSClient callback.
+        name_change->use_stub_callback_ = true;
+
+        // Invoke sendUpdate.
+        ASSERT_NO_THROW(name_change->sendUpdate());
+
+        // Update attempt count should be 1, next event should be NOP_EVT.
+        ASSERT_EQ(1, name_change->getUpdateAttempts());
+        ASSERT_EQ(NameChangeTransaction::NOP_EVT,
+                  name_change->getNextEvent());
+
+        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";
+            }
+        }
+    }
 };
 
 /// @brief Tests NameChangeTransaction construction.
@@ -874,27 +856,16 @@ TEST_F(NameChangeTransactionTest, sendUpdateTimeout) {
     ASSERT_NO_THROW(name_change->initDictionaries());
     ASSERT_TRUE(name_change->selectFwdServer());
 
-    // Create a valid request.
-    D2UpdateMessagePtr req;
-    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
-    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
-    req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
-    req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
-
-    // Set the flag to use the NameChangeStub's DNSClient callback.
-    name_change->use_stub_callback_ = true;
-
-    // Invoke sendUpdate.
-    ASSERT_NO_THROW(name_change->sendUpdate());
-
-    // Update attempt count should be 1, next event should be NOP_EVT.
-    EXPECT_EQ(1, name_change->getUpdateAttempts());
-    ASSERT_EQ(NameChangeTransaction::NOP_EVT,
-              name_change->getNextEvent());
-
-    // Run IO a bit longer than maximum allowed to permit timeout logic to
-    // execute.
-    runTimedIO(NameChangeTransaction::DNS_UPDATE_DEFAULT_TIMEOUT + 100);
+    // Build a valid request, call sendUpdate and process the response.
+    // Note we have to wait for DNSClient timeout plus a bit more to allow
+    // DNSClient to timeout.
+    // The method, doOneExchange, can suffer fatal assertions which invalidate
+    // not only it but the invoking test as well. In other words, if the
+    // doOneExchange blows up the rest of test is pointless. I use
+    // ASSERT_NO_FATAL_FAILURE to abort the test immediately.
+    ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change,
+                                          NameChangeTransaction::
+                                          DNS_UPDATE_DEFAULT_TIMEOUT + 100));
 
     // Verify that next event is IO_COMPLETED_EVT and DNS status is TIMEOUT.
     ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
@@ -914,26 +885,8 @@ TEST_F(NameChangeTransactionTest, sendUpdateCorruptResponse) {
     FauxServer server(*io_service_, *(name_change->getCurrentServer()));
     server.receive(FauxServer::CORRUPT_RESP);
 
-    // Create a valid request for the transaction.
-    D2UpdateMessagePtr req;
-    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
-    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
-    req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
-    req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
-
-    // Set the flag to use the NameChangeStub's DNSClient callback.
-    name_change->use_stub_callback_ = true;
-
-    // Invoke sendUpdate.
-    ASSERT_NO_THROW(name_change->sendUpdate());
-
-    // Update attempt count should be 1, next event should be NOP_EVT.
-    EXPECT_EQ(1, name_change->getUpdateAttempts());
-    ASSERT_EQ(NameChangeTransaction::NOP_EVT,
-              name_change->getNextEvent());
-
-    // Run the IO for 500 ms.  This should be more than enough time.
-    runTimedIO(500);
+    // Build a valid request, call sendUpdate and process the response.
+    ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
 
     // Verify that next event is IO_COMPLETED_EVT and DNS status is INVALID.
     ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,
@@ -952,26 +905,8 @@ TEST_F(NameChangeTransactionTest, sendUpdate) {
     FauxServer server(*io_service_, *(name_change->getCurrentServer()));
     server.receive (FauxServer::USE_RCODE, dns::Rcode::NOERROR());
 
-    // Create a valid request for the transaction.
-    D2UpdateMessagePtr req;
-    ASSERT_NO_THROW(req.reset(new D2UpdateMessage(D2UpdateMessage::OUTBOUND)));
-    ASSERT_NO_THROW(name_change->setDnsUpdateRequest(req));
-    req->setZone(dns::Name("request.example.com"), dns::RRClass::ANY());
-    req->setRcode(dns::Rcode(dns::Rcode::NOERROR_CODE));
-
-    // Set the flag to use the NameChangeStub's DNSClient callback.
-    name_change->use_stub_callback_ = true;
-
-    // Invoke sendUpdate.
-    ASSERT_NO_THROW(name_change->sendUpdate());
-
-    // Update attempt count should be 1, next event should be NOP_EVT.
-    EXPECT_EQ(1, name_change->getUpdateAttempts());
-    ASSERT_EQ(NameChangeTransaction::NOP_EVT,
-              name_change->getNextEvent());
-
-    // Run the IO for 500 ms.  This should be more than enough time.
-    runTimedIO(500);
+    // Build a valid request, call sendUpdate and process the response.
+    ASSERT_NO_FATAL_FAILURE(doOneExchange(name_change));
 
     // Verify that next event is IO_COMPLETED_EVT and DNS status is SUCCESS.
     ASSERT_EQ(NameChangeTransaction::IO_COMPLETED_EVT,

+ 1 - 1
src/bin/d2/tests/test_data_files_config.h.in

@@ -1,4 +1,4 @@
-// Copyright (C) 2009  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

+ 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)

+ 1 - 0
src/bin/dhcp4/.gitignore

@@ -4,3 +4,4 @@
 /dhcp4_messages.h
 /spec_config.h
 /spec_config.h.pre
+/s-messages

+ 11 - 5
src/bin/dhcp4/config_parser.cc

@@ -265,7 +265,7 @@ protected:
         Triplet<uint32_t> valid = getParam("valid-lifetime");
 
         stringstream tmp;
-        tmp << addr.toText() << "/" << (int)len
+        tmp << addr << "/" << (int)len
             << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
 
         LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
@@ -396,6 +396,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
         parser = new HooksLibrariesParser(config_id);
     } else if (config_id.compare("echo-client-id") == 0) {
         parser = new BooleanParser(config_id, globalContext()->boolean_values_);
+    } else if (config_id.compare("dhcp-ddns") == 0) {
+        parser = new D2ClientConfigParser(config_id);
     } else {
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
@@ -433,6 +435,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND,
               DHCP4_CONFIG_START).arg(config_set->str());
 
+    // Before starting any subnet operations, let's reset the subnet-id counter,
+    // so newly recreated configuration starts with first subnet-id equal 1.
+    Subnet::resetSubnetID();
+
     // Some of the values specified in the configuration depend on
     // other values. Typically, the values in the subnet4 structure
     // depend on the global values. Also, option values configuration
@@ -448,7 +454,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     // Some of the parsers alter the state of the system in a way that can't
     // easily be undone. (Or alter it in a way such that undoing the change has
     // the same risk of failure as doing the change.)
-    ParserPtr hooks_parser_;
+    ParserPtr hooks_parser;
 
     // The subnet parsers implement data inheritance by directly
     // accessing global storage. For this reason the global data
@@ -489,7 +495,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 // Executing commit will alter currently-loaded hooks
                 // libraries.  Check if the supplied libraries are valid,
                 // but defer the commit until everything else has committed.
-                hooks_parser_ = parser;
+                hooks_parser = parser;
                 parser->build(config_pair.second);
             } else {
                 // Those parsers should be started before other
@@ -557,8 +563,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
             // This occurs last as if it succeeds, there is no easy way
             // revert it.  As a result, the failure to commit a subsequent
             // change causes problems when trying to roll back.
-            if (hooks_parser_) {
-                hooks_parser_->commit();
+            if (hooks_parser) {
+                hooks_parser->commit();
             }
         }
         catch (const isc::Exception& ex) {

+ 5 - 4
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -226,7 +226,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 +235,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::ConfigData::getFullConfig.
+    /// be later accessed with
+    /// \ref isc::config::ConfigData::getFullConfig().
     ///
     /// @param new_config new configuration.
     ///

+ 30 - 0
src/bin/dhcp4/dhcp4.dox

@@ -155,6 +155,36 @@ The default behaviour is constituted by the set of constants defined in the
 (upper part of) dhcp4_srv.cc file. Once the configuration is implemented,
 these constants will be removed.
 
+@section dhcpv4Classifier DHCPv4 Client Classification
+
+Kea DHCPv4 server currently supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (60) option. More flexible classification is planned, but there
+are no specific development dates agreed.
+
+For each incoming packet, @ref isc::dhcp::Dhcpv4Srv::classifyPacket() method is called.
+It attempts to extract content of the vendor class option and interpret as a name
+of the class. For now, the code has been tested with two classes used in cable modem
+networks: eRouter1.0 and docsis3.0, but any other content of the vendor class option will
+be interpreted as a class name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Neverthless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt4::inClass method should
+be used.
+
+Currently there is a short code section that alternates packet processing depending on
+which class it belongs to. It is planned to move that capability to an external hook
+library. See ticket #3275. The class specific behavior is:
+
+- docsis3.0 packets have siaddr (next server) field set
+- docsis3.0 packets have file field set to the content of the boot-file-name option
+- eRouter1.0 packets have siaddr (next server) field cleared
+
+Aforementioned modifications are conducted in @ref isc::dhcp::Dhcpv4Srv::classSpecificProcessing.
+
 @section dhcpv4Other Other DHCPv4 topics
 
  For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

+ 90 - 1
src/bin/dhcp4/dhcp4.spec

@@ -292,7 +292,96 @@
                   }
                 } ]
          }
-      }
+      },
+
+      { "item_name": "dhcp-ddns",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {"enable-updates": false},
+        "item_description" : "Contains parameters pertaining DHCP-driven DDNS updates",
+        "map_item_spec": [
+            {
+                "item_name": "enable-updates",
+                "item_type": "boolean",
+                "item_optional": false,
+                "item_default": false,
+                "item_description" : "Enables DDNS update processing"
+            },
+            {
+                "item_name": "server-ip",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "127.0.0.1",
+                "item_description" : "IP address of b10-dhcp-ddns (IPv4 or IPv6)"
+            },
+            {
+                "item_name": "server-port",
+                "item_type": "integer",
+                "item_optional": true,
+                "item_default": 53001,
+                "item_description" : "port number of b10-dhcp-ddns"
+            },
+            {
+                "item_name": "ncr-protocol",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "UDP",
+                "item_description" : "Socket protocol to use with b10-dhcp-ddns"
+            },
+            {
+                "item_name": "ncr-format",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "JSON",
+                "item_description" : "Format of the update request packet"
+            },
+            {
+
+                "item_name": "always-include-fqdn",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Enable always including the FQDN option in its response"
+            },
+            {
+                "item_name": "override-no-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Do update, even if client requested no updates with N flag"
+            },
+            {
+                "item_name": "override-client-update",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Server performs an update even if client requested delegation"
+            },
+            {
+                "item_name": "replace-client-name",
+                "item_type": "boolean",
+                "item_optional": true,
+                "item_default": false,
+                "item_description": "Should server replace the domain-name supplied by the client"
+            },
+            {
+                "item_name": "generated-prefix",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "myhost",
+                "item_description": "Prefix to use when generating the client's name"
+            },
+
+            {
+                "item_name": "qualifying-suffix",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": "example.com",
+                "item_description": "Fully qualified domain-name suffix if partial name provided by client"
+            },
+        ]
+      },
+
     ],
     "commands": [
         {

+ 16 - 1
src/bin/dhcp4/dhcp4_messages.mes

@@ -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
@@ -32,6 +32,14 @@ This debug message is issued when the DHCP server was unable to process the
 FQDN or Hostname option sent by a client. This is likely because the client's
 name was malformed or due to internal server error.
 
+% DHCP4_CLASS_PROCESSING_FAILED client class specific processing failed
+This debug message means that the server processing that is unique for each
+client class has reported a failure. The response packet will not be sent.
+
+% DHCP4_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+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.
@@ -175,6 +183,13 @@ IPv4 DHCP server but it is not running.
 A debug message issued during startup, this indicates that the IPv4 DHCP
 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
+This debug message is issued when received DHCPv4 message is dropped because
+it is addressed to a different server, i.e. a server identifier held by
+this message doesn't match the identifier used by our server. The arguments
+of this message hold the name of the transaction id and interface on which
+the message has been received.
+
 % DHCP4_OPEN_SOCKET_FAIL failed to create socket: %1
 A warning message issued when IfaceMgr fails to open and bind a socket. The reason
 for the failure is appended as an argument of the log message.

+ 222 - 151
src/bin/dhcp4/dhcp4_srv.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
@@ -21,6 +21,7 @@
 #include <dhcp/option_int.h>
 #include <dhcp/option_int_array.h>
 #include <dhcp/option_vendor.h>
+#include <dhcp/option_string.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/docsis3_option_defs.h>
 #include <dhcp4/dhcp4_log.h>
@@ -79,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),
@@ -259,6 +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;
@@ -303,6 +283,9 @@ Dhcpv4Srv::run() {
             callout_handle->getArgument("query4", query);
         }
 
+        // Assign this packet to one or more classes if needed
+        classifyPacket(query);
+
         try {
             switch (query->getType()) {
             case DHCPDISCOVER:
@@ -359,6 +342,18 @@ Dhcpv4Srv::run() {
             continue;
         }
 
+        // Let's do class specific processing. This is done before
+        // pkt4_send.
+        //
+        /// @todo: decide whether we want to add a new hook point for
+        /// doing class specific processing.
+        if (!classSpecificProcessing(query, rsp)) {
+            /// @todo add more verbosity here
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_PROCESSING_FAILED);
+
+            continue;
+        }
+
         // Specifies if server should do the packing
         bool skip_pack = false;
 
@@ -571,8 +566,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()))
                         );
@@ -641,8 +636,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));
 
@@ -748,68 +743,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);
-    }
+    // 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);
 
-    // 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);
-    }
+    // Carry over the client's E flag.
+    fqdn_resp->setFlag(Option4ClientFqdn::FLAG_E,
+                       fqdn->getFlag(Option4ClientFqdn::FLAG_E));
 
-    // 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);
 
-        } else {
-            name << fqdn->getDomainName();
-            name << "." << FQDN_PARTIAL_SUFFIX;
-            fqdn_resp->setDomainName(name.str(), Option4ClientFqdn::FULL);
-
-        }
-
-    // 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
@@ -829,15 +775,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;
@@ -855,21 +806,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);
@@ -902,17 +852,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;
+
             }
 
         }
@@ -960,9 +907,9 @@ queueNameChangeRequest(const isc::dhcp_ddns::NameChangeType chg_type,
 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.
+        /// @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();
     }
 }
@@ -981,8 +928,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()));
@@ -995,7 +942,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)
@@ -1038,8 +985,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;
         }
@@ -1049,7 +996,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,
@@ -1074,14 +1021,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.
@@ -1113,22 +1055,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 {
@@ -1169,8 +1107,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_);
@@ -1285,7 +1223,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()));
@@ -1327,7 +1265,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;
@@ -1352,7 +1290,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)")
@@ -1410,9 +1348,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)
@@ -1525,6 +1464,57 @@ Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
     return (subnet);
 }
 
+bool
+Dhcpv4Srv::acceptServerId(const Pkt4Ptr& pkt) 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.
+
+    // Check if server identifier option is present. If it is not present
+    // we accept the message because it is targetted to all servers.
+    // 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);
+    if (!option) {
+        return (true);
+    }
+    // Server identifier is present. Let's convert it to 4-byte address
+    // and try to match with server identifiers used by the server.
+    OptionCustomPtr option_custom =
+        boost::dynamic_pointer_cast<OptionCustom>(option);
+    // Unable to convert the option to the option type which encapsulates it.
+    // We treat this as non-matching server id.
+    if (!option_custom) {
+        return (false);
+    }
+    // The server identifier option should carry exactly one IPv4 address.
+    // If the option definition for the server identifier doesn't change,
+    // the OptionCustom object should have exactly one IPv4 address and
+    // this check is somewhat redundant. On the other hand, if someone
+    // breaks option it may be better to check that here.
+    if (option_custom->getDataFieldsNum() != 1) {
+        return (false);
+    }
+
+    // The server identifier MUST be an IPv4 address. If given address is
+    // v6, it is wrong.
+    IOAddress server_id = option_custom->readAddress();
+    if (!server_id.isV4()) {
+        return (false);
+    }
+
+    // This function iterates over all interfaces on which the
+    // server is listening to find the one which has a socket bound
+    // to the address carried in the server identifier option.
+    // This has some performance implications. However, given that
+    // typically there will be just a few active interfaces the
+    // performance hit should be acceptable. If it turns out to
+    // be significant, we will have to cache server identifiers
+    // when sockets are opened.
+    return (IfaceMgr::instance().hasOpenSocket(server_id));
+}
+
 void
 Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
     OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
@@ -1602,8 +1592,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)) {
@@ -1706,5 +1696,86 @@ Dhcpv4Srv::ifaceMgrSocket4ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp4_logger, DHCP4_OPEN_SOCKET_FAIL).arg(errmsg);
 }
 
+void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
+    boost::shared_ptr<OptionString> vendor_class =
+        boost::dynamic_pointer_cast<OptionString>(pkt->getOption(DHO_VENDOR_CLASS_IDENTIFIER));
+
+    string classes = "";
+
+    if (!vendor_class) {
+        return;
+    }
+
+    // DOCSIS specific section
+
+    // Let's keep this as a series of checks. So far we're supporting only
+    // docsis3.0, but there are also docsis2.0, docsis1.1 and docsis1.0. We
+    // may come up with adding several classes, e.g. for docsis2.0 we would
+    // add classes docsis2.0, docsis1.1 and docsis1.0.
+
+    // Also we are using find, because we have at least one traffic capture
+    // where the user class was followed by a space ("docsis3.0 ").
+
+    // For now, the code is very simple, but it is expected to get much more
+    // complex soon. One specific case is that the vendor class is an option
+    // sent by the client, so we should not trust it. To confirm that the device
+    // is indeed a modem, John B. suggested to check whether chaddr field
+    // quals subscriber-id option that was inserted by the relay (CMTS).
+    // This kind of logic will appear here soon.
+    if (vendor_class->getValue().find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_MODEM);
+        classes += string(DOCSIS3_CLASS_MODEM) + " ";
+    } else
+    if (vendor_class->getValue().find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_EROUTER);
+        classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+    } else {
+        classes += vendor_class->getValue();
+        pkt->addClass(vendor_class->getValue());
+    }
+
+    if (!classes.empty()) {
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
+            .arg(classes);
+    }
+}
+
+bool Dhcpv4Srv::classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp) {
+
+    Subnet4Ptr subnet = selectSubnet(query);
+    if (!subnet) {
+        return (true);
+    }
+
+    if (query->inClass(DOCSIS3_CLASS_MODEM)) {
+
+        // Set next-server. This is TFTP server address. Cable modems will
+        // download their configuration from that server.
+        rsp->setSiaddr(subnet->getSiaddr());
+
+        // Now try to set up file field in DHCPv4 packet. We will just copy
+        // content of the boot-file option, which contains the same information.
+        Subnet::OptionDescriptor desc =
+            subnet->getOptionDescriptor("dhcp4", DHO_BOOT_FILE_NAME);
+
+        if (desc.option) {
+            boost::shared_ptr<OptionString> boot =
+                boost::dynamic_pointer_cast<OptionString>(desc.option);
+            if (boot) {
+                std::string filename = boot->getValue();
+                rsp->setFile((const uint8_t*)filename.c_str(), filename.size());
+            }
+        }
+    }
+
+    if (query->inClass(DOCSIS3_CLASS_EROUTER)) {
+
+        // Do not set TFTP server address for eRouter devices.
+        rsp->setSiaddr(IOAddress("0.0.0.0"));
+    }
+
+    return (true);
+}
+
 }   // namespace dhcp
 }   // namespace isc

+ 35 - 1
src/bin/dhcp4/dhcp4_srv.h

@@ -167,6 +167,21 @@ public:
 
 protected:
 
+    /// @brief Verifies if the server id belongs to our server.
+    ///
+    /// This function checks if the server identifier carried in the specified
+    /// DHCPv4 message belongs to this server. If the server identifier option
+    /// is absent or the value carried by this option is equal to one of the
+    /// server identifiers used by the server, the true is returned. If the
+    /// server identifier option is present, but it doesn't match any server
+    /// identifier used by this server, the false value is returned.
+    ///
+    /// @param pkt DHCPv4 message which server identifier is to be checked.
+    ///
+    /// @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
     ///
     /// Checks if mandatory option is really there, that forbidden option
@@ -247,7 +262,7 @@ protected:
     /// using separate options within their respective vendor-option spaces.
     ///
     /// @param question DISCOVER or REQUEST message from a client.
-    /// @param msg outgoing message (options will be added here)
+    /// @param answer outgoing message (options will be added here)
     void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
 
     /// @brief Assigns a lease and appends corresponding options
@@ -544,6 +559,25 @@ protected:
                          const std::string& option_space,
                          isc::dhcp::OptionCollection& options);
 
+    /// @brief Assigns incoming packet to zero or more classes.
+    ///
+    /// @note For now, the client classification is very simple. It just uses
+    /// content of the vendor-class-identifier option as a class. The resulting
+    /// class will be stored in packet (see @ref isc::dhcp::Pkt4::classes_ and
+    /// @ref isc::dhcp::Pkt4::inClass).
+    ///
+    /// @param pkt packet to be classified
+    void classifyPacket(const Pkt4Ptr& pkt);
+
+    /// @brief Performs packet processing specific to a class
+    ///
+    /// This processing is a likely candidate to be pushed into hooks.
+    ///
+    /// @param query incoming client's packet
+    /// @param rsp server's response
+    /// @return true if successful, false otherwise (will prevent sending response)
+    bool classSpecificProcessing(const Pkt4Ptr& query, const Pkt4Ptr& rsp);
+
 private:
 
     /// @brief Constructs netmask option based on subnet4

+ 3 - 0
src/bin/dhcp4/tests/.gitignore

@@ -1 +1,4 @@
 /dhcp4_unittests
+/marker_file.h
+/test_data_files_config.h
+/test_libraries.h

+ 2 - 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)

+ 515 - 19
src/bin/dhcp4/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
@@ -30,6 +30,7 @@
 
 #include "marker_file.h"
 #include "test_libraries.h"
+#include "test_data_files_config.h"
 
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -50,6 +51,23 @@ using namespace std;
 
 namespace {
 
+/// @brief Prepends the given name with the DHCP4 source directory
+///
+/// @param name file name of the desired file
+/// @return string containing the absolute path of the file in the DHCP source
+/// directory.
+std::string specfile(const std::string& name) {
+    return (std::string(DHCP4_SRC_DIR) + "/" + name);
+}
+
+/// @brief Tests that the spec file is valid.
+/// Verifies that the BIND10 DHCP-DDNS configuration specification file
+//  is valid.
+TEST(Dhcp4SpecTest, basicSpec) {
+    (isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+    ASSERT_NO_THROW(isc::config::moduleSpecFromFile(specfile("dhcp4.spec")));
+}
+
 class Dhcp4ParserTest : public ::testing::Test {
 public:
     Dhcp4ParserTest()
@@ -119,19 +137,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";
@@ -143,7 +161,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));
@@ -194,6 +212,62 @@ 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);
+        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
@@ -215,6 +289,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
@@ -256,6 +348,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.
@@ -321,6 +446,7 @@ public:
             "\"renew-timer\": 1000, "
             "\"valid-lifetime\": 4000, "
             "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
             "\"option-def\": [ ], "
             "\"option-data\": [ ] }";
         static_cast<void>(executeConfiguration(config,
@@ -410,8 +536,195 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
     EXPECT_EQ(1000, subnet->getT1());
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(4000, subnet->getValid());
+
+    // Check that subnet-id is 1
+    EXPECT_EQ(1, subnet->getID());
+}
+
+// Goal of this test is to verify that multiple subnets get unique
+// subnet-ids. Also, test checks that it's possible to do reconfiguration
+// multiple times.
+TEST_F(Dhcp4ParserTest, multipleSubnets) {
+    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\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\" "
+        " },"
+        " {"
+        "    \"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_);
+
+    int cnt = 0; // Number of reconfigurations
+
+    do {
+        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
+
+        // Check subnet-ids of each subnet (it should be monotonously increasing)
+        EXPECT_EQ(1, subnets->at(0)->getID());
+        EXPECT_EQ(2, subnets->at(1)->getID());
+        EXPECT_EQ(3, subnets->at(2)->getID());
+        EXPECT_EQ(4, subnets->at(3)->getID());
+
+        // Repeat reconfiguration process 10 times and check that the subnet-id
+        // is set to the same value. Technically, just two iterations would be
+        // sufficient, but it's nice to have a test that exercises reconfiguration
+        // a bit.
+    } while (++cnt < 10);
 }
 
+// Goal of this test is to verify that a previously configured subnet can be
+// deleted in subsequent reconfiguration.
+TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
+    ConstElementPtr x;
+
+    // All four subnets
+    string config4 = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+        "    \"subnet\": \"192.0.5.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Three subnets (the last one removed)
+    string config_first3 = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+        "    \"subnet\": \"192.0.3.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Second subnet removed
+    string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
+        "    \"subnet\": \"192.0.4.0/24\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"192.0.5.101 - 192.0.5.150\" ],"
+        "    \"subnet\": \"192.0.5.0/24\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // CASE 1: Configure 4 subnets, then reconfigure and remove the
+    // last one.
+
+    ElementPtr json = Element::fromJSON(config4);
+    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
+
+    // Do the reconfiguration (the last subnet is removed)
+    json = Element::fromJSON(config_first3);
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
+
+    // Check subnet-ids of each subnet (it should be monotonously increasing)
+    EXPECT_EQ(1, subnets->at(0)->getID());
+    EXPECT_EQ(2, subnets->at(1)->getID());
+    EXPECT_EQ(3, subnets->at(2)->getID());
+
+    /// CASE 2: Configure 4 subnets, then reconfigure and remove one
+    /// from in between (not first, not last)
+
+#if 0
+    /// @todo: Uncomment subnet removal test as part of #3281.
+    json = Element::fromJSON(config4);
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    // Do reconfiguration
+    json = Element::fromJSON(config_second_removed);
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    subnets = CfgMgr::instance().getSubnets4();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
+
+    EXPECT_EQ(1, subnets->at(0)->getID());
+    // The second subnet (with subnet-id = 2) is no longer there
+    EXPECT_EQ(3, subnets->at(1)->getID());
+    EXPECT_EQ(4, subnets->at(2)->getID());
+#endif
+
+}
+
+/// @todo: implement subnet removal test as part of #3281.
+
 // Checks if the next-server defined as global parameter is taken into
 // consideration.
 TEST_F(Dhcp4ParserTest, nextServerGlobal) {
@@ -1188,7 +1501,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
         "    \"name\": \"dhcp-message\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1261,7 +1574,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
         "    \"name\": \"dhcp-message\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1438,7 +1751,6 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
         " } ]"
         "}";
 
-
     json = Element::fromJSON(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1494,7 +1806,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
         "          \"name\": \"dhcp-message\","
         "          \"space\": \"dhcp4\","
         "          \"code\": 56,"
-        "          \"data\": \"AB CDEF0105\","
+        "          \"data\": \"ABCDEF0105\","
         "          \"csv-format\": False"
         "        },"
         "        {"
@@ -1545,6 +1857,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) {
@@ -1622,6 +2017,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.
@@ -1672,14 +2069,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) {
@@ -1995,7 +2384,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"
         " },"
         " {"
@@ -2127,7 +2516,7 @@ buildHooksLibrariesConfig(const std::vector<std::string>& libraries) {
         "    \"name\": \"dhcp-message\","
         "    \"space\": \"dhcp4\","
         "    \"code\": 56,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -2299,6 +2688,113 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
     EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
 }
 
+// This test checks the ability of the server to parse a configuration
+// containing a full, valid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp4ParserTest, d2ClientConfig) {
+    ConstElementPtr status;
+
+    // Verify that the D2 configuraiton can be fetched and is set to disabled.
+    D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    // Verify that the convenience method agrees.
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+
+    string config_str = "{ \"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\" : true, "
+        "     \"server-ip\" : \"192.168.2.1\", "
+        "     \"server-port\" : 777, "
+        "     \"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 }";
 
+    // Convert the JSON string to configuration elements.
+    ElementPtr config;
+    ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+    // Pass the configuration in for parsing.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
+
+    // check if returned status is OK
+    checkResult(status, 0);
+
+    // Verify that DHCP-DDNS updating is enabled.
+    EXPECT_TRUE(CfgMgr::instance().ddnsEnabled());
+
+    // Verify that the D2 configuration can be retrieved.
+    d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    ASSERT_TRUE(d2_client_config);
+
+    // Verify that the configuration values are correct.
+    EXPECT_TRUE(d2_client_config->getEnableUpdates());
+    EXPECT_EQ("192.168.2.1", d2_client_config->getServerIp().toText());
+    EXPECT_EQ(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->getAlwaysIncludeFqdn());
+    EXPECT_TRUE(d2_client_config->getOverrideNoUpdate());
+    EXPECT_TRUE(d2_client_config->getOverrideClientUpdate());
+    EXPECT_TRUE(d2_client_config->getReplaceClientName());
+    EXPECT_EQ("test.prefix", d2_client_config->getGeneratedPrefix());
+    EXPECT_EQ("test.suffix.", d2_client_config->getQualifyingSuffix());
+}
+
+// This test checks the ability of the server to handle a configuration
+// containing an invalid dhcp-ddns (D2ClientConfig) entry.
+TEST_F(Dhcp4ParserTest, invalidD2ClientConfig) {
+    ConstElementPtr status;
+
+    // Configuration string with an invalid D2 client config,
+    // "server-ip" is missing.
+    string config_str = "{ \"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\" : true, "
+        "     \"server-port\" : 5301, "
+        "     \"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 }";
+
+    // Convert the JSON string to configuration elements.
+    ElementPtr config;
+    ASSERT_NO_THROW(config = Element::fromJSON(config_str));
+
+    // Configuration should not throw, but should fail.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
+
+    // check if returned status is failed.
+    checkResult(status, 1);
+
+    // Verify that the D2 configuraiton can be fetched and is set to disabled.
+    D2ClientConfigPtr d2_client_config = CfgMgr::instance().getD2ClientConfig();
+    EXPECT_FALSE(d2_client_config->getEnableUpdates());
+
+    // Verify that the convenience method agrees.
+    ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
+}
 
 }

+ 98 - 24
src/bin/dhcp4/tests/dhcp4_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
@@ -584,7 +584,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverHint) {
     // lifetime is correct, that T1 and T2 are returned properly
     checkAddressParams(offer, subnet_);
 
-    EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+    EXPECT_EQ(offer->getYiaddr(), hint);
 
     // Check identifiers
     checkServerId(offer, srv->getServerID());
@@ -624,7 +624,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverNoClientId) {
     // lifetime is correct, that T1 and T2 are returned properly
     checkAddressParams(offer, subnet_);
 
-    EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+    EXPECT_EQ(offer->getYiaddr(), hint);
 
     // Check identifiers
     checkServerId(offer, srv->getServerID());
@@ -664,7 +664,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, DiscoverInvalidHint) {
     // lifetime is correct, that T1 and T2 are returned properly
     checkAddressParams(offer, subnet_);
 
-    EXPECT_NE(offer->getYiaddr().toText(), hint.toText());
+    EXPECT_NE(offer->getYiaddr(), hint);
 
     // Check identifiers
     checkServerId(offer, srv->getServerID());
@@ -735,12 +735,12 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyDiscovers) {
     checkClientId(offer3, clientid3);
 
     // Finally check that the addresses offered are different
-    EXPECT_NE(addr1.toText(), addr2.toText());
-    EXPECT_NE(addr2.toText(), addr3.toText());
-    EXPECT_NE(addr3.toText(), addr1.toText());
-    cout << "Offered address to client1=" << addr1.toText() << endl;
-    cout << "Offered address to client2=" << addr2.toText() << endl;
-    cout << "Offered address to client3=" << addr3.toText() << endl;
+    EXPECT_NE(addr1, addr2);
+    EXPECT_NE(addr2, addr3);
+    EXPECT_NE(addr3, addr1);
+    cout << "Offered address to client1=" << addr1 << endl;
+    cout << "Offered address to client2=" << addr2 << endl;
+    cout << "Offered address to client3=" << addr3 << endl;
 }
 
 // Checks whether echoing back client-id is controllable, i.e.
@@ -802,7 +802,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RequestBasic) {
 
     // Check if we get response at all
     checkResponse(ack, DHCPACK, 1234);
-    EXPECT_EQ(hint.toText(), ack->getYiaddr().toText());
+    EXPECT_EQ(hint, ack->getYiaddr());
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
@@ -886,9 +886,9 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
     IOAddress addr3 = ack3->getYiaddr();
 
     // Check that every client received the address it requested
-    EXPECT_EQ(req_addr1.toText(), addr1.toText());
-    EXPECT_EQ(req_addr2.toText(), addr2.toText());
-    EXPECT_EQ(req_addr3.toText(), addr3.toText());
+    EXPECT_EQ(req_addr1, addr1);
+    EXPECT_EQ(req_addr2, addr2);
+    EXPECT_EQ(req_addr3, addr3);
 
     // Check that the assigned address is indeed from the configured pool
     checkAddressParams(ack1, subnet_);
@@ -910,12 +910,12 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, ManyRequests) {
     l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
 
     // Finally check that the addresses offered are different
-    EXPECT_NE(addr1.toText(), addr2.toText());
-    EXPECT_NE(addr2.toText(), addr3.toText());
-    EXPECT_NE(addr3.toText(), addr1.toText());
-    cout << "Offered address to client1=" << addr1.toText() << endl;
-    cout << "Offered address to client2=" << addr2.toText() << endl;
-    cout << "Offered address to client3=" << addr3.toText() << endl;
+    EXPECT_NE(addr1, addr2);
+    EXPECT_NE(addr2, addr3);
+    EXPECT_NE(addr3, addr1);
+    cout << "Offered address to client1=" << addr1 << endl;
+    cout << "Offered address to client2=" << addr2 << endl;
+    cout << "Offered address to client3=" << addr3 << endl;
 }
 
 // Checks whether echoing back client-id is controllable
@@ -1003,7 +1003,7 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) {
 
     // Check if we get response at all
     checkResponse(ack, DHCPACK, 1234);
-    EXPECT_EQ(addr.toText(), ack->getYiaddr().toText());
+    EXPECT_EQ(addr, ack->getYiaddr());
 
     // Check that address was returned from proper range, that its lease
     // lifetime is correct, that T1 and T2 are returned properly
@@ -1031,6 +1031,56 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, RenewBasic) {
     EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
 }
 
+// This test verifies that the logic which matches server identifier in the
+// received message with server identifiers used by a server works correctly:
+// - a message with no server identifier is accepted,
+// - a message with a server identifier which matches one of the server
+// identifiers used by a server is accepted,
+// - a message with a server identifier which doesn't match any server
+// identifier used by a server, is not accepted.
+TEST_F(Dhcpv4SrvFakeIfaceTest, acceptServerId) {
+    NakedDhcpv4Srv srv(0);
+
+    Pkt4Ptr pkt(new Pkt4(DHCPREQUEST, 1234));
+    // If no server identifier option is present, the message is always
+    // accepted.
+    EXPECT_TRUE(srv.acceptServerId(pkt));
+
+    // Create definition of the server identifier option.
+    OptionDefinition def("server-identifier", DHO_DHCP_SERVER_IDENTIFIER,
+                         "ipv4-address", false);
+
+    // Add a server identifier option which doesn't match server ids being
+    // used by the server. The accepted server ids are the IPv4 addresses
+    // configured on the interfaces. The 10.1.2.3 is not configured on
+    // any interfaces.
+    OptionCustomPtr other_serverid(new OptionCustom(def, Option::V6));
+    other_serverid->writeAddress(IOAddress("10.1.2.3"));
+    pkt->addOption(other_serverid);
+    EXPECT_FALSE(srv.acceptServerId(pkt));
+
+    // Remove the server identifier.
+    ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+    // Add a server id being an IPv4 address configured on eth0 interface.
+    // A DHCPv4 message holding this server identifier should be accepted.
+    OptionCustomPtr eth0_serverid(new OptionCustom(def, Option::V6));
+    eth0_serverid->writeAddress(IOAddress("192.0.3.1"));
+    ASSERT_NO_THROW(pkt->addOption(eth0_serverid));
+    EXPECT_TRUE(srv.acceptServerId(pkt));
+
+    // Remove the server identifier.
+    ASSERT_NO_THROW(pkt->delOption(DHO_DHCP_SERVER_IDENTIFIER));
+
+    // Add a server id being an IPv4 address configured on eth1 interface.
+    // A DHCPv4 message holding this server identifier should be accepted.
+    OptionCustomPtr eth1_serverid(new OptionCustom(def, Option::V6));
+    eth1_serverid->writeAddress(IOAddress("10.0.0.1"));
+    ASSERT_NO_THROW(pkt->addOption(eth1_serverid));
+    EXPECT_TRUE(srv.acceptServerId(pkt));
+
+}
+
 // @todo: Implement tests for rejecting renewals
 
 // This test verifies if the sanityCheck() really checks options presence.
@@ -3123,8 +3173,32 @@ TEST_F(Dhcpv4SrvFakeIfaceTest, vendorOptionsDocsisDefinitions) {
     ASSERT_EQ(0, rcode_);
 }
 
+// Checks if client packets are classified properly
+TEST_F(Dhcpv4SrvTest, clientClassification) {
+
+    NakedDhcpv4Srv srv(0);
+
+    // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+    // vendor-class set to docsis3.0
+    Pkt4Ptr dis1;
+    ASSERT_NO_THROW(dis1 = captureRelayedDiscover());
+    ASSERT_NO_THROW(dis1->unpack());
+
+    srv.classifyPacket(dis1);
+
+    EXPECT_TRUE(dis1->inClass("docsis3.0"));
+    EXPECT_FALSE(dis1->inClass("eRouter1.0"));
+
+    // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+    // vendor-class set to eRouter1.0
+    Pkt4Ptr dis2;
+    ASSERT_NO_THROW(dis2 = captureRelayedDiscover2());
+    ASSERT_NO_THROW(dis2->unpack());
+
+    srv.classifyPacket(dis2);
+
+    EXPECT_TRUE(dis2->inClass("eRouter1.0"));
+    EXPECT_FALSE(dis2->inClass("docsis3.0"));
 }
 
-    /*I}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace */
+}; // end of anonymous namespace

+ 4 - 4
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -126,7 +126,7 @@ void Dhcpv4SrvTest::messageCheck(const Pkt4Ptr& q, const Pkt4Ptr& a) {
     EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
 
     // Check that something is offered
-    EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
+    EXPECT_NE("0.0.0.0", a->getYiaddr().toText());
 }
 
 ::testing::AssertionResult
@@ -299,14 +299,14 @@ Lease4Ptr Dhcpv4SrvTest::checkLease(const Pkt4Ptr& rsp,
 
     Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
     if (!lease) {
-        cout << "Lease for " << expected_addr.toText()
+        cout << "Lease for " << expected_addr
              << " not found in the database backend.";
         return (Lease4Ptr());
     }
 
-    EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
+    EXPECT_EQ(rsp->getYiaddr(), expected_addr);
 
-    EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
+    EXPECT_EQ(expected_addr, lease->addr_);
     if (client_id) {
         EXPECT_TRUE(*lease->client_id_ == *id);
     }

+ 11 - 1
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -222,7 +222,8 @@ public:
 
     /// @brief returns captured DISCOVER that went through a relay
     ///
-    /// See method code for a detailed explanation.
+    /// See method code for a detailed explanation. This is a discover from
+    /// docsis3.0 device (Cable Modem)
     ///
     /// @return relayed DISCOVER
     Pkt4Ptr captureRelayedDiscover();
@@ -253,6 +254,13 @@ public:
     createPacketFromBuffer(const Pkt4Ptr& src_pkt,
                            Pkt4Ptr& dst_pkt);
 
+    /// @brief returns captured DISCOVER that went through a relay
+    ///
+    /// See method code for a detailed explanation. This is a discover from
+    /// eRouter1.0 device (CPE device integrated with cable modem)
+    ///
+    /// @return relayed DISCOVER
+    Pkt4Ptr captureRelayedDiscover2();
 
     /// @brief generates a DHCPv4 packet based on provided hex string
     ///
@@ -438,10 +446,12 @@ public:
     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;
 };
 
 }; // end of isc::dhcp::test namespace

+ 287 - 69
src/bin/dhcp4/tests/fqdn_unittest.cc

@@ -18,6 +18,7 @@
 #include <dhcp/option_int_array.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>
@@ -32,13 +33,53 @@ namespace {
 
 class NameDhcpv4SrvTest : public Dhcpv4SrvFakeIfaceTest {
 public:
+
+    // 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() : Dhcpv4SrvFakeIfaceTest() {
         srv_ = new NakedDhcpv4Srv(0);
+        // Config DDNS to be enabled, all controls off
+        enableD2();
     }
+
     virtual ~NameDhcpv4SrvTest() {
         delete srv_;
     }
 
+    /// @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("192.0.2.1"), 477,
+                                  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")));
+
+        CfgMgr::instance().setD2ClientConfig(cfg);
+    }
+
     // Create a lease to be used by various tests.
     Lease4Ptr createLease(const isc::asiolink::IOAddress& addr,
                           const std::string& hostname,
@@ -78,15 +119,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.
@@ -182,6 +226,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 +250,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 +277,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 |
@@ -287,6 +343,50 @@ public:
         srv_->name_change_reqs_.pop();
     }
 
+
+    /// @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) {
+        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);
+
+        // 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, srv_->name_change_reqs_.size());
+        } else {
+            // Verify that there is one NameChangeRequest generated as expected.
+            ASSERT_EQ(1, srv_->name_change_reqs_.size());
+            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 +448,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 +627,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) {
@@ -600,18 +752,42 @@ TEST_F(NameDhcpv4SrvTest, processRequestFqdnEmptyDomainName) {
 
     // Verify that there is one NameChangeRequest generated.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
     // 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) {
+    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) {
     Pkt4Ptr req = generatePktWithHostname(DHCPREQUEST, ".");
@@ -626,11 +802,12 @@ TEST_F(NameDhcpv4SrvTest, processRequestEmptyHostname) {
 
     // Verify that there is one NameChangeRequest generated.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
+
     // 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);
 }
@@ -742,21 +919,23 @@ 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) {
+    // 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.
+    // Verify that there is one NameChangeRequest generated for lease.
     ASSERT_EQ(1, srv_->name_change_reqs_.size());
     verifyNameChangeRequest(isc::dhcp_ddns::CHG_ADD, true, true,
                             reply->getYiaddr().toText(), "myhost.example.com.",
@@ -764,18 +943,57 @@ TEST_F(NameDhcpv4SrvTest, processRequestRelease) {
                             "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());
+    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.
+TEST_F(NameDhcpv4SrvTest, processRequestReleaseUpdatesDisabled) {
+    // 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);
+
+    // With DDNS updates disabled, there should be not be a NameChangeRequest
+    // for the add.
+    ASSERT_EQ(0, srv_->name_change_reqs_.size());
+
+    // 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));
+
+    // With DDNS updates disabled, there should be not be a NameChangeRequest
+    // for the remove.
+    ASSERT_EQ(0, srv_->name_change_reqs_.size());
 }
 
+
 } // end of anonymous namespace

+ 17 - 0
src/bin/dhcp4/tests/test_data_files_config.h.in

@@ -0,0 +1,17 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// @brief Path to dhcp4 source dir so tests against the dhcp4.spec file
+/// can find it reliably.
+#define DHCP4_SRC_DIR "@abs_top_srcdir@/src/bin/dhcp4"

+ 58 - 2
src/bin/dhcp4/tests/wireshark.cc

@@ -71,7 +71,10 @@ void Dhcpv4SrvTest::captureSetDefaultFields(const Pkt4Ptr& pkt) {
 
 Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover() {
 
-/* string exported from Wireshark:
+/* This is packet 1 from capture
+   dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
 
 User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
     Source port: bootps (67)
@@ -98,7 +101,7 @@ Bootstrap Protocol
     Magic cookie: DHCP
     Option: (53) DHCP Message Type
     Option: (55) Parameter Request List
-    Option: (60) Vendor class identifier
+    Option: (60) Vendor class identifier (docsis3.0)
     Option: (125) V-I Vendor-specific Information
       - suboption 1 (Option Request): requesting option 2
       - suboption 5 (Modem Caps): 117 bytes
@@ -129,6 +132,59 @@ Bootstrap Protocol
     return (packetFromCapture(hex_string));
 }
 
+Pkt4Ptr Dhcpv4SrvTest::captureRelayedDiscover2() {
+
+/* This is packet 5 from capture
+   dhcp-val/pcap/docsis-*-CG3000DCR-Registration-Filtered.cap
+
+string exported from Wireshark:
+
+User Datagram Protocol, Src Port: bootps (67), Dst Port: bootps (67)
+Bootstrap Protocol
+    Message type: Boot Request (1)
+    Hardware type: Ethernet (0x01)
+    Hardware address length: 6
+    Hops: 1
+    Transaction ID: 0x5d05478f
+    Seconds elapsed: 5
+    Bootp flags: 0x0000 (Unicast)
+    Client IP address: 0.0.0.0 (0.0.0.0)
+    Your (client) IP address: 0.0.0.0 (0.0.0.0)
+    Next server IP address: 0.0.0.0 (0.0.0.0)
+    Relay agent IP address: 10.254.226.1 (10.254.226.1)
+    Client MAC address: Netgear_b8:15:15 (20:e5:2a:b8:15:15)
+    Client hardware address padding: 00000000000000000000
+    Server host name not given
+    Boot file name not given
+    Magic cookie: DHCP
+    Option: (53) DHCP Message Type
+    Option: (55) Parameter Request List
+    Option: (43) Vendor-Specific Information
+    Option: (60) Vendor class identifier (eRouter1.0)
+    Option: (15) Domain Name
+    Option: (61) Client identifier
+    Option: (57) Maximum DHCP Message Size
+    Option: (82) Agent Information Option
+    Option: (255) End */
+
+    string hex_string =
+        "010106015d05478f000500000000000000000000000000000afee20120e52ab8151500"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "0000000000000000000000000000000000000000000000000000000000000000000000"
+        "000000000000000000000000000000000000000000000000000063825363350101370e"
+        "480102030406070c0f171a36337a2b63020745524f55544552030b45434d3a45524f55"
+        "544552040d324252323239553430303434430504312e3034060856312e33332e303307"
+        "07322e332e305232080630303039354209094347333030304443520a074e6574676561"
+        "720f0745524f555445523c0a65526f75746572312e300f14687364312e70612e636f6d"
+        "636173742e6e65742e3d0fff2ab815150003000120e52ab81515390205dc5219010420"
+        "000002020620e52ab8151409090000118b0401020300ff";
+
+    return (packetFromCapture(hex_string));
+}
+
 }; // end of isc::dhcp::test namespace
 }; // end of isc::dhcp namespace
 }; // end of isc namespace

+ 1 - 0
src/bin/dhcp6/.gitignore

@@ -4,3 +4,4 @@
 /dhcp6_messages.h
 /spec_config.h
 /spec_config.h.pre
+/s-messages

+ 6 - 2
src/bin/dhcp6/config_parser.cc

@@ -497,12 +497,12 @@ protected:
                       "parser error: interface (defined for locally reachable "
                       "subnets) and interface-id (defined for subnets reachable"
                       " via relays) cannot be defined at the same time for "
-                      "subnet " << addr.toText() << "/" << (int)len);
+                      "subnet " << addr << "/" << (int)len);
             }
         }
 
         stringstream tmp;
-        tmp << addr.toText() << "/" << static_cast<int>(len)
+        tmp << addr << "/" << static_cast<int>(len)
             << " with params t1=" << t1 << ", t2=" << t2 << ", pref="
             << pref << ", valid=" << valid;
 
@@ -646,6 +646,10 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
     LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND,
               DHCP6_CONFIG_START).arg(config_set->str());
 
+    // Before starting any subnet operations, let's reset the subnet-id counter,
+    // so newly recreated configuration starts with first subnet-id equal 1.
+    Subnet::resetSubnetID();
+
     // Some of the values specified in the configuration depend on
     // other values. Typically, the values in the subnet6 structure
     // depend on the global values. Also, option values configuration

+ 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::ConfigData::getFullConfig.
+    /// be later accessed with
+    /// \ref isc::config::ConfigData::getFullConfig().
     ///
     /// @param new_config new configuration.
     ///

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

@@ -190,6 +190,33 @@ implemented within the context of the server and it has access to all objects
 which define its configuration (including dynamically created option
 definitions).
 
+@section dhcpv6Classifier DHCPv6 Client Classification
+
+Kea DHCPv6 server currently supports simplified client classification. It is called
+"simplified", because the incoming packets are classified based on the content
+of the vendor class (16) option. More flexible classification is planned, but there
+are no specific development dates agreed.
+
+For each incoming packet, @ref isc::dhcp::Dhcpv6Srv::classifyPacket() method is
+called.  It attempts to extract content of the vendor class option and interprets
+as a name of the class. Although the RFC3315 says that the vendor class may
+contain more than one chunk of data, the existing code handles only one data
+block, because that is what actual devices use. For now, the code has been
+tested with two classes used in cable modem networks: eRouter1.0 and docsis3.0,
+but any other content of the vendor class option will be interpreted as a class
+name.
+
+In principle any given packet can belong to zero or more classes. As the current
+classifier is very modest, there's only one way to assign a class (based on vendor class
+option), the ability to assign more than one class to a packet is not yet exercised.
+Neverthless, there is such a possibility and it will be used in a near future. To
+check whether a packet belongs to given class, isc::dhcp::Pkt6::inClass method should
+be used.
+
+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.
+
  @section dhcpv6Other Other DHCPv6 topics
 
  For hooks API support in DHCPv6, see @ref dhcpv6Hooks.

+ 15 - 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
@@ -27,6 +27,10 @@ successfully established a session with the BIND 10 control channel.
 This debug message is issued just before the IPv6 DHCP server attempts
 to establish a session with the BIND 10 control channel.
 
+% DHCP6_CLASS_ASSIGNED client packet has been assigned to the following class(es): %1
+This debug message informs that incoming packet has been assigned to specified
+class or classes. This is a norma
+
 % DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
 This error message indicates that the received message is being dropped
 because it does not include the mandatory client-id option necessary for
@@ -100,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
@@ -233,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.
@@ -268,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

+ 251 - 132
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)
@@ -316,6 +346,9 @@ bool Dhcpv6Srv::run() {
             callout_handle->getArgument("query6", query);
         }
 
+        // Assign this packet to a class, if possible
+        classifyPacket(query);
+
         try {
                 NameChangeRequestPtr ncr;
             switch (query->getType()) {
@@ -610,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()));
@@ -626,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.
@@ -887,8 +921,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.
@@ -941,10 +974,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);
             }
@@ -964,14 +996,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,
@@ -1022,13 +1054,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.
@@ -1039,58 +1072,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;
     }
 
@@ -1103,6 +1095,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.
@@ -1130,8 +1130,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.
@@ -1157,31 +1157,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;
@@ -1189,7 +1191,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;
     }
 
@@ -1199,7 +1202,7 @@ Dhcpv6Srv::createRemovalNameChangeRequest(const Lease6Ptr& lease) {
     if (!lease->duid_) {
         isc_throw(isc::Unexpected, "DUID must be set when creating"
                   << " NameChangeRequest for DNS records removal for "
-                  << lease->addr_.toText());
+                  << lease->addr_);
 
     }
     isc::dhcp_ddns::D2Dhcid dhcid(*lease->duid_, hostname_wire);
@@ -1230,14 +1233,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
@@ -1252,16 +1255,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,
@@ -1285,6 +1288,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.
@@ -1306,13 +1311,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()) {
@@ -1347,26 +1354,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 {
@@ -1434,13 +1440,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()) {
 
@@ -1488,8 +1496,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()));
@@ -1538,6 +1546,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;
@@ -1730,8 +1740,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.
@@ -1775,10 +1784,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);
             }
@@ -2175,13 +2183,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);
 }
 
@@ -2197,10 +2206,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);
 }
@@ -2216,12 +2225,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
@@ -2352,10 +2361,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) {
@@ -2423,5 +2435,112 @@ Dhcpv6Srv::ifaceMgrSocket6ErrorHandler(const std::string& errmsg) {
     LOG_WARN(dhcp6_logger, DHCP6_OPEN_SOCKET_FAIL).arg(errmsg);
 }
 
+void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
+
+    boost::shared_ptr<OptionCustom> vclass =
+        boost::dynamic_pointer_cast<OptionCustom>(pkt->getOption(D6O_VENDOR_CLASS));
+
+    if (!vclass) {
+        return;
+    }
+
+    string classes = "";
+
+    // DOCSIS specific section
+    if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
+        .find(DOCSIS3_CLASS_MODEM) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_MODEM);
+        classes += string(DOCSIS3_CLASS_MODEM) + " ";
+    } else
+    if (vclass->readString(VENDOR_CLASS_STRING_INDEX)
+        .find(DOCSIS3_CLASS_EROUTER) != std::string::npos) {
+        pkt->addClass(DOCSIS3_CLASS_EROUTER);
+        classes += string(DOCSIS3_CLASS_EROUTER) + " ";
+    }else
+    {
+        // Otherwise use the string as is
+        classes += vclass->readString(VENDOR_CLASS_STRING_INDEX);
+        pkt->addClass(vclass->readString(VENDOR_CLASS_STRING_INDEX));
+    }
+
+    if (!classes.empty()) {
+        LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
+            .arg(classes);
+    }
+}
+
+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());
+    }
+}
+
 };
 };

+ 86 - 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
     ///
@@ -533,6 +526,16 @@ protected:
                          size_t* relay_msg_offset,
                          size_t* relay_msg_len);
 
+    /// @brief Assigns incoming packet to zero or more classes.
+    ///
+    /// @note For now, the client classification is very simple. It just uses
+    /// content of the vendor-class-identifier option as a class. The resulting
+    /// class will be stored in packet (see @ref isc::dhcp::Pkt6::classes_ and
+    /// @ref isc::dhcp::Pkt6::inClass).
+    ///
+    /// @param pkt packet to be classified
+    void classifyPacket(const Pkt6Ptr& pkt);
+
 private:
 
     /// @brief Implements the error handler for socket open failure.
@@ -544,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

+ 3 - 0
src/bin/dhcp6/tests/.gitignore

@@ -1 +1,4 @@
 /dhcp6_unittests
+/marker_file.h
+/test_data_files_config.h
+/test_libraries.h

+ 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)

+ 399 - 20
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,62 @@ 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);
+        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 +378,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,6 +437,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 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)
@@ -436,8 +560,190 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
     EXPECT_EQ(2000, subnet->getT2());
     EXPECT_EQ(3000, subnet->getPreferred());
     EXPECT_EQ(4000, subnet->getValid());
+
+    // Check that subnet-id is 1
+    EXPECT_EQ(1, subnet->getID());
+}
+
+// Goal of this test is to verify that multiple subnets get unique
+// subnet-ids. Also, test checks that it's possible to do reconfiguration
+// multiple times.
+TEST_F(Dhcp6ParserTest, multipleSubnets) {
+    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\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/80\" ],"
+        "    \"subnet\": \"2001:db8:4::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    int cnt = 0; // Number of reconfigurations
+
+    do {
+        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
+
+        // Check subnet-ids of each subnet (it should be monotonously increasing)
+        EXPECT_EQ(1, subnets->at(0)->getID());
+        EXPECT_EQ(2, subnets->at(1)->getID());
+        EXPECT_EQ(3, subnets->at(2)->getID());
+        EXPECT_EQ(4, subnets->at(3)->getID());
+
+        // Repeat reconfiguration process 10 times and check that the subnet-id
+        // is set to the same value. Technically, just two iterations would be
+        // sufficient, but it's nice to have a test that exercises reconfiguration
+        // a bit.
+    } while (++cnt < 10);
+}
+
+// Goal of this test is to verify that a previously configured subnet can be
+// deleted in subsequent reconfiguration.
+TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
+    ConstElementPtr x;
+
+    // All four subnets
+    string config4 = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/80\" ],"
+        "    \"subnet\": \"2001:db8:4::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Three subnets (the last one removed)
+    string config_first3 = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:2::/80\" ],"
+        "    \"subnet\": \"2001:db8:2::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Second subnet removed
+    string config_second_removed = "{ \"interfaces\": [ \"*\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pool\": [ \"2001:db8:1::/80\" ],"
+        "    \"subnet\": \"2001:db8:1::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:3::/80\" ],"
+        "    \"subnet\": \"2001:db8:3::/64\" "
+        " },"
+        " {"
+        "    \"pool\": [ \"2001:db8:4::/80\" ],"
+        "    \"subnet\": \"2001:db8:4::/64\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // CASE 1: Configure 4 subnets, then reconfigure and remove the
+    // last one.
+
+    ElementPtr json = Element::fromJSON(config4);
+    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
+
+    // Do the reconfiguration (the last subnet is removed)
+    json = Element::fromJSON(config_first3);
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 3 subnets now (4th is removed)
+
+    EXPECT_EQ(1, subnets->at(0)->getID());
+    EXPECT_EQ(2, subnets->at(1)->getID());
+    EXPECT_EQ(3, subnets->at(2)->getID());
+
+    /// CASE 2: Configure 4 subnets, then reconfigure and remove one
+    /// from in between (not first, not last)
+
+#if 0
+    /// @todo: Uncomment subnet removal test as part of #3281.
+    json = Element::fromJSON(config4);
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    // Do reconfiguration
+    json = Element::fromJSON(config_second_removed);
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    ASSERT_TRUE(x);
+    comment_ = parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    subnets = CfgMgr::instance().getSubnets6();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size()); // We expect 4 subnets
+
+    EXPECT_EQ(1, subnets->at(0)->getID());
+    // The second subnet (with subnet-id = 2) is no longer there
+    EXPECT_EQ(3, subnets->at(1)->getID());
+    EXPECT_EQ(4, subnets->at(2)->getID());
+#endif
 }
 
+
+
 // This test checks if it is possible to override global values
 // on a per subnet basis.
 TEST_F(Dhcp6ParserTest, subnetLocal) {
@@ -783,8 +1089,7 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     // verify that it was interpreted correctly by checking the last address
     // value.
     isc::asiolink::IOAddress prefixAddress("2001:db8:1::");
-    EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64).toText(),
-              p6->getLastAddress().toText());
+    EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
 }
 
 // Goal of this test is verify that a list of PD pools can be configured.
@@ -917,8 +1222,7 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     // verify that it was interpreted correctly by checking the last address
     // value.
     isc::asiolink::IOAddress prefixAddress("2001:db8:1::");
-    EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64).toText(),
-              p6->getLastAddress().toText());
+    EXPECT_EQ(lastAddrInPrefix(prefixAddress, 64), p6->getLastAddress());
 }
 
 
@@ -1519,7 +1823,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
         "    \"name\": \"subscriber-id\","
         "    \"space\": \"dhcp6\","
         "    \"code\": 38,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1600,7 +1904,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
         "    \"name\": \"subscriber-id\","
         "    \"space\": \"dhcp6\","
         "    \"code\": 38,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"
@@ -1892,6 +2196,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.
@@ -1949,14 +2336,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) {
@@ -2063,7 +2442,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
         "    \"name\": \"option-one\","
         "    \"space\": \"vendor-4491\","
         "    \"code\": 100,"
-        "    \"data\": \"AB CDEF0105\","
+        "    \"data\": \"ABCDEF0105\","
         "    \"csv-format\": False"
         " },"
         " {"

+ 78 - 13
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
@@ -642,12 +642,12 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
     checkClientId(reply3, clientid3);
 
     // Finally check that the addresses offered are different
-    EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
-    EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
-    EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
-    cout << "Offered address to client1=" << addr1->getAddress().toText() << endl;
-    cout << "Offered address to client2=" << addr2->getAddress().toText() << endl;
-    cout << "Offered address to client3=" << addr3->getAddress().toText() << endl;
+    EXPECT_NE(addr1->getAddress(), addr2->getAddress());
+    EXPECT_NE(addr2->getAddress(), addr3->getAddress());
+    EXPECT_NE(addr3->getAddress(), addr1->getAddress());
+    cout << "Offered address to client1=" << addr1->getAddress() << endl;
+    cout << "Offered address to client2=" << addr2->getAddress() << endl;
+    cout << "Offered address to client3=" << addr3->getAddress() << endl;
 }
 
 // This test verifies that incoming REQUEST can be handled properly, that a
@@ -852,12 +852,12 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     checkClientId(reply3, clientid3);
 
     // Finally check that the addresses offered are different
-    EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
-    EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
-    EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
-    cout << "Assigned address to client1=" << addr1->getAddress().toText() << endl;
-    cout << "Assigned address to client2=" << addr2->getAddress().toText() << endl;
-    cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
+    EXPECT_NE(addr1->getAddress(), addr2->getAddress());
+    EXPECT_NE(addr2->getAddress(), addr3->getAddress());
+    EXPECT_NE(addr3->getAddress(), addr1->getAddress());
+    cout << "Assigned address to client1=" << addr1->getAddress() << endl;
+    cout << "Assigned address to client2=" << addr2->getAddress() << endl;
+    cout << "Assigned address to client3=" << addr3->getAddress() << endl;
 }
 
 // This test verifies that incoming (positive) RENEW can be handled properly, that a
@@ -1073,6 +1073,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.
@@ -1673,6 +1709,35 @@ TEST_F(Dhcpv6SrvTest, unpackOptions) {
     EXPECT_EQ(0x0, option_bar->getValue());
 }
 
+// Checks if client packets are classified properly
+TEST_F(Dhcpv6SrvTest, clientClassification) {
+
+    NakedDhcpv6Srv srv(0);
+
+    // Let's create a relayed SOLICIT. This particular relayed SOLICIT has
+    // vendor-class set to docsis3.0
+    Pkt6Ptr sol1;
+    ASSERT_NO_THROW(sol1 = captureDocsisRelayedSolicit());
+    ASSERT_NO_THROW(sol1->unpack());
+
+    srv.classifyPacket(sol1);
+
+    // It should belong to docsis3.0 class. It should not belong to eRouter1.0
+    EXPECT_TRUE(sol1->inClass("docsis3.0"));
+    EXPECT_FALSE(sol1->inClass("eRouter1.0"));
+
+    // Let's get a relayed SOLICIT. This particular relayed SOLICIT has
+    // vendor-class set to eRouter1.0
+    Pkt6Ptr sol2;
+    ASSERT_NO_THROW(sol2 = captureeRouterRelayedSolicit());
+    ASSERT_NO_THROW(sol2->unpack());
+
+    srv.classifyPacket(sol2);
+
+    EXPECT_TRUE(sol2->inClass("eRouter1.0"));
+    EXPECT_FALSE(sol2->inClass("docsis3.0"));
+}
+
 
 /// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
 /// to call processX() methods.

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

@@ -80,12 +80,12 @@ Dhcpv6SrvTest::checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
     Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
                                                             addr->getAddress());
     if (!lease) {
-        std::cout << "Lease for " << addr->getAddress().toText()
+        std::cout << "Lease for " << addr->getAddress()
                   << " not found in the database backend.";
         return (Lease6Ptr());
     }
 
-    EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+    EXPECT_EQ(addr->getAddress(), lease->addr_);
     EXPECT_TRUE(*lease->duid_ == *duid);
     EXPECT_EQ(ia->getIAID(), lease->iaid_);
     EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
@@ -101,12 +101,12 @@ Dhcpv6SrvTest::checkPdLease(const DuidPtr& duid, const OptionPtr& ia_pd,
     Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD,
                                                             prefix->getAddress());
     if (!lease) {
-        std::cout << "PD lease for " << prefix->getAddress().toText()
+        std::cout << "PD lease for " << prefix->getAddress()
                   << " not found in the database backend.";
         return (Lease6Ptr());
     }
 
-    EXPECT_EQ(prefix->getAddress().toText(), lease->addr_.toText());
+    EXPECT_EQ(prefix->getAddress(), lease->addr_);
     EXPECT_TRUE(*lease->duid_ == *duid);
     EXPECT_EQ(ia->getIAID(), lease->iaid_);
     EXPECT_EQ(subnet_->getID(), lease->subnet_id_);

+ 4 - 1
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
@@ -111,7 +111,9 @@ public:
     using Dhcpv6Srv::createRemovalNameChangeRequest;
     using Dhcpv6Srv::createStatusCode;
     using Dhcpv6Srv::selectSubnet;
+    using Dhcpv6Srv::testServerID;
     using Dhcpv6Srv::sanityCheck;
+    using Dhcpv6Srv::classifyPacket;
     using Dhcpv6Srv::loadServerID;
     using Dhcpv6Srv::writeServerID;
     using Dhcpv6Srv::unpackOptions;
@@ -477,6 +479,7 @@ public:
     Pkt6Ptr captureSimpleSolicit();
     Pkt6Ptr captureRelayedSolicit();
     Pkt6Ptr captureDocsisRelayedSolicit();
+    Pkt6Ptr captureeRouterRelayedSolicit();
 
     /// @brief Auxiliary method that sets Pkt6 fields
     ///

+ 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

+ 145 - 5
src/bin/dhcp6/tests/wireshark.cc

@@ -17,9 +17,9 @@
 #include <string>
 
 /// @file   wireshark.cc
-/// 
+///
 /// @brief  contains packet captures imported from Wireshark
-/// 
+///
 /// These are actual packets captured over wire. They are used in various
 /// tests.
 ///
@@ -33,6 +33,10 @@
 /// 6. Coding guidelines line restrictions apply, so wrap your code as necessary
 /// 7. Make sure you decribe the capture appropriately
 /// 8. Follow whatever rest of the methods are doing (set ports, ifaces etc.)
+/// 9. To easily copy packet description, click File... -> Extract packet
+///    dissections -> as plain text file...
+///    (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 std;
 
@@ -81,7 +85,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
     //     - ORO (7)
 
     // string exported from Wireshark
-    string hex_string = 
+    string hex_string =
         "0c0500000000000000000000000000000000fc00000000000000000000000000000900"
         "12000231350009002c010517100001000e0001000151b5e46208002758f1e80003000c"
         "000000010000000000000000000600020007";
@@ -102,7 +106,7 @@ Pkt6Ptr Dhcpv6SrvTest::captureRelayedSolicit() {
 Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
 
     // This is an actual DOCSIS packet
-    // RELAY-FORW (12) 
+    // RELAY-FORW (12)
     //  - Relay Message
     //    - SOLICIT (1)
     //      - client-id
@@ -132,7 +136,7 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
     //    - Suboption 1026: Cable Modem MAC addr = 10:0d:7f:00:07:88
 
     // string exported from Wireshark
-    string hex_string = 
+    string hex_string =
         "0c002a0288fe00fe00015a8d09fffe7af955fe80000000000000120d7ffffe00078800"
         "090189010d397f0001000a00030001100d7f000788000300287f000788000000000000"
         "000000050018000000000000000000000000000000000000000000000000000e000000"
@@ -159,5 +163,141 @@ Pkt6Ptr isc::test::Dhcpv6SrvTest::captureDocsisRelayedSolicit() {
     return (pkt);
 }
 
+/// returns a buffer with relayed SOLICIT (from DOCSIS3.0 eRouter)
+Pkt6Ptr isc::test::Dhcpv6SrvTest::captureeRouterRelayedSolicit() {
+
+/* Packet description exported from wireshark:
+DHCPv6
+    Message type: Relay-forw (12)
+    Hopcount: 0
+    Link address: 2001:558:ffa8::1 (2001:558:ffa8::1)
+    Peer address: fe80::22e5:2aff:feb8:1515 (fe80::22e5:2aff:feb8:1515)
+    Relay Message
+        Option: Relay Message (9)
+        Length: 241
+        Value: 01a90044000e000000140000000600080011001700180019...
+        DHCPv6
+            Message type: Solicit (1)
+            Transaction ID: 0xa90044
+            Rapid Commit
+                Option: Rapid Commit (14)
+                Length: 0
+            Reconfigure Accept
+                Option: Reconfigure Accept (20)
+                Length: 0
+            Option Request
+                Option: Option Request (6)
+                Length: 8
+                Value: 0011001700180019
+                Requested Option code: Vendor-specific Information (17)
+                Requested Option code: DNS recursive name server (23)
+                Requested Option code: Domain Search List (24)
+                Requested Option code: Identity Association for Prefix Delegation (25)
+            Vendor Class
+                Option: Vendor Class (16)
+                Length: 16
+                Value: 0000118b000a65526f75746572312e30
+                Enterprise ID: Cable Television Laboratories, Inc. (4491)
+                vendor-class-data: eRouter1.0
+            Vendor-specific Information
+                Option: Vendor-specific Information (17)
+                Length: 112
+                Value: 0000118b0002000745524f555445520003000b45434d3a45...
+                Enterprise ID: Cable Television Laboratories, Inc. (4491)
+                Suboption: Device Type =  (2)"EROUTER"
+                Suboption: Embedded Components =  (3)"ECM:EROUTER"
+                Suboption: Serial Number =  (4)"2BR229U40044C"
+                Suboption: Hardware Version =  (5)"1.04"
+                Suboption: Software Version =  (6)"V1.33.03"
+                Suboption: Boot ROM Version =  (7)"2.3.0R2"
+                Suboption: Organization Unique Identifier =  (8)"00095B"
+                Suboption: Model Number =  (9)"CG3000DCR"
+                Suboption: Vendor Name =  (10)"Netgear"
+            Client Identifier
+                Option: Client Identifier (1)
+                Length: 10
+                Value: 0003000120e52ab81515
+                DUID: 0003000120e52ab81515
+                DUID Type: link-layer address (3)
+                Hardware type: Ethernet (1)
+                Link-layer address: 20:e5:2a:b8:15:15
+            Identity Association for Prefix Delegation
+                Option: Identity Association for Prefix Delegation (25)
+                Length: 41
+                Value: 2ab815150000000000000000001a00190000000000000000...
+                IAID: 2ab81515
+                T1: 0
+                T2: 0
+                IA Prefix
+                    Option: IA Prefix (26)
+                    Length: 25
+                    Value: 000000000000000038000000000000000000000000000000...
+                    Preferred lifetime: 0
+                    Valid lifetime: 0
+                    Prefix length: 56
+                    Prefix address: :: (::)
+            Identity Association for Non-temporary Address
+                Option: Identity Association for Non-temporary Address (3)
+                Length: 12
+                Value: 2ab815150000000000000000
+                IAID: 2ab81515
+                T1: 0
+                T2: 0
+            Elapsed time
+                Option: Elapsed time (8)
+                Length: 2
+                Value: 0000
+                Elapsed time: 0 ms
+    Vendor-specific Information
+        Option: Vendor-specific Information (17)
+        Length: 22
+        Value: 0000118b0402000620e52ab815140401000401020300
+        Enterprise ID: Cable Television Laboratories, Inc. (4491)
+        Suboption: CM MAC Address Option =  (1026)20:e5:2a:b8:15:14
+        Suboption: CMTS Capabilities Option :  (1025)
+    Interface-Id
+        Option: Interface-Id (18)
+        Length: 4
+        Value: 00000022
+        Interface-ID:
+    Remote Identifier
+        Option: Remote Identifier (37)
+        Length: 14
+        Value: 0000101300015c228d4110000122
+        Enterprise ID: Arris Interactive LLC (4115)
+        Remote-ID: 00015c228d4110000122
+    DHCP option 53
+        Option: Unknown (53)
+        Length: 10
+        Value: 0003000100015c228d3d
+        DUID: 0003000100015c228d3d
+        DUID Type: link-layer address (3)
+        Hardware type: Ethernet (1)
+        Link-layer address: 00:01:5c:22:8d:3d */
+
+    // string exported from Wireshark
+    string hex_string =
+        "0c0020010558ffa800000000000000000001fe8000000000000022e52afffeb8151500"
+        "0900f101a90044000e000000140000000600080011001700180019001000100000118b"
+        "000a65526f75746572312e30001100700000118b0002000745524f555445520003000b"
+        "45434d3a45524f555445520004000d3242523232395534303034344300050004312e30"
+        "340006000856312e33332e303300070007322e332e3052320008000630303039354200"
+        "090009434733303030444352000a00074e6574676561720001000a0003000120e52ab8"
+        "1515001900292ab815150000000000000000001a001900000000000000003800000000"
+        "0000000000000000000000000003000c2ab81515000000000000000000080002000000"
+        "1100160000118b0402000620e52ab81514040100040102030000120004000000220025"
+        "000e0000101300015c228d41100001220035000a0003000100015c228d3d";
+
+    std::vector<uint8_t> bin;
+
+    // Decode the hex string and store it in bin (which happens
+    // to be OptionBuffer format)
+    isc::util::encode::decodeHex(hex_string, bin);
+
+    Pkt6Ptr pkt(new Pkt6(&bin[0], bin.size()));
+    captureSetDefaultFields(pkt);
+    return (pkt);
+}
+
 }; // end of isc::test namespace
 }; // end of isc namespace

+ 1 - 0
src/bin/resolver/.gitignore

@@ -6,3 +6,4 @@
 /spec_config.h
 /spec_config.h.pre
 /b10-resolver.8
+/s-messages

+ 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

+ 2 - 0
src/hooks/dhcp/user_chk/tests/.gitignore

@@ -0,0 +1,2 @@
+/libdhcp_user_chk_unittests
+/test_data_files_config.h

+ 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 - 0
src/lib/asiodns/.gitignore

@@ -1,2 +1,3 @@
 /asiodns_messages.cc
 /asiodns_messages.h
+/s-messages

+ 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");
     }
 };
 

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

@@ -100,19 +100,36 @@ 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.");
     }
 }
 
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address) {
+    os << address.toText();
+    return (os);
+}
+
 } // namespace asiolink
 } // namespace isc

+ 28 - 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));
     }
@@ -232,6 +234,22 @@ private:
     asio::ip::address asio_address_;
 };
 
+/// \brief Insert the IOAddress as a string into stream.
+///
+/// This method converts the \c address into a string and inserts it
+/// into the output stream \c os.
+///
+/// This function overloads the global operator<< to behave as described
+/// in ostream::operator<< but applied to \c IOAddress objects.
+///
+/// \param os A \c std::ostream object on which the insertion operation is
+/// performed.
+/// \param address The \c IOAddress object output by the operation.
+/// \return A reference to the same \c std::ostream object referenced by
+/// parameter \c os after the insertion operation.
+std::ostream&
+operator<<(std::ostream& os, const IOAddress& address);
+
 } // namespace asiolink
 } // namespace isc
 #endif // IO_ADDRESS_H

+ 2 - 2
src/lib/asiolink/io_endpoint.cc

@@ -64,12 +64,12 @@ IOEndpoint::operator!=(const IOEndpoint& other) const {
 ostream&
 operator<<(ostream& os, const IOEndpoint& endpoint) {
     if (endpoint.getFamily() == AF_INET6) {
-        os << "[" << endpoint.getAddress().toText() << "]";
+        os << "[" << endpoint.getAddress() << "]";
     } else {
         // In practice this should be AF_INET, but it's not guaranteed by
         // the interface.  We'll use the result of textual address
         // representation opaquely.
-        os << endpoint.getAddress().toText();
+        os << endpoint.getAddress();
     }
     os << ":" << boost::lexical_cast<string>(endpoint.getPort());
     return (os);

+ 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

+ 46 - 1
src/lib/asiolink/tests/io_address_unittest.cc

@@ -21,6 +21,7 @@
 #include <algorithm>
 #include <cstring>
 #include <vector>
+#include <sstream>
 
 using namespace isc::asiolink;
 
@@ -83,7 +84,7 @@ TEST(IOAddressTest, fromBytes) {
     EXPECT_NO_THROW({
         addr = IOAddress::fromBytes(AF_INET, v4);
     });
-    EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
+    EXPECT_EQ(addr, IOAddress("192.0.2.3"));
 }
 
 TEST(IOAddressTest, toBytesV4) {
@@ -172,3 +173,47 @@ TEST(IOAddressTest, lessThanEqual) {
 
     EXPECT_TRUE(addr6 <= addr7);
 }
+
+// test operator<<.  We simply confirm it appends the result of toText().
+TEST(IOAddressTest, LeftShiftOperator) {
+    const IOAddress addr("192.0.2.5");
+
+    std::ostringstream oss;
+    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;

+ 1 - 0
src/lib/cache/.gitignore

@@ -1,2 +1,3 @@
 /cache_messages.cc
 /cache_messages.h
+/s-messages

+ 1 - 0
src/lib/cc/.gitignore

@@ -3,3 +3,4 @@
 /proto_defs.h
 /session_config.h
 /session_config.h.pre
+/s-messages

+ 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")));

+ 1 - 0
src/lib/config/.gitignore

@@ -1,2 +1,3 @@
 /config_messages.cc
 /config_messages.h
+/s-messages

+ 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
                   }
                 ]
               }

+ 2 - 0
src/lib/datasrc/.gitignore

@@ -5,3 +5,5 @@
 /static.zone
 /sqlite3_datasrc_messages.cc
 /sqlite3_datasrc_messages.h
+/s-messages1
+/s-messages2

+ 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,

+ 6 - 6
src/lib/datasrc/database.cc

@@ -103,7 +103,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 +344,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 +646,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 +760,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 +908,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
@@ -1110,7 +1110,7 @@ DatabaseClient::Finder::findNSEC3(const Name& name, bool recursive) {
 
     // 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) {
+    for (unsigned labels = qlabels; labels >= olabels; --labels) {
         const string hash(calculator->calculate(labels == qlabels ? name :
                                                 name.split(qlabels - labels,
                                                            labels)));

+ 1 - 0
src/lib/datasrc/memory/.gitignore

@@ -1,2 +1,3 @@
 /memory_messages.cc
 /memory_messages.h
+/s-messages

+ 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

+ 8 - 1
src/lib/datasrc/memory/treenode_rrset.h

@@ -155,7 +155,7 @@ public:
         node_(node), rdataset_(rdataset),
         rrsig_count_(rdataset_->getSigRdataCount()), rrclass_(rrclass),
         dnssec_ok_(dnssec_ok), name_(NULL), realname_(new dns::Name(realname)),
-	ttl_data_(rdataset->getTTLData()), ttl_(NULL)
+        ttl_data_(rdataset->getTTLData()), ttl_(NULL)
     {}
 
     virtual ~TreeNodeRRset() {
@@ -168,6 +168,8 @@ public:
         return (rdataset_->getRdataCount());
     }
 
+    virtual uint16_t getLength() const;
+
     virtual const dns::Name& getName() const;
     virtual const dns::RRClass& getClass() const {
         return (rrclass_);
@@ -204,6 +206,11 @@ public:
     /// It throws \c isc::Unexpected unconditionally.
     virtual void addRdata(const dns::rdata::Rdata& rdata);
 
+    /// \brief Specialized version of \c addRdata() for \c TreeNodeRRset.
+    ///
+    /// It throws \c isc::Unexpected unconditionally.
+    virtual void addRdata(const std::string& rdata_str);
+
     virtual dns::RdataIteratorPtr getRdataIterator() const;
 
     /// \brief Specialized version of \c getRRsig() for \c TreeNodeRRset.

+ 1 - 1
src/lib/datasrc/memory/zone_data_loader.cc

@@ -69,7 +69,7 @@ typedef boost::function<void(isc::dns::ConstRRsetPtr)> LoadCallback;
 class ZoneDataLoader : boost::noncopyable {
 public:
     ZoneDataLoader(util::MemorySegment& mem_sgmt,
-                   const isc::dns::RRClass rrclass,
+                   const isc::dns::RRClass& rrclass,
                    const isc::dns::Name& zone_name, ZoneData& zone_data) :
         updater_(mem_sgmt, rrclass, zone_name, zone_data)
     {}

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


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