Browse Source

[3251] Merge branch 'master' into trac3251

Marcin Siodelski 11 years ago
parent
commit
4063c0a8aa
49 changed files with 5723 additions and 734 deletions
  1. 1 1
      .gitignore
  2. 36 2
      ChangeLog
  3. 221 219
      configure.ac
  4. 39 29
      doc/guide/bind10-guide.xml
  5. 1 0
      src/bin/d2/Makefile.am
  6. 91 4
      src/bin/d2/d2_messages.mes
  7. 44 26
      src/bin/d2/nc_add.cc
  8. 13 8
      src/bin/d2/nc_add.h
  9. 695 0
      src/bin/d2/nc_remove.cc
  10. 435 0
      src/bin/d2/nc_remove.h
  11. 14 10
      src/bin/d2/nc_trans.cc
  12. 4 4
      src/bin/d2/nc_trans.h
  13. 2 0
      src/bin/d2/tests/Makefile.am
  14. 33 11
      src/bin/d2/tests/nc_add_unittests.cc
  15. 1872 0
      src/bin/d2/tests/nc_remove_unittests.cc
  16. 269 5
      src/bin/d2/tests/nc_test_utils.cc
  17. 97 4
      src/bin/d2/tests/nc_test_utils.h
  18. 7 26
      src/bin/d2/tests/nc_trans_unittests.cc
  19. 19 2
      src/bin/dhcp4/config_parser.cc
  20. 6 0
      src/bin/dhcp4/dhcp4.spec
  21. 3 1
      src/bin/dhcp4/dhcp4_srv.cc
  22. 41 0
      src/bin/dhcp4/tests/config_parser_unittest.cc
  23. 50 0
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  24. 19 4
      src/bin/dhcp4/tests/dhcp4_test_utils.cc
  25. 6 2
      src/bin/dhcp4/tests/dhcp4_test_utils.h
  26. 5 2
      src/lib/dhcp/iface_mgr.h
  27. 95 3
      src/lib/dhcp/iface_mgr_bsd.cc
  28. 1 1
      src/lib/dhcp/iface_mgr_linux.cc
  29. 100 4
      src/lib/dhcp/iface_mgr_sun.cc
  30. 2 2
      src/lib/dhcp/libdhcp++.cc
  31. 3 2
      src/lib/dhcp/libdhcp++.dox
  32. 184 2
      src/lib/dhcp/tests/iface_mgr_unittest.cc
  33. 4 1
      src/lib/dhcp/tests/libdhcp++_unittest.cc
  34. 1 1
      src/lib/dhcpsrv/cfgmgr.cc
  35. 19 0
      src/lib/dhcpsrv/cfgmgr.h
  36. 17 0
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  37. 2 1
      tests/tools/perfdhcp/Makefile.am
  38. 40 15
      tests/tools/perfdhcp/command_options.cc
  39. 8 2
      tests/tools/perfdhcp/command_options.h
  40. 158 0
      tests/tools/perfdhcp/rate_control.cc
  41. 180 0
      tests/tools/perfdhcp/rate_control.h
  42. 29 5
      tests/tools/perfdhcp/stats_mgr.h
  43. 148 140
      tests/tools/perfdhcp/test_control.cc
  44. 48 50
      tests/tools/perfdhcp/test_control.h
  45. 3 0
      tests/tools/perfdhcp/tests/Makefile.am
  46. 66 2
      tests/tools/perfdhcp/tests/command_options_unittest.cc
  47. 207 0
      tests/tools/perfdhcp/tests/rate_control_unittest.cc
  48. 20 0
      tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
  49. 365 143
      tests/tools/perfdhcp/tests/test_control_unittest.cc

+ 1 - 1
.gitignore

@@ -13,7 +13,7 @@ TAGS
 
 *.log
 *.trs
-
+config.h.in~
 /aclocal.m4
 /autom4te.cache/
 /config.guess

+ 36 - 2
ChangeLog

@@ -1,3 +1,37 @@
+720.	[func]		tmark
+	Added the initial implementation of the class, NameAddTransaction,
+	to b10-dhcp-ddns.  This class provides a state machine which implements
+	the logic required to remove forward and reverse DNS entries as described
+	in RFC 4703, section 5.5. This includes the ability to construct the
+	necessary DNS requests.
+	(Trac# 3088, git ca58ac00fce4cb5f46e534d7ffadb2db4e4ffaf3)
+
+719.	[func]		tomek
+	b10-dhcp4: Support for sending back client-id (RFC6842) has been
+	added now. Also a configuration parameter (echo-client-id) has
+	been added, so it is possible to enable backward compatibility
+	("echo-client-id false").
+	(Trac #3210, git 88a4858db206dfcd53a227562198f308f7779a72)
+
+718.	[func]		dclink, tomek
+	libdhcp++: Interface detection implemented for FreeBSD, NetBSD,
+	OpenBSD, Mac OS X and Solaris 11. Thanks to David Carlier for
+	contributing a patch.
+	(Trac #2246, git d8045b5e1580a1d0b89a232fd61c10d25a95e769)
+
+717.	[bug]		marcin
+	Fixed the bug which incorrectly treated DHCPv4 option codes 224-254 as
+	standard options, barring them from being used as custom options.
+	(Trac #2772, git c6158690c389d75686545459618ae0bf16f2cdb8)
+
+716.	[func]		marcin
+	perfdhcp: added support for sending DHCPv6 Relese messages at the specified
+	rate and measure performance. The orphan messages counters are not
+	displayed for individual exchanges anymore. The following ticket: #3261
+	has been submitted to implement global orphan counting for all exchange
+	types.
+	(Trac #3181, git 684524bc130080e4fa31b65edfd14d58eec37e50)
+
 715.	[bug]		marcin
 	libdhcp++: Used the CMSG_SPACE instead of CMSG_LEN macro to calculate
 	msg_controllen field of the DHCPv6 message. Use of CMSG_LEN causes
@@ -13,7 +47,7 @@
 	b10-dhcp-ddns.  The class now generates all DNS update request variations
 	needed to fulfill it's state machine in compliance with RFC 4703, sections
 	5.3 and 5.4.
-	(Trac# 3207, git dceca9554cb9410dd8d12371b68198b797cb6cfb)
+	(Trac# 3241, git dceca9554cb9410dd8d12371b68198b797cb6cfb)
 
 712.	[func]		marcin,dclink
 	b10-dhcp4: If server fails to open a socket on one interface it
@@ -30,7 +64,7 @@
 	reverse DNS entries for a given FQDN.  It does not yet construct
 	the actual DNS update requests, this will be added under Trac#
 	3241.
-	(Trac# 3207, git 8f99da735a9f39d514c40d0a295f751dc8edfbcd)
+	(Trac# 3087, git 8f99da735a9f39d514c40d0a295f751dc8edfbcd)
 
 710.	[build]		jinmei
 	Fixed various build time issues for MacOS X 10.9.  Those include

+ 221 - 219
configure.ac

@@ -1301,302 +1301,303 @@ AC_TRY_LINK(
 AM_CONDITIONAL(HAVE_OPTRESET, test "x$var_optreset_exists" != "xno")
 AM_COND_IF([HAVE_OPTRESET], [AC_DEFINE([HAVE_OPTRESET], [1], [Check for optreset?])])
 
-AC_CONFIG_FILES([Makefile
-                 doc/Makefile
-                 doc/guide/Makefile
-                 doc/design/Makefile
+AC_CONFIG_FILES([compatcheck/Makefile
+                 dns++.pc
                  doc/design/datasrc/Makefile
-                 ext/Makefile
-                 ext/asio/Makefile
+                 doc/design/Makefile
+                 doc/guide/Makefile
+                 doc/Makefile
+                 doc/version.ent
                  ext/asio/asio/Makefile
-                 compatcheck/Makefile
-                 src/Makefile
-                 src/bin/Makefile
+                 ext/asio/Makefile
+                 ext/Makefile
+                 m4macros/Makefile
+                 Makefile
+                 src/bin/auth/auth.spec.pre
+                 src/bin/auth/benchmarks/Makefile
+                 src/bin/auth/gen-statisticsitems.py.pre
+                 src/bin/auth/Makefile
+                 src/bin/auth/spec_config.h.pre
+                 src/bin/auth/tests/Makefile
+                 src/bin/auth/tests/testdata/example-base.zone
+                 src/bin/auth/tests/testdata/example-nsec3.zone
+                 src/bin/auth/tests/testdata/example.zone
+                 src/bin/auth/tests/testdata/Makefile
                  src/bin/bind10/bind10
+                 src/bin/bind10/init.py
                  src/bin/bind10/Makefile
+                 src/bin/bind10/run_bind10.sh
+                 src/bin/bind10/tests/init_test.py
                  src/bin/bind10/tests/Makefile
-                 src/bin/cmdctl/Makefile
-                 src/bin/cmdctl/tests/Makefile
+                 src/bin/bindctl/bindctl_main.py
                  src/bin/bindctl/Makefile
+                 src/bin/bindctl/run_bindctl.sh
+                 src/bin/bindctl/tests/bindctl_test
                  src/bin/bindctl/tests/Makefile
-                 src/bin/cfgmgr/Makefile
+                 src/bin/cfgmgr/b10-cfgmgr.py
                  src/bin/cfgmgr/local_plugins/Makefile
+                 src/bin/cfgmgr/Makefile
+                 src/bin/cfgmgr/plugins/datasrc.spec.pre
                  src/bin/cfgmgr/plugins/Makefile
                  src/bin/cfgmgr/plugins/tests/Makefile
+                 src/bin/cfgmgr/tests/b10-cfgmgr_test.py
                  src/bin/cfgmgr/tests/Makefile
+                 src/bin/cmdctl/cmdctl.py
+                 src/bin/cmdctl/cmdctl.spec.pre
+                 src/bin/cmdctl/Makefile
+                 src/bin/cmdctl/run_b10-cmdctl.sh
+                 src/bin/cmdctl/tests/cmdctl_test
+                 src/bin/cmdctl/tests/Makefile
+                 src/bin/d2/Makefile
+                 src/bin/d2/spec_config.h.pre
+                 src/bin/d2/tests/Makefile
+                 src/bin/d2/tests/test_data_files_config.h
+                 src/bin/dbutil/dbutil.py
                  src/bin/dbutil/Makefile
+                 src/bin/dbutil/run_dbutil.sh
+                 src/bin/dbutil/tests/dbutil_test.sh
                  src/bin/dbutil/tests/Makefile
                  src/bin/dbutil/tests/testdata/Makefile
+                 src/bin/ddns/ddns.py
+                 src/bin/ddns/Makefile
+                 src/bin/ddns/tests/Makefile
+                 src/bin/dhcp4/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_libraries.h
+                 src/bin/dhcp6/Makefile
+                 src/bin/dhcp6/spec_config.h.pre
+                 src/bin/dhcp6/tests/Makefile
+                 src/bin/dhcp6/tests/marker_file.h
+                 src/bin/dhcp6/tests/test_data_files_config.h
+                 src/bin/dhcp6/tests/test_libraries.h
+                 src/bin/loadzone/loadzone.py
                  src/bin/loadzone/Makefile
-                 src/bin/loadzone/tests/Makefile
+                 src/bin/loadzone/run_loadzone.sh
+                 src/bin/loadzone/tests/correct/correct_test.sh
                  src/bin/loadzone/tests/correct/Makefile
+                 src/bin/loadzone/tests/Makefile
+                 src/bin/Makefile
                  src/bin/memmgr/Makefile
+                 src/bin/memmgr/memmgr.py
+                 src/bin/memmgr/memmgr.spec.pre
                  src/bin/memmgr/tests/Makefile
                  src/bin/msgq/Makefile
+                 src/bin/msgq/msgq.py
+                 src/bin/msgq/run_msgq.sh
                  src/bin/msgq/tests/Makefile
-                 src/bin/auth/Makefile
-                 src/bin/auth/tests/Makefile
-                 src/bin/auth/tests/testdata/Makefile
-                 src/bin/auth/benchmarks/Makefile
-                 src/bin/ddns/Makefile
-                 src/bin/ddns/tests/Makefile
-                 src/bin/dhcp6/Makefile
-                 src/bin/dhcp6/tests/Makefile
-                 src/bin/dhcp4/Makefile
-                 src/bin/dhcp4/tests/Makefile
-                 src/bin/d2/Makefile
-                 src/bin/d2/tests/Makefile
+                 src/bin/resolver/bench/Makefile
                  src/bin/resolver/Makefile
+                 src/bin/resolver/resolver.spec.pre
+                 src/bin/resolver/spec_config.h.pre
                  src/bin/resolver/tests/Makefile
-                 src/bin/resolver/bench/Makefile
-                 src/bin/sysinfo/Makefile
                  src/bin/sockcreator/Makefile
                  src/bin/sockcreator/tests/Makefile
+                 src/bin/stats/Makefile
+                 src/bin/stats/stats_httpd.py
+                 src/bin/stats/stats.py
+                 src/bin/stats/tests/Makefile
+                 src/bin/stats/tests/testdata/Makefile
+                 src/bin/sysinfo/Makefile
+                 src/bin/sysinfo/run_sysinfo.sh
+                 src/bin/sysinfo/sysinfo.py
+                 src/bin/tests/Makefile
+                 src/bin/tests/process_rename_test.py
+                 src/bin/usermgr/b10-cmdctl-usermgr.py
+                 src/bin/usermgr/Makefile
+                 src/bin/usermgr/run_b10-cmdctl-usermgr.sh
+                 src/bin/usermgr/tests/Makefile
                  src/bin/xfrin/Makefile
+                 src/bin/xfrin/run_b10-xfrin.sh
                  src/bin/xfrin/tests/Makefile
                  src/bin/xfrin/tests/testdata/Makefile
+                 src/bin/xfrin/tests/xfrin_test
+                 src/bin/xfrin/xfrin.py
                  src/bin/xfrout/Makefile
+                 src/bin/xfrout/run_b10-xfrout.sh
                  src/bin/xfrout/tests/Makefile
+                 src/bin/xfrout/tests/xfrout_test
+                 src/bin/xfrout/tests/xfrout_test.py
+                 src/bin/xfrout/xfrout.py
+                 src/bin/xfrout/xfrout.spec.pre
                  src/bin/zonemgr/Makefile
+                 src/bin/zonemgr/run_b10-zonemgr.sh
                  src/bin/zonemgr/tests/Makefile
-                 src/bin/stats/Makefile
-                 src/bin/stats/tests/Makefile
-                 src/bin/stats/tests/testdata/Makefile
-                 src/bin/usermgr/Makefile
-                 src/bin/usermgr/tests/Makefile
-                 src/bin/tests/Makefile
-                 src/hooks/Makefile
+                 src/bin/zonemgr/tests/zonemgr_test
+                 src/bin/zonemgr/zonemgr.py
+                 src/bin/zonemgr/zonemgr.spec.pre
                  src/hooks/dhcp/Makefile
                  src/hooks/dhcp/user_chk/Makefile
                  src/hooks/dhcp/user_chk/tests/Makefile
-                 src/lib/Makefile
-                 src/lib/asiolink/Makefile
-                 src/lib/asiolink/tests/Makefile
+                 src/hooks/dhcp/user_chk/tests/test_data_files_config.h
+                 src/hooks/Makefile
+                 src/lib/acl/Makefile
+                 src/lib/acl/tests/Makefile
                  src/lib/asiodns/Makefile
                  src/lib/asiodns/tests/Makefile
-                 src/lib/bench/Makefile
+                 src/lib/asiolink/Makefile
+                 src/lib/asiolink/tests/Makefile
                  src/lib/bench/example/Makefile
+                 src/lib/bench/Makefile
                  src/lib/bench/tests/Makefile
+                 src/lib/cache/Makefile
+                 src/lib/cache/tests/Makefile
                  src/lib/cc/Makefile
+                 src/lib/cc/session_config.h.pre
                  src/lib/cc/tests/Makefile
-                 src/lib/python/Makefile
-                 src/lib/python/isc/Makefile
+                 src/lib/cc/tests/session_unittests_config.h
+                 src/lib/config/Makefile
+                 src/lib/config/tests/data_def_unittests_config.h
+                 src/lib/config/tests/Makefile
+                 src/lib/config/tests/testdata/Makefile
+                 src/lib/cryptolink/Makefile
+                 src/lib/cryptolink/tests/Makefile
+                 src/lib/datasrc/datasrc_config.h.pre
+                 src/lib/datasrc/Makefile
+                 src/lib/datasrc/memory/benchmarks/Makefile
+                 src/lib/datasrc/memory/Makefile
+                 src/lib/datasrc/tests/Makefile
+                 src/lib/datasrc/tests/memory/Makefile
+                 src/lib/datasrc/tests/memory/testdata/Makefile
+                 src/lib/datasrc/tests/testdata/Makefile
+                 src/lib/dhcp_ddns/Makefile
+                 src/lib/dhcp_ddns/tests/Makefile
+                 src/lib/dhcp/Makefile
+                 src/lib/dhcpsrv/Makefile
+                 src/lib/dhcpsrv/tests/Makefile
+                 src/lib/dhcpsrv/tests/test_libraries.h
+                 src/lib/dhcp/tests/Makefile
+                 src/lib/dns/benchmarks/Makefile
+                 src/lib/dns/gen-rdatacode.py
+                 src/lib/dns/Makefile
+                 src/lib/dns/python/Makefile
+                 src/lib/dns/python/tests/Makefile
+                 src/lib/dns/tests/Makefile
+                 src/lib/dns/tests/testdata/Makefile
+                 src/lib/exceptions/Makefile
+                 src/lib/exceptions/tests/Makefile
+                 src/lib/hooks/Makefile
+                 src/lib/hooks/tests/Makefile
+                 src/lib/hooks/tests/marker_file.h
+                 src/lib/hooks/tests/test_libraries.h
+                 src/lib/log/compiler/Makefile
+                 src/lib/log/interprocess/Makefile
+                 src/lib/log/interprocess/tests/Makefile
+                 src/lib/log/Makefile
+                 src/lib/log/tests/buffer_logger_test.sh
+                 src/lib/log/tests/console_test.sh
+                 src/lib/log/tests/destination_test.sh
+                 src/lib/log/tests/init_logger_test.sh
+                 src/lib/log/tests/local_file_test.sh
+                 src/lib/log/tests/logger_lock_test.sh
+                 src/lib/log/tests/Makefile
+                 src/lib/log/tests/severity_test.sh
+                 src/lib/log/tests/tempdir.h
+                 src/lib/Makefile
+                 src/lib/nsas/Makefile
+                 src/lib/nsas/tests/Makefile
+                 src/lib/python/bind10_config.py
                  src/lib/python/isc/acl/Makefile
                  src/lib/python/isc/acl/tests/Makefile
-                 src/lib/python/isc/util/Makefile
-                 src/lib/python/isc/util/tests/Makefile
-                 src/lib/python/isc/util/cio/Makefile
-                 src/lib/python/isc/util/cio/tests/Makefile
-                 src/lib/python/isc/datasrc/Makefile
-                 src/lib/python/isc/datasrc/tests/Makefile
-                 src/lib/python/isc/datasrc/tests/testdata/Makefile
-                 src/lib/python/isc/dns/Makefile
-                 src/lib/python/isc/cc/Makefile
+                 src/lib/python/isc/bind10/Makefile
+                 src/lib/python/isc/bind10/tests/Makefile
                  src/lib/python/isc/cc/cc_generated/Makefile
+                 src/lib/python/isc/cc/Makefile
+                 src/lib/python/isc/cc/tests/cc_test
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/config/Makefile
+                 src/lib/python/isc/config/tests/config_test
                  src/lib/python/isc/config/tests/Makefile
+                 src/lib/python/isc/datasrc/Makefile
+                 src/lib/python/isc/datasrc/tests/Makefile
+                 src/lib/python/isc/datasrc/tests/testdata/Makefile
+                 src/lib/python/isc/ddns/Makefile
+                 src/lib/python/isc/ddns/tests/Makefile
+                 src/lib/python/isc/dns/Makefile
                  src/lib/python/isc/log/Makefile
-                 src/lib/python/isc/log/tests/Makefile
                  src/lib/python/isc/log_messages/Makefile
+                 src/lib/python/isc/log_messages/work/__init__.py
                  src/lib/python/isc/log_messages/work/Makefile
+                 src/lib/python/isc/log/tests/log_console.py
+                 src/lib/python/isc/log/tests/Makefile
+                 src/lib/python/isc/Makefile
+                 src/lib/python/isc/memmgr/Makefile
+                 src/lib/python/isc/memmgr/tests/Makefile
+                 src/lib/python/isc/memmgr/tests/testdata/Makefile
                  src/lib/python/isc/net/Makefile
                  src/lib/python/isc/net/tests/Makefile
                  src/lib/python/isc/notify/Makefile
                  src/lib/python/isc/notify/tests/Makefile
-                 src/lib/python/isc/testutils/Makefile
-                 src/lib/python/isc/bind10/Makefile
-                 src/lib/python/isc/bind10/tests/Makefile
-                 src/lib/python/isc/ddns/Makefile
-                 src/lib/python/isc/ddns/tests/Makefile
-                 src/lib/python/isc/memmgr/Makefile
-                 src/lib/python/isc/memmgr/tests/Makefile
-                 src/lib/python/isc/memmgr/tests/testdata/Makefile
-                 src/lib/python/isc/xfrin/Makefile
-                 src/lib/python/isc/xfrin/tests/Makefile
+                 src/lib/python/isc/notify/tests/notify_out_test
                  src/lib/python/isc/server_common/Makefile
                  src/lib/python/isc/server_common/tests/Makefile
-                 src/lib/python/isc/sysinfo/Makefile
-                 src/lib/python/isc/sysinfo/tests/Makefile
                  src/lib/python/isc/statistics/Makefile
                  src/lib/python/isc/statistics/tests/Makefile
-                 src/lib/config/Makefile
-                 src/lib/config/tests/Makefile
-                 src/lib/config/tests/testdata/Makefile
-                 src/lib/cryptolink/Makefile
-                 src/lib/cryptolink/tests/Makefile
-                 src/lib/dns/Makefile
-                 src/lib/dns/tests/Makefile
-                 src/lib/dns/tests/testdata/Makefile
-                 src/lib/dns/python/Makefile
-                 src/lib/dns/python/tests/Makefile
-                 src/lib/dns/benchmarks/Makefile
-                 src/lib/dhcp/Makefile
-                 src/lib/dhcp/tests/Makefile
-                 src/lib/dhcp_ddns/Makefile
-                 src/lib/dhcp_ddns/tests/Makefile
-                 src/lib/dhcpsrv/Makefile
-                 src/lib/dhcpsrv/tests/Makefile
-                 src/lib/exceptions/Makefile
-                 src/lib/exceptions/tests/Makefile
-                 src/lib/datasrc/Makefile
-                 src/lib/datasrc/memory/Makefile
-                 src/lib/datasrc/memory/benchmarks/Makefile
-                 src/lib/datasrc/tests/Makefile
-                 src/lib/datasrc/tests/testdata/Makefile
-                 src/lib/datasrc/tests/memory/Makefile
-                 src/lib/datasrc/tests/memory/testdata/Makefile
-                 src/lib/xfr/Makefile
-                 src/lib/xfr/tests/Makefile
-                 src/lib/hooks/Makefile
-                 src/lib/hooks/tests/Makefile
-                 src/lib/log/Makefile
-                 src/lib/log/interprocess/Makefile
-                 src/lib/log/interprocess/tests/Makefile
-                 src/lib/log/compiler/Makefile
-                 src/lib/log/tests/Makefile
+                 src/lib/python/isc/sysinfo/Makefile
+                 src/lib/python/isc/sysinfo/tests/Makefile
+                 src/lib/python/isc/testutils/Makefile
+                 src/lib/python/isc/util/cio/Makefile
+                 src/lib/python/isc/util/cio/tests/Makefile
+                 src/lib/python/isc/util/Makefile
+                 src/lib/python/isc/util/tests/Makefile
+                 src/lib/python/isc/xfrin/Makefile
+                 src/lib/python/isc/xfrin/tests/Makefile
+                 src/lib/python/Makefile
                  src/lib/resolve/Makefile
                  src/lib/resolve/tests/Makefile
-                 src/lib/testutils/Makefile
-                 src/lib/testutils/testdata/Makefile
-                 src/lib/nsas/Makefile
-                 src/lib/nsas/tests/Makefile
-                 src/lib/cache/Makefile
-                 src/lib/cache/tests/Makefile
                  src/lib/server_common/Makefile
+                 src/lib/server_common/tests/data_path.h
                  src/lib/server_common/tests/Makefile
-                 src/lib/util/Makefile
+                 src/lib/statistics/Makefile
+                 src/lib/statistics/tests/Makefile
+                 src/lib/testutils/Makefile
+                 src/lib/testutils/testdata/Makefile
                  src/lib/util/io/Makefile
-                 src/lib/util/threads/Makefile
-                 src/lib/util/threads/tests/Makefile
-                 src/lib/util/unittests/Makefile
+                 src/lib/util/Makefile
+                 src/lib/util/python/doxygen2pydoc.py
+                 src/lib/util/python/gen_wiredata.py
                  src/lib/util/python/Makefile
+                 src/lib/util/python/mkpywrapper.py
                  src/lib/util/pyunittests/Makefile
                  src/lib/util/tests/Makefile
-                 src/lib/acl/Makefile
-                 src/lib/acl/tests/Makefile
-                 src/lib/statistics/Makefile
-                 src/lib/statistics/tests/Makefile
+                 src/lib/util/threads/Makefile
+                 src/lib/util/threads/tests/Makefile
+                 src/lib/util/unittests/Makefile
+                 src/lib/xfr/Makefile
+                 src/lib/xfr/tests/Makefile
+                 src/Makefile
+                 tests/lettuce/Makefile
+                 tests/lettuce/setup_intree_bind10.sh
                  tests/Makefile
-                 tests/tools/Makefile
                  tests/tools/badpacket/Makefile
                  tests/tools/badpacket/tests/Makefile
+                 tests/tools/Makefile
                  tests/tools/perfdhcp/Makefile
                  tests/tools/perfdhcp/tests/Makefile
                  tests/tools/perfdhcp/tests/testdata/Makefile
-                 tests/lettuce/Makefile
-                 m4macros/Makefile
-                 dns++.pc
-               ])
-AC_OUTPUT([doc/version.ent
-           src/bin/cfgmgr/b10-cfgmgr.py
-           src/bin/cfgmgr/tests/b10-cfgmgr_test.py
-           src/bin/cfgmgr/plugins/datasrc.spec.pre
-           src/bin/cmdctl/cmdctl.py
-           src/bin/cmdctl/run_b10-cmdctl.sh
-           src/bin/cmdctl/tests/cmdctl_test
-           src/bin/cmdctl/cmdctl.spec.pre
-           src/bin/dbutil/dbutil.py
-           src/bin/dbutil/run_dbutil.sh
-           src/bin/dbutil/tests/dbutil_test.sh
-           src/bin/ddns/ddns.py
-           src/bin/dhcp4/tests/marker_file.h
-           src/bin/dhcp4/tests/test_libraries.h
-           src/bin/dhcp6/tests/marker_file.h
-           src/bin/dhcp6/tests/test_libraries.h
-           src/bin/xfrin/tests/xfrin_test
-           src/bin/xfrin/xfrin.py
-           src/bin/xfrin/run_b10-xfrin.sh
-           src/bin/xfrout/xfrout.py
-           src/bin/xfrout/xfrout.spec.pre
-           src/bin/xfrout/tests/xfrout_test
-           src/bin/xfrout/tests/xfrout_test.py
-           src/bin/xfrout/run_b10-xfrout.sh
-           src/bin/resolver/resolver.spec.pre
-           src/bin/resolver/spec_config.h.pre
-           src/bin/zonemgr/zonemgr.py
-           src/bin/zonemgr/zonemgr.spec.pre
-           src/bin/zonemgr/tests/zonemgr_test
-           src/bin/zonemgr/run_b10-zonemgr.sh
-           src/bin/sysinfo/sysinfo.py
-           src/bin/sysinfo/run_sysinfo.sh
-           src/bin/stats/stats.py
-           src/bin/stats/stats_httpd.py
-           src/bin/bind10/init.py
-           src/bin/bind10/run_bind10.sh
-           src/bin/bind10/tests/init_test.py
-           src/bin/bindctl/run_bindctl.sh
-           src/bin/bindctl/bindctl_main.py
-           src/bin/bindctl/tests/bindctl_test
-           src/bin/loadzone/run_loadzone.sh
-           src/bin/loadzone/tests/correct/correct_test.sh
-           src/bin/loadzone/loadzone.py
-           src/bin/usermgr/run_b10-cmdctl-usermgr.sh
-           src/bin/usermgr/b10-cmdctl-usermgr.py
-           src/bin/memmgr/memmgr.py
-           src/bin/memmgr/memmgr.spec.pre
-           src/bin/msgq/msgq.py
-           src/bin/msgq/run_msgq.sh
-           src/bin/auth/auth.spec.pre
-           src/bin/auth/spec_config.h.pre
-           src/bin/auth/tests/testdata/example.zone
-           src/bin/auth/tests/testdata/example-base.zone
-           src/bin/auth/tests/testdata/example-nsec3.zone
-           src/bin/auth/gen-statisticsitems.py.pre
-           src/bin/dhcp4/spec_config.h.pre
-           src/bin/dhcp6/spec_config.h.pre
-           src/bin/dhcp6/tests/test_data_files_config.h
-           src/bin/d2/spec_config.h.pre
-           src/bin/d2/tests/test_data_files_config.h
-           src/bin/tests/process_rename_test.py
-           src/hooks/dhcp/user_chk/tests/test_data_files_config.h
-           src/lib/config/tests/data_def_unittests_config.h
-           src/lib/dhcpsrv/tests/test_libraries.h
-           src/lib/python/isc/config/tests/config_test
-           src/lib/python/isc/cc/tests/cc_test
-           src/lib/python/isc/notify/tests/notify_out_test
-           src/lib/python/isc/log/tests/log_console.py
-           src/lib/python/isc/log_messages/work/__init__.py
-           src/lib/dns/gen-rdatacode.py
-           src/lib/python/bind10_config.py
-           src/lib/cc/session_config.h.pre
-           src/lib/cc/tests/session_unittests_config.h
-           src/lib/datasrc/datasrc_config.h.pre
-           src/lib/hooks/tests/marker_file.h
-           src/lib/hooks/tests/test_libraries.h
-           src/lib/log/tests/console_test.sh
-           src/lib/log/tests/destination_test.sh
-           src/lib/log/tests/init_logger_test.sh
-           src/lib/log/tests/buffer_logger_test.sh
-           src/lib/log/tests/local_file_test.sh
-           src/lib/log/tests/logger_lock_test.sh
-           src/lib/log/tests/severity_test.sh
-           src/lib/log/tests/tempdir.h
-           src/lib/util/python/doxygen2pydoc.py
-           src/lib/util/python/mkpywrapper.py
-           src/lib/util/python/gen_wiredata.py
-           src/lib/server_common/tests/data_path.h
-           tests/lettuce/setup_intree_bind10.sh
-          ], [
-           chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
-           chmod +x src/bin/xfrin/run_b10-xfrin.sh
-           chmod +x src/bin/xfrout/run_b10-xfrout.sh
-           chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
+])
+
+ AC_CONFIG_COMMANDS([permissions], [
            chmod +x src/bin/bind10/bind10
            chmod +x src/bin/bind10/run_bind10.sh
+           chmod +x src/bin/bindctl/run_bindctl.sh
+           chmod +x src/bin/bindctl/tests/bindctl_test
+           chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
            chmod +x src/bin/cmdctl/tests/cmdctl_test
            chmod +x src/bin/dbutil/run_dbutil.sh
            chmod +x src/bin/dbutil/tests/dbutil_test.sh
-           chmod +x src/bin/xfrin/tests/xfrin_test
-           chmod +x src/bin/xfrout/tests/xfrout_test
-           chmod +x src/bin/zonemgr/tests/zonemgr_test
-           chmod +x src/bin/bindctl/tests/bindctl_test
-           chmod +x src/bin/bindctl/run_bindctl.sh
            chmod +x src/bin/loadzone/run_loadzone.sh
            chmod +x src/bin/loadzone/tests/correct/correct_test.sh
+           chmod +x src/bin/msgq/run_msgq.sh
            chmod +x src/bin/sysinfo/run_sysinfo.sh
            chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
-           chmod +x src/bin/msgq/run_msgq.sh
+           chmod +x src/bin/xfrin/run_b10-xfrin.sh
+           chmod +x src/bin/xfrin/tests/xfrin_test
+           chmod +x src/bin/xfrout/run_b10-xfrout.sh
+           chmod +x src/bin/xfrout/tests/xfrout_test
+           chmod +x src/bin/zonemgr/run_b10-zonemgr.sh
+           chmod +x src/bin/zonemgr/tests/zonemgr_test
            chmod +x src/lib/dns/gen-rdatacode.py
            chmod +x src/lib/log/tests/console_test.sh
            chmod +x src/lib/log/tests/destination_test.sh
@@ -1604,11 +1605,12 @@ AC_OUTPUT([doc/version.ent
            chmod +x src/lib/log/tests/local_file_test.sh
            chmod +x src/lib/log/tests/logger_lock_test.sh
            chmod +x src/lib/log/tests/severity_test.sh
+           chmod +x src/lib/python/isc/log/tests/log_console.py
            chmod +x src/lib/util/python/doxygen2pydoc.py
-           chmod +x src/lib/util/python/mkpywrapper.py
            chmod +x src/lib/util/python/gen_wiredata.py
-           chmod +x src/lib/python/isc/log/tests/log_console.py
-          ])
+           chmod +x src/lib/util/python/mkpywrapper.py
+])
+
 AC_OUTPUT
 
 dnl Print the results

+ 39 - 29
doc/guide/bind10-guide.xml

@@ -4419,6 +4419,29 @@ Dhcp4/subnet4	[]	list	(default)
 
     </section>
 
+    <section id="dhcp4-echo-client-id">
+      <title>Echoing client-id (RFC6842)</title>
+      <para>Original DHCPv4 spec (RFC2131) states that the DHCPv4
+      server must not send back client-id options when responding to
+      clients. However, in some cases that confused clients that did
+      not have MAC address or client-id. See RFC6842 for details. That
+      behavior has changed with the publication of RFC6842 which
+      updated RFC2131. That update now states that the server must
+      send client-id if client sent it. That is the default behaviour
+      that Kea offers. However, in some cases older devices that do
+      not support RFC6842 may refuse to accept responses that include
+      client-id option. To enable backward compatibility, an optional
+      configuration parameter has been introduced. To configure it,
+      use the following commands:</para>
+
+<screen>
+&gt; <userinput>config add Dhcp4/echo-client-id</userinput>
+&gt; <userinput>config set Dhcp4/echo-client-id False</userinput>
+&gt; <userinput>config commit</userinput>
+</screen>
+
+    </section>
+
     <section id="dhcp4-std">
       <title>Supported Standards</title>
       <para>The following standards and draft standards are currently
@@ -4429,7 +4452,8 @@ Dhcp4/subnet4	[]	list	(default)
             REQUEST, RELEASE, ACK, and NAK.</simpara>
           </listitem>
           <listitem>
-            <simpara><ulink url="http://tools.ietf.org/html/rfc2132">RFC 2132</ulink>: Supported options are: PAD (0),
+            <simpara><ulink url="http://tools.ietf.org/html/rfc2132">RFC 2132</ulink>:
+            Supported options are: PAD (0),
             END(255), Message Type(53), DHCP Server Identifier (54),
             Domain Name (15), DNS Servers (6), IP Address Lease Time
             (51), Subnet mask (1), and Routers (3).</simpara>
@@ -4437,6 +4461,10 @@ Dhcp4/subnet4	[]	list	(default)
           <listitem>
             <simpara><ulink url="http://tools.ietf.org/html/rfc3046">RFC 3046</ulink>:
             Relay Agent Information option is supported.</simpara>
+            <simpara><ulink url="http://tools.ietf.org/html/rfc6842">RFC 6842</ulink>:
+            Server by default sends back client-id option. That capability may be
+            disabled. See <xref linkend="dhcp4-echo-client-id"/> for details.
+            </simpara>
           </listitem>
       </itemizedlist>
     </section>
@@ -4460,21 +4488,6 @@ Dhcp4/renew-timer	1000	integer	(default)
 &gt; <userinput>config commit</userinput></screen>
           </para>
         </listitem>
-          <listitem>
-            <simpara>During the initial IPv4 node configuration, the
-            server is expected to send packets to a node that does not
-            have IPv4 address assigned yet. The server requires
-            certain tricks (or hacks) to transmit such packets. This
-            is not implemented yet, therefore DHCPv4 server supports
-            relayed traffic only (that is, normal point to point
-            communication).</simpara>
-          </listitem>
-
-          <listitem>
-            <simpara>Upon start, the server will open sockets on all
-            interfaces that are not loopback, are up and running and
-            have IPv4 address.</simpara>
-          </listitem>
 
           <listitem>
             <simpara>The DHCPv4 server does not support
@@ -4484,7 +4497,7 @@ Dhcp4/renew-timer	1000	integer	(default)
             available from <ulink url="http://www.isc.org/software/dhcp"/>.</simpara>
           </listitem>
           <listitem>
-            <simpara>Interface detection is currently working on Linux
+            <simpara>Raw sockets operation is working on Linux
             only. See <xref linkend="iface-detect"/> for details.</simpara>
           </listitem>
           <listitem>
@@ -5401,10 +5414,6 @@ Dhcp6/renew-timer	1000	integer	(default)
         <listitem>
           <simpara>DNS Update is not supported.</simpara>
         </listitem>
-        <listitem>
-          <simpara>Interface detection is currently working on Linux
-          only. See <xref linkend="iface-detect"/> for details.</simpara>
-        </listitem>
       </itemizedlist>
     </section>
 
@@ -5439,16 +5448,17 @@ Dhcp6/renew-timer	1000	integer	(default)
 <!-- TODO: point to doxygen docs -->
 
     <section id="iface-detect">
-      <title>Interface detection</title>
+      <title>Interface detection and Socket handling</title>
       <para>Both the DHCPv4 and DHCPv6 components share network
       interface detection routines. Interface detection is
-      currently only supported on Linux systems.</para>
-
-      <para>For non-Linux systems, there is currently a stub
-      implementation provided. The interface manager detects loopback
-      interfaces only as their name (lo or lo0) can be easily predicted.
-      Please contact the BIND 10 development team if you are interested
-      in running DHCP components on systems other than Linux.</para>
+      currently supported on Linux, all BSD family (FreeBSD, NetBSD,
+      OpenBSD), Mac OS X and Solaris 11 systems.</para>
+
+      <para>DHCPv4 requires special raw socket processing to send and receive
+      packets from hosts that do not have IPv4 address assigned yet. Support
+      for this operation is implemented on Linux only, so it is likely that
+      DHCPv4 component will not work in certain cases on systems other than
+      Linux.</para>
     </section>
 
 <!--

+ 1 - 0
src/bin/d2/Makefile.am

@@ -65,6 +65,7 @@ b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
 b10_dhcp_ddns_SOURCES += labeled_value.cc labeled_value.h
 b10_dhcp_ddns_SOURCES += nc_add.cc nc_add.h
+b10_dhcp_ddns_SOURCES += nc_remove.cc nc_remove.h
 b10_dhcp_ddns_SOURCES += nc_trans.cc nc_trans.h
 b10_dhcp_ddns_SOURCES += state_model.cc state_model.h
 

+ 91 - 4
src/bin/d2/d2_messages.mes

@@ -325,19 +325,19 @@ message but the attempt to send it suffered a unexpected error. This is most
 likely a programmatic error, rather than a communications issue. Some or all
 of the DNS updates requested as part of this request did not succeed.
 
-% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE A DNS update message to add a forward DNS entry could not be constructed for this request: %1, reason: %2
+% DHCP_DDNS_FORWARD_ADD_BUILD_FAILURE DNS udpate message to add a forward DNS entry could not be constructed for this request: %1, reason: %2
 This is an error message issued when an error occurs attempting to construct
 the server bound packet requesting a forward address addition.  This is due
 to invalid data contained in the NameChangeRequest. The request will be aborted.
 This is most likely a configuration issue.
 
-% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE A DNS update message to replace a forward DNS entry could not be constructed from this request: %1, reason: %2
+% DHCP_DDNS_FORWARD_REPLACE_BUILD_FAILURE DNS update message to replace a forward DNS entry could not be constructed from this request: %1, reason: %2
 This is an error message issued when an error occurs attempting to construct
 the server bound packet requesting a forward address replacement.  This is
 due to invalid data contained in the NameChangeRequest. The request will be
 aborted.  This is most likely a configuration issue.
 
-% DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE A DNS update message to replace a reverse DNS entry could not be constructed from this request: %1, reason: %2
+% DHCP_DDNS_REVERSE_REPLACE_BUILD_FAILURE DNS update message to replace a reverse DNS entry could not be constructed from this request: %1, reason: %2
 This is an error message issued when an error occurs attempting to construct
 the server bound packet requesting a reverse PTR replacement.  This is
 due to invalid data contained in the NameChangeRequest. The request will be
@@ -347,7 +347,94 @@ aborted.  This is most likely a configuration issue.
 This is a debug message issued after DHCP_DDNS has submitted DNS mapping
 additions which were received and accepted by an appropriate DNS server.
 
-% DHCP_DDNS_ADD_FAILED DHCP_DDNS failed attempting to make DNS mapping additions for this request: %1
+% DHCP_DDNS_ADD_FAILED DHCP_DDNS failed attempting to make DNS mapping additions for this request: %1, event: %2
 This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
 entry additions have failed.  The precise reason for the failure should be
 documented in preceding log entries.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE DNS udpate message to remove a forward DNS Address entry could not be constructed for this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a forward address (A or AAAA) removal.  This
+is due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED DNS Server, %1, rejected a DNS update request to remove the forward address mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward mapping address removal for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward address remove.  The application will retry
+against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a forward address mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing a forward address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a forward address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE DNS udpate message to remove forward DNS RR entries could not be constructed for this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting forward RR (DHCID RR) removal.  This is due
+to invalid data contained in the NameChangeRequest. The request will be aborted.This is most likely a configuration issue.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED DNS Server, %1, rejected a DNS update request to remove forward RR entries for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR DHCP_DDNS encountered an IO error sending a forward RR removal for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a forward RR remove.  The application will retry
+against the same server.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing forward RRs for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove forward RRs mapping, is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing forward RRs for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing forward RRs.  The request will be aborted. This is
+most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE DNS update message to remove a reverse DNS entry could not be constructed from this request: %1,  reason: %2
+This is an error message issued when an error occurs attempting to construct
+the server bound packet requesting a reverse PTR removal.  This is
+due to invalid data contained in the NameChangeRequest. The request will be
+aborted.  This is most likely a configuration issue.
+
+% DHCP_DDNS_REVERSE_REMOVE_REJECTED DNS Server, %1, rejected a DNS update request to remove the reverse mapping for FQDN, %2, with an RCODE: %3
+This is an error message issued when an update was rejected by the DNS server
+it was sent to for the reason given by the RCODE. The rcode values are defined
+in RFC 2136.
+
+% DHCP_DDNS_REVERSE_REMOVE_IO_ERROR DHCP_DDNS encountered an IO error sending a reverse mapping remove for FQDN %1 to DNS server %2
+This is an error message issued when a communication error occurs while
+DHCP_DDNS is carrying out a reverse address update.  The application will
+retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT DHCP_DDNS received a corrupt response from the DNS server, %1, while removing reverse address mapping for FQDN, %2
+This is an error message issued when the response received by DHCP_DDNS, to a
+update request to remove a reverse address,  is mangled or malformed.
+The application will retry against the same server or others as appropriate.
+
+% DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS DHCP_DDNS received an unknown DNSClient status: %1, while removing reverse address mapping for FQDN %2 to DNS server %3
+This is an error message issued when DNSClient returns an unrecognized status
+while DHCP_DDNS was removing a reverse address mapping.  The request will be
+aborted.  This is most likely a programmatic issue and should be reported.
+
+% DHCP_DDNS_REMOVE_SUCCEEDED DHCP_DDNS successfully removed the DNS mapping addition for this request: %1
+This is a debug message issued after DHCP_DDNS has submitted DNS mapping
+removals which were received and accepted by an appropriate DNS server.
+
+% DHCP_DDNS_REMOVE_FAILED DHCP_DDNS failed attempting to make DNS mapping removals for this request: %1, event: %2
+This is an error message issued after DHCP_DDNS attempts to submit DNS mapping
+entry removals have failed.  The precise reason for the failure should be
+documented in preceding log entries.
+

+ 44 - 26
src/bin/d2/nc_add.cc

@@ -54,17 +54,25 @@ NameAddTransaction::defineEvents() {
     // Call superclass impl first.
     NameChangeTransaction::defineEvents();
 
-    // Define NCT events.
+    // Define NameAddTransaction events.
     defineEvent(FQDN_IN_USE_EVT, "FQDN_IN_USE_EVT");
     defineEvent(FQDN_NOT_IN_USE_EVT, "FQDN_NOT_IN_USE_EVT");
 }
 
 void
 NameAddTransaction::verifyEvents() {
-    // Call superclass impl first.
+    // Call superclass implementation first to verify its events. These are
+    // events common to all transactions, and they must be defined.
+    // SELECT_SERVER_EVT
+    // SERVER_SELECTED_EVT
+    // SERVER_IO_ERROR_EVT
+    // NO_MORE_SERVERS_EVT
+    // IO_COMPLETED_EVT
+    // UPDATE_OK_EVT
+    // UPDATE_FAILED_EVT
     NameChangeTransaction::verifyEvents();
 
-    // Verify NCT events.
+    // Verify NameAddTransaction events by attempting to fetch them.
     getEvent(FQDN_IN_USE_EVT);
     getEvent(FQDN_NOT_IN_USE_EVT);
 }
@@ -74,7 +82,7 @@ NameAddTransaction::defineStates() {
     // Call superclass impl first.
     NameChangeTransaction::defineStates();
 
-    // Define the states.
+    // Define NameAddTransaction states.
     defineState(READY_ST, "READY_ST",
              boost::bind(&NameAddTransaction::readyHandler, this));
 
@@ -102,10 +110,16 @@ NameAddTransaction::defineStates() {
 }
 void
 NameAddTransaction::verifyStates() {
-    // Call superclass impl first.
+    // Call superclass implementation first to verify its states. These are
+    // states common to all transactions, and they must be defined.
+    // READY_ST
+    // SELECTING_FWD_SERVER_ST
+    // SELECTING_REV_SERVER_ST
+    // PROCESS_TRANS_OK_ST
+    // PROCESS_TRANS_FAILED_ST
     NameChangeTransaction::verifyStates();
 
-    // Verify NCT states. This ensures that derivations provide the handlers.
+    // Verify NameAddTransaction states by attempting to fetch them.
     getState(ADDING_FWD_ADDRS_ST);
     getState(REPLACING_FWD_ADDRS_ST);
     getState(REPLACING_REV_PTRS_ST);
@@ -541,7 +555,9 @@ void
 NameAddTransaction::processAddFailedHandler() {
     switch(getNextEvent()) {
     case UPDATE_FAILED_EVT:
-        LOG_ERROR(dctl_logger, DHCP_DDNS_ADD_FAILED).arg(getNcr()->toText());
+    case NO_MORE_SERVERS_EVT:
+        LOG_ERROR(dctl_logger, DHCP_DDNS_ADD_FAILED).arg(getNcr()->toText())
+        .arg(getContextStr());
         setNcrStatus(dhcp_ddns::ST_FAILED);
         endModel();
         break;
@@ -560,27 +576,28 @@ NameAddTransaction::buildAddFwdAddressRequest() {
     // Construct dns::Name from NCR fqdn.
     dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
 
+    // Content on this request is based on RFC 4703, section 5.3.1
     // First build the Prerequisite Section.
 
-    // Create 'FQDN Is Not In Use' prerequisite (RFC 2136, section 2.4.5)
-    // Add the RR to prerequisite section.
+    // Create 'FQDN Is Not In Use' prerequisite and add it to the
+    // prerequisite section.
+    // Based on RFC 2136, section 2.4.5
     dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::NONE(),
                              dns::RRType::ANY(), dns::RRTTL(0)));
     request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
 
     // Next build the Update Section.
 
-    // Create the FQDN/IP 'add' RR (RFC 2136, section 2.5.1)
-    // Set the message RData to lease address.
-    // Add the RR to update section.
+    // Create the FQDN/IP 'add' RR and add it to the to update section.
+    // Based on RFC 2136, section 2.5.1
     dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::IN(),
                          getAddressRRType(), dns::RRTTL(0)));
 
     addLeaseAddressRdata(update);
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
-    // Now create the FQDN/DHCID 'add' RR per RFC 4701)
-    // Set the message RData to DHCID.
-    // Add the RR to update section.
+
+    // Now create the FQDN/DHCID 'add' RR and add it to update section.
+    // Based on RFC 2136, section 2.5.1
     update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
                                 dns::RRType::DHCID(), dns::RRTTL(0)));
     addDhcidRdata(update);
@@ -598,17 +615,19 @@ NameAddTransaction::buildReplaceFwdAddressRequest() {
     // Construct dns::Name from NCR fqdn.
     dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
 
+    // Content on this request is based on RFC 4703, section 5.3.2
     // First build the Prerequisite Section.
 
-    // Create an 'FQDN Is In Use' prerequisite (RFC 2136, section 2.4.4)
-    // Add it to the pre-requisite section.
+    // Create an 'FQDN Is In Use' prerequisite and add it to the
+    // pre-requisite section.
+    // Based on RFC 2136, section 2.4.4
     dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::ANY(),
                                dns::RRType::ANY(), dns::RRTTL(0)));
     request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
 
-    // Now create an DHCID matches prerequisite RR.
-    // Set the RR's RData to DHCID.
-    // Add it to the pre-requisite section.
+    // Create an DHCID matches prerequisite RR and add it to the
+    // pre-requisite section.
+    // Based on RFC 2136, section 2.4.2.
     prereq.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
                  dns::RRType::DHCID(), dns::RRTTL(0)));
     addDhcidRdata(prereq);
@@ -616,16 +635,14 @@ NameAddTransaction::buildReplaceFwdAddressRequest() {
 
     // Next build the Update Section.
 
-    // Create the FQDN/IP 'delete' RR (RFC 2136, section 2.5.1)
-    // Set the message RData to lease address.
-    // Add the RR to update section.
+    // Create the FQDN/IP 'delete' RR and add it to the update section.
+    // Based on RFC 2136, section 2.5.2
     dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
                          getAddressRRType(), dns::RRTTL(0)));
     request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
 
-    // Create the FQDN/IP 'add' RR (RFC 2136, section 2.5.1)
-    // Set the message RData to lease address.
-    // Add the RR to update section.
+    // Create the FQDN/IP 'add' RR and add it to the update section.
+    // Based on RFC 2136, section 2.5.1
     update.reset(new dns::RRset(fqdn, dns::RRClass::IN(),
                                 getAddressRRType(), dns::RRTTL(0)));
     addLeaseAddressRdata(update);
@@ -644,6 +661,7 @@ NameAddTransaction::buildReplaceRevPtrsRequest() {
     std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
     dns::Name rev_ip(rev_addr);
 
+    // Content on this request is based on RFC 4703, section 5.4
     // Reverse replacement has no prerequisites so straight on to
     // building the Update section.
 

+ 13 - 8
src/bin/d2/nc_add.h

@@ -34,9 +34,10 @@ public:
 /// @brief Embodies the "life-cycle" required to carry out a DDNS Add update.
 ///
 /// NameAddTransaction implements a state machine for adding (or replacing) a
-/// forward DNS mapping. This state machine is based upon the processing logic
-/// described in RFC 4703, Sections 5.3 and 5.4.  That logic may be paraphrased
-/// as follows:
+/// forward and/or reverse DNS mapping. This state machine is based upon the
+/// processing logic described in RFC 4703, Sections 5.3 and 5.4.  That logic
+/// may be paraphrased as follows:
+///
 /// @code
 ///
 /// If the request includes a forward change:
@@ -109,7 +110,8 @@ protected:
     /// @brief Validates the contents of the set of events.
     ///
     /// Invokes NameChangeTransaction's implementation and then verifies the
-    /// Add transaction's events.
+    /// Add transaction's.  This tests that the needed events are in the event
+    /// dictionary.
     ///
     /// @throw StateModelError if an event value is undefined.
     virtual void verifyEvents();
@@ -125,7 +127,8 @@ protected:
     /// @brief Validates the contents of the set of states.
     ///
     /// Invokes NameChangeTransaction's implementation and then verifies the
-    /// Add transaction's states.
+    /// Add transaction's states. This tests that the needed states are in the
+    /// state dictionary.
     ///
     /// @throw StateModelError if an event value is undefined.
     virtual void verifyStates();
@@ -166,7 +169,7 @@ protected:
     /// handler simply attempts to select the next server.
     ///
     /// Transitions to:
-    /// - ADDING_REV_PTRS_ST with next event of SERVER_SELECTED upon successful
+    /// - ADDING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon successful
     /// server selection
     ///
     /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
@@ -329,7 +332,7 @@ protected:
     /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
     /// successful replacement.
     ///
-    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_OK_EVT If the
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
     /// DNS server rejected the update for any reason or the IO completed
     /// with an unrecognized status.
     ///
@@ -365,8 +368,10 @@ protected:
     /// @brief State handler for PROCESS_TRANS_FAILED_ST.
     ///
     /// Entered from:
+    /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
     /// - ADDING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
     /// - REPLACING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
     /// - REPLACING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
     ///
     /// Sets the transaction status to indicate failure and ends
@@ -438,7 +443,7 @@ protected:
     void buildReplaceRevPtrsRequest();
 };
 
-/// @brief Defines a pointer to a NameChangeTransaction.
+/// @brief Defines a pointer to a NameAddTransaction.
 typedef boost::shared_ptr<NameAddTransaction> NameAddTransactionPtr;
 
 } // namespace isc::d2

+ 695 - 0
src/bin/d2/nc_remove.cc

@@ -0,0 +1,695 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/d2_cfg_mgr.h>
+#include <d2/nc_remove.h>
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace d2 {
+
+
+// NameRemoveTransaction states
+const int NameRemoveTransaction::REMOVING_FWD_ADDRS_ST;
+const int NameRemoveTransaction::REMOVING_FWD_RRS_ST;
+const int NameRemoveTransaction::REMOVING_REV_PTRS_ST;
+
+// NameRemoveTransaction events
+// Currently NameRemoveTransaction does not define any events.
+
+NameRemoveTransaction::
+NameRemoveTransaction(IOServicePtr& io_service,
+                   dhcp_ddns::NameChangeRequestPtr& ncr,
+                   DdnsDomainPtr& forward_domain,
+                   DdnsDomainPtr& reverse_domain)
+    : NameChangeTransaction(io_service, ncr, forward_domain, reverse_domain) {
+    if (ncr->getChangeType() != isc::dhcp_ddns::CHG_REMOVE) {
+        isc_throw (NameRemoveTransactionError,
+                   "NameRemoveTransaction, request type must be CHG_REMOVE");
+    }
+}
+
+NameRemoveTransaction::~NameRemoveTransaction(){
+}
+
+void
+NameRemoveTransaction::defineEvents() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineEvents();
+
+    // Define NameRemoveTransaction events.
+    // Currently NameRemoveTransaction does not define any events.
+    // defineEvent(TBD_EVENT, "TBD_EVT");
+}
+
+void
+NameRemoveTransaction::verifyEvents() {
+    // Call superclass implementation first to verify its events. These are
+    // events common to all transactions, and they must be defined.
+    // SELECT_SERVER_EVT
+    // SERVER_SELECTED_EVT
+    // SERVER_IO_ERROR_EVT
+    // NO_MORE_SERVERS_EVT
+    // IO_COMPLETED_EVT
+    // UPDATE_OK_EVT
+    // UPDATE_FAILED_EVT
+    NameChangeTransaction::verifyEvents();
+
+    // Verify NameRemoveTransaction events by attempting to fetch them.
+    // Currently NameRemoveTransaction does not define any events.
+    // getEvent(TBD_EVENT);
+}
+
+void
+NameRemoveTransaction::defineStates() {
+    // Call superclass impl first.
+    NameChangeTransaction::defineStates();
+
+    // Define NameRemoveTransaction states.
+    defineState(READY_ST, "READY_ST",
+                boost::bind(&NameRemoveTransaction::readyHandler, this));
+
+    defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
+                boost::bind(&NameRemoveTransaction::selectingFwdServerHandler,
+                            this));
+
+    defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
+                boost::bind(&NameRemoveTransaction::selectingRevServerHandler,
+                            this));
+
+    defineState(REMOVING_FWD_ADDRS_ST, "REMOVING_FWD_ADDRS_ST",
+                boost::bind(&NameRemoveTransaction::removingFwdAddrsHandler,
+                            this));
+
+    defineState(REMOVING_FWD_RRS_ST, "REMOVING_FWD_RRS_ST",
+                boost::bind(&NameRemoveTransaction::removingFwdRRsHandler,
+                            this));
+
+    defineState(REMOVING_REV_PTRS_ST, "REMOVING_REV_PTRS_ST",
+                boost::bind(&NameRemoveTransaction::removingRevPtrsHandler,
+                            this));
+
+    defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
+                boost::bind(&NameRemoveTransaction::processRemoveOkHandler,
+                            this));
+
+    defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
+                boost::bind(&NameRemoveTransaction::processRemoveFailedHandler,
+                            this));
+}
+
+void
+NameRemoveTransaction::verifyStates() {
+    // Call superclass implementation first to verify its states. These are
+    // states common to all transactions, and they must be defined.
+    // READY_ST
+    // SELECTING_FWD_SERVER_ST
+    // SELECTING_REV_SERVER_ST
+    // PROCESS_TRANS_OK_ST
+    // PROCESS_TRANS_FAILED_ST
+    NameChangeTransaction::verifyStates();
+
+    // Verify NameRemoveTransaction states by attempting to fetch them.
+    getState(REMOVING_FWD_ADDRS_ST);
+    getState(REMOVING_FWD_RRS_ST);
+    getState(REMOVING_REV_PTRS_ST);
+}
+
+void
+NameRemoveTransaction::readyHandler() {
+    switch(getNextEvent()) {
+    case START_EVT:
+        if (getForwardDomain()) {
+            // Request includes a forward change, do that first.
+            transition(SELECTING_FWD_SERVER_ST, SELECT_SERVER_EVT);
+        } else {
+            // Reverse change only, transition accordingly.
+            transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+        }
+
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameRemoveTransaction::selectingFwdServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getForwardDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REMOVING_FWD_ADDRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+void
+NameRemoveTransaction::removingFwdAddrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildRemoveFwdAddressRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(dctl_logger,
+                          DHCP_DDNS_FORWARD_REMOVE_ADDRS_BUILD_FAILURE)
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if ((rcode == dns::Rcode::NOERROR()) ||
+                (rcode == dns::Rcode::NXDOMAIN())) {
+                // We were able to remove it or it wasn't there, now we
+                // need to remove any other RRs for this FQDN.
+                transition(REMOVING_FWD_RRS_ST, UPDATE_OK_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should we try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_REJECTED)
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_IO_ERROR)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REMOVE_ADDRS_RESP_CORRUPT)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            retryTransition(SELECTING_FWD_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger,
+                      DHCP_DDNS_FORWARD_REMOVE_ADDRS_BAD_DNSCLIENT_STATUS)
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+
+void
+NameRemoveTransaction::removingFwdRRsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildRemoveFwdRRsRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(dctl_logger,
+                          DHCP_DDNS_FORWARD_REMOVE_RRS_BUILD_FAILURE)
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            // @todo Not sure if NXDOMAIN is ok here, but I think so.
+            // A Rcode of NXDOMAIN would mean there are no RRs for the FQDN,
+            // which is fine.  We were asked to delete them, they are not there
+            // so all is well.
+            if ((rcode == dns::Rcode::NOERROR()) ||
+                (rcode == dns::Rcode::NXDOMAIN())) {
+                // We were able to remove the forward mapping. Mark it as done.
+                setForwardChangeCompleted(true);
+
+                // If request calls for reverse update then do that next,
+                // otherwise we can process ok.
+                if (getReverseDomain()) {
+                    transition(SELECTING_REV_SERVER_ST, SELECT_SERVER_EVT);
+                } else {
+                    transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+                }
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_REJECTED)
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_IO_ERROR)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            // @note If we exhaust the IO retries for the current server
+            // due to IO failures, we will abort the remaining updates.
+            // The rational is that we are only in this state, if the remove
+            // of the forward address RR succeeded (removingFwdAddrsHandler)
+            // on the current server. Therefore  we should not attempt another
+            // removal on a different server.  This is perhaps a point
+            // for discussion.
+            // @todo Should we go ahead with the reverse remove?
+            retryTransition(PROCESS_TRANS_FAILED_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_FORWARD_REMOVE_RRS_RESP_CORRUPT)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            // If we are out of retries on this server abandon the transaction.
+            // (Same logic as the case for TIMEOUT above).
+            retryTransition(PROCESS_TRANS_FAILED_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger,
+                      DHCP_DDNS_FORWARD_REMOVE_RRS_BAD_DNSCLIENT_STATUS)
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+
+void
+NameRemoveTransaction::selectingRevServerHandler() {
+    switch(getNextEvent()) {
+    case SELECT_SERVER_EVT:
+        // First time through for this transaction, so initialize server
+        // selection.
+        initServerSelection(getReverseDomain());
+        break;
+    case SERVER_IO_ERROR_EVT:
+        // We failed to communicate with current server. Attempt to select
+        // another one below.
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+
+    // Select the next server from the list of forward servers.
+    if (selectNextServer()) {
+        // We have a server to try.
+        transition(REMOVING_REV_PTRS_ST, SERVER_SELECTED_EVT);
+    }
+    else {
+        // Server list is exhausted, so fail the transaction.
+        transition(PROCESS_TRANS_FAILED_ST, NO_MORE_SERVERS_EVT);
+    }
+}
+
+
+void
+NameRemoveTransaction::removingRevPtrsHandler() {
+    if (doOnEntry()) {
+        // Clear the request on initial transition. This allows us to reuse
+        // the request on retries if necessary.
+        clearDnsUpdateRequest();
+    }
+
+    switch(getNextEvent()) {
+    case SERVER_SELECTED_EVT:
+        if (!getDnsUpdateRequest()) {
+            // Request hasn't been constructed yet, so build it.
+            try {
+                buildRemoveRevPtrsRequest();
+            } catch (const std::exception& ex) {
+                // While unlikely, the build might fail if we have invalid
+                // data.  Should that be the case, we need to fail the
+                // transaction.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REMOVE_BUILD_FAILURE)
+                          .arg(getNcr()->toText())
+                          .arg(ex.what());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+                break;
+            }
+        }
+
+        // Call sendUpdate() to initiate the async send. Note it also sets
+        // next event to NOP_EVT.
+        sendUpdate();
+        break;
+
+    case IO_COMPLETED_EVT: {
+        switch (getDnsUpdateStatus()) {
+        case DNSClient::SUCCESS: {
+            // We successfully received a response packet from the server.
+            const dns::Rcode& rcode = getDnsUpdateResponse()->getRcode();
+            if ((rcode == dns::Rcode::NOERROR()) ||
+                (rcode == dns::Rcode::NXDOMAIN())) {
+                // We were able to update the reverse mapping. Mark it as done.
+                // @todo For now we are also treating NXDOMAIN as success.
+                setReverseChangeCompleted(true);
+                transition(PROCESS_TRANS_OK_ST, UPDATE_OK_EVT);
+            } else {
+                // Per RFC4703 any other value means cease.
+                // If we get not authorized should try the next server in
+                // the list? @todo  This needs some discussion perhaps.
+                LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REMOVE_REJECTED)
+                          .arg(getCurrentServer()->getIpAddress())
+                          .arg(getNcr()->getFqdn())
+                          .arg(rcode.getCode());
+                transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            }
+
+            break;
+        }
+
+        case DNSClient::TIMEOUT:
+        case DNSClient::OTHER:
+            // We couldn't send to the current server, log it and set up
+            // to select the next server for a retry.
+            // @note For now we treat OTHER as an IO error like TIMEOUT. It
+            // is not entirely clear if this is accurate.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REMOVE_IO_ERROR)
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        case DNSClient::INVALID_RESPONSE:
+            // A response was received but was corrupt. Retry it like an IO
+            // error.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_REVERSE_REMOVE_RESP_CORRUPT)
+                      .arg(getCurrentServer()->getIpAddress())
+                      .arg(getNcr()->getFqdn());
+
+            // If we are out of retries on this server, we go back and start
+            // all over on a new server.
+            retryTransition(SELECTING_REV_SERVER_ST);
+            break;
+
+        default:
+            // Any other value and we will fail this transaction, something
+            // bigger is wrong.
+            LOG_ERROR(dctl_logger,
+                      DHCP_DDNS_REVERSE_REMOVE_BAD_DNSCLIENT_STATUS)
+                      .arg(getDnsUpdateStatus())
+                      .arg(getNcr()->getFqdn())
+                      .arg(getCurrentServer()->getIpAddress());
+
+            transition(PROCESS_TRANS_FAILED_ST, UPDATE_FAILED_EVT);
+            break;
+        } // end switch on dns_status
+
+        break;
+    } // end case IO_COMPLETE_EVT
+
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+
+void
+NameRemoveTransaction::processRemoveOkHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_OK_EVT:
+        LOG_DEBUG(dctl_logger, DBGLVL_TRACE_DETAIL, DHCP_DDNS_REMOVE_SUCCEEDED)
+                  .arg(getNcr()->toText());
+        setNcrStatus(dhcp_ddns::ST_COMPLETED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameRemoveTransaction::processRemoveFailedHandler() {
+    switch(getNextEvent()) {
+    case UPDATE_FAILED_EVT:
+    case NO_MORE_SERVERS_EVT:
+    case SERVER_IO_ERROR_EVT:
+        LOG_ERROR(dctl_logger, DHCP_DDNS_REMOVE_FAILED).arg(getNcr()->toText())
+        .arg(getEventLabel(getNextEvent()));
+        setNcrStatus(dhcp_ddns::ST_FAILED);
+        endModel();
+        break;
+    default:
+        // Event is invalid.
+        isc_throw(NameRemoveTransactionError,
+                  "Wrong event for context: " << getContextStr());
+    }
+}
+
+void
+NameRemoveTransaction::buildRemoveFwdAddressRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+    // Content on this request is based on RFC 4703, section 5.5, paragraph 4.
+    // Construct dns::Name from NCR fqdn.
+    dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+    // First build the Prerequisite Section
+
+    // Create an DHCID matches prerequisite RR and add it to the
+    // pre-requisite section
+    // Based on RFC 2136, section 2.4.2.
+    dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::IN(),
+                                        dns::RRType::DHCID(), dns::RRTTL(0)));
+    addDhcidRdata(prereq);
+    request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+    // Next build the Update Section
+
+    // Create the FQDN/IP 'delete' RR and add it to the update section.
+    // Add the RR to update section.
+    // Based on 2136 section 2.5.4
+    dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::NONE(),
+                         getAddressRRType(), dns::RRTTL(0)));
+    addLeaseAddressRdata(update);
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+void
+NameRemoveTransaction::buildRemoveFwdRRsRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getForwardDomain());
+
+    // Construct dns::Name from NCR fqdn.
+    dns::Name fqdn(dns::Name(getNcr()->getFqdn()));
+
+    // Content on this request is based on RFC 4703, section 5.5, paragraph 5.
+    // First build the Prerequisite Section.
+
+    // Now create an DHCID matches prerequisite RR.
+    // Set the RR's RData to DHCID.
+    // Add it to the pre-requisite section.
+    // Based on RFC 2136, section 2.4.2.
+    dns::RRsetPtr prereq(new dns::RRset(fqdn, dns::RRClass::IN(),
+                         dns::RRType::DHCID(), dns::RRTTL(0)));
+    addDhcidRdata(prereq);
+    request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+    // Create an assertion that there are no A RRs for the FQDN.
+    // Add it to the pre-reqs.
+    // Based on RFC 2136, section 2.4.3.
+    prereq.reset(new dns::RRset(fqdn, dns::RRClass::NONE(),
+                                dns::RRType::A(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+    // Create an assertion that there are no A RRs for the FQDN.
+    // Add it to the pre-reqs.
+    // Based on RFC 2136, section 2.4.3.
+    prereq.reset(new dns::RRset(fqdn, dns::RRClass::NONE(),
+                                dns::RRType::AAAA(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+    // Next build the Update Section.
+
+    // Create the 'delete' of all RRs for FQDN.
+    // Set the message RData to lease address.
+    // Add the RR to update section.
+    // Based on RFC 2136, section 2.5.3.
+    dns::RRsetPtr update(new dns::RRset(fqdn, dns::RRClass::ANY(),
+                         dns::RRType::ANY(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+void
+NameRemoveTransaction::buildRemoveRevPtrsRequest() {
+    // Construct an empty request.
+    D2UpdateMessagePtr request = prepNewRequest(getReverseDomain());
+
+    // Create the reverse IP address "FQDN".
+    std::string rev_addr = D2CfgMgr::reverseIpAddress(getNcr()->getIpAddress());
+    dns::Name rev_ip(rev_addr);
+
+    // Content on this request is based on RFC 4703, section 5.5, paragraph 2.
+    // First build the Prerequisite Section.
+    // (Note that per RFC 4703, section 5.4, there is no need to validate
+    // DHCID RR for PTR entries.)
+
+    // Create an assertion that the PTRDNAME in the PTR record matches the
+    // client's FQDN for the address that was released.
+    // Based on RFC 2136, section 3.2.3
+    dns::RRsetPtr prereq(new dns::RRset(rev_ip, dns::RRClass::IN(),
+                                        dns::RRType::PTR(), dns::RRTTL(0)));
+    addPtrRdata(prereq);
+    request->addRRset(D2UpdateMessage::SECTION_PREREQUISITE, prereq);
+
+    // Now, build the Update section.
+
+    // Create a delete of any RRs for the FQDN and add it to update section.
+    // Based on RFC 2136, section 3.4.2.3
+    dns::RRsetPtr update(new dns::RRset(rev_ip, dns::RRClass::ANY(),
+                         dns::RRType::ANY(), dns::RRTTL(0)));
+    request->addRRset(D2UpdateMessage::SECTION_UPDATE, update);
+
+    // Set the transaction's update request to the new request.
+    setDnsUpdateRequest(request);
+}
+
+} // namespace isc::d2
+} // namespace isc

+ 435 - 0
src/bin/d2/nc_remove.h

@@ -0,0 +1,435 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NC_REMOVE_H
+#define NC_REMOVE_H
+
+/// @file nc_remove.h This file defines the class NameRemoveTransaction.
+
+#include <d2/nc_trans.h>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown if the NameRemoveTransaction encounters a general error.
+class NameRemoveTransactionError : public isc::Exception {
+public:
+    NameRemoveTransactionError(const char* file, size_t line,
+                               const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Embodies the "life-cycle" required to carry out a DDNS Remove update.
+///
+/// NameRemoveTransaction implements a state machine for removing a forward
+/// and/or reverse DNS mappings. This state machine is based upon the processing
+/// logic described in RFC 4703, Section 5.5. That logic may be paraphrased as
+/// follows:
+///
+/// @code
+///
+/// If the request includes a forward change:
+///     Select a forward server
+///     Send the server a request to remove client's specific forward address RR
+///     If it succeeds or the server responds with name no longer in use
+///         Send a server a request to delete any other RRs for that FQDN, such
+///         as the DHCID RR.
+///     otherwise
+///         abandon the update
+///
+/// If the request includes a reverse change:
+///     Select a reverse server
+///     Send a server a request to delete reverse entry (PTR RR)
+///
+/// @endcode
+///
+/// This class derives from NameChangeTransaction from which it inherits
+/// states, events, and methods common to NameChangeRequest processing.
+class NameRemoveTransaction : public NameChangeTransaction {
+public:
+
+    //@{  Additional states needed for NameRemove state model.
+    /// @brief State that attempts to remove specific forward address record.
+    static const int REMOVING_FWD_ADDRS_ST = NCT_DERIVED_STATE_MIN + 1;
+
+    /// @brief State that attempts to remove any other forward RRs for the DHCID
+    static const int REMOVING_FWD_RRS_ST = NCT_DERIVED_STATE_MIN + 2;
+
+    /// @brief State that attempts to remove reverse PTR records
+    static const int REMOVING_REV_PTRS_ST = NCT_DERIVED_STATE_MIN + 3;
+    //@}
+
+    //@{ Additional events needed for NameRemove state model.
+    /// @brief Event sent when replace attempt to fails with address not in use.
+    /// @todo Currently none have been identified.
+    //@}
+
+    /// @brief Constructor
+    ///
+    /// Instantiates an Remove transaction that is ready to be started.
+    ///
+    /// @param io_service IO service to be used for IO processing
+    /// @param ncr is the NameChangeRequest to fulfill
+    /// @param forward_domain is the domain to use for forward DNS updates
+    /// @param reverse_domain is the domain to use for reverse DNS updates
+    ///
+    /// @throw NameRemoveTransaction error if given request is not a CHG_REMOVE,
+    /// NameChangeTransaction error for base class construction errors.
+    NameRemoveTransaction(IOServicePtr& io_service,
+                          dhcp_ddns::NameChangeRequestPtr& ncr,
+                          DdnsDomainPtr& forward_domain,
+                          DdnsDomainPtr& reverse_domain);
+
+    /// @brief Destructor
+    virtual ~NameRemoveTransaction();
+
+protected:
+    /// @brief Adds events defined by NameRemoveTransaction to the event set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// events unique to NCR Remove transaction processing.
+    ///
+    /// @throw StateModelError if an event definition is invalid or a duplicate.
+    virtual void defineEvents();
+
+    /// @brief Validates the contents of the set of events.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Remove transaction's events. This tests that the needed events are in
+    /// the event dictionary.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyEvents();
+
+    /// @brief Adds states defined by NameRemoveTransaction to the state set.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then defines the
+    /// states unique to NCR Remove transaction processing.
+    ///
+    /// @throw StateModelError if an state definition is invalid or a duplicate.
+    virtual void defineStates();
+
+    /// @brief Validates the contents of the set of states.
+    ///
+    /// Invokes NameChangeTransaction's implementation and then verifies the
+    /// Remove transaction's states.  This tests that the needed states are in
+    /// the state dictionary.
+    ///
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyStates();
+
+    /// @brief State handler for READY_ST.
+    ///
+    /// Entered from:
+    /// - INIT_ST with next event of START_EVT
+    ///
+    /// The READY_ST is the state the model transitions into when the inherited
+    /// method, startTransaction() is invoked.  This handler, therefore, is the
+    /// entry point into the state model execution.  Its primary task is to
+    /// determine whether to start with a forward DNS change or a reverse DNS
+    /// change.
+    ///
+    /// Transitions to:
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes a forward change.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_SELECT_ST if request
+    /// includes only a reverse change.
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not
+    /// START_EVT.
+    void readyHandler();
+
+    /// @brief State handler for SELECTING_FWD_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - REMOVING_FWD_ADDRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the forward domain for the forward
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the forward domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - REMOVING_FWD_ADDRS_ST with next event of SERVER_SELECTED upon
+    /// successful server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingFwdServerHandler();
+
+    /// @brief State handler for SELECTING_REV_SERVER_ST.
+    ///
+    /// Entered from:
+    /// - READY_ST with next event of SELECT_SERVER_EVT
+    /// - REMOVING_FWD_RRS_ST with next event of SELECT_SERVER_EVT
+    /// - REMOVING_REV_PTRS_ST with next event of SERVER_IO_ERROR_EVT
+    ///
+    /// Selects the server to be used from the reverse domain for the reverse
+    /// DNS update.  If next event is SELECT_SERVER_EVT the handler initializes
+    /// the reverse domain's server selection mechanism and then attempts to
+    /// select the next server. If next event is SERVER_IO_ERROR_EVT then the
+    /// handler simply attempts to select the next server.
+    ///
+    /// Transitions to:
+    /// - REMOVING_REV_PTRS_ST with next event of SERVER_SELECTED upon
+    /// successful server selection
+    ///
+    /// - PROCESS_TRANS_FAILED with next event of NO_MORE_SERVERS_EVT upon
+    /// failure to select a server
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not
+    /// SELECT_SERVER_EVT or SERVER_IO_ERROR_EVT.
+    void selectingRevServerHandler();
+
+    /// @brief State handler for REMOVING_FWD_ADDRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER with next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to remove the forward DNS entry for a given FQDN, provided
+    /// a DHCID RR exists which matches the requesting DHCID.  If this is
+    /// first invocation of the handler after transitioning into this state,
+    /// any previous update request context is deleted.   If next event
+    /// is SERVER_SELECTED_EVT, the handler builds the forward remove request,
+    /// schedules an asynchronous send via sendUpdate(), and returns.  Note
+    /// that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPELTED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - REMOVING_FWD_RRS_ST with next event of UPDATE_OK_EVT upon successful
+    /// removal or RCODE of indication FQDN is no longer in use (NXDOMAIN).
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with next event of UPDATE_FAILED_EVT if the
+    /// DNS server rejected the update for any reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_FWD_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not
+    /// SERVER_SELECTED_EVT or IO_COMPLETE_EVT
+    void removingFwdAddrsHandler();
+
+    /// @brief State handler for REMOVING_FWD_RRS_ST.
+    ///
+    /// Entered from:
+    /// - REMOVING_FWD_ADDRS_ST with next event of UPDATE_OK_EVT
+    ///
+    /// Attempts to delete any remaining RRs associated with the given FQDN
+    /// such as the DHCID RR.  If this is first invocation of the handler after
+    /// transitioning into this state, any previous update request context is
+    /// deleted and the handler builds the forward remove request. It then
+    /// schedules an asynchronous send via sendUpdate(),
+    /// and returns.  Note that sendUpdate will post NOP_EVT as the next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPELTED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - SELECTING_REV_SERVER_ST with a next event of SELECT_SERVER_EVT upon
+    /// successful completion and the request includes a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_OK_ST with next event of UPDATE_OK_EVT upon successful
+    /// completion and the request does not include a reverse DNS update.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT if the
+    /// DNS server rejected the update for any other reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of SERVER_IO_ERROR_EVT if
+    /// there we have reached maximum number of retries without success on the
+    /// current server.
+    ///
+    /// @note If we exhaust the IO retries for the current server due to IO
+    /// failures, we will abort the remaining updates.  The rational is that
+    /// we are only in this state, if the remove of the forward address RR
+    /// succeeded (removingFwdAddrsHandler) on the current server so we should
+    /// not attempt another removal on a different server.  This is perhaps a
+    /// point for discussion. @todo Should we go ahead with the reverse remove?
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT or IO_COMPLETE_EVT
+    void removingFwdRRsHandler();
+
+    /// @brief State handler for REMOVING_REV_PTRS_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_REV_SERVER_ST with a next event of SERVER_SELECTED_EVT
+    ///
+    /// Attempts to delete a reverse DNS entry for a given FQDN. If this is
+    /// first invocation of the handler after transitioning into this state,
+    /// any previous update request context is deleted.  If next event is
+    /// SERVER_SELECTED_EVT, the handler builds the reverse remove request,
+    /// schedules an asynchronous send via sendUpdate(), and then returns.
+    /// Note that sendUpdate will post NOP_EVT as next event.
+    ///
+    /// Posting the NOP_EVT will cause runModel() to suspend execution of
+    /// the state model thus affecting a "wait" for the update IO to complete.
+    /// Update completion occurs via the DNSClient callback operator() method
+    /// inherited from NameChangeTransaction.  When invoked this callback will
+    /// post a next event of IO_COMPLETED_EVT and then invoke runModel which
+    /// resumes execution of the state model.
+    ///
+    /// When the handler is invoked with a next event of IO_COMPELTED_EVT,
+    /// the DNS update status is checked and acted upon accordingly:
+    ///
+    /// Transitions to:
+    /// - PROCESS_TRANS_OK_ST with a next event of UPDATE_OK_EVT upon
+    /// successful completion.
+    ///
+    /// - PROCESS_TRANS_FAILED_ST with a next event of UPDATE_FAILED_EVT If the
+    /// DNS server rejected the update for any reason or the IO completed
+    /// with an unrecognized status.
+    ///
+    /// - RE-ENTER this state with a next event of SERVER_SELECTED_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has not been exhausted.
+    ///
+    /// - SELECTING_REV_SERVER_ST with next event of SERVER_IO_ERROR_EVT if
+    /// there was an IO error communicating with the server and the number of
+    /// per server retries has been exhausted.
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not:
+    /// SERVER_SELECTED_EVT or IO_COMPLETED_EVT
+    void removingRevPtrsHandler();
+
+    /// @brief State handler for PROCESS_TRANS_OK_ST.
+    ///
+    /// Entered from:
+    /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_OK_EVT
+    /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_OK_EVT
+    ///
+    /// Sets the transaction action status to indicate success and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of END_EVT.
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not:
+    /// UPDATE_OK_EVT
+    void processRemoveOkHandler();
+
+    /// @brief State handler for PROCESS_TRANS_FAILED_ST.
+    ///
+    /// Entered from:
+    /// - SELECTING_FWD_SERVER_ST with a next event of NO_MORE_SERVERS
+    /// - REMOVING_FWD_ADDRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - REMOVING_FWD_RRS_ST with a next event of UPDATE_FAILED_EVT
+    /// - REMOVING_FWD_RRS_ST with a next event of SERVER_IO_ERROR_EVT
+    /// - SELECTING_REV_SERVER_ST with a next event of NO_MORE_SERVERS
+    /// - REMOVING_REV_PTRS_ST with a next event of UPDATE_FAILED_EVT
+    ///
+    /// Sets the transaction status to indicate failure and ends
+    /// model execution.
+    ///
+    /// Transitions to:
+    /// - END_ST with a next event of FAIL_EVT.
+    ///
+    /// @throw NameRemoveTransactionError if upon entry next event is not:
+    /// UPDATE_FAILED_EVT
+    void processRemoveFailedHandler();
+
+    /// @brief Builds a DNS request to remove a forward DNS address for a FQDN.
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for removing a
+    /// forward DNS address mapping. Once constructed, the request is stored as
+    /// the transaction's DNS update request.
+    ///
+    /// The request content is adherent to RFC 4703 section 5.5, paragraph 4.
+    ///
+    /// Prerequisite RRsets:
+    /// 1. An assertion that a matching DHCID RR exists
+    ///
+    /// Updates RRsets:
+    /// 1. A delete of the FQDN/IP RR (type A for IPv4, AAAA for IPv6)
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildRemoveFwdAddressRequest();
+
+    /// @brief Builds a DNS request to remove all forward DNS RRs for a FQDN.
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for removing any
+    /// remaining forward DNS RRs, once all A or AAAA entries for the FQDN
+    /// have been removed. Once constructed, the request is stored as the
+    /// transaction's DNS update request.
+    ///
+    /// The request content is adherent to RFC 4703 section 5.5, paragraph 5.
+    ///
+    /// Prerequisite RRsets:
+    /// 1. An assertion that a matching DHCID RR exists
+    /// 2. An assertion that no A RRs for the FQDN exist
+    /// 3. An assertion that no AAAA RRs for the FQDN exist
+    ///
+    /// Updates RRsets:
+    /// 1. A delete of all RRs for the FQDN
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildRemoveFwdRRsRequest();
+
+    /// @brief Builds a DNS request to remove a reverse DNS entry for a FQDN
+    ///
+    /// Constructs a DNS update request, based upon the NCR, for removing a
+    /// reverse DNS mapping.  Once constructed, the request is stored as
+    /// the transaction's DNS update request.
+    ///
+    /// The request content is adherent to RFC 4703 section 5.5, paragraph 2:
+    ///
+    /// Prerequisite RRsets:
+    /// 1. An assertion that a PTR record matching the client's FQDN exists.
+    ///
+    /// Updates RRsets:
+    /// 1. A delete of all RRs for the FQDN
+    ///
+    /// @throw This method does not throw but underlying methods may.
+    void buildRemoveRevPtrsRequest();
+};
+
+/// @brief Defines a pointer to a NameRemoveTransaction.
+typedef boost::shared_ptr<NameRemoveTransaction> NameRemoveTransactionPtr;
+
+
+} // namespace isc::d2
+} // namespace isc
+#endif

+ 14 - 10
src/bin/d2/nc_trans.cc

@@ -14,6 +14,7 @@
 
 #include <d2/d2_log.h>
 #include <d2/nc_trans.h>
+#include <dns/rdata.h>
 
 namespace isc {
 namespace d2 {
@@ -181,14 +182,14 @@ NameChangeTransaction::onModelFailure(const std::string& explanation) {
 }
 
 void
-NameChangeTransaction::retryTransition(const int server_sel_state) {
+NameChangeTransaction::retryTransition(const int fail_to_state) {
     if (update_attempts_ < MAX_UPDATE_TRIES_PER_SERVER) {
         // Re-enter the current state with same server selected.
         transition(getCurrState(), SERVER_SELECTED_EVT);
     } else  {
-        // Transition to given server selection state if we are out
+        // Transition to given fail_to_state state if we are out
         // of retries.
-        transition(server_sel_state, SERVER_IO_ERROR_EVT);
+        transition(fail_to_state, SERVER_IO_ERROR_EVT);
     }
 }
 
@@ -262,13 +263,13 @@ NameChangeTransaction::addLeaseAddressRdata(dns::RRsetPtr& rrset) {
 
     try {
         // Manufacture an RData from the lease address then add it to the RR.
+        dns::rdata::ConstRdataPtr rdata;
         if (ncr_->isV4()) {
-            dns::rdata::in::A a_rdata(ncr_->getIpAddress());
-            rrset->addRdata(a_rdata);
+            rdata.reset(new dns::rdata::in::A(ncr_->getIpAddress()));
         } else {
-            dns::rdata::in::AAAA rdata(ncr_->getIpAddress());
-            rrset->addRdata(rdata);
+            rdata.reset(new dns::rdata::in::AAAA(ncr_->getIpAddress()));
         }
+        rrset->addRdata(rdata);
     } catch (const std::exception& ex) {
         isc_throw(NameChangeTransactionError, "Cannot add address rdata: "
                   << ex.what());
@@ -285,12 +286,14 @@ NameChangeTransaction::addDhcidRdata(dns::RRsetPtr& rrset) {
     try {
         const std::vector<uint8_t>& ncr_dhcid = ncr_->getDhcid().getBytes();
         util::InputBuffer buffer(ncr_dhcid.data(), ncr_dhcid.size());
-        dns::rdata::in::DHCID rdata(buffer, ncr_dhcid.size());
+        dns::rdata::ConstRdataPtr rdata (new dns::rdata::in::
+                                         DHCID(buffer, ncr_dhcid.size()));
         rrset->addRdata(rdata);
     } catch (const std::exception& ex) {
         isc_throw(NameChangeTransactionError, "Cannot add DCHID rdata: "
                   << ex.what());
     }
+
 }
 
 void
@@ -301,7 +304,8 @@ NameChangeTransaction::addPtrRdata(dns::RRsetPtr& rrset) {
     }
 
     try {
-        dns::rdata::generic::PTR rdata(getNcr()->getFqdn());
+        dns::rdata::ConstRdataPtr rdata(new dns::rdata::generic::
+                                        PTR(getNcr()->getFqdn()));
         rrset->addRdata(rdata);
     } catch (const std::exception& ex) {
         isc_throw(NameChangeTransactionError, "Cannot add PTR rdata: "
@@ -412,7 +416,7 @@ NameChangeTransaction::getUpdateAttempts() const {
 
 const dns::RRType&
 NameChangeTransaction::getAddressRRType() const {
-    return (ncr_->isV4() ?  dns::RRType::A(): dns::RRType::AAAA());
+    return (ncr_->isV4() ?  dns::RRType::A() : dns::RRType::AAAA());
 }
 
 } // namespace isc::d2

+ 4 - 4
src/bin/d2/nc_trans.h

@@ -277,10 +277,10 @@ protected:
     /// If the maximum number of attempts has been reached, it will transition
     /// to the given state with a next event of SERVER_IO_ERROR_EVT.
     ///
-    /// @param server_sel_state  State to transition to if maximum attempts
+    /// @param fail_to_state  State to transition to if maximum attempts
     /// have been tried.
     ///
-    void retryTransition(const int server_sel_state);
+    void retryTransition(const int fail_to_state);
 
     /// @brief Sets the update request packet to the given packet.
     ///
@@ -435,13 +435,13 @@ public:
 
     /// @brief Fetches the forward DdnsDomain.
     ///
-    /// @return A pointer reference to the forward DdnsDomain.  If 
+    /// @return A pointer reference to the forward DdnsDomain.  If
     /// the request does not include a forward change, the pointer will empty.
     DdnsDomainPtr& getForwardDomain();
 
     /// @brief Fetches the reverse DdnsDomain.
     ///
-    /// @return A pointer reference to the reverse DdnsDomain.  If 
+    /// @return A pointer reference to the reverse DdnsDomain.  If
     /// the request does not include a reverse change, the pointer will empty.
     DdnsDomainPtr& getReverseDomain();
 

+ 2 - 0
src/bin/d2/tests/Makefile.am

@@ -67,6 +67,7 @@ d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
 d2_unittests_SOURCES += ../labeled_value.cc ../labeled_value.h
 d2_unittests_SOURCES += ../nc_add.cc ../nc_add.h
+d2_unittests_SOURCES += ../nc_remove.cc ../nc_remove.h
 d2_unittests_SOURCES += ../nc_trans.cc ../nc_trans.h
 d2_unittests_SOURCES += ../state_model.cc ../state_model.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
@@ -83,6 +84,7 @@ d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += labeled_value_unittests.cc
 d2_unittests_SOURCES += nc_add_unittests.cc
+d2_unittests_SOURCES += nc_remove_unittests.cc
 d2_unittests_SOURCES += nc_test_utils.cc nc_test_utils.h
 d2_unittests_SOURCES += nc_trans_unittests.cc
 d2_unittests_SOURCES += state_model_unittests.cc

+ 33 - 11
src/bin/d2/tests/nc_add_unittests.cc

@@ -407,14 +407,14 @@ TEST_F(NameAddTransactionTest, buildForwardAdd) {
     NameAddStubPtr name_add;
     ASSERT_NO_THROW(name_add = makeTransaction4());
     ASSERT_NO_THROW(name_add->buildAddFwdAddressRequest());
-    checkForwardAddRequest(*name_add);
+    checkAddFwdAddressRequest(*name_add);
 
     // Create a IPv6 forward add transaction.
     // Verify the request builds without error.
     // and then verify the request contents.
     ASSERT_NO_THROW(name_add = makeTransaction6());
     ASSERT_NO_THROW(name_add->buildAddFwdAddressRequest());
-    checkForwardAddRequest(*name_add);
+    checkAddFwdAddressRequest(*name_add);
 }
 
 /// @brief Tests construction of a DNS update request for replacing a forward
@@ -426,14 +426,14 @@ TEST_F(NameAddTransactionTest, buildReplaceFwdAddressRequest) {
     NameAddStubPtr name_add;
     ASSERT_NO_THROW(name_add = makeTransaction4());
     ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
-    checkForwardReplaceRequest(*name_add);
+    checkReplaceFwdAddressRequest(*name_add);
 
     // Create a IPv6 forward replace transaction.
     // Verify the request builds without error.
     // and then verify the request contents.
     ASSERT_NO_THROW(name_add = makeTransaction6());
     ASSERT_NO_THROW(name_add->buildReplaceFwdAddressRequest());
-    checkForwardReplaceRequest(*name_add);
+    checkReplaceFwdAddressRequest(*name_add);
 }
 
 /// @brief Tests the construction of a DNS update request for replacing a
@@ -445,14 +445,14 @@ TEST_F(NameAddTransactionTest, buildReplaceRevPtrsRequest) {
     NameAddStubPtr name_add;
     ASSERT_NO_THROW(name_add = makeTransaction4());
     ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
-    checkReverseReplaceRequest(*name_add);
+    checkReplaceRevPtrsRequest(*name_add);
 
     // Create a IPv6 reverse replace transaction.
     // Verify the request builds without error.
     // and then verify the request contents.
     ASSERT_NO_THROW(name_add = makeTransaction6());
     ASSERT_NO_THROW(name_add->buildReplaceRevPtrsRequest());
-    checkReverseReplaceRequest(*name_add);
+    checkReplaceRevPtrsRequest(*name_add);
 }
 
 // Tests the readyHandler functionality.
@@ -632,7 +632,7 @@ TEST_F(NameAddTransactionTest, addingFwdAddrsHandler_FwdOnlyAddOK) {
     EXPECT_NO_THROW(name_add->addingFwdAddrsHandler());
 
     // Verify that an update message was constructed properly.
-    checkForwardAddRequest(*name_add);
+    checkAddFwdAddressRequest(*name_add);
 
     // Verify that we are still in this state and next event is NOP_EVT.
     // This indicates we "sent" the message and are waiting for IO completion.
@@ -943,7 +943,7 @@ TEST_F(NameAddTransactionTest, replacingFwdAddrsHandler_FwdOnlyAddOK) {
     EXPECT_NO_THROW(name_add->replacingFwdAddrsHandler());
 
     // Verify that an update message was constructed properly.
-    checkForwardReplaceRequest(*name_add);
+    checkReplaceFwdAddressRequest(*name_add);
 
     // Verify that we are still in this state and next event is NOP_EVT.
     // This indicates we "sent" the message and are waiting for IO completion.
@@ -1366,7 +1366,7 @@ TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_FwdOnlyAddOK) {
     EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
 
     // Verify that an update message was constructed properly.
-    checkReverseReplaceRequest(*name_add);
+    checkReplaceRevPtrsRequest(*name_add);
 
     // Verify that we are still in this state and next event is NOP_EVT.
     // This indicates we "sent" the message and are waiting for IO completion.
@@ -1419,8 +1419,7 @@ TEST_F(NameAddTransactionTest, replacingRevPtrsHandler_OtherRcode) {
     name_add->fakeResponse(DNSClient::SUCCESS, dns::Rcode::REFUSED());
 
     // Run replacingRevPtrsHandler again to process the response.
-    //EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
-    (name_add->replacingRevPtrsHandler());
+    EXPECT_NO_THROW(name_add->replacingRevPtrsHandler());
 
     // Completion flags should still be false.
     EXPECT_FALSE(name_add->getForwardChangeCompleted());
@@ -1634,6 +1633,29 @@ TEST_F(NameAddTransactionTest, processAddFailedHandler) {
     EXPECT_THROW(name_add->processAddFailedHandler(), NameAddTransactionError);
 }
 
+// Tests the processAddFailedHandler functionality.
+// It verifies behavior for posted event of NO_MORE_SERVERS_EVT.
+TEST_F(NameAddTransactionTest, processAddFailedHandler_NoMoreServers) {
+    NameAddStubPtr name_remove;
+    // Create and prep a transaction, poised to run the handler.
+    ASSERT_NO_THROW(name_remove =
+                    prepHandlerTest(NameChangeTransaction::
+                                    PROCESS_TRANS_FAILED_ST,
+                                    NameChangeTransaction::
+                                    NO_MORE_SERVERS_EVT));
+
+    // Run processAddFailedHandler.
+    EXPECT_NO_THROW(name_remove->processAddFailedHandler());
+
+    // Verify that a server was selected.
+    EXPECT_EQ(dhcp_ddns::ST_FAILED, name_remove->getNcrStatus());
+
+    // Verify that the model has ended. (Remember, the transaction failed NOT
+    // the model.  The model should have ended normally.)
+    EXPECT_EQ(StateModel::END_ST, name_remove->getCurrState());
+    EXPECT_EQ(StateModel::END_EVT, name_remove->getNextEvent());
+}
+
 // Tests addingFwdAddrsHandler with the following scenario:
 //
 //  The request includes only a forward change.

File diff suppressed because it is too large
+ 1872 - 0
src/bin/d2/tests/nc_remove_unittests.cc


+ 269 - 5
src/bin/d2/tests/nc_test_utils.cc

@@ -29,6 +29,11 @@ namespace d2 {
 const char* TEST_DNS_SERVER_IP = "127.0.0.1";
 size_t TEST_DNS_SERVER_PORT = 5301;
 
+const bool HAS_RDATA = true;
+const bool NO_RDATA = false;
+
+//*************************** FauxServer class ***********************
+
 FauxServer::FauxServer(asiolink::IOService& io_service,
                        asiolink::IOAddress& address, size_t port)
     :io_service_(io_service), address_(address), port_(port),
@@ -136,6 +141,135 @@ FauxServer::requestHandler(const asio::error_code& error,
     }
 }
 
+//********************** TransactionTest class ***********************
+
+const unsigned int TransactionTest::FORWARD_CHG = 0x01;
+const unsigned int TransactionTest::REVERSE_CHG = 0x02;
+const unsigned int TransactionTest::FWD_AND_REV_CHG = REVERSE_CHG | FORWARD_CHG;
+
+TransactionTest::TransactionTest()
+    : io_service_(new isc::asiolink::IOService()), ncr_(),
+    timer_(*io_service_), run_time_(0) {
+}
+
+TransactionTest::~TransactionTest() {
+}
+
+void
+TransactionTest::runTimedIO(int run_time) {
+    run_time_ = run_time;
+    timer_.setup(boost::bind(&TransactionTest::timesUp, this), run_time_);
+    io_service_->run();
+}
+
+void
+TransactionTest::timesUp() {
+    io_service_->stop();
+    FAIL() << "Test Time: " << run_time_ << " expired";
+}
+
+void
+TransactionTest::setupForIPv4Transaction(dhcp_ddns::NameChangeType chg_type,
+                                         int change_mask) {
+    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.
+    ncr_ = dhcp_ddns::NameChangeRequest::fromJSON(msg_str);
+
+    // Set the change type.
+    ncr_->setChangeType(chg_type);
+
+    // 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",
+                        "127.0.0.1", 5301);
+        addDomainServer(forward_domain_, "forward2.example.com",
+                        "127.0.0.1", 5302);
+    }
+
+    // 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",
+                        "127.0.0.1", 5301);
+        addDomainServer(reverse_domain_, "reverse2.example.com",
+                        "127.0.0.1", 5302);
+    }
+}
+
+void
+TransactionTest::setupForIPv6Transaction(dhcp_ddns::NameChangeType chg_type,
+                                         int change_mask) {
+    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.
+    ncr_ = makeNcrFromString(msg_str);
+
+    // Set the change type.
+    ncr_->setChangeType(chg_type);
+
+    // 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",
+                        "::1", 5301);
+        addDomainServer(forward_domain_, "fwd6-server2.example.com",
+                        "::1", 5302);
+    }
+
+    // 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",
+                        "::1", 5301);
+        addDomainServer(reverse_domain_, "rev6-server2.example.com",
+                        "::1", 5302);
+    }
+}
+
+
+//********************** Functions ****************************
 
 void
 checkRRCount(const D2UpdateMessagePtr& request,
@@ -158,13 +292,15 @@ checkZone(const D2UpdateMessagePtr& request, const std::string& exp_zone_name) {
 void
 checkRR(dns::RRsetPtr rrset, const std::string& exp_name,
               const dns::RRClass& exp_class, const dns::RRType& exp_type,
-              unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr) {
+              unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr,
+              bool has_rdata) {
     // Verify the FQDN/DHCID RR fields.
     EXPECT_EQ(exp_name, rrset->getName().toText());
     EXPECT_EQ(exp_class.getCode(), rrset->getClass().getCode());
     EXPECT_EQ(exp_type.getCode(), rrset->getType().getCode());
     EXPECT_EQ(exp_ttl, rrset->getTTL().getValue());
-    if (exp_type == dns::RRType::ANY() || exp_class == dns::RRClass::ANY()) {
+    if ((!has_rdata) || 
+       (exp_type == dns::RRType::ANY() || exp_class == dns::RRClass::ANY())) {
         // ANY types do not have RData
         ASSERT_EQ(0, rrset->getRdataCount());
         return;
@@ -229,7 +365,7 @@ void addDomainServer(DdnsDomainPtr& domain, const std::string& name,
 
 // Verifies that the contents of the given transaction's  DNS update request
 // is correct for adding a forward DNS entry
-void checkForwardAddRequest(NameChangeTransaction& tran) {
+void checkAddFwdAddressRequest(NameChangeTransaction& tran) {
     const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
     ASSERT_TRUE(request);
 
@@ -276,7 +412,7 @@ void checkForwardAddRequest(NameChangeTransaction& tran) {
 
 // Verifies that the contents of the given transaction's  DNS update request
 // is correct for replacing a forward DNS entry
-void checkForwardReplaceRequest(NameChangeTransaction& tran) {
+void checkReplaceFwdAddressRequest(NameChangeTransaction& tran) {
     const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
     ASSERT_TRUE(request);
 
@@ -332,7 +468,7 @@ void checkForwardReplaceRequest(NameChangeTransaction& tran) {
 
 // Verifies that the contents of the given transaction's  DNS update request
 // is correct for replacing a reverse DNS entry
-void checkReverseReplaceRequest(NameChangeTransaction& tran) {
+void checkReplaceRevPtrsRequest(NameChangeTransaction& tran) {
     const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
     ASSERT_TRUE(request);
 
@@ -390,5 +526,133 @@ void checkReverseReplaceRequest(NameChangeTransaction& tran) {
     ASSERT_NO_THROW(request->toWire(renderer));
 }
 
+void checkRemoveFwdAddressRequest(NameChangeTransaction& tran) {
+    const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+    ASSERT_TRUE(request);
+
+    // Safety check.
+    dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+    ASSERT_TRUE(ncr);
+
+    std::string exp_zone_name = tran.getForwardDomain()->getName();
+    std::string exp_fqdn = ncr->getFqdn();
+
+    // Verify the zone section.
+    checkZone(request, exp_zone_name);
+
+    // Verify there is 1 RR in the PREREQUISITE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+
+    // Verify the DHCID matching assertion RR.
+    dns::RRsetPtr rrset;
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_PREREQUISITE, 0));
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+            0, ncr);
+
+    // Verify there is 1 RR in the UPDATE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+    // Verify the FQDN/IP delete RR.
+    const dns::RRType& exp_ip_rr_type = tran.getAddressRRType();
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 0));
+    checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), exp_ip_rr_type,
+            0, ncr);
+
+    // Verify that it will render toWire without throwing.
+    dns::MessageRenderer renderer;
+    ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void checkRemoveFwdRRsRequest(NameChangeTransaction& tran) {
+    const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+    ASSERT_TRUE(request);
+
+    // Safety check.
+    dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+    ASSERT_TRUE(ncr);
+
+    std::string exp_zone_name = tran.getForwardDomain()->getName();
+    std::string exp_fqdn = ncr->getFqdn();
+
+    // Verify the zone section.
+    checkZone(request, exp_zone_name);
+
+    // Verify there is 1 RR in the PREREQUISITE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 3);
+
+    // Verify the DHCID matches assertion.
+    dns::RRsetPtr rrset;
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_PREREQUISITE, 0));
+    checkRR(rrset, exp_fqdn, dns::RRClass::IN(), dns::RRType::DHCID(),
+            0, ncr);
+
+    // Verify the NO A RRs assertion.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_PREREQUISITE, 1));
+    checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::A(),
+            0, ncr, NO_RDATA);
+
+    // Verify the NO AAAA RRs assertion.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_PREREQUISITE, 2));
+    checkRR(rrset, exp_fqdn, dns::RRClass::NONE(), dns::RRType::AAAA(),
+            0, ncr, NO_RDATA);
+
+    // Verify there is 1 RR in the UPDATE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+    // Verify the delete all for the FQDN RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 0));
+    checkRR(rrset, exp_fqdn, dns::RRClass::ANY(), dns::RRType::ANY(),
+            0, ncr);
+
+    // Verify that it will render toWire without throwing.
+    dns::MessageRenderer renderer;
+    ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+void checkRemoveRevPtrsRequest(NameChangeTransaction& tran) {
+    const D2UpdateMessagePtr& request = tran.getDnsUpdateRequest();
+    ASSERT_TRUE(request);
+
+    // Safety check.
+    dhcp_ddns::NameChangeRequestPtr ncr = tran.getNcr();
+    ASSERT_TRUE(ncr);
+
+    std::string exp_zone_name = tran.getReverseDomain()->getName();
+    std::string exp_rev_addr = D2CfgMgr::reverseIpAddress(ncr->getIpAddress());
+
+    // Verify the zone section.
+    checkZone(request, exp_zone_name);
+
+    // Verify there is 1 RR in the PREREQUISITE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_PREREQUISITE, 1);
+
+    // Verify the FQDN-PTRNAME assertion RR.
+    dns::RRsetPtr rrset;
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_PREREQUISITE, 0));
+    checkRR(rrset, exp_rev_addr, dns::RRClass::IN(), dns::RRType::PTR(),
+            0, ncr);
+
+    // Verify there is 1 RR in the UPDATE Section.
+    checkRRCount(request, D2UpdateMessage::SECTION_UPDATE, 1);
+
+    // Verify the delete all for the FQDN RR.
+    ASSERT_TRUE(rrset = getRRFromSection(request, D2UpdateMessage::
+                                                  SECTION_UPDATE, 0));
+    checkRR(rrset, exp_rev_addr, dns::RRClass::ANY(), dns::RRType::ANY(),
+            0, ncr);
+
+    // Verify that it will render toWire without throwing.
+    dns::MessageRenderer renderer;
+    ASSERT_NO_THROW(request->toWire(renderer));
+}
+
+
 }; // namespace isc::d2
 }; // namespace isc

+ 97 - 4
src/bin/d2/tests/nc_test_utils.h

@@ -21,6 +21,7 @@
 
 #include <asio/ip/udp.hpp>
 #include <asio/socket_base.hpp>
+#include <gtest/gtest.h>
 
 namespace isc {
 namespace d2 {
@@ -94,6 +95,68 @@ public:
                         const dns::Rcode& response_rcode);
 };
 
+/// @brief Base class Test fixture for testing transactions.
+class TransactionTest : public ::testing::Test {
+public:
+    IOServicePtr io_service_;
+    dhcp_ddns::NameChangeRequestPtr ncr_;
+    DdnsDomainPtr forward_domain_;
+    DdnsDomainPtr reverse_domain_;
+    asiolink::IntervalTimer timer_;
+    int run_time_;
+
+    /// #brief constants used to specify change directions for a transaction.
+    static const unsigned int FORWARD_CHG;      // Only forward change.
+    static const unsigned int REVERSE_CHG;      // Only reverse change.
+    static const unsigned int FWD_AND_REV_CHG;  // Both forward and reverse.
+
+    TransactionTest();
+    virtual ~TransactionTest();
+
+    /// @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);
+
+    /// @brief IO Timer expiration handler
+    ///
+    /// Stops the IOSerivce and fails the current test.
+    virtual void timesUp();
+
+    /// @brief Creates a transaction which requests an IPv4 DNS update.
+    ///
+    /// The transaction is constructed around a predefined (i.e. "canned")
+    /// IPv4 NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_type selects the type of change requested, CHG_ADD or
+    /// CHG_REMOVE.
+    /// @param change_mask determines which change directions are requested
+    /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+    void setupForIPv4Transaction(dhcp_ddns::NameChangeType change_type,
+                                 int change_mask);
+
+    /// @brief Creates a transaction which requests an IPv6 DNS update.
+    ///
+    /// The transaction is constructed around a predefined (i.e. "canned")
+    /// IPv6 NameChangeRequest. The request has both forward and reverse DNS
+    /// changes requested.  Based upon the change mask, the transaction
+    /// will have either the forward, reverse, or both domains populated.
+    ///
+    /// @param change_type selects the type of change requested, CHG_ADD or
+    /// CHG_REMOVE.
+    /// @param change_mask determines which change directions are requested
+    /// FORWARD_CHG, REVERSE_CHG, or FWD_AND_REV_CHG.
+    void setupForIPv6Transaction(dhcp_ddns::NameChangeType change_type,
+                                 int change_mask);
+};
+
+
 /// @brief Tests the number of RRs in a request section against a given count.
 ///
 /// This function actually returns the number of RRsetPtrs in a section. Since
@@ -124,9 +187,14 @@ extern void checkZone(const D2UpdateMessagePtr& request,
 /// @param exp_typ expected RRType value of RRset
 /// @param exp_ttl  expected TTL value of RRset
 /// @param ncr NameChangeRequest on which the RRset is based
+/// @param has_rdata if true, RRset's rdata will be checked based on it's
+/// RRType.  Set this to false if the RRset's type supports Rdata but it does
+/// not contain it.  For instance, prerequisites of type NONE have no Rdata
+/// where udpates of type NONE may.
 extern void checkRR(dns::RRsetPtr rrset, const std::string& exp_name,
                     const dns::RRClass& exp_class, const dns::RRType& exp_type,
-                    unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr);
+                    unsigned int exp_ttl, dhcp_ddns::NameChangeRequestPtr ncr,
+                    bool has_rdata=true);
 
 /// @brief Fetches an RR(set) from a given section of a request
 ///
@@ -163,7 +231,7 @@ extern dns::RRsetPtr getRRFromSection(const D2UpdateMessagePtr& request,
 /// adding a forward DNS mapping.
 ///
 /// @param tran Transaction containing the request to be verified.
-extern void checkForwardAddRequest(NameChangeTransaction& tran);
+extern void checkAddFwdAddressRequest(NameChangeTransaction& tran);
 
 /// @brief Verifies a forward mapping replacement DNS update request
 ///
@@ -171,7 +239,7 @@ extern void checkForwardAddRequest(NameChangeTransaction& tran);
 /// replacing a forward DNS mapping.
 ///
 /// @param tran Transaction containing the request to be verified.
-extern void checkForwardReplaceRequest(NameChangeTransaction& tran);
+extern void checkReplaceFwdAddressRequest(NameChangeTransaction& tran);
 
 /// @brief Verifies a reverse mapping replacement DNS update request
 ///
@@ -179,7 +247,32 @@ extern void checkForwardReplaceRequest(NameChangeTransaction& tran);
 /// replacing a reverse DNS mapping.
 ///
 /// @param tran Transaction containing the request to be verified.
-extern void checkReverseReplaceRequest(NameChangeTransaction& tran);
+extern void checkReplaceRevPtrsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward address removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing the forward address DNS entry.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveFwdAddressRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a forward RR removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing forward RR DNS entries.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveFwdRRsRequest(NameChangeTransaction& tran);
+
+/// @brief Verifies a reverse mapping removal DNS update request
+///
+/// Tests that the DNS Update request for a given transaction, is correct for
+/// removing a reverse DNS mapping.
+///
+/// @param tran Transaction containing the request to be verified.
+extern void checkRemoveRevPtrsRequest(NameChangeTransaction& tran);
+
 
 /// @brief Creates a NameChangeRequest from JSON string.
 ///

+ 7 - 26
src/bin/d2/tests/nc_trans_unittests.cc

@@ -997,19 +997,10 @@ TEST_F(NameChangeTransactionTest, addLeaseAddressRData) {
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
     dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
 
-    // Test a lease rdata add failure.
-    // As you cannot stuff an invalid address into an NCR, the only failure
-    // that can be induced is a mismatch between the RData and the RRset.
-    // Attempt to add a lease address Rdata, this should fail.
-    // Create an Any class/Any type RRset, they are not allowed to contain
-    // rdata.
-    dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::ANY(),
-                         dns::RRType::ANY(), dns::RRTTL(0)));
-    ASSERT_THROW(name_change->addLeaseAddressRdata(rrset), std::exception);
-
     // Verify we can add a lease RData to an valid RRset.
-    rrset.reset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
-                               name_change->getAddressRRType(), dns::RRTTL(0)));
+    dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+                                       name_change->getAddressRRType(),
+                                       dns::RRTTL(0)));
     ASSERT_NO_THROW(name_change->addLeaseAddressRdata(rrset));
 
     // Verify the Rdata was added and the value is correct.
@@ -1026,14 +1017,9 @@ TEST_F(NameChangeTransactionTest, addDhcidRdata) {
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
     dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
 
-    // Test a DHCID rdata add failure.
-    dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::ANY(),
-                         dns::RRType::ANY(), dns::RRTTL(0)));
-    ASSERT_THROW(name_change->addDhcidRdata(rrset), std::exception);
-
     // Verify we can add a lease RData to an valid RRset.
-    rrset.reset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
-                               dns::RRType::DHCID(), dns::RRTTL(0)));
+    dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+                                       dns::RRType::DHCID(), dns::RRTTL(0)));
     ASSERT_NO_THROW(name_change->addDhcidRdata(rrset));
 
     // Verify the Rdata was added and the value is correct.
@@ -1053,14 +1039,9 @@ TEST_F(NameChangeTransactionTest, addPtrRdata) {
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
     dhcp_ddns::NameChangeRequestPtr ncr = name_change->getNcr();
 
-    // Test a PTR rdata add failure.
-    dns::RRsetPtr rrset(new dns::RRset(dns::Name("bs"), dns::RRClass::ANY(),
-                         dns::RRType::ANY(), dns::RRTTL(0)));
-    ASSERT_THROW(name_change->addPtrRdata(rrset), std::exception);
-
     // Verify we can add a PTR RData to an valid RRset.
-    rrset.reset(new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
-                               dns::RRType::PTR(), dns::RRTTL(0)));
+    dns::RRsetPtr rrset (new dns::RRset(dns::Name("bs"), dns::RRClass::IN(),
+                                        dns::RRType::PTR(), dns::RRTTL(0)));
     ASSERT_NO_THROW(name_change->addPtrRdata(rrset));
 
     // Verify the Rdata was added and the value is correct.

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

@@ -216,7 +216,6 @@ protected:
         return (parser);
     }
 
-
     /// @brief Determines if the given option space name and code describe
     /// a standard option for the DCHP4 server.
     ///
@@ -395,6 +394,8 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
         parser = new DbAccessParser(config_id);
     } else if (config_id.compare("hooks-libraries") == 0) {
         parser = new HooksLibrariesParser(config_id);
+    } else if (config_id.compare("echo-client-id") == 0) {
+        parser = new BooleanParser(config_id, globalContext()->boolean_values_);
     } else {
         isc_throw(NotImplemented,
                 "Parser error: Global configuration parameter not supported: "
@@ -404,6 +405,20 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     return (parser);
 }
 
+void commitGlobalOptions() {
+    // Although the function is modest for now, it is certain that the number
+    // of global switches will increase over time, hence the name.
+
+    // Set whether v4 server is supposed to echo back client-id (yes = RFC6842
+    // compatible, no = backward compatibility)
+    try {
+        bool echo_client_id = globalContext()->boolean_values_->getParam("echo-client-id");
+        CfgMgr::instance().echoClientId(echo_client_id);
+    } catch (...) {
+        // Ignore errors. This flag is optional
+    }
+}
+
 isc::data::ConstElementPtr
 configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
     if (!config_set) {
@@ -536,6 +551,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
                 iface_parser->commit();
             }
 
+            // Apply global options
+            commitGlobalOptions();
+
             // 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.
@@ -579,4 +597,3 @@ ParserContextPtr& globalContext() {
 
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
-

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

@@ -54,6 +54,12 @@
         "item_default": ""
       },
 
+      { "item_name": "echo-client-id",
+        "item_type": "boolean",
+        "item_optional": true,
+        "item_default": true
+      },
+
       { "item_name": "option-def",
         "item_type": "list",
         "item_optional": false,

+ 3 - 1
src/bin/dhcp4/dhcp4_srv.cc

@@ -653,8 +653,10 @@ Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setGiaddr(question->getGiaddr());
 
     // Let's copy client-id to response. See RFC6842.
+    // It is possible to disable RFC6842 to keep backward compatibility
+    bool echo = CfgMgr::instance().echoClientId();
     OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
-    if (client_id) {
+    if (client_id && echo) {
         answer->addOption(client_id);
     }
 

+ 41 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -557,6 +557,47 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
 }
 
+// Check whether it is possible to configure echo-client-id
+TEST_F(Dhcp4ParserTest, echoClientId) {
+
+    ConstElementPtr status;
+
+    string config_false = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"echo-client-id\": false,"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    string config_true = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"echo-client-id\": true,"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json_false = Element::fromJSON(config_false);
+    ElementPtr json_true = Element::fromJSON(config_true);
+
+    // Let's check the default. It should be true
+    ASSERT_TRUE(CfgMgr::instance().echoClientId());
+
+    // Now check that "false" configuration is really applied.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json_false));
+    ASSERT_FALSE(CfgMgr::instance().echoClientId());
+
+    // Now check that "true" configuration is really applied.
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json_true));
+    ASSERT_TRUE(CfgMgr::instance().echoClientId());
+
+    // In any case revert back to the default value (true)
+    CfgMgr::instance().echoClientId(true);
+}
+
 // This test checks if it is possible to override global values
 // on a per subnet basis.
 TEST_F(Dhcp4ParserTest, subnetLocal) {

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

@@ -608,6 +608,32 @@ TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
     cout << "Offered address to client3=" << addr3.toText() << endl;
 }
 
+// Checks whether echoing back client-id is controllable, i.e.
+// whether the server obeys echo-client-id and sends (or not)
+// client-id
+TEST_F(Dhcpv4SrvTest, discoverEchoClientId) {
+    NakedDhcpv4Srv srv(0);
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get an offer
+    Pkt4Ptr offer = srv.processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+    checkClientId(offer, clientid);
+
+    CfgMgr::instance().echoClientId(false);
+    offer = srv.processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(offer, DHCPOFFER, 1234);
+    checkClientId(offer, clientid);
+}
+
 // This test verifies that incoming REQUEST can be handled properly, that an
 // ACK is generated, that the response has an address and that address
 // really belongs to the configured pool.
@@ -750,6 +776,30 @@ TEST_F(Dhcpv4SrvTest, ManyRequests) {
     cout << "Offered address to client3=" << addr3.toText() << endl;
 }
 
+// Checks whether echoing back client-id is controllable
+TEST_F(Dhcpv4SrvTest, requestEchoClientId) {
+    NakedDhcpv4Srv srv(0);
+
+    Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+    dis->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+
+    // Pass it to the server and get ACK
+    Pkt4Ptr ack = srv.processRequest(dis);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPACK, 1234);
+    checkClientId(ack, clientid);
+
+    CfgMgr::instance().echoClientId(false);
+    ack = srv.processDiscover(dis);
+
+    // Check if we get response at all
+    checkResponse(ack, DHCPOFFER, 1234);
+    checkClientId(ack, clientid);
+}
+
 
 // This test verifies that incoming (positive) REQUEST/Renewing can be handled properly, that a
 // REPLY is generated, that the response has an address and that address

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

@@ -67,6 +67,12 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     valid_iface_ = ifaces.begin()->getName();
 }
 
+Dhcpv4SrvTest::~Dhcpv4SrvTest() {
+
+    // Make sure that we revert to default value
+    CfgMgr::instance().echoClientId(true);
+}
+
 void Dhcpv4SrvTest::addPrlOption(Pkt4Ptr& pkt) {
 
     OptionUint8ArrayPtr option_prl =
@@ -332,12 +338,21 @@ void Dhcpv4SrvTest::checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_
 }
 
 void Dhcpv4SrvTest::checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
+
+    bool include_clientid = CfgMgr::instance().echoClientId();
+
     // check that server included our own client-id
     OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
-    ASSERT_TRUE(opt);
-    EXPECT_EQ(expected_clientid->getType(), opt->getType());
-    EXPECT_EQ(expected_clientid->len(), opt->len());
-    EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+    if (include_clientid) {
+        // Normal mode: echo back (see RFC6842)
+        ASSERT_TRUE(opt);
+        EXPECT_EQ(expected_clientid->getType(), opt->getType());
+        EXPECT_EQ(expected_clientid->len(), opt->len());
+        EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+    } else {
+        // Backward compatibility mode for pre-RFC6842 devices
+        ASSERT_FALSE(opt);
+    }
 }
 
 ::testing::AssertionResult

+ 6 - 2
src/bin/dhcp4/tests/dhcp4_test_utils.h

@@ -81,8 +81,7 @@ public:
     Dhcpv4SrvTest();
 
     /// @brief destructor
-    virtual ~Dhcpv4SrvTest() {
-    }
+    virtual ~Dhcpv4SrvTest();
 
     /// @brief Add 'Parameter Request List' option to the packet.
     ///
@@ -189,6 +188,11 @@ public:
     void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid);
 
     /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
+    ///
+    /// This method follows values reported by CfgMgr in echoClientId() method.
+    /// Depending on its configuration, the client-id is either mandatory or
+    /// forbidden to appear in the response.
+    ///
     /// @param rsp response packet to be validated
     /// @param expected_clientid expected value of client-id
     void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid);

+ 5 - 2
src/lib/dhcp/iface_mgr.h

@@ -208,9 +208,10 @@ public:
     ///
     /// @note Implementation of this method is OS-dependent as bits have
     /// different meaning on each OS.
+    /// We need to make it 64 bits, because Solaris uses 64, not 32 bits.
     ///
     /// @param flags bitmask value returned by OS in interface detection
-    void setFlags(uint32_t flags);
+    void setFlags(uint64_t flags);
 
     /// @brief Returns interface index.
     ///
@@ -364,7 +365,9 @@ public:
 
     /// Interface flags (this value is as is returned by OS,
     /// it may mean different things on different OSes).
-    uint32_t flags_;
+    /// Solaris based os have unsigned long flags field (64 bits).
+    /// It is usually 32 bits, though.
+    uint64_t flags_;
 
     /// Indicates that IPv4 sockets should (true) or should not (false)
     /// be opened on this interface.

+ 95 - 3
src/lib/dhcp/iface_mgr_bsd.cc

@@ -20,6 +20,12 @@
 #include <dhcp/pkt_filter_inet.h>
 #include <exceptions/exceptions.h>
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
@@ -28,11 +34,97 @@ using namespace isc::dhcp;
 namespace isc {
 namespace dhcp {
 
+/// This is a BSD specific interface detection method.
 void
 IfaceMgr::detectIfaces() {
-    /// @todo do the actual detection on BSDs. Currently just calling
-    /// stub implementation.
-    stubDetectIfaces();
+    struct ifaddrs* iflist = 0;// The whole interface list
+    struct ifaddrs* ifptr = 0; // The interface we're processing now
+
+    // Gets list of ifaddrs struct
+    if(getifaddrs(&iflist) != 0) {
+        isc_throw(Unexpected, "Network interfaces detection failed.");
+    }
+
+    typedef map<string, Iface> ifaceLst;
+    ifaceLst::iterator iface_iter;
+    ifaceLst ifaces;
+
+    // First lookup for getting interfaces ...
+    for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        const char * ifname = ifptr->ifa_name;
+        uint ifindex = 0;
+
+        if (!(ifindex = if_nametoindex(ifname))) {
+            // Interface name does not have corresponding index ...
+            freeifaddrs(iflist);
+            isc_throw(Unexpected, "Interface " << ifname << " has no index");
+        }
+
+        if ((iface_iter = ifaces.find(ifname)) != ifaces.end()) {
+            continue;
+        }
+
+        Iface iface(ifname, ifindex);
+        iface.setFlags(ifptr->ifa_flags);
+        ifaces.insert(pair<string, Iface>(ifname, iface));
+    }
+
+    // Second lookup to get MAC and IP addresses
+    for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        if ((iface_iter = ifaces.find(ifptr->ifa_name)) == ifaces.end()) {
+            continue;
+        }
+        // Common byte pointer for following data
+        const uint8_t * ptr = 0;
+        if(ifptr->ifa_addr->sa_family == AF_LINK) {
+            // HWAddr
+            struct sockaddr_dl * ldata =
+                reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));
+
+            iface_iter->second.setHWType(ldata->sdl_type);
+            iface_iter->second.setMac(ptr, ldata->sdl_alen);
+        } else if(ifptr->ifa_addr->sa_family == AF_INET6) {
+            // IPv6 Addr
+            struct sockaddr_in6 * adata =
+                reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(&adata->sin6_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
+            iface_iter->second.addAddress(a);
+        } else {
+            // IPv4 Addr
+            struct sockaddr_in * adata =
+                reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(&adata->sin_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
+            iface_iter->second.addAddress(a);
+        }
+    }
+
+    freeifaddrs(iflist);
+
+    // Interfaces registering
+    for(ifaceLst::const_iterator iface_iter = ifaces.begin();
+        iface_iter != ifaces.end(); ++iface_iter) {
+        ifaces_.push_back(iface_iter->second);
+    }
+}
+
+/// @brief sets flag_*_ fields
+///
+/// Like Linux version, os specific flags
+///
+/// @params flags
+void Iface::setFlags(uint64_t flags) {
+    flags_ = flags;
+
+    flag_loopback_ = flags & IFF_LOOPBACK;
+    flag_up_ = flags & IFF_UP;
+    flag_running_ = flags & IFF_RUNNING;
+    flag_multicast_ = flags & IFF_MULTICAST;
+    flag_broadcast_ = flags & IFF_BROADCAST;
 }
 
 void IfaceMgr::os_send4(struct msghdr& /*m*/,

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

@@ -502,7 +502,7 @@ void IfaceMgr::detectIfaces() {
 /// on different OSes.
 ///
 /// @param flags flags bitfield read from OS
-void Iface::setFlags(uint32_t flags) {
+void Iface::setFlags(uint64_t flags) {
     flags_ = flags;
 
     flag_loopback_ = flags & IFF_LOOPBACK;

+ 100 - 4
src/lib/dhcp/iface_mgr_sun.cc

@@ -20,6 +20,12 @@
 #include <dhcp/pkt_filter_inet.h>
 #include <exceptions/exceptions.h>
 
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if_dl.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
 using namespace std;
 using namespace isc;
 using namespace isc::asiolink;
@@ -28,11 +34,100 @@ using namespace isc::dhcp;
 namespace isc {
 namespace dhcp {
 
+/// This is a Solaris specific interface detection code. It works on Solaris 11
+/// only, as earlier versions did not support getifaddrs() API.
 void
 IfaceMgr::detectIfaces() {
-    /// @todo do the actual detection on Solaris. Currently just calling
-    /// stub implementation.
-    stubDetectIfaces();
+    struct ifaddrs * iflist = 0, * ifptr = 0;
+
+    // Gets list of ifaddrs struct
+    if(getifaddrs(& iflist) != 0) {
+        isc_throw(Unexpected, "Network interfaces detection failed.");
+    }
+
+    typedef std::map<string, Iface> ifaceLst;
+    ifaceLst::iterator iface_iter;
+    ifaceLst ifaces;
+
+    // First lookup for getting interfaces ...
+    for(ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        const char * ifname = ifptr->ifa_name;
+        uint ifindex = 0;
+
+        if (!(ifindex = if_nametoindex(ifname))) {
+            // Interface name does not have corresponding index ...
+            freeifaddrs(iflist);
+            isc_throw(Unexpected, "Interface " << ifname << " has no index");
+        }
+
+        iface_iter = ifaces.find(ifname);
+        if (iface_iter != ifaces.end()) {
+            continue;
+        }
+
+        Iface iface(ifname, ifindex);
+        iface.setFlags(ifptr->ifa_flags);
+        ifaces.insert(pair<string, Iface>(ifname, iface));
+    }
+
+    // Second lookup to get MAC and IP addresses
+    for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+
+        iface_iter = ifaces.find(ifptr->ifa_name);
+        if (iface_iter == ifaces.end()) {
+            continue;
+        }
+        // Common byte pointer for following data
+        const uint8_t * ptr = 0;
+        if (ifptr->ifa_addr->sa_family == AF_LINK) {
+            // HWAddr
+            struct sockaddr_dl * ldata =
+                reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(LLADDR(ldata));
+
+            iface_iter->second.setHWType(ldata->sdl_type);
+            iface_iter->second.setMac(ptr, ldata->sdl_alen);
+        } else if (ifptr->ifa_addr->sa_family == AF_INET6) {
+            // IPv6 Addr
+            struct sockaddr_in6 * adata =
+                reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(& adata->sin6_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET6, ptr);
+            iface_iter->second.addAddress(a);
+        } else {
+            // IPv4 Addr
+            struct sockaddr_in * adata =
+                reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+            ptr = reinterpret_cast<uint8_t *>(& adata->sin_addr);
+
+            IOAddress a = IOAddress::fromBytes(AF_INET, ptr);
+            iface_iter->second.addAddress(a);
+        }
+    }
+
+    freeifaddrs(iflist);
+
+    // Interfaces registering
+    for (ifaceLst::const_iterator iface_iter = ifaces.begin();
+        iface_iter != ifaces.end(); ++iface_iter) {
+        ifaces_.push_back(iface_iter->second);
+    }
+}
+
+/// @brief sets flag_*_ fields
+///
+/// Like Linux version, os specific flags
+///
+/// @params flags
+void Iface::setFlags(uint64_t flags) {
+    flags_ = flags;
+
+    flag_loopback_ = flags & IFF_LOOPBACK;
+    flag_up_ = flags & IFF_UP;
+    flag_running_ = flags & IFF_RUNNING;
+    flag_multicast_ = flags & IFF_MULTICAST;
+    flag_broadcast_ = flags & IFF_BROADCAST;
 }
 
 void IfaceMgr::os_send4(struct msghdr& /*m*/,
@@ -40,7 +135,8 @@ void IfaceMgr::os_send4(struct msghdr& /*m*/,
                         size_t /*control_buf_len*/,
                         const Pkt4Ptr& /*pkt*/) {
   // @todo: Are there any specific actions required before sending IPv4 packet
-  // on BSDs? See iface_mgr_linux.cc for working Linux implementation.
+  // on Solaris based systems? See iface_mgr_linux.cc
+  // for working Linux implementation.
 }
 
 bool IfaceMgr::os_receive4(struct msghdr& /*m*/, Pkt4Ptr& /*pkt*/) {

+ 2 - 2
src/lib/dhcp/libdhcp++.cc

@@ -163,9 +163,9 @@ LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
               code == 126 ||
               code == 127 ||
               (code > 146 && code < 150) ||
-              (code > 177  && code < 208) ||
+              (code > 177 && code < 208) ||
               (code > 213 && code <  220) ||
-              (code > 221 && code < 224))) {
+              (code > 221 && code < 255))) {
                 return (true);
             }
 

+ 3 - 2
src/lib/dhcp/libdhcp++.dox

@@ -19,8 +19,9 @@
 
 libdhcp++ is an all-purpose DHCP-manipulation library, written in
 C++. It offers packet parsing and assembly, DHCPv4 and DHCPv6
-options parsing and ssembly, interface detection (currently on
-Linux systems only) and socket operations. It is a generic purpose library that
+options parsing and assembly, interface detection (currently on
+Linux, FreeBSD, NetBSD, OpenBSD, Max OS X, and Solaris 11) and socket operations.
+It is a generic purpose library that
 can be used by server, client, relay, performance tools and other DHCP-related
 tools. For server specific library, see \ref libdhcpsrv. Please do not
 add any server-specific code to libdhcp++ and use \ref libdhcpsrv instead.

+ 184 - 2
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -2158,7 +2158,7 @@ void parse_ifconfig(const std::string& textFile, IfaceMgr::IfaceCollection& ifac
 // TODO: temporarily disabled, see ticket #1529
 TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
 
-    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
     IfaceMgr::IfaceCollection& detectedIfaces = ifacemgr->getIfacesLst();
 
     const std::string textFile = "ifconfig.txt";
@@ -2263,10 +2263,192 @@ TEST_F(IfaceMgrTest, DISABLED_detectIfaces_linux) {
             FAIL();
         }
     }
+}
+#endif
 
-    delete ifacemgr;
+#if defined(OS_BSD)
+#include <net/if_dl.h>
+#endif
+
+#include <sys/socket.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+
+/// @brief Checks the index of this interface
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+/// @return true if index is returned properly
+bool
+checkIfIndex(const Iface & iface,
+             struct ifaddrs *& ifptr) {
+    return (iface.getIndex() == if_nametoindex(ifptr->ifa_name));
+}
+
+/// @brief Checks if the interface has proper flags set
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+/// @return true if flags are set properly
+bool
+checkIfFlags(const Iface & iface,
+             struct ifaddrs *& ifptr) {
+        bool flag_loopback_ = ifptr->ifa_flags & IFF_LOOPBACK;
+        bool flag_up_ = ifptr->ifa_flags & IFF_UP;
+        bool flag_running_ = ifptr->ifa_flags & IFF_RUNNING;
+        bool flag_multicast_ = ifptr->ifa_flags & IFF_MULTICAST;
+
+    return ((iface.flag_loopback_ == flag_loopback_) &&
+                        (iface.flag_up_ == flag_up_) &&
+                        (iface.flag_running_ == flag_running_) &&
+                        (iface.flag_multicast_ == flag_multicast_));
 }
+
+/// @brief Checks if MAC Address/IP Addresses are properly well formed
+/// @param iface Kea interface structure to be checked
+/// @param ifptr original structure returned by getifaddrs
+/// @return true if addresses are returned properly
+bool
+checkIfAddrs(const Iface & iface, struct ifaddrs *& ifptr) {
+    const unsigned char * p = 0;
+#if defined(OS_LINUX)
+    // Workaround for Linux ...
+    if(ifptr->ifa_data != 0) {
+        // We avoid localhost as it has no MAC Address
+        if (!strncmp(iface.getName().c_str(), "lo", 2)) {
+            return (true);
+        }
+
+        struct ifreq ifr;
+        memset(& ifr.ifr_name, 0, sizeof ifr.ifr_name);
+        strncpy(ifr.ifr_name, iface.getName().c_str(), sizeof ifr.ifr_name);
+
+        int s = -1; // Socket descriptor
+
+        if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+            isc_throw(Unexpected, "Cannot create AF_INET socket");
+        }
+
+        if (ioctl(s, SIOCGIFHWADDR, & ifr) < 0) {
+            close(s);
+            isc_throw(Unexpected, "Cannot set SIOCGIFHWADDR flag");
+        }
+
+        const uint8_t * p =
+            reinterpret_cast<uint8_t *>(ifr.ifr_ifru.ifru_hwaddr.sa_data);
+
+        close(s);
+
+        /// @todo: Check MAC address length. For some interfaces it is
+        /// different than 6. Some have 0, while some exotic ones (like
+        /// infiniband) have 20.
+        return (!memcmp(p, iface.getMac(), iface.getMacLen()));
+    }
+#endif
+
+    if(!ifptr->ifa_addr) {
+        return (false);
+    }
+
+    switch(ifptr->ifa_addr->sa_family) {
+#if defined(OS_BSD)
+        case AF_LINK: {
+            // We avoid localhost as it has no MAC Address
+            if (!strncmp(iface.getName().c_str(), "lo", 2)) {
+                return (true);
+            }
+
+            struct sockaddr_dl * hwdata =
+                   reinterpret_cast<struct sockaddr_dl *>(ifptr->ifa_addr);
+            p = reinterpret_cast<uint8_t *>(LLADDR(hwdata));
+
+            // Extract MAC address length
+            if (hwdata->sdl_alen != iface.getMacLen()) {
+                return (false);
+            }
+
+            return (!memcmp(p, iface.getMac(), hwdata->sdl_alen));
+        }
 #endif
+        case AF_INET: {
+            struct sockaddr_in * v4data =
+                   reinterpret_cast<struct sockaddr_in *>(ifptr->ifa_addr);
+            p = reinterpret_cast<uint8_t *>(& v4data->sin_addr);
+
+            IOAddress addrv4 = IOAddress::fromBytes(AF_INET, p);
+
+            for (Iface::AddressCollection::const_iterator a =
+                     iface.getAddresses().begin();
+                 a != iface.getAddresses().end(); ++ a) {
+                if(a->isV4() && (*a) == addrv4) {
+                    return (true);
+                }
+            }
+
+            return (false);
+        }
+        case AF_INET6: {
+            struct sockaddr_in6 * v6data =
+                   reinterpret_cast<struct sockaddr_in6 *>(ifptr->ifa_addr);
+            p = reinterpret_cast<uint8_t *>(& v6data->sin6_addr);
+
+            IOAddress addrv6 = IOAddress::fromBytes(AF_INET6, p);
+
+            for(Iface::AddressCollection::const_iterator a =
+                    iface.getAddresses().begin();
+                a != iface.getAddresses().end(); ++ a) {
+                if(a->isV6() && (*a) == addrv6) {
+                    return (true);
+                }
+            }
+
+            return (false);
+        }
+        default:
+            return (true);
+    }
+}
+
+TEST_F(IfaceMgrTest, detectIfaces) {
+
+    NakedIfaceMgr* ifacemgr = new NakedIfaceMgr();
+    IfaceMgr::IfaceCollection& detectedIfaces = ifacemgr->getIfacesLst();
+
+    // We are using struct ifaddrs as it is the only good portable one
+    // ifreq and ioctls are far from portabe. For BSD ifreq::ifa_flags field
+    // is only a short which, nowadays, can be negative
+    struct ifaddrs * iflist = 0, * ifptr = 0;
+
+    if(getifaddrs(& iflist) != 0) {
+        isc_throw(Unexpected, "Cannot detect interfaces");
+    }
+
+    for (ifptr = iflist; ifptr != 0; ifptr = ifptr->ifa_next) {
+        for (IfaceMgr::IfaceCollection::const_iterator i = detectedIfaces.begin();
+            i != detectedIfaces.end(); ++ i) {
+            if (!strncmp(ifptr->ifa_name, (*i).getName().c_str(),
+                         (*i).getName().size())) {
+
+                // Typically unit-tests try to be silent. But interface detection
+                // is tricky enough to warrant additional prints.
+                std::cout << "Checking interface " << i->getName() << std::endl;
+
+                // Check if interface index is reported properly
+                EXPECT_TRUE(checkIfIndex(*i, ifptr));
+
+                // Check if flags are reported properly
+                EXPECT_TRUE(checkIfFlags(*i, ifptr));
+
+                // Check if addresses are reported properly
+                EXPECT_TRUE(checkIfAddrs(*i, ifptr));
+
+            }
+        }
+    }
+
+    freeifaddrs(iflist);
+    iflist = 0;
+
+    delete ifacemgr;
+}
 
 volatile bool callback_ok;
 

+ 4 - 1
src/lib/dhcp/tests/libdhcp++_unittest.cc

@@ -643,7 +643,10 @@ TEST_F(LibDhcpTest, isStandardOption4) {
                                           187, 188, 189, 190, 191, 192, 193, 194, 195,
                                           196, 197, 198, 199, 200, 201, 202, 203, 204,
                                           205, 206, 207, 214, 215, 216, 217, 218, 219,
-                                          222, 223 };
+                                          222, 223, 224, 225, 226, 227, 228, 229, 230,
+                                          231, 232, 233, 234, 235, 236, 237, 238, 239,
+                                          240, 241, 242, 243, 244, 245, 246, 247, 248,
+                                          249, 250, 251, 252, 253, 254 };
     const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
 
     // Try all possible option codes.

+ 1 - 1
src/lib/dhcpsrv/cfgmgr.cc

@@ -350,7 +350,7 @@ CfgMgr::getUnicast(const std::string& iface) const {
 
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR),
-      all_ifaces_active_(false) {
+      all_ifaces_active_(false), echo_v4_client_id_(true) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
     // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
     // See AM_CPPFLAGS definition in Makefile.am

+ 19 - 0
src/lib/dhcpsrv/cfgmgr.h

@@ -316,6 +316,22 @@ public:
     const isc::asiolink::IOAddress*
     getUnicast(const std::string& iface) const;
 
+    /// @brief Sets whether server should send back client-id in DHCPv4
+    ///
+    /// This is a compatibility flag. The default (true) is compliant with
+    /// RFC6842. False is for backward compatibility.
+    ///
+    /// @param echo should the client-id be sent or not
+    void echoClientId(const bool echo) {
+        echo_v4_client_id_ = echo;
+    }
+
+    /// @brief Returns whether server should send back client-id in DHCPv4.
+    /// @return true if client-id should be returned, false otherwise.
+    bool echoClientId() const {
+        return (echo_v4_client_id_);
+    }
+
 protected:
 
     /// @brief Protected constructor.
@@ -392,6 +408,9 @@ private:
     /// A flag which indicates that server should listen on all available
     /// interfaces.
     bool all_ifaces_active_;
+
+    /// Indicates whether v4 server should send back client-id
+    bool echo_v4_client_id_;
 };
 
 } // namespace isc::dhcp

+ 17 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -653,6 +653,23 @@ TEST_F(CfgMgrTest, activateAllIfaces) {
     EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
 }
 
+// This test verifies that RFC6842 (echo client-id) compatibility may be
+// configured.
+TEST_F(CfgMgrTest, echoClientId) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Check that the default is true
+    EXPECT_TRUE(cfg_mgr.echoClientId());
+
+    // Check that it can be modified to false
+    cfg_mgr.echoClientId(false);
+    EXPECT_FALSE(cfg_mgr.echoClientId());
+
+    // Check that the default value can be restored
+    cfg_mgr.echoClientId(true);
+    EXPECT_TRUE(cfg_mgr.echoClientId());
+}
+
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with the same interface twice

+ 2 - 1
tests/tools/perfdhcp/Makefile.am

@@ -25,6 +25,7 @@ perfdhcp_SOURCES += perf_pkt6.cc perf_pkt6.h
 perfdhcp_SOURCES += perf_pkt4.cc perf_pkt4.h
 perfdhcp_SOURCES += packet_storage.h
 perfdhcp_SOURCES += pkt_transform.cc pkt_transform.h
+perfdhcp_SOURCES += rate_control.cc rate_control.h
 perfdhcp_SOURCES += stats_mgr.h
 perfdhcp_SOURCES += test_control.cc test_control.h
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -42,4 +43,4 @@ perfdhcp_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 
 
 # ... and the documentation
-EXTRA_DIST = perfdhcp_internals.dox
+EXTRA_DIST = perfdhcp_internals.dox

+ 40 - 15
tests/tools/perfdhcp/command_options.cc

@@ -114,6 +114,7 @@ CommandOptions::reset() {
     lease_type_.set(LeaseType::ADDRESS);
     rate_ = 0;
     renew_rate_ = 0;
+    release_rate_ = 0;
     report_delay_ = 0;
     clients_num_ = 0;
     mac_template_.assign(mac, mac + 6);
@@ -211,7 +212,7 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
     // In this section we collect argument values from command line
     // they will be tuned and validated elsewhere
     while((opt = getopt(argc, argv, "hv46r:t:R:b:n:p:d:D:l:P:a:L:"
-                        "s:iBc1T:X:O:E:S:I:x:w:e:f:")) != -1) {
+                        "s:iBc1T:X:O:E:S:I:x:w:e:f:F:")) != -1) {
         stream << " -" << static_cast<char>(opt);
         if (optarg) {
             stream << " " << optarg;
@@ -307,6 +308,12 @@ CommandOptions::initialize(int argc, char** argv, bool print_cmd_line) {
                                           " must be a positive integer");
             break;
 
+        case 'F':
+            release_rate_ = positiveInteger("value of the release rate:"
+                                            " -F<release-rate> must be a"
+                                            " positive integer");
+            break;
+
         case 'h':
             usage();
             return (true);
@@ -690,6 +697,8 @@ CommandOptions::validate() const {
           "-6 (IPv6) must be set to use -c");
     check((getIpVersion() != 6) && (getRenewRate() !=0),
           "-f<renew-rate> may be used with -6 (IPv6) only");
+    check((getIpVersion() != 6) && (getReleaseRate() != 0),
+          "-F<release-rate> may be used with -6 (IPv6) only");
     check((getExchangeMode() == DO_SA) && (getNumRequests().size() > 1),
           "second -n<num-request> is not compatible with -i");
     check((getIpVersion() == 4) && !getLeaseType().is(LeaseType::ADDRESS),
@@ -719,6 +728,8 @@ CommandOptions::validate() const {
           "-I<ip-offset> is not compatible with -i");
     check((getExchangeMode() == DO_SA) && (getRenewRate() != 0),
           "-f<renew-rate> is not compatible with -i");
+    check((getExchangeMode() == DO_SA) && (getReleaseRate() != 0),
+          "-F<release-rate> is not compatible with -i");
     check((getExchangeMode() != DO_SA) && (isRapidCommit() != 0),
           "-i must be set to use -c");
     check((getRate() == 0) && (getReportDelay() != 0),
@@ -730,12 +741,16 @@ CommandOptions::validate() const {
     check((getRate() == 0) &&
           ((getMaxDrop().size() > 0) || getMaxDropPercentage().size() > 0),
           "-r<rate> must be set to use -D<max-drop>");
-    check((getRate() != 0) && (getRenewRate() > getRate()),
-          "Renew rate specified as -f<renew-rate> must not be greater than"
-          " the rate specified as -r<rate>");
+    check((getRate() != 0) && (getRenewRate() + getReleaseRate() > getRate()),
+          "The sum of Renew rate (-f<renew-rate>) and Release rate"
+          " (-F<release-rate>) must not be greater than the exchange"
+          " rate specified as -r<rate>");
     check((getRate() == 0) && (getRenewRate() != 0),
           "Renew rate specified as -f<renew-rate> must not be specified"
           " when -r<rate> parameter is not specified");
+    check((getRate() == 0) && (getReleaseRate() != 0),
+          "Release rate specified as -F<release-rate> must not be specified"
+          " when -r<rate> parameter is not specified");
     check((getTemplateFiles().size() < getTransactionIdOffset().size()),
           "-T<template-file> must be set to use -X<xid-offset>");
     check((getTemplateFiles().size() < getRandomOffset().size()),
@@ -816,6 +831,9 @@ CommandOptions::printCommandLine() const {
     if (getRenewRate() != 0) {
         std::cout << "renew-rate[1/s]=" << getRenewRate() << std::endl;
     }
+    if (getReleaseRate() != 0) {
+        std::cout << "release-rate[1/s]=" << getReleaseRate() << std::endl;
+    }
     if (report_delay_ != 0) {
         std::cout << "report[s]=" << report_delay_ << std::endl;
     }
@@ -899,13 +917,14 @@ void
 CommandOptions::usage() const {
     std::cout <<
         "perfdhcp [-hv] [-4|-6] [-e<lease-type>] [-r<rate>] [-f<renew-rate>]\n"
-        "         [-t<report>] [-R<range>] [-b<base>] [-n<num-request>]\n"
-        "         [-p<test-period>] [-d<drop-time>] [-D<max-drop>]\n"
-        "         [-l<local-addr|interface>] [-P<preload>] [-a<aggressivity>]\n"
-        "         [-L<local-port>] [-s<seed>] [-i] [-B] [-c] [-1]\n"
-        "         [-T<template-file>] [-X<xid-offset>] [-O<random-offset]\n"
-        "         [-E<time-offset>] [-S<srvid-offset>] [-I<ip-offset>]\n"
-        "         [-x<diagnostic-selector>] [-w<wrapped>] [server]\n"
+        "         [-F<release-rate>] [-t<report>] [-R<range>] [-b<base>]\n"
+        "         [-n<num-request>] [-p<test-period>] [-d<drop-time>]\n"
+        "         [-D<max-drop>] [-l<local-addr|interface>] [-P<preload>]\n"
+        "         [-a<aggressivity>] [-L<local-port>] [-s<seed>] [-i] [-B]\n"
+        "         [-c] [-1] [-T<template-file>] [-X<xid-offset>]\n"
+        "         [-O<random-offset] [-E<time-offset>] [-S<srvid-offset>]\n"
+        "         [-I<ip-offset>] [-x<diagnostic-selector>] [-w<wrapped>]\n"
+        "         [server]\n"
         "\n"
         "The [server] argument is the name/address of the DHCP server to\n"
         "contact.  For DHCPv4 operation, exchanges are initiated by\n"
@@ -948,10 +967,6 @@ CommandOptions::usage() const {
         "-E<time-offset>: Offset of the (DHCPv4) secs field / (DHCPv6)\n"
         "    elapsed-time option in the (second/request) template.\n"
         "    The value 0 disables it.\n"
-        "-f<renew-rate>: A rate at which IPv6 Renew requests are sent to\n"
-        "    a server. This value must not be equal or lower than the rate\n"
-        "    specified as -r<rate>. If -r<rate> is not specified, this\n"
-        "    parameter must not be specified too.\n"
         "-h: Print this help.\n"
         "-i: Do only the initial part of an exchange: DO or SA, depending on\n"
         "    whether -6 is given.\n"
@@ -999,6 +1014,16 @@ CommandOptions::usage() const {
         "\n"
         "DHCPv6 only options:\n"
         "-c: Add a rapid commit option (exchanges will be SA).\n"
+        "-f<renew-rate>: Rate at which IPv6 Renew requests are sent to\n"
+        "    a server. This value is only valid when used in conjunction with\n"
+        "    the exchange rate (given by -r<rate>).  Furthermore the sum of\n"
+        "    this value and the release-rate (given by -F<rate) must be equal\n"
+        "    to or less than the exchange rate.\n"
+        "-F<release-rate>: Rate at which IPv6 Release requests are sent to\n"
+        "    a server. This value is only valid when used in conjunction with\n"
+        "    the exchange rate (given by -r<rate>).  Furthermore the sum of\n"
+        "    this value and the renew-rate (given by -f<rate) must be equal\n"
+        "    to or less than the exchange rate.\n"
         "\n"
         "The remaining options are used only in conjunction with -r:\n"
         "\n"

+ 8 - 2
tests/tools/perfdhcp/command_options.h

@@ -1,4 +1,3 @@
-
 // Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
@@ -156,11 +155,16 @@ public:
     /// \return exchange rate per second.
     int getRate() const { return rate_; }
 
-    /// \brief Returns a rate at which IPv6 Renew messages are sent.
+    /// \brief Returns a rate at which DHCPv6 Renew messages are sent.
     ///
     /// \return A rate at which IPv6 Renew messages are sent.
     int getRenewRate() const { return (renew_rate_); }
 
+    /// \brief Returns a rate at which DHCPv6 Release messages are sent.
+    ///
+    /// \return A rate at which DHCPv6 Release messages are sent.
+    int getReleaseRate() const { return (release_rate_); }
+
     /// \brief Returns delay between two performance reports.
     ///
     /// \return delay between two consecutive performance reports.
@@ -469,6 +473,8 @@ private:
     int rate_;
     /// A rate at which DHCPv6 Renew messages are sent.
     int renew_rate_;
+    /// A rate at which DHCPv6 Release messages are sent.
+    int release_rate_;
     /// Delay between generation of two consecutive
     /// performance reports
     int report_delay_;

+ 158 - 0
tests/tools/perfdhcp/rate_control.cc

@@ -0,0 +1,158 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include "rate_control.h"
+
+namespace isc {
+namespace perfdhcp {
+
+using namespace boost::posix_time;
+
+RateControl::RateControl()
+    : send_due_(currentTime()), last_sent_(currentTime()),
+      aggressivity_(1), rate_(0), late_sent_(false) {
+}
+
+RateControl::RateControl(const int rate, const int aggressivity)
+    : send_due_(currentTime()), last_sent_(currentTime()),
+      aggressivity_(aggressivity), rate_(rate), late_sent_(false) {
+    if (aggressivity_ < 1) {
+        isc_throw(isc::BadValue, "invalid value of aggressivity "
+                  << aggressivity << ", expected value is greater than 0");
+    }
+    if (rate_ < 0) {
+        isc_throw(isc::BadValue, "invalid value of rate " << rate
+                  << ", expected non-negative value");
+    }
+}
+
+uint64_t
+RateControl::getOutboundMessageCount() {
+
+    // We need calculate the due time for sending next set of messages.
+    updateSendDue();
+
+    // Get current time. If we are behind due time, we have to calculate
+    // how many messages to send to catch up with the rate.
+    ptime now = currentTime();
+    if (now >= send_due_) {
+        // Reset number of exchanges.
+        uint64_t due_exchanges = 0;
+        // If rate is specified from the command line we have to
+        // synchornize with it.
+        if (getRate() != 0) {
+            time_period period(send_due_, now);
+            time_duration duration = period.length();
+            // due_factor indicates the number of seconds that
+            // sending next chunk of packets will take.
+            double due_factor = duration.fractional_seconds() /
+                time_duration::ticks_per_second();
+            due_factor += duration.total_seconds();
+            // Multiplying due_factor by expected rate gives the number
+            // of exchanges to be initiated.
+            due_exchanges = static_cast<uint64_t>(due_factor * getRate());
+            // We want to make sure that at least one packet goes out.
+            if (due_exchanges == 0) {
+                due_exchanges = 1;
+            }
+            // We should not exceed aggressivity as it could have been
+            // restricted from command line.
+            if (due_exchanges > getAggressivity()) {
+                due_exchanges = getAggressivity();
+            }
+        } else {
+            // Rate is not specified so we rely on aggressivity
+            // which is the number of packets to be sent in
+            // one chunk.
+            due_exchanges = getAggressivity();
+        }
+        return (due_exchanges);
+    }
+    return (0);
+}
+
+boost::posix_time::ptime
+RateControl::currentTime() {
+    return (microsec_clock::universal_time());
+}
+
+void
+RateControl::updateSendDue() {
+    // There is no sense to update due time if the current due time is in the
+    // future. The due time is calculated as a duration between the moment
+    // when the last message of the given type was sent and the time when
+    // next one is supposed to be sent based on a given rate. The former value
+    // will not change until we send the next message, which we don't do
+    // until we reach the due time.
+    if (send_due_ > currentTime()) {
+        return;
+    }
+    // This is initialized in the class constructor, so if it is not initialized
+    // it is a programmatic error.
+    if (last_sent_.is_not_a_date_time()) {
+        isc_throw(isc::Unexpected, "timestamp of the last sent packet not"
+                  " initialized");
+    }
+    // If rate was not specified we will wait just one clock tick to
+    // send next packet. This simulates best effort conditions.
+    long duration = 1;
+    if (getRate() != 0) {
+        // We use number of ticks instead of nanoseconds because
+        // nanosecond resolution may not be available on some
+        // machines. Number of ticks guarantees the highest possible
+        // timer resolution.
+        duration = time_duration::ticks_per_second() / getRate();
+    }
+    // Calculate due time to initiate next chunk of exchanges.
+    send_due_ = last_sent_ + time_duration(0, 0, 0, duration);
+    if (send_due_ > currentTime()) {
+        late_sent_ = true;
+    } else {
+        late_sent_ = false;
+    }
+}
+
+void
+RateControl::setAggressivity(const int aggressivity) {
+    if (aggressivity < 1) {
+        isc_throw(isc::BadValue, "invalid value of aggressivity "
+                  << aggressivity << ", expected value is greater than 0");
+    }
+    aggressivity_ = aggressivity;
+}
+
+void
+RateControl::setRate(const int rate) {
+    if (rate < 0) {
+        isc_throw(isc::BadValue, "invalid value of rate " << rate
+                  << ", expected non-negative value");
+    }
+    rate_ = rate;
+}
+
+void
+RateControl::setRelativeDue(const int offset) {
+    send_due_ = offset > 0 ?
+        currentTime() + seconds(abs(offset)) :
+        currentTime() - seconds(abs(offset));
+}
+
+void
+RateControl::updateSendTime() {
+    last_sent_ = currentTime();
+}
+
+} // namespace perfdhcp
+} // namespace isc

+ 180 - 0
tests/tools/perfdhcp/rate_control.h

@@ -0,0 +1,180 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RATE_CONTROL_H
+#define RATE_CONTROL_H
+
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief A message sending rate control class for perfdhcp.
+///
+/// This class provides the means to control the rate at which messages
+/// of the specific type are sent by perfdhcp. Each message type,
+/// for which the desired rate can be specified, has a corresponding
+/// \c RateControl object. So, the perfdhcp is using up to three objects
+/// of this type at the same time, to control the rate of the following
+/// messages being sent:
+/// - Discover(DHCPv4) or Solicit (DHCPv6)
+/// - Renew (DHCPv6) or Request (DHCPv4) to renew leases.
+/// - Release
+///
+/// The purpose of the RateControl class is to track the due time for
+/// sending next message (or bunch of messages) to keep outbound rate
+/// of particular messages at the desired level. The due time is calculated
+/// using the desired rate value and the timestamp when the last message of
+/// the particular type has been sent. That puts the responsibility on the
+/// \c TestControl class to invoke the \c RateControl::updateSendDue, every
+/// time the message is sent.
+///
+/// The \c RateControl object returns the number of messages to be sent at
+/// the time. The number returned is 0, if perfdhcp shouldn't send any messages
+/// yet, or 1 (sometimes more) if the send due time has been reached.
+class RateControl {
+public:
+
+    /// \brief Default constructor.
+    RateControl();
+
+    /// \brief Constructor which sets desired rate and aggressivity.
+    ///
+    /// \param rate A desired rate.
+    /// \param aggressivity A desired aggressivity.
+    RateControl(const int rate, const int aggressivity);
+
+    /// \brief Returns the value of aggressivity.
+    int getAggressivity() const {
+        return (aggressivity_);
+    }
+
+    /// \brief Returns current due time to send next message.
+    boost::posix_time::ptime getDue() const {
+        return (send_due_);
+    }
+
+    /// \brief Returns number of messages to be sent "now".
+    ///
+    /// This function calculates how many messages of the given type should
+    /// be sent immediately when the call to the function returns, to catch
+    /// up with the desired message rate.
+    ///
+    /// The value returned depends on the due time calculated with the
+    /// \c RateControl::updateSendDue function and the current time. If
+    /// the due time has been hit, the non-zero number of messages is returned.
+    /// If the due time hasn't been hit, the number returned is 0.
+    ///
+    /// If the rate is non-zero, the number of messages to be sent is calculated
+    /// as follows:
+    /// \code
+    ///          num = duration * rate
+    /// \endcode
+    /// where <b>duration</b> is a time period between the due time to send
+    /// next set of messages and current time. The duration is expressed in
+    /// seconds with the fractional part having 6 or 9 digits (depending on
+    /// the timer resolution). If the calculated value is equal to 0, it is
+    /// rounded to 1, so as at least one message is sent.
+    ///
+    /// The value of aggressivity limits the maximal number of messages to
+    /// be sent one after another. If the number of messages calculated with
+    /// the equation above exceeds the aggressivity, this function will return
+    /// the value equal to aggressivity.
+    ///
+    /// If the rate is not specified (equal to 0), the value calculated by
+    /// this function is equal to aggressivity.
+    ///
+    /// \return A number of messages to be sent immediately.
+    uint64_t getOutboundMessageCount();
+
+    /// \brief Returns the rate.
+    int getRate() const {
+        return (rate_);
+    }
+
+    /// \brief Returns the value of the late send flag.
+    ///
+    /// The flag returned by this function indicates whether the new due time
+    /// calculated by the \c RateControl::updateSendDue is in the past.
+    /// This value is used by the \c TestControl object to increment the counter
+    /// of the late sent messages in the \c StatsMgr.
+    bool isLateSent() const {
+        return (late_sent_);
+    }
+
+    /// \brief Sets the value of aggressivity.
+    ///
+    /// \param aggressivity A new value of aggressivity. This value must be
+    /// a positive integer.
+    /// \throw isc::BadValue if new value is not a positive integer.
+    void setAggressivity(const int aggressivity);
+
+    /// \brief Sets the new rate.
+    ///
+    /// \param rate A new value of rate. This value must not be negative.
+    /// \throw isc::BadValue if new rate is negative.
+    void setRate(const int rate);
+
+    /// \brief Sets the value of the due time.
+    ///
+    /// This function is intended for unit testing. It manipulates the value of
+    /// the due time. The parameter passed to this function specifies the
+    /// (positive or negative) number of seconds relative to current time.
+    ///
+    /// \param offset A number of seconds relative to current time which
+    /// constitutes the new due time.
+    void setRelativeDue(const int offset);
+
+    /// \brief Sets the timestamp of the last sent message to current time.
+    void updateSendTime();
+
+protected:
+
+    /// \brief Convenience function returning current time.
+    ///
+    /// \return current time.
+    static boost::posix_time::ptime currentTime();
+
+    /// \brief Calculates the send due.
+    ///
+    /// This function calculates the send due timestamp using the current time
+    /// and desired rate. The due timestamp is calculated as a sum of the
+    /// timestamp when the last message was sent and the reciprocal of the rate
+    /// in micro or nanoseconds (depending on the timer resolution). If the rate
+    /// is not specified, the duration between two consecutive sends is one
+    /// timer tick.
+    void updateSendDue();
+
+    /// \brief Holds a timestamp when the next message should be sent.
+    boost::posix_time::ptime send_due_;
+
+    /// \brief Holds a timestamp when the last message was sent.
+    boost::posix_time::ptime last_sent_;
+
+    /// \brief Holds an aggressivity value.
+    int aggressivity_;
+
+    /// \brief Holds a desired rate value.
+    int rate_;
+
+    /// \brief A flag which indicates that the calculated due time is in the
+    /// past.
+    bool late_sent_;
+
+};
+
+}
+}
+
+#endif

+ 29 - 5
tests/tools/perfdhcp/stats_mgr.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -124,7 +124,8 @@ public:
         XCHG_RA,  ///< DHCPv4 REQUEST-ACK
         XCHG_SA,  ///< DHCPv6 SOLICIT-ADVERTISE
         XCHG_RR,  ///< DHCPv6 REQUEST-REPLY
-        XCHG_RN   ///< DHCPv6 RENEW-REPLY
+        XCHG_RN,  ///< DHCPv6 RENEW-REPLY
+        XCHG_RL   ///< DHCPv6 RELEASE-REPLY
     };
 
     /// \brief Exchange Statistics.
@@ -632,12 +633,19 @@ public:
         /// Method prints main statistics for particular exchange.
         /// Statistics includes: number of sent and received packets,
         /// number of dropped packets and number of orphans.
+        ///
+        /// \todo Currently the number of orphans is not displayed because
+        /// Reply messages received for Renew and Releases are counted as
+        /// orphans for the 4-way exchanges, which is wrong. We will need to
+        /// move the orphans counting out of the Statistics Manager so as
+        /// orphans counter is increased only if the particular message is
+        /// not identified as a reponse to any of the messages sent by perfdhcp.
         void printMainStats() const {
             using namespace std;
             cout << "sent packets: " << getSentPacketsNum() << endl
                  << "received packets: " << getRcvdPacketsNum() << endl
-                 << "drops: " << getDroppedPacketsNum() << endl
-                 << "orphans: " << getOrphans() << endl;
+                 << "drops: " << getDroppedPacketsNum() << endl;
+            //                 << "orphans: " << getOrphans() << endl;
         }
 
         /// \brief Print round trip time packets statistics.
@@ -871,6 +879,20 @@ public:
                                                boot_time_));
     }
 
+    /// \brief Check if the exchange type has been specified.
+    ///
+    /// This method checks if the \ref ExchangeStats object of a particular type
+    /// exists (has been added using \ref addExchangeStats function).
+    ///
+    /// \param xchg_type A type of the exchange being repersented by the
+    /// \ref ExchangeStats object.
+    ///
+    /// \return true if the \ref ExchangeStats object has been added for a
+    /// specified exchange type.
+    bool hasExchangeStats(const ExchangeType xchg_type) const {
+        return (exchanges_.find(xchg_type) != exchanges_.end());
+    }
+
     /// \brief Add named custom uint64 counter.
     ///
     /// Method creates new named counter and stores in counter's map under
@@ -1159,7 +1181,7 @@ public:
     ///
     /// \param xchg_type exchange type.
     /// \return string representing name of the exchange.
-    std::string exchangeToString(ExchangeType xchg_type) const {
+    static std::string exchangeToString(ExchangeType xchg_type) {
         switch(xchg_type) {
         case XCHG_DO:
             return("DISCOVER-OFFER");
@@ -1171,6 +1193,8 @@ public:
             return("REQUEST-REPLY");
         case XCHG_RN:
             return("RENEW-REPLY");
+        case XCHG_RL:
+            return("RELEASE-REPLY");
         default:
             return("Unknown exchange type");
         }

+ 148 - 140
tests/tools/perfdhcp/test_control.cc

@@ -98,6 +98,21 @@ TestControl::TestControl() {
 }
 
 void
+TestControl::checkLateMessages(RateControl& rate_control) {
+    // If diagnostics is disabled, there is no need to log late sent messages.
+    // If it is enabled and the rate control object indicates that the last
+    // sent message was late, bump up the counter in Stats Manager.
+    if (rate_control.isLateSent() && testDiags('i')) {
+        CommandOptions& options = CommandOptions::instance();
+        if (options.getIpVersion() == 4) {
+            stats_mgr4_->incrementCounter("latesend");
+        } else if (options.getIpVersion() == 6) {
+            stats_mgr6_->incrementCounter("latesend");
+        }
+    }
+}
+
+void
 TestControl::cleanCachedPackets() {
     CommandOptions& options = CommandOptions::instance();
     // When Renews are not sent, Reply packets are not cached so there
@@ -316,28 +331,43 @@ TestControl::checkExitConditions() const {
 }
 
 Pkt6Ptr
-TestControl::createRenew(const Pkt6Ptr& reply) {
+TestControl::createMessageFromReply(const uint16_t msg_type,
+                                    const dhcp::Pkt6Ptr& reply) {
+    // Restrict messages to Release and Renew.
+    if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
+        isc_throw(isc::BadValue, "invalid message type " << msg_type
+                  << " to be created from Reply, expected DHCPV6_RENEW or"
+                  " DHCPV6_RELEASE");
+    }
+    // Get the string representation of the message - to be used for error
+    // logging purposes.
+    const char* msg_type_str = (msg_type == DHCPV6_RENEW ? "Renew" : "Release");
+    // Reply message must be specified.
     if (!reply) {
-        isc_throw(isc::BadValue,"Unable to create Renew packet from the Reply packet"
-                  " because the instance of the Reply is NULL");
+        isc_throw(isc::BadValue, "Unable to create " << msg_type_str
+                  << " message from the Reply message because the instance of"
+                  " the Reply message is NULL");
     }
-    Pkt6Ptr renew(new Pkt6(DHCPV6_RENEW, generateTransid()));
+
+    Pkt6Ptr msg(new Pkt6(msg_type, generateTransid()));
     // Client id.
     OptionPtr opt_clientid = reply->getOption(D6O_CLIENTID);
     if (!opt_clientid) {
-        isc_throw(isc::Unexpected, "failed to create Renew packet because client id"
-                  " option has not been found in the Reply from the server");
+        isc_throw(isc::Unexpected, "failed to create " << msg_type_str
+                  << " message because client id option has not been found"
+                  " in the Reply message");
     }
-    renew->addOption(opt_clientid);
+    msg->addOption(opt_clientid);
     // Server id.
     OptionPtr opt_serverid = reply->getOption(D6O_SERVERID);
     if (!opt_serverid) {
-        isc_throw(isc::Unexpected, "failed to create Renew packet because server id"
-                  " option has not been found in the Reply from the server");
+        isc_throw(isc::Unexpected, "failed to create " << msg_type_str
+                  << " because server id option has not been found in the"
+                  " Reply message");
     }
-    renew->addOption(opt_serverid);
-    copyIaOptions(reply, renew);
-    return (renew);
+    msg->addOption(opt_serverid);
+    copyIaOptions(reply, msg);
+    return (msg);
 }
 
 OptionPtr
@@ -489,16 +519,28 @@ TestControl::getCurrentTimeout() const {
     ptime now(microsec_clock::universal_time());
     // Check that we haven't passed the moment to send the next set of
     // packets.
-    if (now >= send_due_ ||
-        (options.getRenewRate() != 0 && now >= renew_due_)) {
+    if (now >= basic_rate_control_.getDue() ||
+        (options.getRenewRate() != 0 && now >= renew_rate_control_.getDue()) ||
+        (options.getReleaseRate() != 0 &&
+         now >= release_rate_control_.getDue())) {
         return (0);
     }
 
-    // There is a due time to send Solicit and Renew. We should adjust
-    // the timeout to the due time which occurs sooner.
-    ptime due = send_due_ > renew_due_ ? renew_due_ : send_due_;
-    time_period due_period(now, due);
-    return (due_period.length().total_microseconds());
+    // Let's assume that the due time for Solicit is the soonest.
+    ptime due = basic_rate_control_.getDue();
+    // If we are sending Renews and due time for Renew occurs sooner,
+    // set the due time to Renew due time.
+    if ((options.getRenewRate()) != 0 && (renew_rate_control_.getDue() < due)) {
+        due = renew_rate_control_.getDue();
+    }
+    // If we are sending Releases and the due time for Release occurs
+    // sooner than the current due time, let's use the due for Releases.
+    if ((options.getReleaseRate() != 0) &&
+        (release_rate_control_.getDue() < due)) {
+        due = release_rate_control_.getDue();
+    }
+    // Return the timeout in microseconds.
+    return (time_period(now, due).length().total_microseconds());
 }
 
 int
@@ -529,49 +571,6 @@ TestControl::getElapsedTime(const T& pkt1, const T& pkt2) {
     return(elapsed_period.length().total_milliseconds());
 }
 
-
-uint64_t
-TestControl::getNextExchangesNum(const boost::posix_time::ptime& send_due,
-                                 const int rate) {
-    CommandOptions& options = CommandOptions::instance();
-    // Get current time.
-    ptime now(microsec_clock::universal_time());
-    if (now >= send_due) {
-        // Reset number of exchanges.
-        uint64_t due_exchanges = 0;
-        // If rate is specified from the command line we have to
-        // synchornize with it.
-        if (rate != 0) {
-            time_period period(send_due, now);
-            time_duration duration = period.length();
-            // due_factor indicates the number of seconds that
-            // sending next chunk of packets will take.
-            double due_factor = duration.fractional_seconds() /
-                time_duration::ticks_per_second();
-            due_factor += duration.total_seconds();
-            // Multiplying due_factor by expected rate gives the number
-            // of exchanges to be initiated.
-            due_exchanges = static_cast<uint64_t>(due_factor * rate);
-            // We want to make sure that at least one packet goes out.
-            if (due_exchanges == 0) {
-                due_exchanges = 1;
-            }
-            // We should not exceed aggressivity as it could have been
-            // restricted from command line.
-            if (due_exchanges > options.getAggressivity()) {
-                due_exchanges = options.getAggressivity();
-            }
-        } else {
-            // Rate is not specified so we rely on aggressivity
-            // which is the number of packets to be sent in
-            // one chunk.
-            due_exchanges = options.getAggressivity();
-        }
-        return (due_exchanges);
-    }
-    return (0);
-}
-
 int
 TestControl::getRandomOffset(const int arg_idx) const {
     int rand_offset = CommandOptions::instance().getIpVersion() == 4 ?
@@ -695,6 +694,9 @@ TestControl::initializeStatsMgr() {
         if (options.getRenewRate() != 0) {
             stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RN);
         }
+        if (options.getReleaseRate() != 0) {
+            stats_mgr6_->addExchangeStats(StatsMgr6::XCHG_RL);
+        }
     }
     if (testDiags('i')) {
         if (options.getIpVersion() == 4) {
@@ -849,14 +851,15 @@ TestControl::sendPackets(const TestControlSocket& socket,
 }
 
 uint64_t
-TestControl::sendRenewPackets(const TestControlSocket& socket,
-                              const uint64_t packets_num) {
-    for (uint64_t i = 0; i < packets_num; ++i) {
-        if (!sendRenew(socket)) {
+TestControl::sendMultipleMessages6(const TestControlSocket& socket,
+                                   const uint32_t msg_type,
+                                   const uint64_t msg_num) {
+    for (uint64_t i = 0; i < msg_num; ++i) {
+        if (!sendMessageFromReply(msg_type, socket)) {
             return (i);
         }
     }
-    return (packets_num);
+    return (msg_num);
 }
 
 void
@@ -1114,14 +1117,36 @@ TestControl::processReceivedPacket6(const TestControlSocket& socket,
             }
         }
     } else if (packet_type == DHCPV6_REPLY) {
-        Pkt6Ptr sent_packet = stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR,
-                                                          pkt6);
-        if (sent_packet) {
-            if (CommandOptions::instance().getRenewRate() != 0) {
+        // If the received message is Reply, we have to find out which exchange
+        // type the Reply message belongs to. It is doable by matching the Reply
+        // transaction id with the transaction id of the sent Request, Renew
+        // or Release. First we start with the Request.
+        if (stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RR, pkt6)) {
+            // The Reply belongs to Request-Reply exchange type. So, we may need
+            // to keep this Reply in the storage if Renews or/and Releases are
+            // being sent. Note that, Reply messages hold the information about
+            // leases assigned. We use this information to construct Renew and
+            // Release messages.
+            if (stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RN) ||
+                stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RL)) {
+                // Renew or Release messages are sent, because StatsMgr has the
+                // specific exchange type specified. Let's append the Reply
+                // message to a storage.
                 reply_storage_.append(pkt6);
             }
-        } else {
-            stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6);
+        // The Reply message is not a server's response to the Request message
+        // sent within the 4-way exchange. It may be a response to the Renew
+        // or Release message. In the if clause we first check if StatsMgr
+        // has exchange type for Renew specified, and if it has, if there is
+        // a corresponding Renew message for the received Reply. If not,
+        // we check that StatsMgr has exchange type for Release specified,
+        // as possibly the Reply has been sent in response to Release.
+        } else if (!(stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RN) &&
+                     stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RN, pkt6)) &&
+                   stats_mgr6_->hasExchangeStats(StatsMgr6::XCHG_RL)) {
+            // At this point, it is only possible that the Reply has been sent
+            // in response to a Release. Try to match the Reply with Release.
+            stats_mgr6_->passRcvdPacket(StatsMgr6::XCHG_RL, pkt6);
         }
     }
 }
@@ -1250,12 +1275,16 @@ TestControl::registerOptionFactories() const {
 
 void
 TestControl::reset() {
-    send_due_ = microsec_clock::universal_time();
-    last_sent_ = send_due_;
-    last_report_ = send_due_;
-    renew_due_ = send_due_;
-    last_renew_ = send_due_;
+    CommandOptions& options = CommandOptions::instance();
+    basic_rate_control_.setAggressivity(options.getAggressivity());
+    basic_rate_control_.setRate(options.getRate());
+    renew_rate_control_.setAggressivity(options.getAggressivity());
+    renew_rate_control_.setRate(options.getRenewRate());
+    release_rate_control_.setAggressivity(options.getAggressivity());
+    release_rate_control_.setRate(options.getReleaseRate());
+
     transid_gen_.reset();
+    last_report_ = microsec_clock::universal_time();
     // Actual generators will have to be set later on because we need to
     // get command line parameters first.
     setTransidGenerator(NumberGeneratorPtr());
@@ -1321,11 +1350,10 @@ TestControl::run() {
     // Initialize Statistics Manager. Release previous if any.
     initializeStatsMgr();
     for (;;) {
-        // Calculate send due based on when last exchange was initiated.
-        updateSendDue(last_sent_, options.getRate(), send_due_);
         // Calculate number of packets to be sent to stay
         // catch up with rate.
-        uint64_t packets_due = getNextExchangesNum(send_due_, options.getRate());
+        uint64_t packets_due = basic_rate_control_.getOutboundMessageCount();
+        checkLateMessages(basic_rate_control_);
         if ((packets_due == 0) && testDiags('i')) {
             if (options.getIpVersion() == 4) {
                 stats_mgr4_->incrementCounter("shortwait");
@@ -1351,11 +1379,21 @@ TestControl::run() {
         // If -f<renew-rate> option was specified we have to check how many
         // Renew packets should be sent to catch up with a desired rate.
         if ((options.getIpVersion() == 6) && (options.getRenewRate() != 0)) {
-            updateSendDue(last_renew_, options.getRenewRate(), renew_due_);
             uint64_t renew_packets_due =
-                getNextExchangesNum(renew_due_, options.getRenewRate());
-            // Send renew packets.
-            sendRenewPackets(socket, renew_packets_due);
+                renew_rate_control_.getOutboundMessageCount();
+            checkLateMessages(renew_rate_control_);
+            // Send Renew messages.
+            sendMultipleMessages6(socket, DHCPV6_RENEW, renew_packets_due);
+        }
+
+        // If -F<release-rate> option was specified we have to check how many
+        // Release messages should be sent to catch up with a desired rate.
+        if ((options.getIpVersion() == 6) && (options.getReleaseRate() != 0)) {
+            uint64_t release_packets_due =
+                release_rate_control_.getOutboundMessageCount();
+            checkLateMessages(release_rate_control_);
+            // Send Release messages.
+            sendMultipleMessages6(socket, DHCPV6_RELEASE, release_packets_due);
         }
 
         // Report delay means that user requested printing number
@@ -1451,7 +1489,7 @@ TestControl::saveFirstPacket(const Pkt6Ptr& pkt) {
 void
 TestControl::sendDiscover4(const TestControlSocket& socket,
                            const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Generate the MAC address to be passed in the packet.
     uint8_t randomized = 0;
     std::vector<uint8_t> mac_address = generateMacAddress(randomized);
@@ -1496,9 +1534,7 @@ void
 TestControl::sendDiscover4(const TestControlSocket& socket,
                            const std::vector<uint8_t>& template_buf,
                            const bool preload /* = false */) {
-    // last_sent_ has to be updated for each function that initiates
-    // new transaction. The packet exchange synchronization relies on this.
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Get the first argument if mulitple the same arguments specified
     // in the command line. First one refers to DISCOVER packets.
     const uint8_t arg_idx = 0;
@@ -1547,21 +1583,35 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
 }
 
 bool
-TestControl::sendRenew(const TestControlSocket& socket) {
-    last_renew_ = microsec_clock::universal_time();
+TestControl::sendMessageFromReply(const uint16_t msg_type,
+                                  const TestControlSocket& socket) {
+    // We only permit Release or Renew messages to be sent using this function.
+    if (msg_type != DHCPV6_RENEW && msg_type != DHCPV6_RELEASE) {
+        isc_throw(isc::BadValue, "invalid message type " << msg_type
+                  << " to be sent, expected DHCPV6_RENEW or DHCPV6_RELEASE");
+    }
+    // We track the timestamp of last Release and Renew in different variables.
+    if (msg_type == DHCPV6_RENEW) {
+        renew_rate_control_.updateSendTime();
+    } else {
+        release_rate_control_.updateSendTime();
+    }
     Pkt6Ptr reply = reply_storage_.getRandom();
     if (!reply) {
         return (false);
     }
-    Pkt6Ptr renew = createRenew(reply);
-    setDefaults6(socket, renew);
-    renew->pack();
-    IfaceMgr::instance().send(renew);
+    // Prepare the message of the specified type.
+    Pkt6Ptr msg = createMessageFromReply(msg_type, reply);
+    setDefaults6(socket, msg);
+    msg->pack();
+    // And send it.
+    IfaceMgr::instance().send(msg);
     if (!stats_mgr6_) {
         isc_throw(Unexpected, "Statistics Manager for DHCPv6 "
                   "hasn't been initialized");
     }
-    stats_mgr6_->passSentPacket(StatsMgr6::XCHG_RN, renew);
+    stats_mgr6_->passSentPacket((msg_type == DHCPV6_RENEW ? StatsMgr6::XCHG_RN
+                                 : StatsMgr6::XCHG_RL), msg);
     return (true);
 }
 
@@ -1903,7 +1953,7 @@ TestControl::sendRequest6(const TestControlSocket& socket,
 void
 TestControl::sendSolicit6(const TestControlSocket& socket,
                           const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     // Generate DUID to be passed to the packet
     uint8_t randomized = 0;
     std::vector<uint8_t> duid = generateDuid(randomized);
@@ -1952,7 +2002,7 @@ void
 TestControl::sendSolicit6(const TestControlSocket& socket,
                           const std::vector<uint8_t>& template_buf,
                           const bool preload /*= false*/) {
-    last_sent_ = microsec_clock::universal_time();
+    basic_rate_control_.updateSendTime();
     const int arg_idx = 0;
     // Get transaction id offset.
     size_t transid_offset = getTransactionIdOffset(arg_idx);
@@ -2051,47 +2101,5 @@ TestControl::testDiags(const char diag) const {
     return (false);
 }
 
-void
-TestControl::updateSendDue(const boost::posix_time::ptime& last_sent,
-                           const int rate,
-                           boost::posix_time::ptime& send_due) {
-    // If default constructor was called, this should not happen but
-    // if somebody has changed default constructor it is better to
-    // keep this check.
-    if (last_sent.is_not_a_date_time()) {
-        isc_throw(Unexpected, "time of last sent packet not initialized");
-    }
-    // Get the expected exchange rate.
-    CommandOptions& options = CommandOptions::instance();
-    // If rate was not specified we will wait just one clock tick to
-    // send next packet. This simulates best effort conditions.
-    long duration = 1;
-    if (rate != 0) {
-        // We use number of ticks instead of nanoseconds because
-        // nanosecond resolution may not be available on some
-        // machines. Number of ticks guarantees the highest possible
-        // timer resolution.
-        duration = time_duration::ticks_per_second() / rate;
-    }
-    // Calculate due time to initiate next chunk of exchanges.
-    send_due = last_sent + time_duration(0, 0, 0, duration);
-    // Check if it is already due.
-    ptime now(microsec_clock::universal_time());
-    // \todo verify if this condition is not too tight. In other words
-    // verify if this will not produce too many late sends.
-    // We might want to look at this once we are done implementing
-    // microsecond timeouts in IfaceMgr.
-    if (now > send_due) {
-        if (testDiags('i')) {
-            if (options.getIpVersion() == 4) {
-                stats_mgr4_->incrementCounter("latesend");
-            } else if (options.getIpVersion() == 6) {
-                stats_mgr6_->incrementCounter("latesend");
-            }
-        }
-    }
-}
-
-
 } // namespace perfdhcp
 } // namespace isc

+ 48 - 50
tests/tools/perfdhcp/test_control.h

@@ -16,6 +16,7 @@
 #define TEST_CONTROL_H
 
 #include "packet_storage.h"
+#include "rate_control.h"
 #include "stats_mgr.h"
 
 #include <dhcp/iface_mgr.h>
@@ -313,13 +314,23 @@ protected:
     /// has been reached.
     void cleanCachedPackets();
 
-    /// \brief Creates IPv6 packet using options from Reply packet.
+    /// \brief Creates DHCPv6 message from the Reply packet.
     ///
+    /// This function creates DHCPv6 Renew or Release message using the
+    /// data from the Reply message by copying options from the Reply
+    /// message.
+    ///
+    /// \param msg_type A type of the message to be createad.
     /// \param reply An instance of the Reply packet which contents should
-    /// be used to create an instance of the Renew packet.
+    /// be used to create an instance of the new message.
     ///
-    /// \return created Renew packet.
-    dhcp::Pkt6Ptr createRenew(const dhcp::Pkt6Ptr& reply);
+    /// \return created Release or Renew message
+    /// \throw isc::BadValue if the msg_type is neither DHCPV6_RENEW nor
+    /// DHCPV6_RELEASE or if the reply is NULL.
+    /// \throw isc::Unexpected if mandatory options are missing in the
+    /// Reply message.
+    dhcp::Pkt6Ptr createMessageFromReply(const uint16_t msg_type,
+                                         const dhcp::Pkt6Ptr& reply);
 
     /// \brief Factory function to create DHCPv6 ELAPSED_TIME option.
     ///
@@ -481,21 +492,6 @@ protected:
     /// \return A current timeout in microseconds.
     uint32_t getCurrentTimeout() const;
 
-    /// \brief Returns number of exchanges to be started.
-    ///
-    /// Method returns number of new exchanges to be started as soon
-    /// as possible to satisfy expected rate. Calculation used here
-    /// is based on current time, due time calculated with
-    /// \ref updateSendDue function and expected rate.
-    ///
-    /// \param send_due Due time to initiate next chunk set exchanges.
-    /// \param rate A rate at which exchanges are initiated.
-    ///
-    /// \return number of exchanges to be started immediately.
-    static uint64_t
-    getNextExchangesNum(const boost::posix_time::ptime& send_due,
-                        const int rate);
-
     /// \brief Return template buffer.
     ///
     /// Method returns template buffer at specified index.
@@ -738,25 +734,32 @@ protected:
                      const uint64_t packets_num,
                      const bool preload = false);
 
-    /// \brief Send number of DHCPv6 Renew packets to the server.
+    /// \brief Send number of DHCPv6 Renew or Release messages to the server.
     ///
     /// \param socket An object representing socket to be used to send packets.
-    /// \param packets_num A number of Renew packets to be send.
+    /// \param msg_type A type of the messages to be sent (DHCPV6_RENEW or
+    /// DHCPV6_RELEASE).
+    /// \param msg_num A number of messages to be sent.
     ///
-    /// \return A number of packets actually sent.
-    uint64_t sendRenewPackets(const TestControlSocket& socket,
-                              const uint64_t packets_num);
+    /// \return A number of messages actually sent.
+    uint64_t sendMultipleMessages6(const TestControlSocket& socket,
+                                   const uint32_t msg_type,
+                                   const uint64_t msg_num);
 
-    /// \brief Send a renew message using provided socket.
+    /// \brief Send DHCPv6 Renew or Release message using specified socket.
     ///
     /// This method will select an existing lease from the Reply packet cache
-    /// If there is no lease that can be renewed this method will return false.
+    /// If there is no lease that can be renewed or released this method will
+    /// return false.
     ///
+    /// \param msg_type A type of the message to be sent (DHCPV6_RENEW or
+    /// DHCPV6_RELEASE).
     /// \param socket An object encapsulating socket to be used to send
     /// a packet.
     ///
-    /// \return true if packet has been sent, false otherwise.
-    bool sendRenew(const TestControlSocket& socket);
+    /// \return true if the message has been sent, false otherwise.
+    bool sendMessageFromReply(const uint16_t msg_type,
+                              const TestControlSocket& socket);
 
     /// \brief Send DHCPv4 REQUEST message.
     ///
@@ -899,21 +902,18 @@ protected:
     /// \return true if diagnostics flag has been set.
     bool testDiags(const char diag) const;
 
-    /// \brief Update due time to initiate next chunk of exchanges.
+protected:
+
+    /// \brief Increments counter of late sent messages if required.
     ///
-    /// Method updates due time to initiate next chunk of exchanges.
-    /// Function takes current time, last sent packet's time and
-    /// expected rate in its calculations.
+    /// This function checks if the message or set of messages of a given type,
+    /// were sent later than their due time. If they were sent late, it is
+    /// an indication that the perfdhcp doesn't catch up with the desired rate
+    /// for sending messages.
     ///
-    /// \param last_sent A time when the last exchange was initiated.
-    /// \param rate A rate at which exchangesa re initiated
-    /// \param [out] send_due A reference to the time object to be updated
-    /// with the next due time.
-    void updateSendDue(const boost::posix_time::ptime& last_sent,
-                       const int rate,
-                       boost::posix_time::ptime& send_due);
-
-private:
+    /// \param rate_control An object tracking due times for a particular
+    /// type of messages.
+    void checkLateMessages(RateControl& rate_control);
 
     /// \brief Copies IA_NA or IA_PD option from one packet to another.
     ///
@@ -943,7 +943,7 @@ private:
 
     /// \brief Calculate elapsed time between two packets.
     ///
-    /// \param T Pkt4Ptr or Pkt6Ptr class.
+    /// \tparam T Pkt4Ptr or Pkt6Ptr class.
     /// \param pkt1 first packet.
     /// \param pkt2 second packet.
     /// \throw InvalidOperation if packet timestamps are invalid.
@@ -1056,14 +1056,12 @@ private:
     std::string vector2Hex(const std::vector<uint8_t>& vec,
                            const std::string& separator = "") const;
 
-    boost::posix_time::ptime send_due_;    ///< Due time to initiate next chunk
-                                           ///< of exchanges.
-    boost::posix_time::ptime last_sent_;   ///< Indicates when the last exchange
-                                           ///< was initiated.
-    boost::posix_time::ptime renew_due_;   ///< Due time to send next set of
-                                           ///< Renew requests.
-    boost::posix_time::ptime last_renew_;  ///< Indicates when the last Renew
-                                           ///< was attempted.
+    /// \brief A rate control class for Discover and Solicit messages.
+    RateControl basic_rate_control_;
+    /// \brief A rate control class for Renew messages.
+    RateControl renew_rate_control_;
+    /// \brief A rate control class for Release messages.
+    RateControl release_rate_control_;
 
     boost::posix_time::ptime last_report_; ///< Last intermediate report time.
 

+ 3 - 0
tests/tools/perfdhcp/tests/Makefile.am

@@ -1,6 +1,7 @@
 SUBDIRS = . testdata
 
 AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -I$(srcdir)/.. -I$(builddir)/..
 AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -26,6 +27,7 @@ run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += packet_storage_unittest.cc
+run_unittests_SOURCES += rate_control_unittest.cc
 run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += test_control_unittest.cc
 run_unittests_SOURCES += command_options_helper.h
@@ -33,6 +35,7 @@ run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt4.cc
+run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/rate_control.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/test_control.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)

+ 66 - 2
tests/tools/perfdhcp/tests/command_options_unittest.cc

@@ -168,6 +168,8 @@ protected:
         EXPECT_EQ(CommandOptions::DORA_SARR, opt.getExchangeMode());
         EXPECT_TRUE(opt.getLeaseType().is(CommandOptions::LeaseType::ADDRESS));
         EXPECT_EQ(0, opt.getRate());
+        EXPECT_EQ(0, opt.getRenewRate());
+        EXPECT_EQ(0, opt.getReleaseRate());
         EXPECT_EQ(0, opt.getReportDelay());
         EXPECT_EQ(0, opt.getClientsNum());
 
@@ -341,12 +343,18 @@ TEST_F(CommandOptionsTest, RenewRate) {
     EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -f 10 -l ethx all"));
     EXPECT_EQ(10, opt.getRenewRate());
     // Check that the release rate can be set to different value than
-    // rate specified as -r<rate>. Also, swap -f na d-r to make sure
+    // rate specified as -r<rate>. Also, swap -f and -r to make sure
     // that order doesn't matter.
     EXPECT_NO_THROW(process("perfdhcp -6 -f 5 -r 10 -l ethx all"));
     EXPECT_EQ(5, opt.getRenewRate());
+    // The renew rate should not be greater than the rate.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 11 -l ethx all"),
+                 isc::InvalidParameter);
     // The renew-rate of 0 is invalid.
-    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 - l ethx all"),
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f 0 -l ethx all"),
+                 isc::InvalidParameter);
+    // The negative renew-rate is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -f -5 -l ethx all"),
                  isc::InvalidParameter);
     // If -r<rate> is not specified the -f<renew-rate> should not
     // be accepted.
@@ -365,6 +373,62 @@ TEST_F(CommandOptionsTest, RenewRate) {
                  isc::InvalidParameter);
 }
 
+TEST_F(CommandOptionsTest, ReleaseRate) {
+    CommandOptions& opt = CommandOptions::instance();
+    // If -F is specified together with -r the command line should
+    // be accepted and the release rate should be set.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -F 10 -l ethx all"));
+    EXPECT_EQ(10, opt.getReleaseRate());
+    // Check that the release rate can be set to different value than
+    // rate specified as -r<rate>. Also, swap -F and -r to make sure
+    // that order doesn't matter.
+    EXPECT_NO_THROW(process("perfdhcp -6 -F 5 -r 10 -l ethx all"));
+    EXPECT_EQ(5, opt.getReleaseRate());
+    // The release rate should not be greater than the rate.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 11 -l ethx all"),
+                 isc::InvalidParameter);
+    // The release-rate of 0 is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 0 -l ethx all"),
+                 isc::InvalidParameter);
+    // The negative rlease-rate is invalid.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F -5 -l ethx all"),
+                 isc::InvalidParameter);
+    // If -r<rate> is not specified the -F<release-rate> should not
+    // be accepted.
+    EXPECT_THROW(process("perfdhcp -6 -F 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Currently the -F<release-rate> can be specified for IPv6 mode
+    // only.
+    EXPECT_THROW(process("perfdhcp -4 -r 10 -F 10 -l ethx all"),
+                 isc::InvalidParameter);
+    // Release rate should be specified.
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F -l ethx all"),
+                 isc::InvalidParameter);
+
+    // -F and -i are mutually exclusive
+    EXPECT_THROW(process("perfdhcp -6 -r 10 -F 10 -l ethx -i all"),
+                 isc::InvalidParameter);
+}
+
+TEST_F(CommandOptionsTest, ReleaseRenew) {
+    CommandOptions& opt = CommandOptions::instance();
+    // It should be possible to specify the -F, -f and -r options.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 10 -F 3 -f 5 -l ethx all"));
+    EXPECT_EQ(10, opt.getRate());
+    EXPECT_EQ(3, opt.getReleaseRate());
+    EXPECT_EQ(5, opt.getRenewRate());
+    // It should be possible to specify the -F and -f with the values which
+    // sum is equal to the rate specified as -r<rate>.
+    EXPECT_NO_THROW(process("perfdhcp -6 -r 8 -F 3 -f 5 -l ethx all"));
+    EXPECT_EQ(8, opt.getRate());
+    EXPECT_EQ(3, opt.getReleaseRate());
+    EXPECT_EQ(5, opt.getRenewRate());
+    // Check that the sum of the release and renew rate is not greater
+    // than the rate specified as -r<rate>.
+    EXPECT_THROW(process("perfdhcp -6 -F 6 -f 5 -r 10 -l ethx all"),
+                 isc::InvalidParameter);
+}
+
 TEST_F(CommandOptionsTest, ReportDelay) {
     CommandOptions& opt = CommandOptions::instance();
     EXPECT_NO_THROW(process("perfdhcp -r 100 -t 17 -l ethx all"));

+ 207 - 0
tests/tools/perfdhcp/tests/rate_control_unittest.cc

@@ -0,0 +1,207 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include "rate_control.h"
+#include <gtest/gtest.h>
+
+
+using namespace isc;
+using namespace isc::perfdhcp;
+
+/// \brief A class which exposes protected methods and members of the
+/// RateControl class (under test).
+class NakedRateControl : public RateControl {
+public:
+
+    /// \brief Default constructor.
+    NakedRateControl()
+        : RateControl() {
+    }
+
+    /// \brief Constructor which sets up the rate and aggressivity.
+    ///
+    /// \param rate A rate at which messages are sent.
+    /// \param aggressivity A value of aggressivity. This value controls the
+    /// maximal number of messages sent in one chunk.
+    NakedRateControl(const int rate, const int aggressivity)
+        : RateControl(rate, aggressivity) {
+    }
+
+    using RateControl::currentTime;
+    using RateControl::updateSendTime;
+    using RateControl::updateSendDue;
+    using RateControl::send_due_;
+    using RateControl::last_sent_;
+    using RateControl::late_sent_;
+
+};
+
+// Test default constructor.
+TEST(RateControl, constructorDefault) {
+    NakedRateControl rc;
+    EXPECT_EQ(1, rc.getAggressivity());
+    EXPECT_EQ(0, rc.getRate());
+    EXPECT_FALSE(rc.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc.isLateSent());
+}
+
+// Test the constructor which sets the rate and aggressivity.
+TEST(RateControl, constructor) {
+    // Call the constructor and verify that it sets the appropriate
+    // values.
+    NakedRateControl rc1(3, 2);
+    EXPECT_EQ(2, rc1.getAggressivity());
+    EXPECT_EQ(3, rc1.getRate());
+    EXPECT_FALSE(rc1.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc1.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc1.isLateSent());
+
+    // Call the constructor again and make sure that different values
+    // will be set correctly.
+    NakedRateControl rc2(5, 6);
+    EXPECT_EQ(6, rc2.getAggressivity());
+    EXPECT_EQ(5, rc2.getRate());
+    EXPECT_FALSE(rc2.getDue().is_not_a_date_time());
+    EXPECT_FALSE(rc2.last_sent_.is_not_a_date_time());
+    EXPECT_FALSE(rc2.isLateSent());
+
+    // The 0 value of aggressivity < 1 is not acceptable.
+    EXPECT_THROW(RateControl(3, 0), isc::BadValue);
+    // The negative value of rate is not acceptable.
+    EXPECT_THROW(RateControl(-1, 3), isc::BadValue);
+}
+
+// Check the aggressivity accessor.
+TEST(RateControl, getAggressivity) {
+    RateControl rc;
+    ASSERT_EQ(1, rc.getAggressivity());
+    rc.setAggressivity(5);
+    ASSERT_EQ(5, rc.getAggressivity());
+    rc.setAggressivity(10);
+    EXPECT_EQ(10, rc.getAggressivity());
+}
+
+// Check the due time accessor.
+TEST(RateControl, getDue) {
+    NakedRateControl rc;
+    ASSERT_FALSE(rc.getDue().is_not_a_date_time());
+    rc.send_due_ = NakedRateControl::currentTime();
+    EXPECT_TRUE(NakedRateControl::currentTime() >= rc.getDue());
+    rc.send_due_ = NakedRateControl::currentTime() +
+        boost::posix_time::seconds(10);
+    EXPECT_TRUE(NakedRateControl::currentTime() < rc.getDue());
+}
+
+// Check the rate accessor.
+TEST(RateControl, getRate) {
+    RateControl rc;
+    ASSERT_EQ(0, rc.getRate());
+    rc.setRate(5);
+    ASSERT_EQ(5, rc.getRate());
+    rc.setRate(10);
+    EXPECT_EQ(10, rc.getRate());
+}
+
+// Check if late send flag accessor.
+TEST(RateControl, isLateSent) {
+    NakedRateControl rc;
+    ASSERT_FALSE(rc.isLateSent());
+    rc.late_sent_ = true;
+    EXPECT_TRUE(rc.isLateSent());
+}
+
+// Check that the function returns the number of messages to be sent "now"
+// correctly.
+// @todo Possibly extend this test to cover more complex scenarios. Note that
+// it is quite hard to fully test this function as its behaviour strongly
+// depends on time.
+TEST(RateControl, getOutboundMessageCount) {
+    NakedRateControl rc1(1000, 1);
+    // Set the timestamp of the last sent message well to the past.
+    // The resulting due time will be in the past too.
+    rc1.last_sent_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(5);
+    // The number of messages to be sent must be greater than 0.
+    uint64_t count;
+    ASSERT_NO_THROW(count = rc1.getOutboundMessageCount());
+    EXPECT_GT(count, 0);
+    // Now, don't specify the rate. In this case the aggressivity dictates
+    // how many messages to send.
+    NakedRateControl rc2(0, 3);
+    rc2.last_sent_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(5);
+    ASSERT_NO_THROW(count = rc2.getOutboundMessageCount());
+    EXPECT_EQ(3, count);
+    // Specify the rate and set the timestamp of the last sent message well
+    // to the future. If the resulting due time is well in the future too,
+    // the number of messages to be sent must be 0.
+    NakedRateControl rc3(10, 3);
+    rc3.last_sent_ = NakedRateControl::currentTime() +
+        boost::posix_time::seconds(5);
+    ASSERT_NO_THROW(count = rc3.getOutboundMessageCount());
+    EXPECT_EQ(0, count);
+
+}
+
+// Test the aggressivity modifier for valid and invalid values.
+TEST(RateControl, setAggressivity) {
+    NakedRateControl rc;
+    ASSERT_NO_THROW(rc.setAggressivity(1));
+    EXPECT_THROW(rc.setAggressivity(0), isc::BadValue);
+    EXPECT_THROW(rc.setAggressivity(-1), isc::BadValue);
+}
+
+// Test the rate modifier for valid and invalid rate values.
+TEST(RateControl, setRate) {
+    NakedRateControl rc;
+    EXPECT_NO_THROW(rc.setRate(1));
+    EXPECT_NO_THROW(rc.setRate(0));
+    EXPECT_THROW(rc.setRate(-1), isc::BadValue);
+}
+
+// Test the function which calculates the due time to send next set of
+// messages.
+TEST(RateControl, updateSendDue) {
+    NakedRateControl rc;
+    // Set the send due timestamp to the value which is well in the future.
+    // If we don't hit the due time, the function should not modify the
+    // due time.
+    rc.send_due_ =
+        NakedRateControl::currentTime() + boost::posix_time::seconds(10);
+    boost::posix_time::ptime last_send_due = rc.send_due_;
+    ASSERT_NO_THROW(rc.updateSendDue());
+    EXPECT_TRUE(rc.send_due_ == last_send_due);
+    // Set the due time to the value which is already behind.
+    rc.send_due_ =
+        NakedRateControl::currentTime() - boost::posix_time::seconds(10);
+    last_send_due = rc.send_due_;
+    ASSERT_NO_THROW(rc.updateSendDue());
+    // The value should be modified to the new value.
+    EXPECT_TRUE(rc.send_due_ > last_send_due);
+
+}
+
+// Test that the message send time is updated to the current time.
+TEST(RateControl, updateSendTime) {
+    NakedRateControl rc;
+    // Set the timestamp to the future.
+    rc.last_sent_ =
+        NakedRateControl::currentTime() + boost::posix_time::seconds(5);
+    rc.updateSendTime();
+    // Updated timestamp should be set to now or to the past.
+    EXPECT_TRUE(rc.last_sent_ <= NakedRateControl::currentTime());
+
+}

+ 20 - 0
tests/tools/perfdhcp/tests/stats_mgr_unittest.cc

@@ -187,6 +187,8 @@ TEST_F(StatsMgrTest, Exchange) {
                                                       common_transid));
     // This is expected to throw because XCHG_DO was not yet
     // added to Stats Manager for tracking.
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
     EXPECT_THROW(
         stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
         BadValue
@@ -196,8 +198,11 @@ TEST_F(StatsMgrTest, Exchange) {
         BadValue
     );
 
+
     // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
     stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    ASSERT_TRUE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_DO));
+    ASSERT_FALSE(stats_mgr->hasExchangeStats(StatsMgr4::XCHG_RA));
     // The following two attempts are expected to throw because
     // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
     EXPECT_THROW(
@@ -259,6 +264,21 @@ TEST_F(StatsMgrTest, MultipleExchanges) {
               stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
 }
 
+TEST_F(StatsMgrTest, ExchangeToString) {
+    // Test DHCPv4 specific exchange names.
+    EXPECT_EQ("DISCOVER-OFFER",
+              StatsMgr4::exchangeToString(StatsMgr4::XCHG_DO));
+    EXPECT_EQ("REQUEST-ACK", StatsMgr4::exchangeToString(StatsMgr4::XCHG_RA));
+
+    // Test DHCPv6 specific exchange names.
+    EXPECT_EQ("SOLICIT-ADVERTISE",
+              StatsMgr6::exchangeToString(StatsMgr6::XCHG_SA));
+    EXPECT_EQ("REQUEST-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RR));
+    EXPECT_EQ("RENEW-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RN));
+    EXPECT_EQ("RELEASE-REPLY", StatsMgr6::exchangeToString(StatsMgr6::XCHG_RL));
+
+}
+
 TEST_F(StatsMgrTest, SendReceiveSimple) {
     boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
     boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,

+ 365 - 143
tests/tools/perfdhcp/tests/test_control_unittest.cc

@@ -74,8 +74,32 @@ public:
         uint32_t transid_; ///< Last generated transaction id.
     };
 
+    /// \brief Sets the due times for sedning Solicit, Renew and Release.
+    ///
+    /// There are three class members that hold the due time for sending DHCP
+    /// messages:
+    /// - send_due_ - due time to send Solicit,
+    /// - renew_due_ - due time to send Renew,
+    /// - release_due_ - due time to send Release.
+    /// Some tests in this test suite need to modify these values relative to
+    /// the current time. This function modifies this values using time
+    /// offset values (positive or negative) specified as a difference in
+    /// seconds between current time and the due time.
+    ///
+    /// \param send_secs An offset of the due time for Solicit.
+    /// \param renew_secs An offset of the due time for Renew.
+    /// \param release_secs An offset of the due time for Release.
+    void setRelativeDueTimes(const int send_secs, const int renew_secs = 0,
+                             const int release_secs = 0) {
+        ptime now = microsec_clock::universal_time();
+        basic_rate_control_.setRelativeDue(send_secs);
+        renew_rate_control_.setRelativeDue(renew_secs);
+        release_rate_control_.setRelativeDue(release_secs);
+
+    }
+
     using TestControl::checkExitConditions;
-    using TestControl::createRenew;
+    using TestControl::createMessageFromReply;
     using TestControl::factoryElapsedTime6;
     using TestControl::factoryGeneric;
     using TestControl::factoryIana6;
@@ -84,7 +108,7 @@ public:
     using TestControl::factoryRequestList4;
     using TestControl::generateDuid;
     using TestControl::generateMacAddress;
-    using TestControl::getNextExchangesNum;
+    using TestControl::getCurrentTimeout;
     using TestControl::getTemplateBuffer;
     using TestControl::initPacketTemplates;
     using TestControl::initializeStatsMgr;
@@ -92,13 +116,22 @@ public:
     using TestControl::processReceivedPacket4;
     using TestControl::processReceivedPacket6;
     using TestControl::registerOptionFactories;
+    using TestControl::reset;
     using TestControl::sendDiscover4;
     using TestControl::sendPackets;
-    using TestControl::sendRenewPackets;
+    using TestControl::sendMultipleMessages6;
     using TestControl::sendRequest6;
     using TestControl::sendSolicit6;
     using TestControl::setDefaults4;
     using TestControl::setDefaults6;
+    using TestControl::basic_rate_control_;
+    using TestControl::renew_rate_control_;
+    using TestControl::release_rate_control_;
+    using TestControl::last_report_;
+    using TestControl::transid_gen_;
+    using TestControl::macaddr_gen_;
+    using TestControl::first_packet_serverid_;
+    using TestControl::interrupted_;
 
     NakedTestControl() : TestControl() {
         uint32_t clients_num = CommandOptions::instance().getClientsNum() == 0 ?
@@ -626,6 +659,167 @@ public:
         }
     }
 
+    /// \brief Test that the DHCPv4 Release or Renew message is created
+    /// correctly and comprises expected options.
+    ///
+    /// \param msg_type A type of the message to be tested: DHCPV6_RELEASE
+    /// or DHCPV6_RENEW.
+    void testCreateRenewRelease(const uint16_t msg_type) {
+        // This command line specifies that the Release/Renew messages should
+        // be sent with the same rate as the Solicit messages.
+        std::ostringstream s;
+        s << "perfdhcp -6 -l lo -r 10 ";
+        s << (msg_type == DHCPV6_RELEASE ? "-F" : "-f") << " 10 ";
+        s << "-R 10 -L 10547 -n 10 -e address-and-prefix ::1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        // Set the transaction id generator which will be used by the
+        // createRenew or createRelease function to generate transaction id.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+
+        // Create a Reply packet. The createRelease or createReply function will
+        // need Reply packet to create a corresponding Release or Reply.
+        Pkt6Ptr reply = createReplyPkt6(1);
+
+        Pkt6Ptr msg;
+        // Check that the message is created.
+        ASSERT_NO_THROW(msg = tc.createMessageFromReply(msg_type, reply));
+
+        ASSERT_TRUE(msg);
+        // Check that the message type and transaction id is correct.
+        EXPECT_EQ(msg_type, msg->getType());
+        EXPECT_EQ(1, msg->getTransid());
+
+        // Check that the message has expected options. These are the same for
+        // Release and Renew.
+
+        // Client Identifier.
+        OptionPtr opt_clientid = msg->getOption(D6O_CLIENTID);
+        ASSERT_TRUE(opt_clientid);
+        EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() ==
+                    opt_clientid->getData());
+
+        // Server identifier
+        OptionPtr opt_serverid = msg->getOption(D6O_SERVERID);
+        ASSERT_TRUE(opt_serverid);
+        EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() ==
+                opt_serverid->getData());
+
+        // IA_NA
+        OptionPtr opt_ia_na = msg->getOption(D6O_IA_NA);
+        ASSERT_TRUE(opt_ia_na);
+        EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() ==
+                    opt_ia_na->getData());
+
+        // IA_PD
+        OptionPtr opt_ia_pd = msg->getOption(D6O_IA_PD);
+        ASSERT_TRUE(opt_ia_pd);
+        EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() ==
+                    opt_ia_pd->getData());
+
+        // Make sure that exception is thrown if the Reply message is NULL.
+        EXPECT_THROW(tc.createMessageFromReply(msg_type, Pkt6Ptr()),
+                     isc::BadValue);
+
+    }
+
+    /// \brief Test sending DHCPv6 Releases or Renews.
+    ///
+    /// This function simulates acquiring 10 leases from the server. Returned
+    /// Reply messages are cached and used to send Renew or Release messages.
+    /// The maxmimal number of Renew or Release messages which can be sent is
+    /// equal to the number of leases acquired (10). This function also checks
+    /// that an attempt to send more Renew or Release messages than the number
+    /// of leases acquired will fail.
+    ///
+    /// \param msg_type A type of the message which is simulated to be sent
+    /// (DHCPV6_RENEW or DHCPV6_RELEASE).
+    void testSendRenewRelease(const uint16_t msg_type) {
+        std::string loopback_iface(getLocalLoopback());
+        if (loopback_iface.empty()) {
+            std::cout << "Skipping the test because loopback interface could"
+                " not be detected" << std::endl;
+            return;
+        }
+        // Build a command line. Depending on the message type, we will use
+        // -f<renew-rate> or -F<release-rate> parameter.
+        std::ostringstream s;
+        s << "perfdhcp -6 -l " << loopback_iface << " -r 10 ";
+        s << (msg_type == DHCPV6_RENEW ? "-f" : "-F");
+        s << " 10 -R 10 -L 10547 -n 10 ::1";
+        ASSERT_NO_THROW(processCmdLine(s.str()));
+        // Create a test controller class.
+        NakedTestControl tc;
+        tc.initializeStatsMgr();
+        // Set the transaction id generator to sequential to control to
+        // guarantee that transaction ids are predictable.
+        boost::shared_ptr<NakedTestControl::IncrementalGenerator>
+            generator(new NakedTestControl::IncrementalGenerator());
+        tc.setTransidGenerator(generator);
+        // Socket has to be created so as we can actually send packets.
+        int sock_handle = 0;
+        ASSERT_NO_THROW(sock_handle = tc.openSocket());
+        TestControl::TestControlSocket sock(sock_handle);
+
+        // Send a number of Solicit messages. Each generated Solicit will be
+        // assigned a different transaction id, starting from 1 to 10.
+        tc.sendPackets(sock, 10);
+
+        // Simulate Advertise responses from the server. Each advertise is
+        // assigned a transaction id from the range of 1 to 10, so as they
+        // match the transaction ids from the Solicit messages.
+        for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
+            Pkt6Ptr advertise(createAdvertisePkt6(i));
+            // If Advertise is matched with the Solicit the call below will
+            // trigger a corresponding Request. They will be assigned
+            // transaction ids from the range from 11 to 20 (the range of
+            // 1 to 10 has been used by Solicit-Advertise).
+            ASSERT_NO_THROW(tc.processReceivedPacket6(sock, advertise));
+    }
+
+        // Requests have been sent, so now let's simulate responses from the
+        // server. Generate corresponding Reply messages with the transaction
+        // ids from the range from 11 to 20.
+        for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
+            Pkt6Ptr reply(createReplyPkt6(i));
+            // Each Reply packet corresponds to the new lease acquired. Since
+            // -f<renew-rate> option has been specified, received Reply
+            // messages are held so as Renew messages can be sent for
+            // existing leases.
+            ASSERT_NO_THROW(tc.processReceivedPacket6(sock, reply));
+        }
+
+        uint64_t msg_num;
+        // Try to send 5 messages. It should be successful because 10 Reply
+        // messages has been received. For each of them we should be able to
+        // send Renew or Release.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        // Make sure that we have sent 5 messages.
+        EXPECT_EQ(5, msg_num);
+
+        // Try to do it again. We should still have 5 Reply packets for
+        // which Renews or Releases haven't been sent yet.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        EXPECT_EQ(5, msg_num);
+
+        // We used all the Reply packets (we sent Renew or Release for each of
+        // them already). Therefore, no further Renew or Release messages should
+        // be sent before we acquire new leases.
+        ASSERT_NO_THROW(
+            msg_num = tc.sendMultipleMessages6(sock, msg_type, 5)
+        );
+        // Make sure that no message has been sent.
+        EXPECT_EQ(0, msg_num);
+
+    }
+
     /// \brief Parse command line string with CommandOptions.
     ///
     /// \param cmdline command line string to be parsed.
@@ -710,6 +904,25 @@ public:
 
 };
 
+// This test verifies that the class members are reset to expected values.
+TEST_F(TestControlTest, reset) {
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l ethx -r 50 -f 30 -F 10 -a 3 all"));
+    NakedTestControl tc;
+    tc.reset();
+    EXPECT_EQ(3, tc.basic_rate_control_.getAggressivity());
+    EXPECT_EQ(3, tc.renew_rate_control_.getAggressivity());
+    EXPECT_EQ(3, tc.release_rate_control_.getAggressivity());
+    EXPECT_EQ(50, tc.basic_rate_control_.getRate());
+    EXPECT_EQ(30, tc.renew_rate_control_.getRate());
+    EXPECT_EQ(10, tc.release_rate_control_.getRate());
+    EXPECT_FALSE(tc.last_report_.is_not_a_date_time());
+    EXPECT_FALSE(tc.transid_gen_);
+    EXPECT_FALSE(tc.macaddr_gen_);
+    EXPECT_TRUE(tc.first_packet_serverid_.empty());
+    EXPECT_FALSE(tc.interrupted_);
+
+}
+
 TEST_F(TestControlTest, GenerateDuid) {
     // Simple command line that simulates one client only. Always the
     // same DUID will be generated.
@@ -1226,157 +1439,166 @@ TEST_F(TestControlTest, PacketTemplates) {
     EXPECT_THROW(tc.initPacketTemplates(), isc::BadValue);
 }
 
-TEST_F(TestControlTest, RateControl) {
-    // We don't specify the exchange rate here so the aggressivity
-    // value will determine how many packets are to be send each
-    // time we query the getNextExchangesNum.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -l 127.0.0.1 all"));
-    CommandOptions& options = CommandOptions::instance();
+TEST_F(TestControlTest, processRenew) {
+    testSendRenewRelease(DHCPV6_RENEW);
+}
 
-    NakedTestControl tc1;
-    uint64_t xchgs_num = tc1.getNextExchangesNum(microsec_clock::universal_time(),
-                                                 options.getRate());
-    EXPECT_EQ(options.getAggressivity(), xchgs_num);
+TEST_F(TestControlTest, processRelease) {
+    testSendRenewRelease(DHCPV6_RELEASE);
+}
 
-    // The exchange rate is now 1 per second. We don't know how many
-    // exchanges have to initiated exactly but for sure it has to be
-    // non-zero value. Also, since aggressivity is very high we expect
-    // that it will not be restricted by aggressivity.
-    ASSERT_NO_THROW(
-        processCmdLine("perfdhcp -l 127.0.0.1 -a 1000000 -r 1 all")
-    );
-    NakedTestControl tc2;
-    xchgs_num = tc2.getNextExchangesNum(microsec_clock::universal_time(),
-                                        options.getRate());
-    EXPECT_GT(xchgs_num, 0);
-    EXPECT_LT(xchgs_num, options.getAggressivity());
-    // @todo add more thorough checks for rate values.
+// This test verifies that the DHCPV6 Renew message is created correctly
+// and that it comprises all required options.
+TEST_F(TestControlTest, createRenew) {
+    testCreateRenewRelease(DHCPV6_RENEW);
 }
 
-TEST_F(TestControlTest, processRenew) {
-    std::string loopback_iface(getLocalLoopback());
-    if (loopback_iface.empty()) {
-        std::cout << "Skipping the test because loopback interface could"
-            " not be detected" << std::endl;
-        return;
-    }
-    // This command line specifies that the Renew messages should be sent
-    // with the same rate as the Solicit messages.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l " + loopback_iface +
-                                   " -r 10 -f 10 -R 10 -L 10547 -n 10 ::1"));
-    // Create a test controller class.
+// This test verifies that the DHCPv6 Release message is created correctly
+// and that it comprises all required options.
+TEST_F(TestControlTest, createRelease) {
+    testCreateRenewRelease(DHCPV6_RELEASE);
+}
+
+// This test verifies that the current timeout value for waiting for
+// the server's responses is valid. The timeout value corresponds to the
+// time period between now and the next message to be sent from the
+// perfdhcp to a server.
+TEST_F(TestControlTest, getCurrentTimeout) {
+    // Process the command line: set the rate for Discovers to 10,
+    // and set Renew rate to 0 (-f flag absent).
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -4 -l lo -r 10 ::1"));
     NakedTestControl tc;
-    tc.initializeStatsMgr();
-    // Set the transaction id generator to sequential to control to guarantee
-    // that transaction ids are predictable.
-    boost::shared_ptr<NakedTestControl::IncrementalGenerator>
-        generator(new NakedTestControl::IncrementalGenerator());
-    tc.setTransidGenerator(generator);
-    // Socket has to be created so as we can actually send packets.
-    int sock_handle = 0;
-    ASSERT_NO_THROW(sock_handle = tc.openSocket());
-    TestControl::TestControlSocket sock(sock_handle);
-
-    // Send a number of Solicit messages. Each generated Solicit will be
-    // assigned a different transaction id, starting from 1 to 10.
-    tc.sendPackets(sock, 10);
-
-    // Simulate Advertise responses from the server. Each advertise is assigned
-    // a transaction id from the range of 1 to 10, so as they match the
-    // transaction ids from the Solicit messages.
-    for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
-        Pkt6Ptr advertise(createAdvertisePkt6(i));
-        // If Advertise is matched with the Solicit the call below will
-        // trigger a corresponding Request. They will be assigned
-        // transaction ids from the range from 11 to 20 (the range of
-        // 1 to 10 has been used by Solicit-Advertise).
-        ASSERT_NO_THROW(tc.processReceivedPacket6(sock, advertise));
-    }
+    // Make sure that the renew rate is 0.
+    ASSERT_EQ(0, CommandOptions::instance().getRenewRate());
+    // Simulate the case when we are already behind the due time for
+    // the next Discover to be sent.
+    tc.setRelativeDueTimes(-3);
+    // Expected timeout value is 0, which means that perfdhcp should
+    // not wait for server's response but rather send the next
+    // message to a server immediately.
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+    // Now, let's do set the due time to a value in the future. The returned
+    // timeout value should be somewhere between now and this time in the
+    // future. The value of ten seconds ahead should be safe and guarantee
+    // that the returned timeout value is non-zero, even though there is a
+    // delay between setting the send_due_ value and invoking the function.
+    tc.setRelativeDueTimes(10);
+    uint32_t timeout = tc.getCurrentTimeout();
+    EXPECT_GT(timeout, 0);
+    EXPECT_LE(timeout, 10000000);
+}
 
-    // Requests have been sent, so now let's simulate responses from the server.
-    // Generate corresponding Reply messages with the transaction ids from the
-    // range from 11 to 20.
-    for (int i = generator->getNext() - 10; i < generator->getNext(); ++i) {
-        Pkt6Ptr reply(createReplyPkt6(i));
-        // Each Reply packet corresponds to the new lease acquired. Since
-        // -f<renew-rate> option has been specified, received Reply
-        // messages are held so as Renew messages can be sent for
-        // existing leases.
-        ASSERT_NO_THROW(tc.processReceivedPacket6(sock, reply));
-    }
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends Renew requests to the server, apart from the regular 4-way exchanges.
+// The timeout value depends on both the due time to send next Solicit and the
+// due time to send Renew - the timeout should be ajusted to the due time that
+// occurs sooner.
+TEST_F(TestControlTest, getCurrentTimeoutRenew) {
+    // Set the Solicit rate to 10 and the Renew rate 5.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 5 ::1"));
+    NakedTestControl tc;
+
+    // Make sure, that the Renew rate has been set to 5.
+    ASSERT_EQ(5, CommandOptions::instance().getRenewRate());
+    // The send_due_ is in the past, the renew_due_ is in the future.
+    tc.setRelativeDueTimes(-3, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Swap the due times from the previous check. The effect should be the
+    // same.
+    tc.setRelativeDueTimes(3, -3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Set both due times to the future. The renew due time is to occur
+    // sooner. The timeout should be a value between now and the
+    // renew due time.
+    tc.setRelativeDueTimes(10, 5);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+    // Repeat the same check, but swap the due times.
+    tc.setRelativeDueTimes(5, 10);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
 
-    uint64_t renew_num;
-    // Try to send 5 Renew packets. It should be successful because
-    // 10 Reply messages has been received. For each of them we should
-    // be able to send Renew.
-    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
-    // Make sure that we have sent 5 packets.
-    EXPECT_EQ(5, renew_num);
-
-    // Try to do it again. We should still have 5 Reply packets for
-    // which Renews haven't been sent yet.
-    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
-    EXPECT_EQ(5, renew_num);
-
-    // We used all the Reply packets (we sent Renew for each of them
-    // already). Therefore, no further Renew packets should be sent before
-    // We acquire new leases.
-    ASSERT_NO_THROW(renew_num = tc.sendRenewPackets(sock, 5));
-    // Make sure that no Renew has been sent.
-    EXPECT_EQ(0, renew_num);
 }
 
-TEST_F(TestControlTest, createRenew) {
-    // This command line specifies that the Renew messages should be sent
-    // with the same rate as the Solicit messages.
-    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 10 -R 10"
-                                   " -L 10547 -n 10 -e address-and-prefix"
-                                   " ::1"));
-    // Create a test controller class.
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends Release requests to the server, apart from the regular 4-way exchanges.
+TEST_F(TestControlTest, getCurrentTimeoutRelease) {
+    // Set the Solicit rate to 10 and the Release rate 5.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -F 5 ::1"));
     NakedTestControl tc;
-    // Set the transaction id generator because createRenew function requires
-    // it to generate the transaction id for the Renew packet.
-    boost::shared_ptr<NakedTestControl::IncrementalGenerator>
-        generator(new NakedTestControl::IncrementalGenerator());
-    tc.setTransidGenerator(generator);
-
-    // Create a Reply packet. The createRenew function will need Reply
-    // packet to create a corresponding Renew.
-    Pkt6Ptr reply = createReplyPkt6(1);
-    Pkt6Ptr renew;
-    // Check that Renew is created.
-    ASSERT_NO_THROW(renew = tc.createRenew(reply));
-    ASSERT_TRUE(renew);
-    EXPECT_EQ(DHCPV6_RENEW, renew->getType());
-    EXPECT_EQ(1, renew->getTransid());
-
-    // Now check that the Renew packet created, has expected options. The
-    // payload of these options should be the same as the payload of the
-    // options in the Reply.
-
-    // Client Identifier
-    OptionPtr opt_clientid = renew->getOption(D6O_CLIENTID);
-    ASSERT_TRUE(opt_clientid);
-    EXPECT_TRUE(reply->getOption(D6O_CLIENTID)->getData() ==
-                opt_clientid->getData());
-
-    // Server identifier
-    OptionPtr opt_serverid = renew->getOption(D6O_SERVERID);
-    ASSERT_TRUE(opt_serverid);
-    EXPECT_TRUE(reply->getOption(D6O_SERVERID)->getData() ==
-                opt_serverid->getData());
-
-    // IA_NA
-    OptionPtr opt_ia_na = renew->getOption(D6O_IA_NA);
-    ASSERT_TRUE(opt_ia_na);
-    EXPECT_TRUE(reply->getOption(D6O_IA_NA)->getData() ==
-                opt_ia_na->getData());
 
-    // IA_PD
-    OptionPtr opt_ia_pd = renew->getOption(D6O_IA_PD);
-    ASSERT_TRUE(opt_ia_pd);
-    EXPECT_TRUE(reply->getOption(D6O_IA_PD)->getData() ==
-                opt_ia_pd->getData());
+    // Make sure, that the Release rate has been set to 5.
+    ASSERT_EQ(5, CommandOptions::instance().getReleaseRate());
+    // The send_due_ is in the past, the renew_due_ is in the future.
+    tc.setRelativeDueTimes(-3, 0, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Swap the due times from the previous check. The effect should be the
+    // same.
+    tc.setRelativeDueTimes(3, 0, -3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // Set both due times to the future. The renew due time is to occur
+    // sooner. The timeout should be a value between now and the
+    // release due time.
+    tc.setRelativeDueTimes(10, 0, 5);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+    // Repeat the same check, but swap the due times.
+    tc.setRelativeDueTimes(5, 0, 10);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
 
 }
 
+// This test verifies that the current timeout value for waiting for the
+// server's responses is valid. In this case, we are simulating that perfdhcp
+// sends both Renew and Release requests to the server, apart from the regular
+// 4-way exchanges.
+TEST_F(TestControlTest, getCurrentTimeoutRenewRelease) {
+    // Set the Solicit rate to 10 and, Renew rate to 5, Release rate to 3.
+    ASSERT_NO_THROW(processCmdLine("perfdhcp -6 -l lo -r 10 -f 5 -F 3 ::1"));
+    NakedTestControl tc;
+
+    // Make sure the Renew and Release rates has been set to a non-zero value.
+    ASSERT_EQ(5, CommandOptions::instance().getRenewRate());
+    ASSERT_EQ(3, CommandOptions::instance().getReleaseRate());
+
+    // If any of the due times is in the past, the timeout value should be 0,
+    // to indicate that the next message should be sent immediately.
+    tc.setRelativeDueTimes(-3, 3, 5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(-3, 5, 3);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(3, -3, 5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(3, 2, -5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    tc.setRelativeDueTimes(-3, -2, -5);
+    EXPECT_EQ(0, tc.getCurrentTimeout());
+
+    // If due times are in the future, the timeout value should be aligned to
+    // the due time which occurs the soonest.
+    tc.setRelativeDueTimes(10, 9, 8);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 8000000);
+
+    tc.setRelativeDueTimes(10, 8, 9);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 8000000);
+
+    tc.setRelativeDueTimes(5, 8, 9);
+    EXPECT_GT(tc.getCurrentTimeout(), 0);
+    EXPECT_LE(tc.getCurrentTimeout(), 5000000);
+
+}