Parcourir la source

[2225_xfrout] Merge branch 'master' of git://git.bind10.isc.org/bind10 into trac2225_xfrout

Conflicts:
	src/bin/xfrout/tests/xfrout_test.py.in
	src/bin/xfrout/xfrout.py.in
Naoki Kambe il y a 12 ans
Parent
commit
13e8c165b1
100 fichiers modifiés avec 7681 ajouts et 3958 suppressions
  1. 239 10
      ChangeLog
  2. 9 6
      README
  3. 48 10
      configure.ac
  4. 1 1
      doc/Makefile.am
  5. 12 0
      doc/differences.txt
  6. 1412 388
      doc/guide/bind10-guide.xml
  7. 3 2
      examples/configure.ac
  8. 36 26
      examples/m4/ax_isc_rpath.m4
  9. 5 0
      src/bin/auth/.gitignore
  10. 24 4
      src/bin/auth/Makefile.am
  11. 1 217
      src/bin/auth/auth.spec.pre.in
  12. 1 3
      src/bin/auth/auth_config.cc
  13. 6 3
      src/bin/auth/auth_messages.mes
  14. 85 96
      src/bin/auth/auth_srv.cc
  15. 1 4
      src/bin/auth/auth_srv.h
  16. 37 34
      src/bin/auth/b10-auth.xml
  17. 385 0
      src/bin/auth/gen-statisticsitems.py.pre.in
  18. 17 17
      src/bin/auth/main.cc
  19. 19 14
      src/bin/auth/query.cc
  20. 0 284
      src/bin/auth/statistics.cc
  21. 277 0
      src/bin/auth/statistics.cc.pre
  22. 223 142
      src/bin/auth/statistics.h
  23. 0 609
      src/bin/auth/statistics_items.h
  24. 53 0
      src/bin/auth/statistics_items.h.pre
  25. 48 0
      src/bin/auth/statistics_msg_items.def
  26. 1 0
      src/bin/auth/tests/.gitignore
  27. 9 2
      src/bin/auth/tests/Makefile.am
  28. 310 167
      src/bin/auth/tests/auth_srv_unittest.cc
  29. 0 2
      src/bin/auth/tests/command_unittest.cc
  30. 1 2
      src/bin/auth/tests/config_unittest.cc
  31. 2 1
      src/bin/auth/tests/datasrc_clients_builder_unittest.cc
  32. 90 0
      src/bin/auth/tests/gen-statisticsitems_test.py
  33. 165 105
      src/bin/auth/tests/query_unittest.cc
  34. 0 123
      src/bin/auth/tests/statistics_unittest.cc
  35. 714 0
      src/bin/auth/tests/statistics_unittest.cc.pre
  36. 75 0
      src/bin/auth/tests/statistics_util.cc
  37. 38 0
      src/bin/auth/tests/statistics_util.h
  38. 0 1
      src/bin/auth/tests/testdata/Makefile.am
  39. 12 7
      src/bin/auth/tests/testdata/example-base-inc.zone
  40. 1 1
      src/bin/auth/tests/testdata/example-nsec3-inc.zone
  41. 4 2
      src/bin/bind10/.gitignore
  42. 18 14
      src/bin/bind10/Makefile.am
  43. 3 2
      src/bin/bind10/README
  44. 519 0
      src/bin/bind10/b10-init.xml
  45. 11 0
      src/bin/bind10/bind10.in
  46. 13 448
      src/bin/bind10/bind10.xml
  47. 14 14
      src/bin/bind10/creatorapi.txt
  48. 51 46
      src/bin/bind10/bind10_src.py.in
  49. 3 3
      src/bin/bind10/bob.spec
  50. 55 55
      src/bin/bind10/bind10_messages.mes
  51. 2 2
      src/bin/bind10/run_bind10.sh.in
  52. 1 1
      src/bin/bind10/tests/.gitignore
  53. 1 1
      src/bin/bind10/tests/Makefile.am
  54. 65 50
      src/bin/bind10/tests/args_test.py
  55. 541 537
      src/bin/bind10/tests/bind10_test.py.in
  56. 108 69
      src/bin/bindctl/bindcmd.py
  57. 2 5
      src/bin/bindctl/bindctl.xml
  58. 1 1
      src/bin/bindctl/bindctl_main.py.in
  59. 12 12
      src/bin/bindctl/command_sets.py
  60. 15 0
      src/bin/bindctl/mycollections.py
  61. 1 1
      src/bin/bindctl/run_bindctl.sh.in
  62. 114 11
      src/bin/bindctl/tests/bindctl_test.py
  63. 1 1
      src/bin/cfgmgr/b10-cfgmgr.py.in
  64. 8 2
      src/bin/cfgmgr/b10-cfgmgr.xml
  65. 4 4
      src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
  66. 6 11
      src/bin/cmdctl/Makefile.am
  67. 3 0
      src/bin/cmdctl/b10-certgen.xml
  68. 5 2
      src/bin/cmdctl/b10-cmdctl.xml
  69. 42 22
      src/bin/cmdctl/cmdctl.py.in
  70. 10 0
      src/bin/cmdctl/cmdctl_messages.mes
  71. 1 1
      src/bin/cmdctl/run_b10-cmdctl.sh.in
  72. 1 1
      src/bin/cmdctl/tests/Makefile.am
  73. 115 45
      src/bin/cmdctl/tests/cmdctl_test.py
  74. 1 1
      src/bin/dbutil/b10-dbutil.xml
  75. 7 1
      src/bin/dbutil/dbutil.py.in
  76. 4 1
      src/bin/dbutil/run_dbutil.sh.in
  77. 70 32
      src/bin/dbutil/tests/dbutil_test.sh.in
  78. 1 0
      src/bin/dbutil/tests/testdata/Makefile.am
  79. BIN
      src/bin/dbutil/tests/testdata/v2_2.sqlite3
  80. 7 4
      src/bin/ddns/b10-ddns.xml
  81. 28 41
      src/bin/ddns/ddns.py.in
  82. 19 29
      src/bin/ddns/ddns_messages.mes
  83. 20 19
      src/bin/ddns/tests/ddns_test.py
  84. 4 6
      src/bin/dhcp4/Makefile.am
  85. 3 0
      src/bin/dhcp4/b10-dhcp4.xml
  86. 144 42
      src/bin/dhcp4/config_parser.cc
  87. 60 10
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  88. 22 1
      src/bin/dhcp4/ctrl_dhcp4_srv.h
  89. 14 0
      src/bin/dhcp4/dhcp4.dox
  90. 48 4
      src/bin/dhcp4/dhcp4.spec
  91. 67 10
      src/bin/dhcp4/dhcp4_messages.mes
  92. 294 65
      src/bin/dhcp4/dhcp4_srv.cc
  93. 42 3
      src/bin/dhcp4/dhcp4_srv.h
  94. 4 6
      src/bin/dhcp4/tests/Makefile.am
  95. 476 18
      src/bin/dhcp4/tests/config_parser_unittest.cc
  96. 228 14
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  97. 1 1
      src/bin/dhcp4/tests/dhcp4_test.py
  98. 4 6
      src/bin/dhcp6/Makefile.am
  99. 3 0
      src/bin/dhcp6/b10-dhcp6.xml
  100. 0 0
      src/bin/dhcp6/config_parser.cc

+ 239 - 10
ChangeLog

@@ -1,3 +1,232 @@
+581.	[func]*		y-aharen
+	Added statistics items in b10-auth based on
+	http://bind10.isc.org/wiki/StatisticsItems. Qtype counters are
+	dropped as it requires further spec design discussion.
+	(Trac #2154, Trac #2155,
+	             git 61d7c3959eb991b22bc1c0ef8f4ecb96b65d9325)
+	(Trac #2157, git e653adac032f871cbd66cd500c37407a56d14589)
+
+bind10-1.0.0-rc released on February 14, 2013
+
+580.	[func]*		muks
+	There is no longer a default user account. The old default account
+	with username 'root' has been removed. In a fresh installation of
+	BIND 10, the administrator has to configure a user account using
+	the b10-cmdctl-usermgr program.
+	(Trac #2641, git 54e8f4061f92c2f9e5b8564240937515efa6d934)
+
+579.	[bug]		jinmei
+	libdatasrc/b10-auth: corrected some corner cases in query handling
+	of in-memory data source that led to the following invalid/odd
+	responses from b10-auth:
+	- duplicate RRs in answer and additional for type ANY query
+	- incorrect NSEC for no error, no data (NXRRSET) response that
+	  matches a wildcard
+	(Trac #2585, git abe78fae4ba3aca5eb01806dd4e05607b1241745)
+
+578.	[bug]		jinmei
+	b10-auth now returns closest encloser NSEC3 proof to queries for
+	an empty non terminal derived from an Opt-Out NSEC3 RR, as clarified
+	in errata 3441 for RFC5155.  Previously it regarded such case as
+	broken zone and returned SERVFAIL.
+	(Trac #2659, git 24c235cb1b379c6472772d340e21577c3460b742)
+
+577.	[func]		muks
+	Added an SQLite3 index on records(rname, rdtype). This decreases
+	insert performance by ~28% and adds about ~20% to the file size,
+	but increases zone iteration performance. As it introduces a new
+	index, a database upgrade would be required.
+	(Trac #1756, git 9b3c959af13111af1fa248c5010aa33ee7e307ee)
+
+576.	[bug]		tmark, tomek
+	b10-dhcp6: Fixed bug when the server aborts operation when
+	receiving renew and there are no IPv6 subnets configured.
+	(Trac #2719, git 3132b8b19495470bbfd0f2ba0fe7da443926034b)
+
+575.	[bug]		marcin
+	b10-dhcp6: Fixed the bug whereby the subnet for the incoming
+	packet was selected using only its source address. The subnet
+	is now selected using either source address or the name of the
+	server's interface on which the packet has been received.
+	(Trac #2704, git 1cbacf19a28bdae50bb9bd3767bca0147fde37ed)
+
+574.	[func]		tmark
+	b10-dhcp4, b10-dhcp6: Composite key indexes were added to the lease
+	tables to reduce lease search time. The lease4 table now has two
+	additional indexes: a) hwaddr/subnet_id and b) client_id/subnet_id.
+	The lease6 now has the one additional index: iaid/subnet_id/duid.
+	Adding these indexes significantly improves lease acquisition
+	performance.
+	(Trac #2699,#2703, git 54bbed5fcbe237c5a49b515ae4c55148723406ce)
+
+573.	[bug]		stephen
+	Fixed problem whereby the DHCP server crashed if it ran out of
+	addresses.  Such a condition now causes a packet to be returned
+	to the client refusing the allocation of an address.
+	(Trac #2681, git 87ce14cdb121b37afb5b1931af51bed7f6323dd6)
+
+572.	[bug]		marcin
+	perfdhcp: Fixed bug where the command line switches used to
+	run the perfdhcp where printed as ASCII codes.
+	(Trac #2700, git b8d6b949eb7f4705e32fbdfd7694ca2e6a6a5cdc)
+
+571.	[build]		jinmei
+	The ./configure script can now handle output from python-config
+	--ldflags that contains a space after -L switches.  This fixes
+	failure reported on some Solaris environments.
+	(Trac #2661, git e6f86f2f5eec8e6003c13d36804a767a840d96d6)
+
+570.	[bug]		tmark, marcin, tomek
+	b10-dhcp4: Address renewal now works properly for DHCPv4 clients
+	that do not send client ID.
+	(Trac #2702, git daf2abe68ce9c111334a15c14e440730f3a085e2)
+
+569.	[bug]		tomek
+	b10-dhcp4: Fix bug whereby a DHCP packet without a client ID
+	could crash the MySQL lease database backend.
+	(Trac #2697, git b5e2be95d21ed750ad7cf5e15de2058aa8bc45f4)
+
+568.	[func]		muks
+	Various message IDs have been renamed to remove the word 'ERROR'
+	from them when they are not logged at ERROR severity level.
+	(Trac #2672, git 660a0d164feaf055677f375977f7ed327ead893e)
+
+567.	[doc]		marcin, stephen, tomek
+	Update DHCP sections of the BIND 10 guide.
+	(Trac #2657, git 1d0c2004865d1bf322bf78d13630d992e39179fd)
+
+566.	[func]*		jinmei
+	libdns++/Python isc.dns: In Python isc.dns, function style
+	constants for RRType, RRClass, Rcode and Opcode were deprecated
+	and replaced with straightforward object constants, e.g., from
+	RRType.AAAA() to RRType.AAAA.  This is a backward incompatible
+	change (see the Trac ticket for a conversion script if needed).
+	Also, these constants are now more consistent between C++
+	and Python, and RRType constants for all currently standardized
+	types are now supported (even if Rdata for these are not yet
+	available).
+	(Trac #1866 and #2409, git e5005185351cf73d4a611407c2cfcd163f80e428)
+
+565.	[func]*		jelte
+	The main initializer script (formerly known as either 'bind10',
+	'boss', or 'bob'), has been renamed to b10-init (and Init in
+	configuration). Configuring which components are run is henceforth
+	done through '/Init/components', and the sbin/bind10 script is now
+	simply a shellscript that runs b10-init. Existing configuration is
+	automatically updated. NOTE: once configuration with this update
+	has been saved (by committing any new change with bindctl), you
+	cannot run older versions of BIND 10 anymore with this configuration.
+	(Trac #1901, git bae3798603affdb276f370c1ac6b33b011a5ed4f)
+
+564.	[func]		muks
+	libdns++: the CNAME, DNAME, MX, NS, PTR and SRV Rdata classes now
+	use the generic lexer in constructors from text.  This means that
+	the name fields in such RRs in a zone file can now be non-absolute
+	(the origin name in that context will be used), e.g., when loaded
+	by b10-loadzone. One additional change to the libdns++ API is that
+	the existing string constructors for these Rdata classes also use
+	the generic lexer, and they now expect an absolute name (with the
+	trailing '.') in the name fields.
+	(Trac #2390, git a01569277cda3f78b1171bbf79f15ecf502e81e2)
+	(Trac #2656, git 5a0d055137287f81e23fbeedd35236fee274596d)
+
+563.	[build]		jinmei
+	Added --disable-rpath configure option to avoid embedding library
+	paths to binaries.  Patch from Adam Tkac.
+	(Trac #2667, git 1c50c5a6ee7e9675e3ab154f2c7f975ef519fca2)
+
+562.	[func]*		vorner
+	The b10-xfrin now performs basic sanity check on just received
+	zone. It'll reject severely broken zones (such as missing NS
+	records).
+	(Trac #2439, git 44699b4b18162581cd1dd39be5fb76ca536012e6)
+
+561.	[bug]		kambe, jelte
+	b10-stats-httpd no longer dumps request information to the console,
+	but uses the bind10 logging system. Additionally, the logging
+	identifiers have been changed from STATHTTPD_* to STATSHTTPD_*
+	(Trac #1897, git 93716b025a4755a8a2cbf250a9e4187741dbc9bb)
+
+560.	[bug]		jinmei
+	b10-auth now sets the TTL of SOA RR for negative responses to
+	the minimum of the RR TTL and the minimum TTL of the SOA RDATA
+	as specified in RFC2308; previously the RR TTL was always used.
+	The ZoneFinder class was extended partly for implementing this
+	and partly for allowing further optimization.
+	(Trac #2309 and #2635, git ee17e979fcde48b59d91c74ac368244169065f3b)
+
+559.	[bug]		jelte
+	b10-cmdctl no longer aborts on basic file issues with its https
+	certificate or private key file. It performs additional checks, and
+	provides better error logs if these fail. Additionally, bindctl
+	provides a better error report if it is unable to connect over
+	https connection. This issue could occur if BIND 10 was installed
+	with root privileges but then started as a normal user.
+	(Trac #2595, git 09b1a2f927483b407d70e98f5982f424cc872149)
+
+558.	[func]		marcin
+	b10-dhcp4: server now adds configured options to its
+	responses to a client when client requests them.
+	A few basic options: Routers, Domain Name, Domain
+	Name Servers and Subnet Mask are added regardless
+	if client requested them or not.
+	(Trac #2591, git aeec2dc1b9c511d17971ac63138576c37e7c5164)
+
+557.	[doc]		stephen
+	Update DHCP sections of the BIND 10 guide.
+	(Trac #2642, git e5faeb5fa84b7218fde486347359504cf692510e)
+
+556.	[bug]		marcin
+	Fixed DHCP servers configuration whereby the servers did not
+	receive a configuration stored in the database on their startup.
+	Also, the configuration handler function now uses full configuration
+	instead of partial to configure the server. This guarantees that
+	dependencies between various configuration parameters are
+	fulfilled.
+	(Trac #2637, git 91aa998226f1f91a232f2be59a53c9568c4ece77)
+
+555.	[func]		marcin
+	The encapsulated option space name can be specified for
+	a DHCP option. It comprises sub-options being sent within
+	an option that encapsulates this option space.
+	(Trac #2314, git 27e6119093723a1e46a239ec245a8b4b10677635)
+
+554.	[func]		jinmei
+	b10-loadzone: improved completion log message and intermediate
+	reports: It now logs the precise number of loaded RRs on
+	completion, and intermediate reports show additional information
+	such as the estimated progress in percentage and estimated time
+	to complete.
+	(Trac #2574, git 5b8a824054313bdecb8988b46e55cb2e94cb2d6c)
+
+553.	[func]		stephen
+	Values of the parameters to access the DHCP server lease database
+	can now be set through the BIND 10 configuration mechanism.
+	(Trac #2559, git 6c6f405188cc02d2358e114c33daff58edabd52a)
+
+552.	[bug]		shane
+	Build on Raspberry PI.
+	The main issue was use of char for reading from input streams,
+	which is incorrect, as EOF is returned as an int -1, which would
+	then get cast into a char -1.
+	A number of other minor issues were also fixed.
+	(Trac #2571, git 525333e187cc4bbbbde288105c9582c1024caa4a)
+
+551.	[bug]		shane
+	Kill msgq if we cannot connect to it on startup.
+	When the boss process was unable to connect to the msgq, it would
+	exit. However, it would leave the msgq process running. This has
+	been fixed, and the msgq is now stopped in this case.
+	(Trac #2608, git 016925ef2437e0396127e135c937d3a55539d224)
+
+550.	[func]		tomek
+	b10-dhcp4: The DHCPv4 server now generates a server identifier
+	the first time it is run. The identifier is preserved in a file
+	across server restarts.
+	b10-dhcp6: The server identifier is now preserved in a file across
+	server restarts.
+	(Trac #2597, git fa342a994de5dbefe32996be7eebe58f6304cff7)
+
 549.	[func]		tomek
 	b10-dhcp6: It is now possible to specify that a configured subnet
 	is reachable locally over specified interface (see "interface"
@@ -47,8 +276,8 @@
 543.	[func]*		jelte
 	When calling getFullConfig() as a module, , the configuration is now
 	returned as properly-structured JSON.  Previously, the structure had
-	been flattened, with all data being labelled by fully-qualified element
-	names.
+	been flattened, with all data being labelled by fully-qualified
+	element names.
 	(Trac #2619, git bed3c88c25ea8f7e951317775e99ebce3340ca22)
 
 542.	[func]		marcin
@@ -112,7 +341,7 @@
 	compile-time option --enable-debug.
 	(Trac #1081, git db55f102b30e76b72b134cbd77bd183cd01f95c0)
 
-534.	[func]*			vorner
+534.	[func]*		vorner
 	The b10-msgq now uses the same logging format as the rest
 	of the system. However, it still doesn't obey the common
 	configuration, as due to technical issues it is not able
@@ -689,7 +918,7 @@ bind10-devel-20120816 released on August 16, 2012
 460.	[bug]		muks
 	SSHFP's algorithm and fingerprint type checks have been relaxed
 	such that they will accept any values in [0,255]. This is so that
-	future algorithm and fingerprint types are accomodated.
+	future algorithm and fingerprint types are accommodated.
 	(Trac #2124, git 49e6644811a7ad09e1326f20dd73ab43116dfd21)
 
 459.	[func]		tomek
@@ -2709,7 +2938,7 @@ bind10-devel-20110224 released on February 24, 2011
 	(Trac #496, git b9296ca023cc9e76cda48a7eeebb0119166592c5)
 
 160.	[func]		jelte
-  	Updated the resolver to take 3 different timeout values;
+	Updated the resolver to take 3 different timeout values;
 	timeout_query for outstanding queries we sent while resolving
 	timeout_client for sending an answer back to the client
 	timeout_lookup for stopping the resolving
@@ -2888,7 +3117,7 @@ bind10-devel-20110120 released on January 20, 2011
 	(Trac #226, svn r3989)
 
 136.	[bug]		jelte
-  	bindctl (and the configuration manager in general) now no longer
+	bindctl (and the configuration manager in general) now no longer
 	accepts 'unknown' data; i.e. data for modules that it does not know
 	about, or configuration items that are not specified in the .spec
 	files.
@@ -3130,7 +3359,7 @@ bind10-devel-20100917 released on September 17, 2010
 	(Trac #342, svn r2949)
 
 94.	[bug]		jelte
-  	bin/xfrout:  Fixed a problem in xfrout where only 2 or 3 RRs
+	bin/xfrout:  Fixed a problem in xfrout where only 2 or 3 RRs
 	were used per DNS message in the xfrout stream.
 	(Trac #334, r2931)
 
@@ -3264,7 +3493,7 @@ bind10-devel-20100812 released on August 12, 2010
 	module. (Trac #275, r2459)
 
 73.	[bug]		jelte
-  	Fixed a bug where in bindctl, locally changed settings were
+	Fixed a bug where in bindctl, locally changed settings were
 	reset when the list of running modules is updated. (Trac #285,
 	r2452)
 
@@ -3275,11 +3504,11 @@ bind10-devel-20100812 released on August 12, 2010
 	known such platform. (Trac #148, r2427)
 
 71.	[func]		each
-  	Add "-a" (address) option to bind10 to specify an address for
+	Add "-a" (address) option to bind10 to specify an address for
 	the auth server to listen on.
 
 70.	[func]		each
-  	Added a hot-spot cache to libdatasrc to speed up access to
+	Added a hot-spot cache to libdatasrc to speed up access to
 	repeatedly-queried data and reduce the number of queries to
 	the underlying database; this should substantially improve
 	performance.  Also added a "-n" ("no cache") option to

+ 9 - 6
README

@@ -12,15 +12,18 @@ manager, b10-stats statistics collection and reporting daemon, and
 b10-stats-httpd for HTTP access to XML-formatted stats.
 
 For DNS services, it provides the b10-auth authoritative DNS server
-(with SQLite3 and in-memory backends), b10-resolver recursive or
-forwarding DNS server, b10-xfrin IXFR/AXFR inbound service, b10-xfrout
-outgoing IXFR/AXFR service, b10-zonemgr secondary manager, libdns++
-library for C++ with a python wrapper, and many tests and example
-programs.
+(with SQLite3 and in-memory backends), b10-xfrin IXFR/AXFR inbound
+service, b10-xfrout outgoing IXFR/AXFR service, b10-zonemgr secondary
+manager, libdns++ library for C++ with a python wrapper, and many
+tests and example programs. (It also includes an experimental proof
+of concept recursive or forwarding DNS server, b10-resolver.)
 
 BIND 10 also provides experimental DHCPv4 and DHCPv6 servers,
 b10-dhcp4 and b10-dhcp6, a portable DHCP library, libdhcp++, and
-a DHCP benchmarking tool, perfdhcp.
+a DHCP benchmarking tool, perfdhcp.  In this release of BIND 10,
+the DHCPv4 and DHCPv6 servers must be considered experimental.
+Limitations and known issues with this DHCP release can be found
+at http://bind10.isc.org/wiki/KeaKnownIssues
 
 Documentation is included with the source. See doc/guide/bind10-guide.txt
 (or bind10-guide.html) for installation instructions.  The

+ 48 - 10
configure.ac

@@ -2,9 +2,15 @@
 # Process this file with autoconf to produce a configure script.
 
 AC_PREREQ([2.59])
-AC_INIT(bind10, 20121219, bind10-dev@isc.org)
+AC_INIT(bind10, 20130221, bind10-dev@isc.org)
 AC_CONFIG_SRCDIR(README)
-AM_INIT_AUTOMAKE([foreign])
+# serial-tests is not available in automake version before 1.13. In
+# automake 1.13 and higher, AM_PROG_INSTALL is undefined, so we'll check
+# that and conditionally use serial-tests.
+AM_INIT_AUTOMAKE(
+	[foreign]
+	m4_ifndef([AM_PROG_INSTALL], [serial-tests])
+)
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
 AC_CONFIG_HEADERS([config.h])
 AC_CONFIG_MACRO_DIR([m4macros])
@@ -155,8 +161,10 @@ if test $with_werror = 1; then
    CXXFLAGS_SAVED="$CXXFLAGS"
    CXXFLAGS="$CXXFLAGS $B10_CXXFLAGS -Werror"
    AC_MSG_CHECKING(for in-TU anonymous namespace breakage)
-   AC_TRY_COMPILE([namespace { class Foo {}; }
-   namespace isc {class Bar {Foo foo_;};} ],,
+   # We use struct, not class, here, because some compilers complain about
+   # "unused private members", causing a false positive.
+   AC_TRY_COMPILE([namespace { struct Foo {}; }
+   namespace isc {struct Bar {Foo foo_;};} ],,
 	[AC_MSG_RESULT(no)
 	 werror_ok=1
 	 B10_CXXFLAGS="$B10_CXXFLAGS -Werror"],
@@ -232,7 +240,22 @@ AM_CONDITIONAL(SET_ENV_LIBRARY_PATH, test $SET_ENV_LIBRARY_PATH = yes)
 AC_SUBST(SET_ENV_LIBRARY_PATH)
 AC_SUBST(ENV_LIBRARY_PATH)
 
-m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.2 python3.1 python3])
+# Our experiments have shown Solaris 10 has broken support for the
+# IPV6_USE_MIN_MTU socket option for getsockopt(); it doesn't return the value
+# previously set via setsockopt().  We know it doesn't happen on one instance
+# on Solaris 11, but we don't know whether it happens for any Solaris 10
+# implementations or for earlier versions of Solaris.  In any case, at the
+# moment this matters for only one unittest case, so we'll simply disable
+# the affected test using the following definition with the specific hardcoding
+# of that version of Solaris.
+case "$host" in
+*-solaris2.10)
+	AC_DEFINE([HAVE_BROKEN_GET_IPV6_USE_MIN_MTU], [1],
+	[Define to 1 if getsockopt(IPV6_USE_MIN_MTU) does not work])
+	;;
+esac
+
+m4_define([_AM_PYTHON_INTERPRETER_LIST], [python python3.3 python3.2 python3.1 python3])
 AC_ARG_WITH([pythonpath],
 AC_HELP_STRING([--with-pythonpath=PATH],
   [specify an absolute path to python executable when automatic version check (incorrectly) fails]),
@@ -282,15 +305,26 @@ AC_SUBST(PYTHON_LOGMSGPKG_DIR)
 
 # This is python package paths commonly used in python tests.  See
 # README of log_messages for why it's included.
-COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python"
+# lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_builddir)/src/lib/python/isc/cc:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
 AC_SUBST(COMMON_PYTHON_PATH)
 
 # Check for python development environments
 if test -x ${PYTHON}-config; then
 	PYTHON_INCLUDES=`${PYTHON}-config --includes`
 
-	for flag in `${PYTHON}-config --ldflags`; do
-		# add any '-L..." flags to PYTHON_LDFLAGS
+	# Add any '-L..." flags to PYTHON_LDFLAGS.  We first make a copy of
+	# python-config --ldflags, removing any spaces and tabs
+	# between "-L" and its argument (some instances of python-config
+	# insert a space, which would confuse the code below).
+	# Notes: if -L isn't contained at all we can simply skip this process,
+	# so we only go through the flag if it's contained; also, protecting
+	# the output with [] seems necessary for environment to avoid getting
+	# an empty output accidentally.
+	python_config_ldflags=[`${PYTHON}-config --ldflags | sed -ne 's/\([ \t]*-L\)[ ]*\([^ \t]*[ \t]*\)/\1\2/pg'`]
+	for flag in $python_config_ldflags; do
 		flag=`echo $flag | sed -ne 's/^\(\-L.*\)$/\1/p'`
 		if test "X${flag}" != X; then
 			PYTHON_LDFLAGS="$PYTHON_LDFLAGS ${flag}"
@@ -1124,6 +1158,7 @@ AC_CONFIG_FILES([Makefile
                  compatcheck/Makefile
                  src/Makefile
                  src/bin/Makefile
+                 src/bin/bind10/bind10
                  src/bin/bind10/Makefile
                  src/bin/bind10/tests/Makefile
                  src/bin/cmdctl/Makefile
@@ -1192,6 +1227,7 @@ AC_CONFIG_FILES([Makefile
                  src/lib/python/isc/datasrc/tests/Makefile
                  src/lib/python/isc/dns/Makefile
                  src/lib/python/isc/cc/Makefile
+                 src/lib/python/isc/cc/cc_generated/Makefile
                  src/lib/python/isc/cc/tests/Makefile
                  src/lib/python/isc/config/Makefile
                  src/lib/python/isc/config/tests/Makefile
@@ -1307,9 +1343,9 @@ AC_OUTPUT([doc/version.ent
            src/bin/sysinfo/run_sysinfo.sh
            src/bin/stats/stats.py
            src/bin/stats/stats_httpd.py
-           src/bin/bind10/bind10_src.py
+           src/bin/bind10/init.py
            src/bin/bind10/run_bind10.sh
-           src/bin/bind10/tests/bind10_test.py
+           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
@@ -1325,6 +1361,7 @@ AC_OUTPUT([doc/version.ent
            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/tests/process_rename_test.py
@@ -1373,6 +1410,7 @@ AC_OUTPUT([doc/version.ent
            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
+           chmod +x src/bin/bind10/bind10
            chmod +x src/bin/bind10/run_bind10.sh
            chmod +x src/bin/cmdctl/tests/cmdctl_test
            chmod +x src/bin/dbutil/run_dbutil.sh

+ 1 - 1
doc/Makefile.am

@@ -1,6 +1,6 @@
 SUBDIRS = guide
 
-EXTRA_DIST = version.ent.in
+EXTRA_DIST = version.ent.in differences.txt
 
 devel:
 	mkdir -p html

+ 12 - 0
doc/differences.txt

@@ -0,0 +1,12 @@
+Differences of Bind 10 to other software
+========================================
+
+Bind 9
+------
+
+TODO: There are definitely more differences than just this.
+
+* When an incoming zone transfer fails, for example because the
+  received zone doesn't contain a NS record, bind 9 stops serving the
+  zone and returns SERVFAIL to queries for that zone. Bind 10 still
+  uses the previous version of zone.

Fichier diff supprimé car celui-ci est trop grand
+ 1412 - 388
doc/guide/bind10-guide.xml


+ 3 - 2
examples/configure.ac

@@ -14,9 +14,10 @@ AC_LANG([C++])
 # Checks for BIND 10 headers and libraries
 AX_ISC_BIND10
 
-# We use -R, -rpath etc so the resulting program will be more likekly to
+# We use -R option etc so the resulting program will be more likekly to
 # "just work" by default.  Embedding a specific library path is a controversial
-# practice, though; if you don't like it you can remove the following setting.
+# practice, though; if you don't like it you can remove the following setting,
+# or use the --disable-rpath option.
 if test "x$BIND10_RPATH" != "x"; then
    LDFLAGS="$LDFLAGS $BIND10_RPATH"
 fi

+ 36 - 26
examples/m4/ax_isc_rpath.m4

@@ -3,44 +3,54 @@ dnl
 dnl @summary figure out whether and which "rpath" linker option is available
 dnl
 dnl This macro checks if the linker supports an option to embed a path
-dnl to a runtime library (often installed in an uncommon place), such as
-dnl gcc's -rpath option.  If found, it sets the ISC_RPATH_FLAG variable to
+dnl to a runtime library (often installed in an uncommon place), such as the
+dnl commonly used -R option.  If found, it sets the ISC_RPATH_FLAG variable to
 dnl the found option flag.  The main configure.ac can use it as follows:
 dnl if test "x$ISC_RPATH_FLAG" != "x"; then
 dnl     LDFLAGS="$LDFLAGS ${ISC_RPATH_FLAG}/usr/local/lib/some_library"
 dnl fi
+dnl
+dnl If you pass --disable-rpath to configure, ISC_RPATH_FLAG is not set
 
 AC_DEFUN([AX_ISC_RPATH], [
 
-# We'll tweak both CXXFLAGS and CCFLAGS so this function will work whichever
-# language is used in the main script.  Note also that it's not LDFLAGS;
-# technically this is a linker flag, but we've noticed $LDFLAGS can be placed
-# where the compiler could interpret it as a compiler option, leading to
-# subtle failure mode.  So, in the check below using the compiler flag is
-# safer (in the actual Makefiles the flag should be set in LDFLAGS).
-CXXFLAGS_SAVED="$CXXFLAGS"
-CXXFLAGS="$CXXFLAGS -Wl,-R/usr/lib"
-CCFLAGS_SAVED="$CCFLAGS"
-CCFLAGS="$CCFLAGS -Wl,-R/usr/lib"
+AC_ARG_ENABLE(rpath,
+    [AC_HELP_STRING([--disable-rpath], [don't hardcode library path into binaries])],
+    rpath=$enableval, rpath=yes)
+
+if test x$rpath != xno; then
+    # We'll tweak both CXXFLAGS and CCFLAGS so this function will work
+    # whichever language is used in the main script.  Note also that it's not
+    #LDFLAGS; technically this is a linker flag, but we've noticed $LDFLAGS
+    # can be placed where the compiler could interpret it as a compiler
+    # option, leading to subtle failure mode.  So, in the check below using
+    # the compiler flag is safer (in the actual Makefiles the flag should be
+    # set in LDFLAGS).
+    CXXFLAGS_SAVED="$CXXFLAGS"
+    CXXFLAGS="$CXXFLAGS -Wl,-R/usr/lib"
+    CCFLAGS_SAVED="$CCFLAGS"
+    CCFLAGS="$CCFLAGS -Wl,-R/usr/lib"
 
-# check -Wl,-R and -R rather than gcc specific -rpath to be as portable
-# as possible.  -Wl,-R seems to be safer, so we try it first.  In some cases
-# -R is not actually recognized but AC_TRY_LINK doesn't fail due to that.
-AC_MSG_CHECKING([whether -Wl,-R flag is available in linker])
-AC_TRY_LINK([],[],
-    [ AC_MSG_RESULT(yes)
-        ISC_RPATH_FLAG=-Wl,-R
-    ],[ AC_MSG_RESULT(no)
-        AC_MSG_CHECKING([whether -R flag is available in linker])
-	CXXFLAGS="$CXXFLAGS_SAVED -R"
-	CCFLAGS="$CCFLAGS_SAVED -R"
+    # check -Wl,-R and -R rather than gcc specific -rpath to be as portable
+    # as possible.  -Wl,-R seems to be safer, so we try it first.  In some
+    # cases -R is not actually recognized but AC_TRY_LINK doesn't fail due to
+    # that.
+    AC_MSG_CHECKING([whether -Wl,-R flag is available in linker])
+    AC_TRY_LINK([],[],
+        [ AC_MSG_RESULT(yes)
+            ISC_RPATH_FLAG=-Wl,-R
+        ],[ AC_MSG_RESULT(no)
+            AC_MSG_CHECKING([whether -R flag is available in linker])
+            CXXFLAGS="$CXXFLAGS_SAVED -R"
+            CCFLAGS="$CCFLAGS_SAVED -R"
         AC_TRY_LINK([], [],
             [ AC_MSG_RESULT([yes; note that -R is more sensitive about the position in option arguments])
                 ISC_RPATH_FLAG=-R
             ],[ AC_MSG_RESULT(no) ])
-    ])
+        ])
 
-CXXFLAGS=$CXXFLAGS_SAVED
-CCFLAGS=$CCFLAGS_SAVED
+    CXXFLAGS=$CXXFLAGS_SAVED
+    CCFLAGS=$CCFLAGS_SAVED
+fi
 
 ])dnl AX_ISC_RPATH

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

@@ -6,3 +6,8 @@
 /spec_config.h
 /spec_config.h.pre
 /b10-auth.8
+/b10-auth.xml
+/gen-statisticsitems.py
+/gen-statisticsitems.py.pre
+/statistics.cc
+/statistics_items.h

+ 24 - 4
src/bin/auth/Makefile.am

@@ -18,6 +18,9 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
 
 CLEANFILES  = *.gcno *.gcda auth.spec spec_config.h
 CLEANFILES += auth_messages.h auth_messages.cc
+CLEANFILES += gen-statisticsitems.py
+# auto-generated by gen-statisticsitems.py
+CLEANFILES += statistics.cc statistics_items.h b10-auth.xml tests/statistics_unittest.cc
 
 man_MANS = b10-auth.8
 DISTCLEANFILES = $(man_MANS)
@@ -26,7 +29,7 @@ EXTRA_DIST = $(man_MANS) b10-auth.xml
 if GENERATE_DOCS
 
 b10-auth.8: b10-auth.xml
-	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-auth.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(builddir)/b10-auth.xml
 
 else
 
@@ -36,8 +39,18 @@ $(man_MANS):
 
 endif
 
-auth.spec: auth.spec.pre
-	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" auth.spec.pre >$@
+auth.spec: auth.spec.pre statistics_msg_items.def
+b10-auth.xml: b10-auth.xml.pre statistics_msg_items.def
+statistics_items.h: statistics_items.h.pre statistics_msg_items.def
+statistics.cc: statistics.cc.pre statistics_msg_items.def
+tests/statistics_unittest.cc: tests/statistics_unittest.cc.pre statistics_msg_items.def
+
+gen-statisticsitems.py: gen-statisticsitems.py.pre
+	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" gen-statisticsitems.py.pre >$@
+	chmod +x $@
+
+auth.spec b10-auth.xml statistics_items.h statistics.cc tests/statistics_unittest.cc: Makefile gen-statisticsitems.py
+	./gen-statisticsitems.py
 
 spec_config.h: spec_config.h.pre
 	$(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" spec_config.h.pre >$@
@@ -46,6 +59,8 @@ auth_messages.h auth_messages.cc: auth_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/bin/auth/auth_messages.mes
 
 BUILT_SOURCES = spec_config.h auth_messages.h auth_messages.cc
+# auto-generated by gen-statisticsitems.py
+BUILT_SOURCES += statistics_items.h statistics.cc
 
 pkglibexec_PROGRAMS = b10-auth
 b10_auth_SOURCES = query.cc query.h
@@ -54,13 +69,18 @@ b10_auth_SOURCES += auth_log.cc auth_log.h
 b10_auth_SOURCES += auth_config.cc auth_config.h
 b10_auth_SOURCES += command.cc command.h
 b10_auth_SOURCES += common.h common.cc
-b10_auth_SOURCES += statistics.cc statistics.h statistics_items.h
+b10_auth_SOURCES += statistics.h
 b10_auth_SOURCES += datasrc_clients_mgr.h
 b10_auth_SOURCES += datasrc_config.h datasrc_config.cc
 b10_auth_SOURCES += main.cc
 
 nodist_b10_auth_SOURCES = auth_messages.h auth_messages.cc
+nodist_b10_auth_SOURCES += statistics.cc statistics_items.h
 EXTRA_DIST += auth_messages.mes
+EXTRA_DIST += statistics_msg_items.def
+EXTRA_DIST += b10-auth.xml.pre
+EXTRA_DIST += statistics_items.h.pre statistics.cc.pre
+EXTRA_DIST += tests/statistics_unittest.cc.pre
 
 b10_auth_LDADD =  $(top_builddir)/src/lib/datasrc/libb10-datasrc.la
 b10_auth_LDADD += $(top_builddir)/src/lib/dns/libb10-dns++.la

+ 1 - 217
src/bin/auth/auth.spec.pre.in

@@ -122,7 +122,7 @@
             "item_name": "class", "item_type": "string",
             "item_optional": true, "item_default": "IN"
           },
-	  {
+          {
             "item_name": "origin", "item_type": "string",
             "item_optional": false, "item_default": ""
           }
@@ -140,222 +140,6 @@
       }
     ],
     "statistics": [
-      {
-        "item_name": "queries.tcp",
-        "item_type": "integer",
-        "item_optional": false,
-        "item_default": 0,
-        "item_title": "Queries TCP",
-        "item_description": "A number of total query counts which all auth servers receive over TCP since they started initially"
-      },
-      {
-        "item_name": "queries.udp",
-        "item_type": "integer",
-        "item_optional": false,
-        "item_default": 0,
-        "item_title": "Queries UDP",
-        "item_description": "A number of total query counts which all auth servers receive over UDP since they started initially"
-      },
-      {
-        "item_name": "opcode.query",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received query requests",
-        "item_description": "The number of total request counts whose opcode is query"
-      },
-      {
-        "item_name": "opcode.iquery",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received inverse query requests",
-        "item_description": "The number of total request counts whose opcode is inverse query"
-      },
-      {
-        "item_name": "opcode.status",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received status requests",
-        "item_description": "The number of total request counts whose opcode is status"
-      },
-      {
-        "item_name": "opcode.notify",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received notify requests",
-        "item_description": "The number of total request counts whose opcode is notify"
-      },
-      {
-        "item_name": "opcode.update",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received update requests",
-        "item_description": "The number of total request counts whose opcode is update"
-      },
-      {
-        "item_name": "opcode.other",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Received requests opcode other",
-        "item_description": "The number of total request counts whose opcode is other (not well-known)"
-      },
-      {
-        "item_name": "rcode.noerror",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent success response",
-        "item_description": "The number of total responses with rcode 0 (NOERROR)"
-      },
-      {
-        "item_name": "rcode.formerr",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'format error' response",
-        "item_description": "The number of total responses with rcode 1 (FORMERR)"
-      },
-      {
-        "item_name": "rcode.servfail",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'server failure' response",
-        "item_description": "The number of total responses with rcode 2 (SERVFAIL)"
-      },
-      {
-        "item_name": "rcode.nxdomain",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'name error' response",
-        "item_description": "The number of total responses with rcode 3 (NXDOMAIN)"
-      },
-      {
-        "item_name": "rcode.notimp",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'not implemented' response",
-        "item_description": "The number of total responses with rcode 4 (NOTIMP)"
-      },
-      {
-        "item_name": "rcode.refused",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'refused' response",
-        "item_description": "The number of total responses with rcode 5 (REFUSED)"
-      },
-      {
-        "item_name": "rcode.yxdomain",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'name unexpectedly exists' response",
-        "item_description": "The number of total responses with rcode 6 (YXDOMAIN)"
-      },
-      {
-        "item_name": "rcode.yxrrset",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'rrset unexpectedly exists' response",
-        "item_description": "The number of total responses with rcode 7 (YXRRSET)"
-      },
-      {
-        "item_name": "rcode.nxrrset",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'no such rrset' response",
-        "item_description": "The number of total responses with rcode 8 (NXRRSET)"
-      },
-      {
-        "item_name": "rcode.notauth",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'not authoritative' response",
-        "item_description": "The number of total responses with rcode 9 (NOTAUTH)"
-      },
-      {
-        "item_name": "rcode.notzone",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'name not in zone' response",
-        "item_description": "The number of total responses with rcode 10 (NOTZONE)"
-      },
-      {
-        "item_name": "rcode.badsigvers",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'EDNS version not implemented' response",
-        "item_description": "The number of total responses with rcode 16 (BADVERS)"
-      },
-      {
-        "item_name": "rcode.badkey",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'Key not recognized' response",
-        "item_description": "The number of total responses with rcode 17 (BADKEY)"
-      },
-      {
-        "item_name": "rcode.badtime",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'Signature out of time window' response",
-        "item_description": "The number of total responses with rcode 18 (BADTIME)"
-      },
-      {
-        "item_name": "rcode.badmode",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'Bad TKEY Mode' response",
-        "item_description": "The number of total responses with rcode 19 (BADMODE)"
-      },
-      {
-        "item_name": "rcode.badname",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'Duplicate key name' response",
-        "item_description": "The number of total responses with rcode 20 (BADNAME)"
-      },
-      {
-        "item_name": "rcode.badalg",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'Algorithm not supported' response",
-        "item_description": "The number of total responses with rcode 21 (BADALG)"
-      },
-      {
-        "item_name": "rcode.badtrunc",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent 'Bad Truncation' response",
-        "item_description": "The number of total responses with rcode 22 (BADTRUNC)"
-      },
-      {
-        "item_name": "rcode.other",
-        "item_type": "integer",
-        "item_optional": true,
-        "item_default": 0,
-        "item_title": "Sent responses with rcode other",
-        "item_description": "The number of total responses with rcode other (not well-known)"
-      }
     ]
   }
 }

+ 1 - 3
src/bin/auth/auth_config.cc

@@ -17,8 +17,6 @@
 
 #include <cc/data.h>
 
-#include <datasrc/memory_datasrc.h>
-#include <datasrc/zonetable.h>
 #include <datasrc/factory.h>
 
 #include <auth/auth_srv.h>
@@ -106,7 +104,7 @@ public:
         rollbackAddresses_ = old;
     }
     virtual void commit() {
-        rollbackAddresses_.release();
+        rollbackAddresses_.reset();
     }
 private:
     AuthSrv& server_;

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

@@ -14,7 +14,7 @@
 
 $NAMESPACE isc::auth
 
-% AUTH_AXFR_ERROR error handling AXFR request: %1
+% AUTH_AXFR_PROBLEM error handling AXFR request: %1
 This is a debug message produced by the authoritative server when it
 has encountered an error processing an AXFR request. The message gives
 the reason for the error, and the server will return a SERVFAIL code to
@@ -232,13 +232,13 @@ This is a debug message produced by the authoritative server when it receives
 a NOTIFY packet but the XFRIN process is not running. The packet will be
 dropped and nothing returned to the sender.
 
-% AUTH_PACKET_PARSE_ERROR unable to parse received DNS packet: %1
+% AUTH_PACKET_PARSE_FAILED unable to parse received DNS packet: %1
 This is a debug message, generated by the authoritative server when an
 attempt to parse a received DNS packet has failed due to something other
 than a protocol error. The reason for the failure is given in the message;
 the server will return a SERVFAIL error code to the sender.
 
-% AUTH_PACKET_PROTOCOL_ERROR DNS packet protocol error: %1. Returning %2
+% AUTH_PACKET_PROTOCOL_FAILURE DNS packet protocol error: %1. Returning %2
 This is a debug message, generated by the authoritative server when an
 attempt to parse a received DNS packet has failed due to a protocol error.
 The reason for the failure is given in the message, as is the error code
@@ -312,6 +312,9 @@ been created and is initializing. The AUTH_SERVER_STARTED message will be
 output when initialization has successfully completed and the server starts
 accepting queries.
 
+% AUTH_SERVER_EXITING exiting
+The authoritative server is exiting.
+
 % AUTH_SERVER_FAILED server failed: %1
 The authoritative server has encountered a fatal error and is terminating. The
 reason for the failure is included in the message.

+ 85 - 96
src/bin/auth/auth_srv.cc

@@ -86,6 +86,7 @@ using namespace isc::asiolink;
 using namespace isc::asiodns;
 using namespace isc::server_common::portconfig;
 using isc::auth::statistics::Counters;
+using isc::auth::statistics::MessageAttributes;
 
 namespace {
 // A helper class for cleaning up message renderer.
@@ -102,17 +103,21 @@ namespace {
 // user of this class, so we hide it within the implementation.
 class RendererHolder {
 public:
-    RendererHolder(MessageRenderer& renderer, OutputBuffer* buffer) :
-        renderer_(renderer)
+    RendererHolder(MessageRenderer& renderer, OutputBuffer* buffer,
+                   MessageAttributes& stats_attrs) :
+        renderer_(renderer),
+        stats_attrs_(stats_attrs)
     {
         renderer.setBuffer(buffer);
     }
     ~RendererHolder() {
+        stats_attrs_.setResponseTruncated(renderer_.isTruncated());
         renderer_.setBuffer(NULL);
         renderer_.clear();
     }
 private:
     MessageRenderer& renderer_;
+    MessageAttributes& stats_attrs_;
 };
 
 // Similar to Renderer holder, this is a very basic RAII-style class
@@ -239,15 +244,19 @@ public:
                 BaseSocketSessionForwarder& ddns_forwarder);
     ~AuthSrvImpl();
 
-    bool processNormalQuery(const IOMessage& io_message, Message& message,
+    bool processNormalQuery(const IOMessage& io_message,
+                            ConstEDNSPtr remote_edns, Message& message,
                             OutputBuffer& buffer,
-                            auto_ptr<TSIGContext> tsig_context);
+                            auto_ptr<TSIGContext> tsig_context,
+                            MessageAttributes& stats_attrs);
     bool processXfrQuery(const IOMessage& io_message, Message& message,
                          OutputBuffer& buffer,
-                         auto_ptr<TSIGContext> tsig_context);
+                         auto_ptr<TSIGContext> tsig_context,
+                         MessageAttributes& stats_attrs);
     bool processNotify(const IOMessage& io_message, Message& message,
                        OutputBuffer& buffer,
-                       auto_ptr<TSIGContext> tsig_context);
+                       auto_ptr<TSIGContext> tsig_context,
+                       MessageAttributes& stats_attrs);
     bool processUpdate(const IOMessage& io_message);
 
     IOService io_service_;
@@ -272,10 +281,6 @@ public:
     /// The data source client list manager
     auth::DataSrcClientsMgr datasrc_clients_mgr_;
 
-    /// Bind the ModuleSpec object in config_session_ with
-    /// isc:config::ModuleSpec::validateStatistics.
-    void registerStatisticsValidator();
-
     /// Socket session forwarder for dynamic update requests
     BaseSocketSessionForwarder& ddns_base_forwarder_;
 
@@ -293,25 +298,17 @@ public:
     ///
     /// \param server The DNSServer as passed to processMessage()
     /// \param message The response as constructed by processMessage()
-    /// \param stats_attrs Query/response attributes for statistics which is
-    ///                    not in \p messsage.
-    ///                    Note: This parameter is modified inside this method
-    ///                          to store whether the answer has been sent and
-    ///                          the response is truncated.
     /// \param done If true, it indicates there is a response.
     ///             this value will be passed to server->resume(bool)
     void resumeServer(isc::asiodns::DNSServer* server,
                       isc::dns::Message& message,
-                      statistics::QRAttributes& stats_attrs,
+                      MessageAttributes& stats_attrs,
                       const bool done);
 
 private:
     bool xfrout_connected_;
     AbstractXfroutClient& xfrout_client_;
 
-    // validateStatistics
-    bool validateStatistics(isc::data::ConstElementPtr data) const;
-
     auth::Query query_;
 };
 
@@ -423,6 +420,7 @@ public:
 void
 makeErrorMessage(MessageRenderer& renderer, Message& message,
                  OutputBuffer& buffer, const Rcode& rcode,
+                 MessageAttributes& stats_attrs,
                  std::auto_ptr<TSIGContext> tsig_context =
                  std::auto_ptr<TSIGContext>())
 {
@@ -455,9 +453,10 @@ makeErrorMessage(MessageRenderer& renderer, Message& message,
 
     message.setRcode(rcode);
 
-    RendererHolder holder(renderer, &buffer);
+    RendererHolder holder(renderer, &buffer, stats_attrs);
     if (tsig_context.get() != NULL) {
         message.toWire(renderer, *tsig_context);
+        stats_attrs.setResponseTSIG(true);
     } else {
         message.toWire(renderer);
     }
@@ -484,7 +483,6 @@ AuthSrv::setXfrinSession(AbstractSession* xfrin_session) {
 void
 AuthSrv::setConfigSession(ModuleCCSession* config_session) {
     impl_->config_session_ = config_session;
-    impl_->registerStatisticsValidator();
 }
 
 ModuleCCSession*
@@ -497,11 +495,11 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                         OutputBuffer& buffer, DNSServer* server)
 {
     InputBuffer request_buffer(io_message.getData(), io_message.getDataSize());
-    statistics::QRAttributes stats_attrs;
+    MessageAttributes stats_attrs;
 
-    // statistics: check transport carrying the message (IP, transport)
-    stats_attrs.setQueryIPVersion(io_message.getRemoteEndpoint().getFamily());
-    stats_attrs.setQueryTransportProtocol(
+    stats_attrs.setRequestIPVersion(
+        io_message.getRemoteEndpoint().getFamily());
+    stats_attrs.setRequestTransportProtocol(
         io_message.getRemoteEndpoint().getProtocol());
 
     // First, check the header part.  If we fail even for the base header,
@@ -522,19 +520,26 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
         return;
     }
 
+    const Opcode& opcode = message.getOpcode();
+    // Get opcode at this point; for all requests regardless of message body
+    // sanity check.
+    stats_attrs.setRequestOpCode(opcode);
+
     try {
         // Parse the message.
         message.fromWire(request_buffer);
     } catch (const DNSProtocolError& error) {
-        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_ERROR)
+        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PROTOCOL_FAILURE)
                   .arg(error.getRcode().toText()).arg(error.what());
-        makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode());
+        makeErrorMessage(impl_->renderer_, message, buffer, error.getRcode(),
+                         stats_attrs);
         impl_->resumeServer(server, message, stats_attrs, true);
         return;
     } catch (const Exception& ex) {
-        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_ERROR)
+        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_PACKET_PARSE_FAILED)
                   .arg(ex.what());
-        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL(),
+                         stats_attrs);
         impl_->resumeServer(server, message, stats_attrs, true);
         return;
     } // other exceptions will be handled at a higher layer.
@@ -558,85 +563,82 @@ AuthSrv::processMessage(const IOMessage& io_message, Message& message,
                                            **impl_->keyring_));
         tsig_error = tsig_context->verify(tsig_record, io_message.getData(),
                                           io_message.getDataSize());
-        // statistics: check TSIG attributes
-        // SIG(0) is currently not implemented in Auth, but it is implemented
-        // in BIND 9. At the point we support it, the code to check if the
-        // signature is valid would be around here.
-        stats_attrs.setQuerySig(true, false,
-                                tsig_error == TSIGError::NOERROR());
+        stats_attrs.setRequestTSIG(true, tsig_error != TSIGError::NOERROR());
     }
 
     if (tsig_error != TSIGError::NOERROR()) {
         makeErrorMessage(impl_->renderer_, message, buffer,
-                         tsig_error.toRcode(), tsig_context);
+                         tsig_error.toRcode(), stats_attrs, tsig_context);
         impl_->resumeServer(server, message, stats_attrs, true);
         return;
     }
 
-    const Opcode opcode = message.getOpcode();
     bool send_answer = true;
     try {
-        // statistics: check EDNS
-        //     note: This can only be reliable after TSIG check succeeds.
+        // note: This can only be reliable after TSIG check succeeds.
         ConstEDNSPtr edns = message.getEDNS();
-        if (edns != NULL) {
-            stats_attrs.setQueryEDNS(true, edns->getVersion() == 0);
-            stats_attrs.setQueryDO(edns->getDNSSECAwareness());
+        if (edns) {
+            stats_attrs.setRequestEDNS0(true);
+            stats_attrs.setRequestDO(edns->getDNSSECAwareness());
         }
 
-        // statistics: check OpCode
-        //     note: This can only be reliable after TSIG check succeeds.
-        stats_attrs.setQueryOpCode(opcode.getCode());
-
+        // note: This can only be reliable after TSIG check succeeds.
         if (opcode == Opcode::NOTIFY()) {
             send_answer = impl_->processNotify(io_message, message, buffer,
-                                               tsig_context);
+                                               tsig_context, stats_attrs);
         } else if (opcode == Opcode::UPDATE()) {
             if (impl_->ddns_forwarder_) {
                 send_answer = impl_->processUpdate(io_message);
             } else {
                 makeErrorMessage(impl_->renderer_, message, buffer,
-                                 Rcode::NOTIMP(), tsig_context);
+                                 Rcode::NOTIMP(), stats_attrs, tsig_context);
             }
         } else if (opcode != Opcode::QUERY()) {
             LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_UNSUPPORTED_OPCODE)
                       .arg(message.getOpcode().toText());
             makeErrorMessage(impl_->renderer_, message, buffer,
-                             Rcode::NOTIMP(), tsig_context);
+                             Rcode::NOTIMP(), stats_attrs, tsig_context);
         } else if (message.getRRCount(Message::SECTION_QUESTION) != 1) {
             makeErrorMessage(impl_->renderer_, message, buffer,
-                             Rcode::FORMERR(), tsig_context);
+                             Rcode::FORMERR(), stats_attrs, tsig_context);
         } else {
             ConstQuestionPtr question = *message.beginQuestion();
             const RRType& qtype = question->getType();
             if (qtype == RRType::AXFR()) {
                 send_answer = impl_->processXfrQuery(io_message, message,
-                                                     buffer, tsig_context);
+                                                     buffer, tsig_context,
+                                                     stats_attrs);
             } else if (qtype == RRType::IXFR()) {
                 send_answer = impl_->processXfrQuery(io_message, message,
-                                                     buffer, tsig_context);
+                                                     buffer, tsig_context,
+                                                     stats_attrs);
             } else {
-                send_answer = impl_->processNormalQuery(io_message, message,
-                                                        buffer, tsig_context);
+                send_answer = impl_->processNormalQuery(io_message, edns,
+                                                        message, buffer,
+                                                        tsig_context,
+                                                        stats_attrs);
             }
         }
     } catch (const std::exception& ex) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE)
                   .arg(ex.what());
-        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL(),
+                         stats_attrs);
     } catch (...) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_RESPONSE_FAILURE_UNKNOWN);
-        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(impl_->renderer_, message, buffer, Rcode::SERVFAIL(),
+                         stats_attrs);
     }
     impl_->resumeServer(server, message, stats_attrs, send_answer);
 }
 
 bool
-AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
+AuthSrvImpl::processNormalQuery(const IOMessage& io_message,
+                                ConstEDNSPtr remote_edns, Message& message,
                                 OutputBuffer& buffer,
-                                auto_ptr<TSIGContext> tsig_context)
+                                auto_ptr<TSIGContext> tsig_context,
+                                MessageAttributes& stats_attrs)
 {
-    ConstEDNSPtr remote_edns = message.getEDNS();
     const bool dnssec_ok = remote_edns && remote_edns->getDNSSECAwareness();
     const uint16_t remote_bufsize = remote_edns ? remote_edns->getUDPSize() :
         Message::DEFAULT_MAX_UDPSIZE;
@@ -666,21 +668,24 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
             const Name& qname = question->getName();
             query_.process(*list, qname, qtype, message, dnssec_ok);
         } else {
-            makeErrorMessage(renderer_, message, buffer, Rcode::REFUSED());
+            makeErrorMessage(renderer_, message, buffer, Rcode::REFUSED(),
+                             stats_attrs);
             return (true);
         }
     } catch (const Exception& ex) {
         LOG_ERROR(auth_logger, AUTH_PROCESS_FAIL).arg(ex.what());
-        makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL());
+        makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
+                         stats_attrs);
         return (true);
     }
 
-    RendererHolder holder(renderer_, &buffer);
+    RendererHolder holder(renderer_, &buffer, stats_attrs);
     const bool udp_buffer =
         (io_message.getSocket().getProtocol() == IPPROTO_UDP);
     renderer_.setLengthLimit(udp_buffer ? remote_bufsize : 65535);
     if (tsig_context.get() != NULL) {
         message.toWire(renderer_, *tsig_context);
+        stats_attrs.setResponseTSIG(true);
     } else {
         message.toWire(renderer_);
     }
@@ -697,12 +702,13 @@ AuthSrvImpl::processNormalQuery(const IOMessage& io_message, Message& message,
 bool
 AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
                              OutputBuffer& buffer,
-                             auto_ptr<TSIGContext> tsig_context)
+                             auto_ptr<TSIGContext> tsig_context,
+                             MessageAttributes& stats_attrs)
 {
     if (io_message.getSocket().getProtocol() == IPPROTO_UDP) {
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_UDP);
         makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
-                         tsig_context);
+                         stats_attrs, tsig_context);
         return (true);
     }
 
@@ -725,10 +731,10 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
             xfrout_connected_ = false;
         }
 
-        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_ERROR)
+        LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_AXFR_PROBLEM)
                   .arg(err.what());
         makeErrorMessage(renderer_, message, buffer, Rcode::SERVFAIL(),
-                         tsig_context);
+                         stats_attrs, tsig_context);
         return (true);
     }
 
@@ -738,7 +744,8 @@ AuthSrvImpl::processXfrQuery(const IOMessage& io_message, Message& message,
 bool
 AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
                            OutputBuffer& buffer,
-                           std::auto_ptr<TSIGContext> tsig_context)
+                           std::auto_ptr<TSIGContext> tsig_context,
+                           MessageAttributes& stats_attrs)
 {
     // The incoming notify must contain exactly one question for SOA of the
     // zone name.
@@ -746,7 +753,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_QUESTIONS)
                   .arg(message.getRRCount(Message::SECTION_QUESTION));
         makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
-                         tsig_context);
+                         stats_attrs, tsig_context);
         return (true);
     }
     ConstQuestionPtr question = *message.beginQuestion();
@@ -754,7 +761,7 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
         LOG_DEBUG(auth_logger, DBG_AUTH_DETAIL, AUTH_NOTIFY_RRTYPE)
                   .arg(question->getType().toText());
         makeErrorMessage(renderer_, message, buffer, Rcode::FORMERR(),
-                         tsig_context);
+                         stats_attrs, tsig_context);
         return (true);
     }
 
@@ -812,9 +819,10 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
     message.setHeaderFlag(Message::HEADERFLAG_AA);
     message.setRcode(Rcode::NOERROR());
 
-    RendererHolder holder(renderer_, &buffer);
+    RendererHolder holder(renderer_, &buffer, stats_attrs);
     if (tsig_context.get() != NULL) {
         message.toWire(renderer_, *tsig_context);
+        stats_attrs.setResponseTSIG(true);
     } else {
         message.toWire(renderer_);
     }
@@ -822,7 +830,8 @@ AuthSrvImpl::processNotify(const IOMessage& io_message, Message& message,
 }
 
 bool
-AuthSrvImpl::processUpdate(const IOMessage& io_message) {
+AuthSrvImpl::processUpdate(const IOMessage& io_message)
+{
     // Push the update request to a separate process via the forwarder.
     // On successful push, the request shouldn't be responded from b10-auth,
     // so we return false.
@@ -831,31 +840,10 @@ AuthSrvImpl::processUpdate(const IOMessage& io_message) {
 }
 
 void
-AuthSrvImpl::registerStatisticsValidator() {
-    counters_.registerStatisticsValidator(
-        boost::bind(&AuthSrvImpl::validateStatistics, this, _1));
-}
-
-bool
-AuthSrvImpl::validateStatistics(isc::data::ConstElementPtr data) const {
-    if (config_session_ == NULL) {
-        return (false);
-    }
-    return (
-        config_session_->getModuleSpec().validateStatistics(
-            data, true));
-}
-
-void
 AuthSrvImpl::resumeServer(DNSServer* server, Message& message,
-                          statistics::QRAttributes& stats_attrs,
+                          MessageAttributes& stats_attrs,
                           const bool done) {
-    if (done) {
-        stats_attrs.answerWasSent();
-        // isTruncated from MessageRenderer
-        stats_attrs.setResponseTruncated(renderer_.isTruncated());
-    }
-    counters_.inc(stats_attrs, message);
+    counters_.inc(stats_attrs, message, done);
     server->resume(done);
 }
 
@@ -875,7 +863,7 @@ AuthSrv::updateConfig(ConstElementPtr new_config) {
 }
 
 ConstElementPtr AuthSrv::getStatistics() const {
-    return (impl_->counters_.getStatistics());
+    return (impl_->counters_.get());
 }
 
 const AddressList&
@@ -905,7 +893,8 @@ void
 AuthSrv::createDDNSForwarder() {
     LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_START_DDNS_FORWARDER);
     impl_->ddns_forwarder_.reset(
-        new SocketSessionForwarderHolder("update", impl_->ddns_base_forwarder_));
+        new SocketSessionForwarderHolder("update",
+                                         impl_->ddns_base_forwarder_));
 }
 
 void

+ 1 - 4
src/bin/auth/auth_srv.h

@@ -41,9 +41,6 @@
 
 #include <boost/shared_ptr.hpp>
 
-#include <map>
-#include <string>
-
 namespace isc {
 namespace util {
 namespace io {
@@ -222,7 +219,7 @@ public:
     /// \brief Returns statistics data
     ///
     /// This function can throw an exception from
-    /// Counters::getStatistics().
+    /// Counters::get().
     ///
     /// \return JSON format statistics data.
     isc::data::ConstElementPtr getStatistics() const;

+ 37 - 34
src/bin/auth/b10-auth.xml

@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>December 18, 2012</date>
+    <date>February 5, 2013</date>
   </refentryinfo>
 
   <refmeta>
@@ -53,8 +53,8 @@
     <para>The <command>b10-auth</command> daemon provides the BIND 10
       authoritative DNS server.
       Normally it is started by the
-      <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-      boss process.
+      <citerefentry><refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      process.
     </para>
 
     <para>
@@ -100,7 +100,7 @@
       <varname>database_file</varname> defines the path to the
       SQLite3 zone file when using the sqlite datasource.
       The default is
-      <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
+      <filename>@@LOCALSTATEDIR@@/bind10/zone.sqlite3</filename>.
     </para>
 
     <para>
@@ -144,15 +144,6 @@
     </para>
 
     <para>
-      <varname>statistics-interval</varname> is the timer interval
-      in seconds for <command>b10-auth</command> to share its
-      statistics information to
-      <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
-      Statistics updates can be disabled by setting this to 0.
-      The default is 60.
-    </para>
-
-    <para>
       <varname>tcp_recv_timeout</varname> is the timeout used on
       incoming TCP connections, in milliseconds. If the query
       is not sent within this time, the connection is closed.
@@ -191,10 +182,15 @@
     </para>
 
     <para>
+      <command>getstats</command> tells <command>b10-auth</command>
+      to send its statistics data.
+    </para>
+
+    <para>
       <command>shutdown</command> exits <command>b10-auth</command>.
       This has an optional <varname>pid</varname> argument to
       select the process ID to stop.
-      (Note that the BIND 10 boss process may restart this service
+      (Note that the b10-init process may restart this service
       if configured.)
     </para>
 
@@ -230,32 +226,36 @@
       daemon for <quote>Auth</quote> include:
     </para>
 
-    <variablelist>
-
-      <varlistentry>
-        <term>queries.tcp</term>
-        <listitem><simpara>Total count of queries received by the
-          <command>b10-auth</command> server over TCP since startup.
-        </simpara></listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>queries.udp</term>
-        <listitem><simpara>Total count of queries received by the
-          <command>b10-auth</command> server over UDP since startup.
-        </simpara></listitem>
-      </varlistentry>
-
-    </variablelist>
-
-<!-- TODO: missing stats docs. See ticket #1721 -->
+<!-- ### STATISTICS DATA PLACEHOLDER ### -->
+
+    <note>
+      <para>
+        Opcode of a request message will not be counted if:
+        <itemizedlist>
+          <listitem><para>
+            The request message is too short to parse the message header
+          </para></listitem>
+          <listitem><para>
+            The request message is a response (i.e. QR bit is set)
+          </para></listitem>
+        </itemizedlist>
+      </para>
+
+      <para>
+        Request attributes except for opcode will not be counted if TSIG
+        validation failed as they are not reliable.
+        We always count opcode mainly for compatibility with BIND 9,
+        but remember that if there's any error related to TSIG, some
+        of the counted opcode may not be trustworthy.
+      </para>
+    </note>
 
   </refsect1>
 
   <refsect1>
     <title>FILES</title>
     <para>
-      <filename>/usr/local/var/bind10/zone.sqlite3</filename>
+      <filename>@@LOCALSTATEDIR@@/bind10/zone.sqlite3</filename>
       &mdash; Location for the SQLite3 zone database
       when <emphasis>database_file</emphasis> configuration is not
       defined.
@@ -272,6 +272,9 @@
         <refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>

+ 385 - 0
src/bin/auth/gen-statisticsitems.py.pre.in

@@ -0,0 +1,385 @@
+#!@PYTHON@
+
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""\
+This script generates spec file, docbook XML and some part of statistics code
+from statistics_msg_items.def.
+"""
+
+import os
+import re
+import sys
+import json
+from xml.etree import ElementTree
+
+item_list = []
+localstatedir = '@@LOCALSTATEDIR@@'
+builddir = '@builddir@'
+srcdir = '@srcdir@'
+pre_suffix = '.pre'
+
+xmldocument_command_name = 'b10-auth'
+
+def need_generate(filepath, prepath, mtime):
+    '''Check if we need to generate the specified file.
+
+    To avoid unnecessary compilation, we skip (re)generating the file when
+    the file already exists and newer than the base file, and definition file
+    specified with mtime.
+    '''
+    if os.path.exists(filepath) and\
+        (os.path.getmtime(filepath) > mtime and
+         os.path.getmtime(filepath) > os.path.getmtime(prepath)):
+        return False
+    return True
+
+def import_definitions():
+    '''Load statsitics items definitions from statistics_msg_items.def.
+
+    statistics_msg_items.def defines a tree of message statistics items.
+    Syntax:
+        Each line describes a node; branch node for subset of counters,
+        leaf node for a counter item.
+        Each fields are separated with one or more field separator (Tab).
+        Field separator in the head of a line is ignored.
+
+        branch node:
+        (item name)\t+(internal branch name)\t+(description of the item)\t+'='
+        leaf node:
+        (item name)\t+(internal item counter name)\t+(description of the item)
+
+        Branch nodes contain leaf nodes and/or branch nodes. The end of
+        a branch node is indicated with ';' as item name (first column).
+
+        Internal branch name and internal item counter name must be unique.
+
+    Returns mtime of statistics_msg_items.def. It will be used to check
+    auto-generated files need to be regenerated.
+    '''
+    global item_list
+
+    items_definition_file = srcdir + os.sep + 'statistics_msg_items.def'
+    with open(items_definition_file, 'r') as item_definition:
+        re_splitter = re.compile('\t+')
+        l = item_list
+        lp = None
+        for line in item_definition.readlines():
+            element = re_splitter.split(line.rstrip())
+            # pop first element if it is empty to skip indent
+            if element[0] == '':
+                element.pop(0)
+
+            # last element is '=': a branch node definition.
+            if element[-1] == '=':
+                l.append({'name': element[0], 'child': [], 'index': element[1],
+                          'description': element[2], 'parent': lp})
+                lp = l
+                l = l[-1]['child']
+            # last element is ';': end of a branch node.
+            elif element[-1] == ';':
+                l = lp
+                lp = l[-1]['parent']
+            # otherwise, a leaf node definition.
+            else:
+                l.append({'name': element[0], 'child': None,
+                          'index': element[1], 'description': element[2],
+                          'parent': lp})
+    return os.path.getmtime(items_definition_file)
+
+def generate_specfile(specfile, def_mtime):
+    '''Generate spec in specfile from skeleton (specfille+'.pre').
+    If the specfile is newer than both skeleton and def_mtime, file generation
+    will be skipped.
+
+    This method reads the content of skeleton and appends statistics items
+    definition into { "module_spec": { "statistics": } }.
+    LOCALSTATEDIR is also expanded.
+
+    Returns nothing.
+    '''
+
+    def convert_list(items, prefix=''):
+        spec_list = []
+        default_map = {}
+        for item in items:
+            full_item_name = prefix + item['name']
+            if item['child'] is None:
+                default_map[item['name']] = 0
+                spec_list.append({
+                        'item_name': item['name'],
+                        'item_optional': False,
+                        'item_type': 'integer',
+                        'item_default': 0,
+                        'item_title': full_item_name,
+                        'item_description': item['description'],
+                    })
+            else:
+                child_spec_list, child_default_map = \
+                    convert_list(item['child'], full_item_name + '.')
+                spec_list.append({
+                        'item_name': item['name'],
+                        'item_type': 'map',
+                        'item_optional': False,
+                        'item_title': full_item_name,
+                        'item_description': item['description'],
+                        'item_default': child_default_map,
+                        'map_item_spec': child_spec_list,
+                    })
+                default_map[item['name']] = child_default_map
+        return spec_list, default_map
+
+    item_spec_list, item_default_map = convert_list(item_list)
+
+    statistics_spec_list = [{
+        'item_name': 'zones',
+        'item_type': 'named_set',
+        'item_optional': False,
+        'item_title': 'Zone statistics',
+        'item_description':
+                'Zone statistics items. ' +
+                "Items for all zones are stored in '_SERVER_'.",
+        'item_default': { '_SERVER_': item_default_map },
+        'named_set_item_spec': {
+            'item_name': 'zone',
+            'item_type': 'map',
+            'item_optional': False,
+            'item_default': {},
+            'map_item_spec': item_spec_list,
+            },
+        }]
+
+    if need_generate(builddir+os.sep+specfile,
+                     builddir+os.sep+specfile+pre_suffix, def_mtime):
+        with open(builddir+os.sep+specfile+pre_suffix, 'r') as stats_pre:
+            # split LOCALSTATEDIR to avoid substitution
+            stats_pre_json = \
+                json.loads(stats_pre.read().replace('@@LOCAL'+'STATEDIR@@',
+                                                    localstatedir))
+        stats_pre_json['module_spec']['statistics'] = statistics_spec_list
+        statistics_spec_json = json.dumps(stats_pre_json, sort_keys=True,
+                                          indent=2)
+        with open(builddir+os.sep+specfile, 'w') as stats_spec:
+            stats_spec.write(statistics_spec_json)
+    else:
+        print('skip generating ' + specfile)
+    return
+
+def generate_docfile(docfile, def_mtime):
+    '''Generate docbook XML in docfile from skeleton (docfile+'.pre').
+    If the docfile is newer than both skeleton and def_mtime, file generation
+    will be skipped.
+
+    This method reads the content of skeleton and replaces
+    <!-- ### STATISTICS DATA PLACEHOLDER ### --> with statistics items
+    definition. LOCALSTATEDIR is also expanded.
+
+    Returns nothing.
+    '''
+    def convert_list(items, tree, prefix=''):
+        '''
+        Build XML tree from items.
+            <varlistentry>
+              <term>##item_full_name##</term>
+              <listitem><simpara>##item_description##</simpara></listitem>
+            </varlistentry>
+        xmldocument_command_name in item_description is put inside <command>
+        element.
+        '''
+        for item in items:
+            full_item_name = prefix + item['name']
+            if item['child'] is None:
+                # the item is a leaf node: build varlistentry
+                child_element = ElementTree.SubElement(tree, 'varlistentry')
+                term = ElementTree.SubElement(child_element, 'term')
+                term.text = full_item_name
+                list_item = ElementTree.SubElement(child_element, 'listitem')
+                sim_para = ElementTree.SubElement(list_item, 'simpara')
+                sim_para.text = ''
+                prev = None
+                # put xmldocument_command_name in <command> node
+                for word in item['description'].split():
+                    if word == xmldocument_command_name:
+                        command = ElementTree.SubElement(sim_para, 'command')
+                        command.text = word
+                        # at this point command.tail is None
+                        # append a space as trailing text for the next word
+                        # so it can be concatenated with trailing words
+                        command.tail = ' '
+                        prev = command
+                    else:
+                        if prev is None:
+                            sim_para.text += word + ' '
+                        else:
+                            prev.tail += word + ' '
+                # remove trailing whitespaces at the end of the description
+                if prev is None:
+                    sim_para.text = sim_para.text.rstrip()
+                else:
+                    prev.tail = prev.tail.rstrip()
+            else:
+                # the item is a branch node: call myself for child nodes
+                convert_list(item['child'], tree, full_item_name + '.')
+        return
+
+    if need_generate(builddir+os.sep+docfile,
+                     srcdir+os.sep+docfile+pre_suffix, def_mtime):
+        with open(srcdir+os.sep+docfile+pre_suffix, 'r') as doc_pre:
+            # split LOCALSTATEDIR to avoid substitution
+            doc_pre_xml = doc_pre.read().replace('@@LOCAL'+'STATEDIR@@',
+                                                 localstatedir)
+
+        variable_tree = ElementTree.Element('variablelist')
+        convert_list(item_list, variable_tree)
+        pretty_xml = ElementTree.tostring(variable_tree)
+        if not isinstance(pretty_xml, str):
+            pretty_xml = pretty_xml.decode('utf-8')
+        # put newline around <variablelist> and <varlistentry> element
+        pretty_xml = \
+            re.sub(r'(</?var(?:iablelist|listentry)>)', r'\1\n', pretty_xml)
+        # indent <term> and <listitem>
+        pretty_xml = \
+            re.sub(r'(<(?:term|listitem)>)', r'  \1', pretty_xml)
+        # put newline after </term> and </listitem>
+        pretty_xml = \
+            re.sub(r'(</(?:term|listitem)>)', r'\1\n', pretty_xml)
+
+        with open(builddir+os.sep+docfile, 'w') as doc:
+            doc.write(doc_pre_xml.replace(
+                '<!-- ### STATISTICS DATA PLACEHOLDER ### -->',
+                pretty_xml))
+    else:
+        print('skip generating ' + docfile)
+    return
+
+def generate_cxx(itemsfile, ccfile, utfile, def_mtime):
+    '''Generate some part of statistics code in itemsfile, ccfile, utfile from
+    skeleton (itemsfile+'.pre', ccfile+'.pre', utfile+'.pre').
+    If the file is newer than both skeleton and def_mtime, file generation
+    will be skipped.
+
+    This method reads the content of skeleton and replaces
+    // ### STATISTICS ITEMS DEFINITION ### with statistics items definition in
+    ccfile and utfile,
+    // ### STATISTICS ITEMS DECLARATION ### with statistics items declaration
+    in itemsfile.
+
+    Returns nothing.
+    '''
+    msg_counter_types = ['enum MSGCounterType {']
+    item_names =  ['// using -1 as counter_id to state it is not a '
+                   + 'counter item']
+    item_names += ['const int NOT_ITEM = -1;', '']
+
+    def convert_list(items, item_head, msg_counter_types, item_names):
+        '''Convert item tree to a set of C++ code fragment in given lists.
+
+        This method recursively builds two lists:
+        - msg_counter_types consists of strings for all leaf items, each
+          defines one enum element with a comment, e.g.
+          COUNTER_ITEM, ///< item's descriptin
+        - item_names consists of tuples of three elements, depending on
+          whether it's a leaf element (no child from it) or not:
+          (leaf)   ( "item_name", NULL, COUNTER_ITEM )
+          (branch) ( "item_name", CHILD_NAME, NOT_ITEM )
+
+        Each single call to this function builds a C++ structure beginning
+        with the given item_head, which is a string that reads like
+        'const struct CounterSpec some_counters[] = {'
+        followed by the item_names tuples corresponding to that level.
+        If some of the items of this level have a child, recursive calls
+        to this function extends msg_counter_types and item_names.
+        item_names for this level will be concatenated at the end end of
+        the given item_names.
+
+        '''
+        item_names_current = [item_head]
+        for item in items:
+            item_spec = '    { "' + item['name'] + '", '
+            if item['child'] is None:
+                item_spec += 'NULL, ' + item['index']
+                msg_counter_types.append('    ' + item['index'] + ',    ' +
+                                         '///< ' + item['description'])
+            else:
+                item_spec += item['index'] + ', NOT_ITEM'
+                child_head = 'const struct CounterSpec ' + \
+                    item['index'] + '[] = {'
+                convert_list(item['child'], child_head,
+                             msg_counter_types, item_names)
+            item_names_current.append(item_spec + ' },')
+
+        item_names_current.append('    { NULL, NULL, NOT_ITEM }\n};')
+        item_names.extend(item_names_current)
+
+    convert_list(item_list, 'const struct CounterSpec msg_counter_tree[] = {',
+                 msg_counter_types, item_names)
+    msg_counter_types.extend([
+            '    // End of counter types',
+            '    MSG_COUNTER_TYPES  ///< The number of defined counters',
+            '};'])
+
+    item_decls = '\n'.join(msg_counter_types)
+    item_defs = '\n'.join(item_names)
+
+    if need_generate(builddir+os.sep+itemsfile,
+                     srcdir+os.sep+itemsfile+pre_suffix, def_mtime):
+        with open(srcdir+os.sep+itemsfile+pre_suffix, 'r') \
+            as statistics_items_h_pre:
+            items_pre = statistics_items_h_pre.read()
+
+        with open(builddir+os.sep+itemsfile, 'w') as statistics_items_h:
+            statistics_items_h.write(items_pre.replace(
+                '// ### STATISTICS ITEMS DECLARATION ###', item_decls))
+    else:
+        print('skip generating ' + itemsfile)
+
+    if need_generate(builddir+os.sep+ccfile,
+                     srcdir+os.sep+ccfile+pre_suffix, def_mtime):
+        with open(srcdir+os.sep+ccfile+pre_suffix, 'r') as statistics_cc_pre:
+            items_pre = statistics_cc_pre.read()
+
+        with open(builddir+os.sep+ccfile, 'w') as statistics_cc:
+            statistics_cc.write(items_pre.replace(
+                '// ### STATISTICS ITEMS DEFINITION ###', item_defs))
+    else:
+        print('skip generating ' + ccfile)
+
+    if need_generate(builddir+os.sep+utfile,
+                     srcdir+os.sep+utfile+pre_suffix, def_mtime):
+        with open(srcdir+os.sep+utfile+pre_suffix, 'r') \
+            as statistics_ut_cc_pre:
+            items_pre = statistics_ut_cc_pre.read()
+
+        with open(builddir+os.sep+utfile, 'w') as statistics_ut_cc:
+            statistics_ut_cc.write(items_pre.replace(
+                '// ### STATISTICS ITEMS DEFINITION ###', item_defs))
+    else:
+        print('skip generating ' + utfile)
+
+    return
+
+if __name__ == "__main__":
+    try:
+        def_mtime = import_definitions()
+        generate_specfile('auth.spec', def_mtime)
+        generate_docfile('b10-auth.xml', def_mtime)
+        generate_cxx('statistics_items.h',
+                     'statistics.cc',
+                     'tests'+os.sep+'statistics_unittest.cc',
+                     def_mtime)
+    except:
+        sys.stderr.write('File generation failed due to exception: %s\n' %
+                         sys.exc_info()[1])
+        exit(1)

+ 17 - 17
src/bin/auth/main.cc

@@ -44,6 +44,7 @@
 #include <server_common/socket_request.h>
 
 #include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
 
 #include <sys/types.h>
 #include <sys/socket.h>
@@ -152,10 +153,11 @@ main(int argc, char* argv[]) {
     int ret = 0;
 
     // XXX: we should eventually pass io_service here.
-    Session* cc_session = NULL;
-    Session* xfrin_session = NULL;
+    boost::scoped_ptr<AuthSrv> auth_server_; // placeholder
+    boost::scoped_ptr<Session> cc_session;
+    boost::scoped_ptr<Session> xfrin_session;
     bool xfrin_session_established = false; // XXX (see Trac #287)
-    ModuleCCSession* config_session = NULL;
+    boost::scoped_ptr<ModuleCCSession> config_session;
     XfroutClient xfrout_client(getXfroutSocketPath());
     SocketSessionForwarder ddns_forwarder(getDDNSSocketPath());
     try {
@@ -167,7 +169,8 @@ main(int argc, char* argv[]) {
             specfile = string(AUTH_SPECFILE_LOCATION);
         }
 
-        auth_server = new AuthSrv(xfrout_client, ddns_forwarder);
+        auth_server_.reset(new AuthSrv(xfrout_client, ddns_forwarder));
+        auth_server = auth_server_.get();
         LOG_INFO(auth_logger, AUTH_SERVER_CREATED);
 
         SimpleCallback* checkin = auth_server->getCheckinProvider();
@@ -179,7 +182,7 @@ main(int argc, char* argv[]) {
         auth_server->setDNSService(dns_service);
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_DNS_SERVICES_CREATED);
 
-        cc_session = new Session(io_service.get_io_service());
+        cc_session.reset(new Session(io_service.get_io_service()));
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_CREATED);
         // Initialize the Socket Requestor
         isc::server_common::initSocketRequestor(*cc_session, AUTH_NAME);
@@ -187,22 +190,22 @@ main(int argc, char* argv[]) {
         // We delay starting listening to new commands/config just before we
         // go into the main loop to avoid confusion due to mixture of
         // synchronous and asynchronous operations (this would happen in
-        // initial communication with the boss that takes place in
+        // initial communication with b10-init that takes place in
         // updateConfig() for listen_on and in initializing TSIG keys below).
         // Until then all operations on the CC session will take place
         // synchronously.
-        config_session = new ModuleCCSession(specfile, *cc_session,
-                                             my_config_handler,
-                                             my_command_handler, false);
+        config_session.reset(new ModuleCCSession(specfile, *cc_session,
+                                                 my_config_handler,
+                                                 my_command_handler, false));
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_CONFIG_CHANNEL_ESTABLISHED);
 
-        xfrin_session = new Session(io_service.get_io_service());
+        xfrin_session.reset(new Session(io_service.get_io_service()));
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_XFRIN_CHANNEL_CREATED);
         xfrin_session->establish(NULL);
         xfrin_session_established = true;
         LOG_DEBUG(auth_logger, DBG_AUTH_START, AUTH_XFRIN_CHANNEL_ESTABLISHED);
 
-        auth_server->setXfrinSession(xfrin_session);
+        auth_server->setXfrinSession(xfrin_session.get());
 
         // Configure the server.  configureAuthServer() is expected to install
         // all initial configurations, but as a short term workaround we
@@ -210,7 +213,7 @@ main(int argc, char* argv[]) {
         // updateConfig().
         // if server load configure failed, we won't exit, give user second
         // chance to correct the configure.
-        auth_server->setConfigSession(config_session);
+        auth_server->setConfigSession(config_session.get());
         try {
             configureAuthServer(*auth_server, config_session->getFullConfig());
             auth_server->updateConfig(ElementPtr());
@@ -228,7 +231,7 @@ main(int argc, char* argv[]) {
         config_session->addRemoteConfig("data_sources",
                                         boost::bind(datasrcConfigHandler,
                                                     auth_server, &first_time,
-                                                    config_session,
+                                                    config_session.get(),
                                                     _1, _2, _3),
                                         false);
 
@@ -260,10 +263,7 @@ main(int argc, char* argv[]) {
         config_session->removeRemoteConfig("data_sources");
     }
 
-    delete xfrin_session;
-    delete config_session;
-    delete cc_session;
-    delete auth_server;
+    LOG_INFO(auth_logger, AUTH_SERVER_EXITING);
 
     return (ret);
 }

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

@@ -101,8 +101,11 @@ Query::ResponseCreator::create(Message& response,
 
 void
 Query::addSOA(ZoneFinder& finder) {
-    ZoneFinderContextPtr soa_ctx = finder.find(finder.getOrigin(),
-                                               RRType::SOA(), dnssec_opt_);
+    // This method is always called in finding SOA for a negative response,
+    // so we specify the use of min(RRTTL, SOA MINTTL) as specified in
+    // Section 3 of RFC2308.
+    ZoneFinderContextPtr soa_ctx = finder.findAtOrigin(RRType::SOA(), true,
+                                                       dnssec_opt_);
     if (soa_ctx->code != ZoneFinder::SUCCESS) {
         isc_throw(NoSOA, "There's no SOA record in zone " <<
             finder.getOrigin().toText());
@@ -295,14 +298,18 @@ Query::addNXRRsetProof(ZoneFinder& finder,
             addWildcardNXRRSETProof(finder, db_context.rrset);
         }
     } else if (db_context.isNSEC3Signed() && !db_context.isWildcard()) {
-        if (*qtype_ == RRType::DS()) {
-            // RFC 5155, Section 7.2.4.  Add either NSEC3 for the qname or
-            // closest (provable) encloser proof in case of optout.
-            addClosestEncloserProof(finder, *qname_, true);
-        } else {
-            // RFC 5155, Section 7.2.3.  Just add NSEC3 for the qname.
-            addNSEC3ForName(finder, *qname_, true);
-        }
+        // Section 7.2.3 and 7.2.4 of RFC 5155 with clarification by errata
+        // http://www.rfc-editor.org/errata_search.php?rfc=5155&eid=3441
+        // In the end, these two cases are basically the same: if the qname is
+        // equal to or derived from insecure delegation covered by an Opt-Out
+        // NSEC3 RR, include the closest provable encloser proof; otherwise we
+        // have a matching NSEC3, so we include it.
+        //
+        // Note: This implementation does not check in the former case whether
+        // the NSEC3 for the next closer has Opt-Out bit on; this must be the
+        // case as long as the zone is correctly signed, and if it's broken
+        // we'd just return what we are given and have the validator detect it.
+        addClosestEncloserProof(finder, *qname_, true);
     } else if (db_context.isNSEC3Signed() && db_context.isWildcard()) {
         // Case for RFC 5155 Section 7.2.5: add closest encloser proof for the
         // qname, construct the matched wildcard name and add NSEC3 for it.
@@ -318,11 +325,9 @@ void
 Query::addAuthAdditional(ZoneFinder& finder,
                          vector<ConstRRsetPtr>& additionals)
 {
-    const Name& origin = finder.getOrigin();
-
     // Fill in authority and addtional sections.
-    ConstZoneFinderContextPtr ns_context = finder.find(origin, RRType::NS(),
-                                                       dnssec_opt_);
+    ConstZoneFinderContextPtr ns_context =
+        finder.findAtOrigin(RRType::NS(), false, dnssec_opt_);
 
     // zone origin name should have NS records
     if (ns_context->code != ZoneFinder::SUCCESS) {

+ 0 - 284
src/bin/auth/statistics.cc

@@ -1,284 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <auth/statistics.h>
-#include <auth/statistics_items.h>
-#include <auth/auth_log.h>
-
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-
-#include <cc/data.h>
-#include <cc/session.h>
-
-#include <statistics/counter.h>
-#include <statistics/counter_dict.h>
-
-#include <algorithm>
-#include <cctype>
-#include <cassert>
-#include <string>
-#include <sstream>
-#include <iostream>
-
-#include <boost/noncopyable.hpp>
-
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-
-using namespace isc::dns;
-using namespace isc::auth;
-using namespace isc::statistics;
-
-namespace isc {
-namespace auth {
-namespace statistics {
-
-// TODO: Make use of wrappers like isc::dns::Opcode
-// for counter item type.
-
-class CountersImpl : boost::noncopyable {
-public:
-    CountersImpl();
-    ~CountersImpl();
-    void inc(const QRAttributes& qrattrs, const Message& response);
-    isc::data::ConstElementPtr getStatistics() const;
-    void registerStatisticsValidator(Counters::validator_type validator);
-private:
-    // counter for query/response
-    Counter server_qr_counter_;
-    // set of counters for zones
-    CounterDictionary zone_qr_counters_;
-    // validator
-    Counters::validator_type validator_;
-};
-
-CountersImpl::CountersImpl() :
-    // size of server_qr_counter_, zone_qr_counters_: QR_COUNTER_TYPES
-    server_qr_counter_(QR_COUNTER_TYPES),
-    zone_qr_counters_(QR_COUNTER_TYPES),
-    validator_()
-{}
-
-CountersImpl::~CountersImpl()
-{}
-
-void
-CountersImpl::inc(const QRAttributes& qrattrs, const Message& response) {
-    // protocols carrying request
-    if (qrattrs.req_ip_version_ == AF_INET) {
-        server_qr_counter_.inc(QR_REQUEST_IPV4);
-    } else if (qrattrs.req_ip_version_ == AF_INET6) {
-        server_qr_counter_.inc(QR_REQUEST_IPV6);
-    }
-    if (qrattrs.req_transport_protocol_ == IPPROTO_UDP) {
-        server_qr_counter_.inc(QR_REQUEST_UDP);
-    } else if (qrattrs.req_transport_protocol_ == IPPROTO_TCP) {
-        server_qr_counter_.inc(QR_REQUEST_TCP);
-    }
-
-    // query TSIG
-    if (qrattrs.req_is_tsig_) {
-        server_qr_counter_.inc(QR_REQUEST_TSIG);
-    }
-    if (qrattrs.req_is_sig0_) {
-        server_qr_counter_.inc(QR_REQUEST_SIG0);
-    }
-    if (qrattrs.req_is_badsig_) {
-        server_qr_counter_.inc(QR_REQUEST_BADSIG);
-        // If signature validation is failed, no other attributes are reliable
-        return;
-    }
-
-    // query EDNS
-    if (qrattrs.req_is_edns_0_) {
-        server_qr_counter_.inc(QR_REQUEST_EDNS0);
-    }
-    if (qrattrs.req_is_edns_badver_) {
-        server_qr_counter_.inc(QR_REQUEST_BADEDNSVER);
-    }
-
-    // query DNSSEC
-    if (qrattrs.req_is_dnssec_ok_) {
-        server_qr_counter_.inc(QR_REQUEST_DNSSEC_OK);
-    }
-
-    // QTYPE
-    unsigned int qtype_type = QR_QTYPE_OTHER;
-    const QuestionIterator qiter = response.beginQuestion();
-    if (qiter != response.endQuestion()) {
-        // get the first and only question section and
-        // get the qtype code
-        const unsigned int qtype = (*qiter)->getType().getCode();
-        if (qtype < 258) {
-            // qtype 0..257: lookup qtype-countertype table
-            qtype_type = QRQTypeToQRCounterType[qtype];
-        } else if (qtype < 32768) {
-            // qtype 258..32767: (Unassigned)
-            qtype_type = QR_QTYPE_OTHER;
-        } else if (qtype < 32770) {
-            // qtype 32768..32769: TA and DLV
-            qtype_type = QR_QTYPE_TA + (qtype - 32768);
-        } else {
-            // qtype 32770..65535: (Unassigned, Private use, Reserved)
-            qtype_type = QR_QTYPE_OTHER;
-        }
-    }
-    server_qr_counter_.inc(qtype_type);
-    // OPCODE
-    server_qr_counter_.inc(QROpCodeToQRCounterType[qrattrs.req_opcode_]);
-
-    // response
-    if (qrattrs.answer_sent_) {
-        // responded
-        server_qr_counter_.inc(QR_RESPONSE);
-
-        // response truncated
-        if (qrattrs.res_is_truncated_) {
-            server_qr_counter_.inc(QR_RESPONSE_TRUNCATED);
-        }
-
-        // response EDNS
-        ConstEDNSPtr response_edns = response.getEDNS();
-        if (response_edns != NULL && response_edns->getVersion() == 0) {
-            server_qr_counter_.inc(QR_RESPONSE_EDNS0);
-        }
-
-        // response TSIG
-        if (qrattrs.req_is_tsig_) {
-            // assume response is TSIG signed if request is TSIG signed
-            server_qr_counter_.inc(QR_RESPONSE_TSIG);
-        }
-
-        // response SIG(0) is currently not implemented
-
-        // RCODE
-        const unsigned int rcode = response.getRcode().getCode();
-        unsigned int rcode_type = QR_RCODE_OTHER;
-        if (rcode < 23) {
-            // rcode 0..22: lookup rcode-countertype table
-            rcode_type = QRRCodeToQRCounterType[rcode];
-        } else {
-            // opcode larger than 22 is reserved or unassigned
-            rcode_type = QR_RCODE_OTHER;
-        }
-        server_qr_counter_.inc(rcode_type);
-
-        // compound attributes
-        const unsigned int answer_rrs =
-            response.getRRCount(Message::SECTION_ANSWER);
-        const bool is_aa_set = response.getHeaderFlag(Message::HEADERFLAG_AA);
-
-        if (is_aa_set) {
-            // QryAuthAns
-            server_qr_counter_.inc(QR_QRYAUTHANS);
-        } else {
-            // QryNoAuthAns
-            server_qr_counter_.inc(QR_QRYNOAUTHANS);
-        }
-
-        if (rcode == Rcode::NOERROR_CODE) {
-            if (answer_rrs > 0) {
-                // QrySuccess
-                server_qr_counter_.inc(QR_QRYSUCCESS);
-            } else {
-                if (is_aa_set) {
-                    // QryNxrrset
-                    server_qr_counter_.inc(QR_QRYNXRRSET);
-                } else {
-                    // QryReferral
-                    server_qr_counter_.inc(QR_QRYREFERRAL);
-                }
-            }
-        } else if (rcode == Rcode::REFUSED_CODE) {
-            // AuthRej
-            server_qr_counter_.inc(QR_QRYREJECT);
-        }
-    }
-}
-
-isc::data::ConstElementPtr
-CountersImpl::getStatistics() const {
-    std::stringstream statistics_string;
-    statistics_string << "{ \"queries.udp\": "
-                      << server_qr_counter_.get(QR_REQUEST_UDP)
-                      << ", \"queries.tcp\": "
-                      << server_qr_counter_.get(QR_REQUEST_TCP);
-    // Insert non 0 Opcode counters.
-    for (int i = QR_OPCODE_QUERY; i <= QR_OPCODE_OTHER; ++i) {
-        const Counter::Type counter = server_qr_counter_.get(i);
-        if (counter != 0) {
-            statistics_string << ", \"" << "opcode." <<
-                                 QRCounterOpcode[i - QR_OPCODE_QUERY].name <<
-                                 "\": " << counter;
-        }
-    }
-    // Insert non 0 Rcode counters.
-    for (int i = QR_RCODE_NOERROR; i <= QR_RCODE_OTHER; ++i) {
-        const Counter::Type counter = server_qr_counter_.get(i);
-        if (counter != 0) {
-            statistics_string << ", \"" << "rcode." <<
-                                 QRCounterRcode[i - QR_RCODE_NOERROR].name <<
-                                 "\": " << counter;
-        }
-    }
-    statistics_string << "}";
-
-    isc::data::ConstElementPtr statistics_element =
-        isc::data::Element::fromJSON(statistics_string);
-    // validate the statistics data before send
-    if (validator_) {
-        if (!validator_(statistics_element)) {
-            LOG_ERROR(auth_logger, AUTH_INVALID_STATISTICS_DATA);
-            return (isc::data::ElementPtr());
-        }
-    }
-    return (statistics_element);
-}
-
-void
-CountersImpl::registerStatisticsValidator
-    (Counters::validator_type validator)
-{
-    validator_ = validator;
-}
-
-Counters::Counters() : impl_(new CountersImpl())
-{}
-
-Counters::~Counters() {}
-
-void
-Counters::inc(const QRAttributes& qrattrs, const Message& response) {
-    impl_->inc(qrattrs, response);
-}
-
-isc::data::ConstElementPtr
-Counters::getStatistics() const {
-    return (impl_->getStatistics());
-}
-
-void
-Counters::registerStatisticsValidator
-    (Counters::validator_type validator) const
-{
-    return (impl_->registerStatisticsValidator(validator));
-}
-
-} // namespace statistics
-} // namespace auth
-} // namespace isc

+ 277 - 0
src/bin/auth/statistics.cc.pre

@@ -0,0 +1,277 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <auth/statistics.h>
+#include <auth/statistics_items.h>
+#include <auth/auth_log.h>
+
+#include <cc/data.h>
+
+#include <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+
+#include <statistics/counter.h>
+
+#include <boost/optional.hpp>
+
+using namespace isc::dns;
+using namespace isc::auth;
+using namespace isc::statistics;
+using namespace isc::auth::statistics;
+
+namespace {
+
+/// \brief Fill isc::data::ElementPtr with given counter.
+/// \param counter Counter which stores values to fill
+/// \param type_tree CounterSpec corresponding to counter for building item
+///                  name
+/// \param trees isc::data::ElementPtr to be filled in; caller has ownership of
+///              isc::data::ElementPtr
+void
+fillNodes(const Counter& counter,
+          const struct isc::auth::statistics::CounterSpec type_tree[],
+          isc::data::ElementPtr& trees)
+{
+    using namespace isc::data;
+
+    for (int i = 0; type_tree[i].name != NULL; ++i) {
+        if (type_tree[i].sub_counters != NULL) {
+            isc::data::ElementPtr sub_counters = Element::createMap();
+            trees->set(type_tree[i].name, sub_counters);
+            fillNodes(counter, type_tree[i].sub_counters, sub_counters);
+        } else {
+            trees->set(type_tree[i].name,
+                       Element::create(static_cast<long int>(
+                           counter.get(type_tree[i].counter_id)))
+                       );
+        }
+    }
+}
+
+// ### STATISTICS ITEMS DEFINITION ###
+
+} // anonymous namespace
+
+namespace isc {
+namespace auth {
+namespace statistics {
+
+// Note: opcode in this array must be start with 0 and be sequential
+const int opcode_to_msgcounter[] = {
+    MSG_OPCODE_QUERY,    // Opcode =  0: Query
+    MSG_OPCODE_IQUERY,   // Opcode =  1: IQuery
+    MSG_OPCODE_STATUS,   // Opcode =  2: Status
+    MSG_OPCODE_OTHER,    // Opcode =  3: (Unassigned)
+    MSG_OPCODE_NOTIFY,   // Opcode =  4: Notify
+    MSG_OPCODE_UPDATE,   // Opcode =  5: Update
+    MSG_OPCODE_OTHER,    // Opcode =  6: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode =  7: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode =  8: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode =  9: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode = 10: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode = 11: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode = 12: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode = 13: (Unassigned)
+    MSG_OPCODE_OTHER,    // Opcode = 14: (Unassigned)
+    MSG_OPCODE_OTHER     // Opcode = 15: (Unassigned)
+};
+const size_t num_opcode_to_msgcounter =
+    sizeof(opcode_to_msgcounter) / sizeof(opcode_to_msgcounter[0]);
+
+// Note: rcode in this array must be start with 0 and be sequential
+const int rcode_to_msgcounter[] = {
+    MSG_RCODE_NOERROR,       // Rcode =  0: NoError
+    MSG_RCODE_FORMERR,       // Rcode =  1: FormErr
+    MSG_RCODE_SERVFAIL,      // Rcode =  2: ServFail
+    MSG_RCODE_NXDOMAIN,      // Rcode =  3: NXDomain
+    MSG_RCODE_NOTIMP,        // Rcode =  4: NotImp
+    MSG_RCODE_REFUSED,       // Rcode =  5: Refused
+    MSG_RCODE_YXDOMAIN,      // Rcode =  6: YXDomain
+    MSG_RCODE_YXRRSET,       // Rcode =  7: YXRRSet
+    MSG_RCODE_NXRRSET,       // Rcode =  8: NXRRSet
+    MSG_RCODE_NOTAUTH,       // Rcode =  9: NotAuth
+    MSG_RCODE_NOTZONE,       // Rcode = 10: NotZone
+    MSG_RCODE_OTHER,         // Rcode = 11: (Unassigned)
+    MSG_RCODE_OTHER,         // Rcode = 12: (Unassigned)
+    MSG_RCODE_OTHER,         // Rcode = 13: (Unassigned)
+    MSG_RCODE_OTHER,         // Rcode = 14: (Unassigned)
+    MSG_RCODE_OTHER,         // Rcode = 15: (Unassigned)
+    MSG_RCODE_BADVERS        // Rcode = 16: BADVERS
+};
+const size_t num_rcode_to_msgcounter =
+    sizeof(rcode_to_msgcounter) / sizeof(rcode_to_msgcounter[0]);
+
+Counters::Counters() :
+    server_msg_counter_(MSG_COUNTER_TYPES)
+{}
+
+void
+Counters::incRequest(const MessageAttributes& msgattrs) {
+    // protocols carrying request
+    if (msgattrs.getRequestIPVersion() == AF_INET) {
+        server_msg_counter_.inc(MSG_REQUEST_IPV4);
+    } else if (msgattrs.getRequestIPVersion() == AF_INET6) {
+        server_msg_counter_.inc(MSG_REQUEST_IPV6);
+    }
+    if (msgattrs.getRequestTransportProtocol() == IPPROTO_UDP) {
+        server_msg_counter_.inc(MSG_REQUEST_UDP);
+    } else if (msgattrs.getRequestTransportProtocol() == IPPROTO_TCP) {
+        server_msg_counter_.inc(MSG_REQUEST_TCP);
+    }
+
+    // Opcode
+    const boost::optional<isc::dns::Opcode>& opcode =
+        msgattrs.getRequestOpCode();
+    // Increment opcode counter only if the opcode exists; opcode can be empty
+    // if a short message which does not contain DNS header is received, or
+    // a response message (i.e. QR bit is set) is received.
+    if (opcode) {
+        server_msg_counter_.inc(opcode_to_msgcounter[opcode.get().getCode()]);
+    }
+
+    // TSIG
+    if (msgattrs.requestHasTSIG()) {
+        server_msg_counter_.inc(MSG_REQUEST_TSIG);
+    }
+    if (msgattrs.requestHasBadSig()) {
+        server_msg_counter_.inc(MSG_REQUEST_BADSIG);
+        // If signature validation failed, no other request attributes (except
+        // for opcode) are reliable. Skip processing of the rest of request
+        // counters.
+        return;
+    }
+
+    // EDNS0
+    if (msgattrs.requestHasEDNS0()) {
+        server_msg_counter_.inc(MSG_REQUEST_EDNS0);
+    }
+
+    // DNSSEC OK bit
+    if (msgattrs.requestHasDO()) {
+        server_msg_counter_.inc(MSG_REQUEST_DNSSEC_OK);
+    }
+}
+
+void
+Counters::incResponse(const MessageAttributes& msgattrs,
+                      const Message& response)
+{
+    // responded
+    server_msg_counter_.inc(MSG_RESPONSE);
+
+    // response truncated
+    if (msgattrs.responseIsTruncated()) {
+        server_msg_counter_.inc(MSG_RESPONSE_TRUNCATED);
+    }
+
+    // response EDNS
+    ConstEDNSPtr response_edns = response.getEDNS();
+    if (response_edns && response_edns->getVersion() == 0) {
+        server_msg_counter_.inc(MSG_RESPONSE_EDNS0);
+    }
+
+    // response TSIG
+    if (msgattrs.responseHasTSIG()) {
+        server_msg_counter_.inc(MSG_RESPONSE_TSIG);
+    }
+
+    // response SIG(0) is currently not implemented
+
+    // RCODE
+    const unsigned int rcode = response.getRcode().getCode();
+    const unsigned int rcode_type =
+        rcode < num_rcode_to_msgcounter ?
+        rcode_to_msgcounter[rcode] : MSG_RCODE_OTHER;
+    server_msg_counter_.inc(rcode_type);
+    // Unsupported EDNS version
+    if (rcode == Rcode::BADVERS().getCode()) {
+        server_msg_counter_.inc(MSG_REQUEST_BADEDNSVER);
+    }
+
+    const boost::optional<isc::dns::Opcode>& opcode =
+        msgattrs.getRequestOpCode();
+    if (!opcode) {
+        isc_throw(isc::Unexpected, "Opcode of the request is empty while it is"
+                                   " responded");
+    }
+    if (!msgattrs.requestHasBadSig() && opcode.get() == Opcode::QUERY()) {
+        // compound attributes
+        const unsigned int answer_rrs =
+            response.getRRCount(Message::SECTION_ANSWER);
+        const bool is_aa_set =
+            response.getHeaderFlag(Message::HEADERFLAG_AA);
+
+        if (is_aa_set) {
+            // QryAuthAns
+            server_msg_counter_.inc(MSG_QRYAUTHANS);
+        } else {
+            // QryNoAuthAns
+            server_msg_counter_.inc(MSG_QRYNOAUTHANS);
+        }
+
+        if (rcode == Rcode::NOERROR_CODE) {
+            if (answer_rrs > 0) {
+                // QrySuccess
+                server_msg_counter_.inc(MSG_QRYSUCCESS);
+            } else {
+                if (is_aa_set) {
+                    // QryNxrrset
+                    server_msg_counter_.inc(MSG_QRYNXRRSET);
+                } else {
+                    // QryReferral
+                    server_msg_counter_.inc(MSG_QRYREFERRAL);
+                }
+            }
+        } else if (rcode == Rcode::REFUSED_CODE) {
+            if (!response.getHeaderFlag(Message::HEADERFLAG_RD)) {
+                // AuthRej
+                server_msg_counter_.inc(MSG_QRYREJECT);
+            }
+        }
+    }
+}
+
+void
+Counters::inc(const MessageAttributes& msgattrs, const Message& response,
+              const bool done)
+{
+    // increment request counters
+    incRequest(msgattrs);
+
+    if (done) {
+        // increment response counters if answer was sent
+        incResponse(msgattrs, response);
+    }
+}
+
+Counters::ConstItemTreePtr
+Counters::get() const {
+    using namespace isc::data;
+
+    isc::data::ElementPtr item_tree = Element::createMap();
+
+    isc::data::ElementPtr zones = Element::createMap();
+    item_tree->set("zones", zones);
+
+    isc::data::ElementPtr server = Element::createMap();
+    fillNodes(server_msg_counter_, msg_counter_tree, server);
+    zones->set("_SERVER_", server);
+
+    return (item_tree);
+}
+
+} // namespace statistics
+} // namespace auth
+} // namespace isc

+ 223 - 142
src/bin/auth/statistics.h

@@ -15,208 +15,289 @@
 #ifndef STATISTICS_H
 #define STATISTICS_H 1
 
-#include <cc/session.h>
 #include <cc/data.h>
 
 #include <dns/message.h>
+#include <dns/opcode.h>
 
-#include <string>
+#include <statistics/counter.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/optional.hpp>
+
+#include <bitset>
 
 #include <stdint.h>
-#include <boost/scoped_ptr.hpp>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
 
 namespace isc {
 namespace auth {
 namespace statistics {
 
-class CountersImpl;
-
-class QRAttributes {
-/// \brief Query/Response attributes for statistics.
+/// \brief DNS Message attributes for statistics.
 ///
-/// This class holds some attributes related to a query/response
+/// This class holds some attributes related to a DNS message
 /// for statistics data collection.
-///
-/// This class does not have getter methods since it exposes private members
-/// to \c CountersImpl directly.
-friend class CountersImpl;
+class MessageAttributes {
+public:
+    /// \brief IP version of DNS message.
+    enum IPVersionType {
+        IP_VERSION_UNSPEC,          // (initial value; internal use only)
+        IP_VERSION_IPV4,            ///< IPv4 message
+        IP_VERSION_IPV6             ///< IPv6 message
+    };
+
+    /// \brief Transport protocol of DNS message.
+    enum TransportProtocolType {
+        TRANSPORT_UNSPEC,           // (initial value; internal use only)
+        TRANSPORT_UDP,              ///< UDP message
+        TRANSPORT_TCP               ///< TCP message
+    };
 private:
     // request attributes
-    int req_ip_version_;            // IP version
+    int req_address_family_;        // IP version
     int req_transport_protocol_;    // Transport layer protocol
-    int req_opcode_;                // OpCode
-    bool req_is_edns_0_;            // EDNS ver.0
-    bool req_is_edns_badver_;       // other EDNS version
-    bool req_is_dnssec_ok_;         // DO bit
-    bool req_is_tsig_;              // signed with valid TSIG
-    bool req_is_sig0_;              // signed with valid SIG(0)
-    bool req_is_badsig_;            // signed but bad signature
-    // zone origin
-    std::string zone_origin_;       // zone origin
-    // response attributes
-    bool answer_sent_;              // DNS message has sent
-    bool res_is_truncated_;         // DNS message is truncated
+    boost::optional<isc::dns::Opcode> req_opcode_;  // OpCode
+    enum BitAttributes {
+        REQ_WITH_EDNS_0,            // request with EDNS ver.0
+        REQ_WITH_DNSSEC_OK,         // DNSSEC OK (DO) bit is set in request
+        REQ_TSIG_SIGNED,            // request is signed with valid TSIG
+        REQ_BADSIG,                 // request is signed but bad signature
+        RES_IS_TRUNCATED,           // response is truncated
+        RES_TSIG_SIGNED,            // response is signed with TSIG
+        BIT_ATTRIBUTES_TYPES
+    };
+    std::bitset<BIT_ATTRIBUTES_TYPES> bit_attributes_;
 public:
-    /// The constructor.
+    /// \brief The constructor.
     ///
-    /// This constructor is mostly exception free. But it may still throw
-    /// a standard exception if memory allocation fails inside the method.
-    ///
-    QRAttributes() {
-        reset();
-    };
+    /// \throw None
+    MessageAttributes() : req_address_family_(0), req_transport_protocol_(0)
+    {}
 
-    /// The destructor.
+    /// \brief Return opcode of the request.
     ///
-    /// This method never throws an exception.
+    /// \return opcode of the request wrapped with boost::optional; it's
+    ///         converted to false if Opcode hasn't been set.
+    /// \throw None
+    const boost::optional<isc::dns::Opcode>& getRequestOpCode() const {
+        return (req_opcode_);
+    }
+
+    /// \brief Set opcode of the request.
     ///
-    ~QRAttributes() {};
-    /// \brief Set query opcode.
+    /// \param opcode Opcode of the request
     /// \throw None
-    void setQueryOpCode(const int opcode) {
+    void setRequestOpCode(const isc::dns::Opcode& opcode) {
         req_opcode_ = opcode;
-    };
-    /// \brief Set IP version carrying a query.
+    }
+
+    /// \brief Get IP version carrying a request.
+    ///
+    /// \return IP address family carrying a request (AF_INET or AF_INET6)
     /// \throw None
-    void setQueryIPVersion(const int ip_version) {
-        req_ip_version_ = ip_version;
-    };
-    /// \brief Set transport protocol carrying a query.
+    int getRequestIPVersion() const {
+        return (req_address_family_);
+    }
+
+    /// \brief Set IP address family carrying a request.
+    ///
+    /// \param address_family AF_INET or AF_INET6
+    /// \throw None
+    void setRequestIPVersion(const int address_family) {
+        if (address_family != AF_INET && address_family != AF_INET6) {
+            isc_throw(isc::InvalidParameter, "Unknown address family");
+        }
+        req_address_family_ = address_family;
+    }
+
+    /// \brief Get transport protocol carrying a request.
+    ///
+    /// \return Transport protocol carrying a request
+    ///         (IPPROTO_UDP or IPPROTO_TCP)
     /// \throw None
-    void setQueryTransportProtocol(const int transport_protocol) {
+    int getRequestTransportProtocol() const {
+        return (req_transport_protocol_);
+    }
+
+    /// \brief Set transport protocol carrying a request.
+    ///
+    /// \param transport_protocol IPPROTO_UDP or IPPROTO_TCP
+    /// \throw None
+    void setRequestTransportProtocol(const int transport_protocol) {
+        if (transport_protocol != IPPROTO_UDP &&
+            transport_protocol != IPPROTO_TCP)
+        {
+            isc_throw(isc::InvalidParameter, "Unknown transport protocol");
+        }
         req_transport_protocol_ = transport_protocol;
-    };
-    /// \brief Set query EDNS attributes.
+    }
+
+    /// \brief Return whether EDNS version of the request is 0 or not.
+    ///
+    /// \return true if EDNS version of the request is 0
     /// \throw None
-    void setQueryEDNS(const bool is_edns_0, const bool is_edns_badver) {
-        req_is_edns_0_ = is_edns_0;
-        req_is_edns_badver_ = is_edns_badver;
-    };
-    /// \brief Set query DO bit.
+    bool requestHasEDNS0() const {
+        return (bit_attributes_[REQ_WITH_EDNS_0]);
+    }
+
+    /// \brief Set whether EDNS version of the request is 0 or not.
+    ///
+    /// \param with_edns_0 true if EDNS version of the request is 0
     /// \throw None
-    void setQueryDO(const bool is_dnssec_ok) {
-        req_is_dnssec_ok_ = is_dnssec_ok;
-    };
-    /// \brief Set query TSIG attributes.
-    /// \throw None
-    void setQuerySig(const bool is_tsig, const bool is_sig0,
-                            const bool is_badsig)
-    {
-        req_is_tsig_ = is_tsig;
-        req_is_sig0_ = is_sig0;
-        req_is_badsig_ = is_badsig;
-    };
-    /// \brief Set zone origin.
+    void setRequestEDNS0(const bool with_edns_0) {
+        bit_attributes_[REQ_WITH_EDNS_0] = with_edns_0;
+    }
+
+    /// \brief Return DNSSEC OK (DO) bit of the request.
+    ///
+    /// \return true if DNSSEC OK (DO) bit of the request is set
     /// \throw None
-    void setOrigin(const std::string& origin) {
-        zone_origin_ = origin;
-    };
-    /// \brief Set if the answer was sent.
+    bool requestHasDO() const {
+        return (bit_attributes_[REQ_WITH_DNSSEC_OK]);
+    }
+
+    /// \brief Set DNSSEC OK (DO) bit of the request.
+    ///
+    /// \param with_dnssec_ok true if DNSSEC OK (DO) bit of the request is set
     /// \throw None
-    void answerWasSent() {
-        answer_sent_ = true;
-    };
-    /// \brief Set if the response is truncated.
+    void setRequestDO(const bool with_dnssec_ok) {
+        bit_attributes_[REQ_WITH_DNSSEC_OK] = with_dnssec_ok;
+    }
+
+    /// \brief Return whether the request is TSIG signed or not.
+    ///
+    /// \return true if the request is TSIG signed
+    /// \throw None
+    bool requestHasTSIG() const {
+        return (bit_attributes_[REQ_TSIG_SIGNED]);
+    }
+
+    /// \brief Return whether the signature of the request is bad or not.
+    ///
+    /// \return true if the signature of the request is bad
+    /// \throw None
+    bool requestHasBadSig() const {
+        return (bit_attributes_[REQ_BADSIG]);
+    }
+
+    /// \brief Set TSIG attributes of the request.
+    ///
+    /// \param signed_tsig true if the request is signed with TSIG
+    /// \param badsig true if the signature of the request is bad; it must not
+    //                be true unless signed_tsig is true
+    /// \throw isc::InvalidParameter if badsig is true though the request is
+    ///                              not signed
+    void setRequestTSIG(const bool signed_tsig, const bool badsig) {
+        if (!signed_tsig && badsig) {
+            isc_throw(isc::InvalidParameter, "Message is not signed but badsig"
+                                             " is true");
+        }
+        bit_attributes_[REQ_TSIG_SIGNED] = signed_tsig;
+        bit_attributes_[REQ_BADSIG] = badsig;
+    }
+
+    /// \brief Return TC (truncated) bit of the response.
+    ///
+    /// \return true if the response is truncated
+    /// \throw None
+    bool responseIsTruncated() const {
+        return (bit_attributes_[RES_IS_TRUNCATED]);
+    }
+
+    /// \brief Set TC (truncated) bit of the response.
+    ///
+    /// \param is_truncated true if the response is truncated
     /// \throw None
     void setResponseTruncated(const bool is_truncated) {
-        res_is_truncated_ = is_truncated;
-    };
-    /// \brief Reset attributes.
-    /// \throw None
-    void reset() {
-        req_ip_version_ = 0;
-        req_transport_protocol_ = 0;
-        req_opcode_ = 0;
-        req_is_edns_0_ = false;
-        req_is_edns_badver_ = false;
-        req_is_dnssec_ok_ = false;
-        req_is_tsig_ = false;
-        req_is_sig0_ = false;
-        req_is_badsig_ = false;
-        zone_origin_.clear();
-        answer_sent_ = false;
-        res_is_truncated_ = false;
-    };
+        bit_attributes_[RES_IS_TRUNCATED] = is_truncated;
+    }
+
+    /// \brief Return whether the response is TSIG signed or not.
+    ///
+    /// \return true if the response is signed with TSIG
+    /// \throw None
+    bool responseHasTSIG() const {
+        return (bit_attributes_[RES_TSIG_SIGNED]);
+    }
+
+    /// \brief Set whether the response is TSIG signed or not.
+    ///
+    /// \param signed_tsig true if the response is signed with TSIG
+    /// \throw None
+    void setResponseTSIG(const bool signed_tsig) {
+        bit_attributes_[RES_TSIG_SIGNED] = signed_tsig;
+    }
 };
 
-/// \brief Set of query counters.
+/// \brief Set of DNS message counters.
 ///
-/// \c Counters is set of query counters class. It holds query counters
-/// and provides an interface to increment the counter of specified type
-/// (e.g. UDP query, TCP query).
-///
-/// This class also provides a function to send statistics information to
-/// statistics module.
+/// \c Counters is a set of DNS message counters class. It holds DNS message
+/// counters and provides an interface to increment the counter of specified
+/// type (e.g. UDP message, TCP message).
 ///
 /// This class is designed to be a part of \c AuthSrv.
-/// Call \c inc() to increment a counter for the query.
-/// Call \c getStatistics() to answer statistics information to statistics
-/// module with statistics_session, when the command \c getstats is received.
+/// Call \c inc() to increment a counter for the message.
+/// Call \c get() to get a set of DNS message counters.
 ///
 /// We may eventually want to change the structure to hold values that are
 /// not counters (such as concurrent TCP connections), or seperate generic
 /// part to src/lib to share with the other modules.
 ///
-/// This class uses pimpl idiom and hides detailed implementation.
 /// This class is constructed on startup of the server, so
 /// construction overhead of this approach should be acceptable.
 ///
-/// \todo Hold counters for each query types (Notify, Axfr, Ixfr, Normal)
 /// \todo Consider overhead of \c Counters::inc()
-class Counters {
+class Counters : boost::noncopyable {
 private:
-    boost::scoped_ptr<CountersImpl> impl_;
+    // counter for DNS message attributes
+    isc::statistics::Counter server_msg_counter_;
+    void incRequest(const MessageAttributes& msgattrs);
+    void incResponse(const MessageAttributes& msgattrs,
+                     const isc::dns::Message& response);
 public:
-    /// The constructor.
+    /// \brief A type of statistics item tree in isc::data::MapElement.
+    /// \verbatim
+    ///        {
+    ///          zone_name => {
+    ///                         item_name => item_value,
+    ///                         item_name => item_value, ...
+    ///                       },
+    ///          ...
+    ///        }
+    ///        item_name is a string seperated by '.'.
+    ///        item_value is an integer.
+    /// \endverbatim
+    typedef isc::data::ConstElementPtr ConstItemTreePtr;
+
+    /// \brief The constructor.
     ///
     /// This constructor is mostly exception free. But it may still throw
     /// a standard exception if memory allocation fails inside the method.
-    ///
     Counters();
-    /// The destructor.
-    ///
-    /// This method never throws an exception.
-    ///
-    ~Counters();
 
     /// \brief Increment counters according to the parameters.
     ///
-    /// \param qrattrs Query/Response attributes.
+    /// \param msgattrs DNS message attributes.
     /// \param response DNS response message.
-    ///
-    /// \throw None
-    ///
-    void inc(const QRAttributes& qrattrs, const isc::dns::Message& response);
+    /// \param done DNS response was sent to the client.
+    /// \throw isc::Unexpected Internal condition check failed.
+    void inc(const MessageAttributes& msgattrs,
+             const isc::dns::Message& response, const bool done);
 
-    /// \brief Answers statistics counters to statistics module.
+    /// \brief Get statistics counters.
     ///
-    /// This method is mostly exception free (error conditions are
-    /// represented via the return value). But it may still throw
-    /// a standard exception if memory allocation fails inside the method.
+    /// This method is mostly exception free. But it may still throw a
+    /// standard exception if memory allocation fails inside the method.
     ///
     /// \return statistics data
-    ///
-    isc::data::ConstElementPtr getStatistics() const;
-
-    /// \brief A type of validation function for the specification in
-    /// isc::config::ModuleSpec.
-    ///
-    /// This type might be useful for not only statistics
-    /// specificatoin but also for config_data specification and for
-    /// commnad.
-    ///
-    typedef boost::function<bool(const isc::data::ConstElementPtr&)>
-    validator_type;
-
-    /// \brief Register a function type of the statistics validation
-    /// function for Counters.
-    ///
-    /// This method never throws an exception.
-    ///
-    /// \param validator A function type of the validation of
-    /// statistics specification.
-    ///
-    void registerStatisticsValidator(Counters::validator_type validator) const;
+    /// \throw std::bad_alloc Internal resource allocation fails
+    ConstItemTreePtr get() const;
 };
 
 } // namespace statistics

+ 0 - 609
src/bin/auth/statistics_items.h

@@ -1,609 +0,0 @@
-// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __STATISTICS_ITEMS_H
-#define __STATISTICS_ITEMS_H 1
-
-/// This file defines a set of statistics items in Auth module for internal
-/// use. This file is intended to be included in statistics.cc.
-
-namespace {
-
-struct CounterTypeTree {
-    const char* const name;
-    const struct CounterTypeTree* const sub_tree;
-    const int counter_id;
-};
-
-// enum for query/response counters
-enum QRCounterType {
-    // Request Attributes
-    QR_REQUEST_IPV4,        ///< Number of IPv4 requests received
-    QR_REQUEST_IPV6,        ///< Number of IPv6 requests received
-    QR_REQUEST_EDNS0,       ///< Number of requests with EDNS(0) received
-    QR_REQUEST_BADEDNSVER,  ///< Number of requests with unsupported EDNS version received
-    QR_REQUEST_TSIG,        ///< Number of requests with TSIG received
-    QR_REQUEST_SIG0,        ///< Number of requests with SIG(0) received; not implemented in BIND 10
-    QR_REQUEST_BADSIG,      ///< Number of requests with invalid TSIG or SIG(0) signature received
-    QR_REQUEST_UDP,         ///< Number of UDP requests received
-    QR_REQUEST_TCP,         ///< Number of TCP requests received
-    QR_REQUEST_DNSSEC_OK,   ///< Number of requests with DO bit
-    // Request Opcodes
-    QR_OPCODE_QUERY,        ///< Number of Opcode=QUERY requests received
-    QR_OPCODE_IQUERY,       ///< Number of Opcode=IQUERY requests received
-    QR_OPCODE_STATUS,       ///< Number of Opcode=STATUS requests received
-    QR_OPCODE_NOTIFY,       ///< Number of Opcode=NOTIFY requests received
-    QR_OPCODE_UPDATE,       ///< Number of Opcode=UPDATE requests received
-    QR_OPCODE_OTHER,        ///< Number of requests in other OpCode received
-    // Query Types
-    QR_QTYPE_A,             ///< Number of QTYPE = A queries received
-    QR_QTYPE_NS,            ///< Number of QTYPE = NS queries received
-    QR_QTYPE_MD,            ///< Number of QTYPE = MD queries received
-    QR_QTYPE_MF,            ///< Number of QTYPE = MF queries received
-    QR_QTYPE_CNAME,         ///< Number of QTYPE = CNAME queries received
-    QR_QTYPE_SOA,           ///< Number of QTYPE = SOA queries received
-    QR_QTYPE_MB,            ///< Number of QTYPE = MB queries received
-    QR_QTYPE_MG,            ///< Number of QTYPE = MG queries received
-    QR_QTYPE_MR,            ///< Number of QTYPE = MR queries received
-    QR_QTYPE_NULL,          ///< Number of QTYPE = NULL queries received
-    QR_QTYPE_WKS,           ///< Number of QTYPE = WKS queries received
-    QR_QTYPE_PTR,           ///< Number of QTYPE = PTR queries received
-    QR_QTYPE_HINFO,         ///< Number of QTYPE = HINFO queries received
-    QR_QTYPE_MINFO,         ///< Number of QTYPE = MINFO queries received
-    QR_QTYPE_MX,            ///< Number of QTYPE = MX queries received
-    QR_QTYPE_TXT,           ///< Number of QTYPE = TXT queries received
-    QR_QTYPE_RP,            ///< Number of QTYPE = RP queries received
-    QR_QTYPE_AFSDB,         ///< Number of QTYPE = AFSDB queries received
-    QR_QTYPE_X25,           ///< Number of QTYPE = X25 queries received
-    QR_QTYPE_ISDN,          ///< Number of QTYPE = ISDN queries received
-    QR_QTYPE_RT,            ///< Number of QTYPE = RT queries received
-    QR_QTYPE_NSAP,          ///< Number of QTYPE = NSAP queries received
-    QR_QTYPE_NSAP_PTR,      ///< Number of QTYPE = NSAP-PTR queries received
-    QR_QTYPE_SIG,           ///< Number of QTYPE = SIG queries received
-    QR_QTYPE_KEY,           ///< Number of QTYPE = KEY queries received
-    QR_QTYPE_PX,            ///< Number of QTYPE = PX queries received
-    QR_QTYPE_GPOS,          ///< Number of QTYPE = GPOS queries received
-    QR_QTYPE_AAAA,          ///< Number of QTYPE = AAAA queries received
-    QR_QTYPE_LOC,           ///< Number of QTYPE = LOC queries received
-    QR_QTYPE_NXT,           ///< Number of QTYPE = NXT queries received
-    QR_QTYPE_EID,           ///< Number of QTYPE = EID queries received
-    QR_QTYPE_NIMLOC,        ///< Number of QTYPE = NIMLOC queries received
-    QR_QTYPE_SRV,           ///< Number of QTYPE = SRV queries received
-    QR_QTYPE_ATMA,          ///< Number of QTYPE = ATMA queries received
-    QR_QTYPE_NAPTR,         ///< Number of QTYPE = NAPTR queries received
-    QR_QTYPE_KX,            ///< Number of QTYPE = KX queries received
-    QR_QTYPE_CERT,          ///< Number of QTYPE = CERT queries received
-    QR_QTYPE_A6,            ///< Number of QTYPE = A6 queries received
-    QR_QTYPE_DNAME,         ///< Number of QTYPE = DNAME queries received
-    QR_QTYPE_SINK,          ///< Number of QTYPE = SINK queries received
-    QR_QTYPE_OPT,           ///< Number of QTYPE = OPT queries received
-    QR_QTYPE_APL,           ///< Number of QTYPE = APL queries received
-    QR_QTYPE_DS,            ///< Number of QTYPE = DS queries received
-    QR_QTYPE_SSHFP,         ///< Number of QTYPE = SSHFP queries received
-    QR_QTYPE_IPSECKEY,      ///< Number of QTYPE = IPSECKEY queries received
-    QR_QTYPE_RRSIG,         ///< Number of QTYPE = RRSIG queries received
-    QR_QTYPE_NSEC,          ///< Number of QTYPE = NSEC queries received
-    QR_QTYPE_DNSKEY,        ///< Number of QTYPE = DNSKEY queries received
-    QR_QTYPE_DHCID,         ///< Number of QTYPE = DHCID queries received
-    QR_QTYPE_NSEC3,         ///< Number of QTYPE = NSEC3 queries received
-    QR_QTYPE_NSEC3PARAM,    ///< Number of QTYPE = NSEC3PARAM queries received
-    QR_QTYPE_HIP,           ///< Number of QTYPE = HIP queries received
-    QR_QTYPE_NINFO,         ///< Number of QTYPE = NINFO queries received
-    QR_QTYPE_RKEY,          ///< Number of QTYPE = RKEY queries received
-    QR_QTYPE_TALINK,        ///< Number of QTYPE = TALINK queries received
-    QR_QTYPE_SPF,           ///< Number of QTYPE = SPF queries received
-    QR_QTYPE_UINFO,         ///< Number of QTYPE = UINFO queries received
-    QR_QTYPE_UID,           ///< Number of QTYPE = UID queries received
-    QR_QTYPE_GID,           ///< Number of QTYPE = GID queries received
-    QR_QTYPE_UNSPEC,        ///< Number of QTYPE = UNSPEC queries received
-    QR_QTYPE_TKEY,          ///< Number of QTYPE = TKEY queries received
-    QR_QTYPE_TSIG,          ///< Number of QTYPE = TSIG queries received
-    QR_QTYPE_IXFR,          ///< Number of QTYPE = IXFR queries received
-    QR_QTYPE_AXFR,          ///< Number of QTYPE = AXFR queries received
-    QR_QTYPE_MAILB,         ///< Number of QTYPE = MAILB queries received
-    QR_QTYPE_MAILA,         ///< Number of QTYPE = MAILA queries received
-    QR_QTYPE_URI,           ///< Number of QTYPE = URI queries received
-    QR_QTYPE_CAA,           ///< Number of QTYPE = CAA queries received
-    QR_QTYPE_TA,            ///< Number of QTYPE = TA queries received
-    QR_QTYPE_DLV,           ///< Number of QTYPE = DLV queries received
-    QR_QTYPE_OTHER,         ///< Number of queries in other QTYPE received
-    // Respose Attributes
-    QR_RESPONSE,            ///< Number of responses sent
-    QR_RESPONSE_TRUNCATED,  ///< Number of truncated responses sent
-    QR_RESPONSE_EDNS0,      ///< Number of responses with EDNS0; not implemented in BIND 10
-    QR_RESPONSE_TSIG,       ///< Number of responses with TSIG
-    QR_RESPONSE_SIG0,       ///< Number of responses with SIG(0); not implemented in BIND 10
-    QR_QRYSUCCESS,          ///< Number of queries resulted in rcode = NOERROR and answer RR >= 1
-    QR_QRYAUTHANS,          ///< Number of queries resulted in authoritative answer
-    QR_QRYNOAUTHANS,        ///< Number of queries resulted in non-authoritative answer
-    QR_QRYREFERRAL,         ///< Number of queries resulted in referral answer
-    QR_QRYNXRRSET,          ///< Number of queries resulted in NOERROR but answer RR == 0
-    QR_QRYREJECT,           ///< Number of queries rejected
-    // Response Rcodes
-    QR_RCODE_NOERROR,       ///< Number of queries resulted in RCODE = 0 (NoError)
-    QR_RCODE_FORMERR,       ///< Number of queries resulted in RCODE = 1 (FormErr)
-    QR_RCODE_SERVFAIL,      ///< Number of queries resulted in RCODE = 2 (ServFail)
-    QR_RCODE_NXDOMAIN,      ///< Number of queries resulted in RCODE = 3 (NXDomain)
-    QR_RCODE_NOTIMP,        ///< Number of queries resulted in RCODE = 4 (NotImp)
-    QR_RCODE_REFUSED,       ///< Number of queries resulted in RCODE = 5 (Refused)
-    QR_RCODE_YXDOMAIN,      ///< Number of queries resulted in RCODE = 6 (YXDomain)
-    QR_RCODE_YXRRSET,       ///< Number of queries resulted in RCODE = 7 (YXRRSet)
-    QR_RCODE_NXRRSET,       ///< Number of queries resulted in RCODE = 8 (NXRRSet)
-    QR_RCODE_NOTAUTH,       ///< Number of queries resulted in RCODE = 9 (NotAuth)
-    QR_RCODE_NOTZONE,       ///< Number of queries resulted in RCODE = 10 (NotZone)
-    QR_RCODE_BADSIGVERS,    ///< Number of queries resulted in RCODE = 16 (BADVERS, BADSIG)
-    QR_RCODE_BADKEY,        ///< Number of queries resulted in RCODE = 17 (BADKEY)
-    QR_RCODE_BADTIME,       ///< Number of queries resulted in RCODE = 18 (BADTIME)
-    QR_RCODE_BADMODE,       ///< Number of queries resulted in RCODE = 19 (BADMODE)
-    QR_RCODE_BADNAME,       ///< Number of queries resulted in RCODE = 20 (BADNAME)
-    QR_RCODE_BADALG,        ///< Number of queries resulted in RCODE = 21 (BADALG)
-    QR_RCODE_BADTRUNC,      ///< Number of queries resulted in RCODE = 22 (BADTRUNC)
-    QR_RCODE_OTHER,         ///< Number of queries resulted in other RCODEs
-    // End of counter types
-    QR_COUNTER_TYPES  ///< The number of defined counters
-};
-
-// item names for query/response counters
-const struct CounterTypeTree QRCounterRequest[] = {
-    { "v4",         NULL,   QR_REQUEST_IPV4       },
-    { "v6",         NULL,   QR_REQUEST_IPV6       },
-    { "edns0",      NULL,   QR_REQUEST_EDNS0      },
-    { "badednsver", NULL,   QR_REQUEST_BADEDNSVER },
-    { "tsig",       NULL,   QR_REQUEST_TSIG       },
-    { "sig0",       NULL,   QR_REQUEST_SIG0       },
-    { "badsig",     NULL,   QR_REQUEST_BADSIG     },
-    { "udp",        NULL,   QR_REQUEST_UDP        },
-    { "tcp",        NULL,   QR_REQUEST_TCP        },
-    { "dnssec_ok",  NULL,   QR_REQUEST_DNSSEC_OK  },
-    { NULL,         NULL,   -1                    }
-};
-const struct CounterTypeTree QRCounterOpcode[] = {
-    { "query",  NULL,   QR_OPCODE_QUERY  },
-    { "iquery", NULL,   QR_OPCODE_IQUERY },
-    { "status", NULL,   QR_OPCODE_STATUS },
-    { "notify", NULL,   QR_OPCODE_NOTIFY },
-    { "update", NULL,   QR_OPCODE_UPDATE },
-    { "other",  NULL,   QR_OPCODE_OTHER  },
-    { NULL,     NULL,   -1               }
-};
-const struct CounterTypeTree QRCounterQtype[] = {
-    { "a",          NULL,   QR_QTYPE_A,         },
-    { "ns",         NULL,   QR_QTYPE_NS         },
-    { "md",         NULL,   QR_QTYPE_MD         },
-    { "mf",         NULL,   QR_QTYPE_MF         },
-    { "cname",      NULL,   QR_QTYPE_CNAME      },
-    { "soa",        NULL,   QR_QTYPE_SOA        },
-    { "mb",         NULL,   QR_QTYPE_MB         },
-    { "mg",         NULL,   QR_QTYPE_MG         },
-    { "mr",         NULL,   QR_QTYPE_MR         },
-    { "null",       NULL,   QR_QTYPE_NULL       },
-    { "wks",        NULL,   QR_QTYPE_WKS        },
-    { "ptr",        NULL,   QR_QTYPE_PTR        },
-    { "hinfo",      NULL,   QR_QTYPE_HINFO      },
-    { "minfo",      NULL,   QR_QTYPE_MINFO      },
-    { "mx",         NULL,   QR_QTYPE_MX         },
-    { "txt",        NULL,   QR_QTYPE_TXT        },
-    { "rp",         NULL,   QR_QTYPE_RP         },
-    { "afsdb",      NULL,   QR_QTYPE_AFSDB      },
-    { "x25",        NULL,   QR_QTYPE_X25        },
-    { "isdn",       NULL,   QR_QTYPE_ISDN       },
-    { "rt",         NULL,   QR_QTYPE_RT         },
-    { "nsap",       NULL,   QR_QTYPE_NSAP       },
-    { "nsap-ptr",   NULL,   QR_QTYPE_NSAP_PTR   },
-    { "sig",        NULL,   QR_QTYPE_SIG        },
-    { "key",        NULL,   QR_QTYPE_KEY        },
-    { "px",         NULL,   QR_QTYPE_PX         },
-    { "gpos",       NULL,   QR_QTYPE_GPOS       },
-    { "aaaa",       NULL,   QR_QTYPE_AAAA       },
-    { "loc",        NULL,   QR_QTYPE_LOC        },
-    { "nxt",        NULL,   QR_QTYPE_NXT        },
-    { "eid",        NULL,   QR_QTYPE_EID        },
-    { "nimloc",     NULL,   QR_QTYPE_NIMLOC     },
-    { "srv",        NULL,   QR_QTYPE_SRV        },
-    { "atma",       NULL,   QR_QTYPE_ATMA       },
-    { "naptr",      NULL,   QR_QTYPE_NAPTR      },
-    { "kx",         NULL,   QR_QTYPE_KX         },
-    { "cert",       NULL,   QR_QTYPE_CERT       },
-    { "a6",         NULL,   QR_QTYPE_A6         },
-    { "dname",      NULL,   QR_QTYPE_DNAME      },
-    { "sink",       NULL,   QR_QTYPE_SINK       },
-    { "opt",        NULL,   QR_QTYPE_OPT        },
-    { "apl",        NULL,   QR_QTYPE_APL        },
-    { "ds",         NULL,   QR_QTYPE_DS         },
-    { "sshfp",      NULL,   QR_QTYPE_SSHFP      },
-    { "ipseckey",   NULL,   QR_QTYPE_IPSECKEY   },
-    { "rrsig",      NULL,   QR_QTYPE_RRSIG      },
-    { "nsec",       NULL,   QR_QTYPE_NSEC       },
-    { "dnskey",     NULL,   QR_QTYPE_DNSKEY     },
-    { "dhcid",      NULL,   QR_QTYPE_DHCID      },
-    { "nsec3",      NULL,   QR_QTYPE_NSEC3      },
-    { "nsec3param", NULL,   QR_QTYPE_NSEC3PARAM },
-    { "hip",        NULL,   QR_QTYPE_HIP        },
-    { "ninfo",      NULL,   QR_QTYPE_NINFO      },
-    { "rkey",       NULL,   QR_QTYPE_RKEY       },
-    { "talink",     NULL,   QR_QTYPE_TALINK     },
-    { "spf",        NULL,   QR_QTYPE_SPF        },
-    { "uinfo",      NULL,   QR_QTYPE_UINFO      },
-    { "uid",        NULL,   QR_QTYPE_UID        },
-    { "gid",        NULL,   QR_QTYPE_GID        },
-    { "unspec",     NULL,   QR_QTYPE_UNSPEC     },
-    { "tkey",       NULL,   QR_QTYPE_TKEY       },
-    { "tsig",       NULL,   QR_QTYPE_TSIG       },
-    { "ixfr",       NULL,   QR_QTYPE_IXFR       },
-    { "axfr",       NULL,   QR_QTYPE_AXFR       },
-    { "mailb",      NULL,   QR_QTYPE_MAILB      },
-    { "maila",      NULL,   QR_QTYPE_MAILA      },
-    { "uri",        NULL,   QR_QTYPE_URI        },
-    { "caa",        NULL,   QR_QTYPE_CAA        },
-    { "ta",         NULL,   QR_QTYPE_TA         },
-    { "dlv",        NULL,   QR_QTYPE_DLV        },
-    { "other",      NULL,   QR_QTYPE_OTHER      },
-    { NULL,         NULL,   -1                  }
-};
-const struct CounterTypeTree QRCounterResponse[] = {
-    { "truncated",  NULL,   QR_RESPONSE_TRUNCATED },
-    { "edns0",      NULL,   QR_RESPONSE_EDNS0     },
-    { "tsig",       NULL,   QR_RESPONSE_TSIG      },
-    { "sig0",       NULL,   QR_RESPONSE_SIG0      },
-    { NULL,         NULL,   -1                    }
-};
-const struct CounterTypeTree QRCounterRcode[] = {
-    { "noerror",    NULL,   QR_RCODE_NOERROR    },
-    { "formerr",    NULL,   QR_RCODE_FORMERR    },
-    { "servfail",   NULL,   QR_RCODE_SERVFAIL   },
-    { "nxdomain",   NULL,   QR_RCODE_NXDOMAIN   },
-    { "notimp",     NULL,   QR_RCODE_NOTIMP     },
-    { "refused",    NULL,   QR_RCODE_REFUSED    },
-    { "yxdomain",   NULL,   QR_RCODE_YXDOMAIN   },
-    { "yxrrset",    NULL,   QR_RCODE_YXRRSET    },
-    { "nxrrset",    NULL,   QR_RCODE_NXRRSET    },
-    { "notauth",    NULL,   QR_RCODE_NOTAUTH    },
-    { "notzone",    NULL,   QR_RCODE_NOTZONE    },
-    { "badsigvers", NULL,   QR_RCODE_BADSIGVERS },
-    { "badkey",     NULL,   QR_RCODE_BADKEY     },
-    { "badtime",    NULL,   QR_RCODE_BADTIME    },
-    { "badmode",    NULL,   QR_RCODE_BADMODE    },
-    { "badname",    NULL,   QR_RCODE_BADNAME    },
-    { "badalg",     NULL,   QR_RCODE_BADALG     },
-    { "badtrunc",   NULL,   QR_RCODE_BADTRUNC   },
-    { "other",      NULL,   QR_RCODE_OTHER      },
-    { NULL,         NULL,   -1 }
-};
-const struct CounterTypeTree QRCounterTree[] = {
-    { "request",        QRCounterRequest,   -1              },
-    { "opcode",         QRCounterOpcode,    -1              },
-    { "qtype",          QRCounterQtype,     -1              },
-    { "responses",      NULL,               QR_RESPONSE     },
-    { "response",       QRCounterResponse,  -1              },
-    { "qrysuccess",     NULL,               QR_QRYSUCCESS   },
-    { "qryauthans",     NULL,               QR_QRYAUTHANS   },
-    { "qrynoauthans",   NULL,               QR_QRYNOAUTHANS },
-    { "qryreferral",    NULL,               QR_QRYREFERRAL  },
-    { "qrynxrrset",     NULL,               QR_QRYNXRRSET   },
-    { "authqryrej",     NULL,               QR_QRYREJECT    },
-    { "rcode",          QRCounterRcode,     -1              },
-    { NULL,             NULL,               -1              }
-};
-
-const int QROpCodeToQRCounterType[16] = {
-    QR_OPCODE_QUERY,    // Opcode =  0: Query
-    QR_OPCODE_IQUERY,   // Opcode =  1: Iquery
-    QR_OPCODE_STATUS,   // Opcode =  2: STATUS
-    QR_OPCODE_OTHER,    // Opcode =  3: (Unassigned)
-    QR_OPCODE_NOTIFY,   // Opcode =  4: Notify
-    QR_OPCODE_UPDATE,   // Opcode =  5: Update
-    QR_OPCODE_OTHER,    // Opcode =  6: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode =  7: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode =  8: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode =  9: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode = 10: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode = 11: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode = 12: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode = 13: (Unassigned)
-    QR_OPCODE_OTHER,    // Opcode = 14: (Unassigned)
-    QR_OPCODE_OTHER     // Opcode = 15: (Unassigned)
-};
-const int QRQTypeToQRCounterType[258] = {
-    QR_QTYPE_OTHER,         // RRtype =   0: special use
-    QR_QTYPE_A,             // RRtype =   1: A
-    QR_QTYPE_NS,            // RRtype =   2: NS
-    QR_QTYPE_MD,            // RRtype =   3: MD
-    QR_QTYPE_MF,            // RRtype =   4: MF
-    QR_QTYPE_CNAME,         // RRtype =   5: CNAME
-    QR_QTYPE_SOA,           // RRtype =   6: SOA
-    QR_QTYPE_MB,            // RRtype =   7: MB
-    QR_QTYPE_MG,            // RRtype =   8: MG
-    QR_QTYPE_MR,            // RRtype =   9: MR
-    QR_QTYPE_NULL,          // RRtype =  10: NULL
-    QR_QTYPE_WKS,           // RRtype =  11: WKS
-    QR_QTYPE_PTR,           // RRtype =  12: PTR
-    QR_QTYPE_HINFO,         // RRtype =  13: HINFO
-    QR_QTYPE_MINFO,         // RRtype =  14: MINFO
-    QR_QTYPE_MX,            // RRtype =  15: MX
-    QR_QTYPE_TXT,           // RRtype =  16: TXT
-    QR_QTYPE_RP,            // RRtype =  17: RP
-    QR_QTYPE_AFSDB,         // RRtype =  18: AFSDB
-    QR_QTYPE_X25,           // RRtype =  19: X25
-    QR_QTYPE_ISDN,          // RRtype =  20: ISDN
-    QR_QTYPE_RT,            // RRtype =  21: RT
-    QR_QTYPE_NSAP,          // RRtype =  22: NSAP
-    QR_QTYPE_NSAP_PTR,      // RRtype =  23: NSAP-PTR
-    QR_QTYPE_SIG,           // RRtype =  24: SIG
-    QR_QTYPE_KEY,           // RRtype =  25: KEY
-    QR_QTYPE_PX,            // RRtype =  26: PX
-    QR_QTYPE_GPOS,          // RRtype =  27: GPOS
-    QR_QTYPE_AAAA,          // RRtype =  28: AAAA
-    QR_QTYPE_LOC,           // RRtype =  29: LOC
-    QR_QTYPE_NXT,           // RRtype =  30: NXT
-    QR_QTYPE_EID,           // RRtype =  31: EID        
-    QR_QTYPE_NIMLOC,        // RRtype =  32: NIMLOC     
-    QR_QTYPE_SRV,           // RRtype =  33: SRV        
-    QR_QTYPE_ATMA,          // RRtype =  34: ATMA       
-    QR_QTYPE_NAPTR,         // RRtype =  35: NAPTR      
-    QR_QTYPE_KX,            // RRtype =  36: KX         
-    QR_QTYPE_CERT,          // RRtype =  37: CERT       
-    QR_QTYPE_A6,            // RRtype =  38: A6         
-    QR_QTYPE_DNAME,         // RRtype =  39: DNAME      
-    QR_QTYPE_SINK,          // RRtype =  40: SINK       
-    QR_QTYPE_OPT,           // RRtype =  41: OPT        
-    QR_QTYPE_APL,           // RRtype =  42: APL        
-    QR_QTYPE_DS,            // RRtype =  43: DS         
-    QR_QTYPE_SSHFP,         // RRtype =  44: SSHFP      
-    QR_QTYPE_IPSECKEY,      // RRtype =  45: IPSECKEY   
-    QR_QTYPE_RRSIG,         // RRtype =  46: RRSIG      
-    QR_QTYPE_NSEC,          // RRtype =  47: NSEC       
-    QR_QTYPE_DNSKEY,        // RRtype =  48: DNSKEY     
-    QR_QTYPE_DHCID,         // RRtype =  49: DHCID      
-    QR_QTYPE_NSEC3,         // RRtype =  50: NSEC3      
-    QR_QTYPE_NSEC3PARAM,    // RRtype =  51: NSEC3PARAM 
-    QR_QTYPE_OTHER,         // RRtype =  52: TLSA
-    QR_QTYPE_OTHER,         // RRtype =  53: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  54: (Unassigned)
-    QR_QTYPE_HIP,           // RRtype =  55: HIP
-    QR_QTYPE_NINFO,         // RRtype =  56: NINFO
-    QR_QTYPE_RKEY,          // RRtype =  57: RKEY
-    QR_QTYPE_TALINK,        // RRtype =  58: TALINK
-    QR_QTYPE_OTHER,         // RRtype =  59: CDS
-    QR_QTYPE_OTHER,         // RRtype =  60: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  61: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  62: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  63: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  64: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  65: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  66: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  67: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  68: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  69: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  70: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  71: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  72: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  73: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  74: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  75: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  76: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  77: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  78: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  79: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  80: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  81: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  82: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  83: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  84: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  85: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  86: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  87: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  88: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  89: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  90: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  91: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  92: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  93: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  94: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  95: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  96: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  97: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype =  98: (Unassigned)
-    QR_QTYPE_SPF,           // RRtype =  99: SPF
-    QR_QTYPE_UINFO,         // RRtype = 100: UINFO
-    QR_QTYPE_UID,           // RRtype = 101: UID
-    QR_QTYPE_GID,           // RRtype = 102: GID
-    QR_QTYPE_UNSPEC,        // RRtype = 103: UNSPEC
-    QR_QTYPE_OTHER,         // RRtype = 104: NID
-    QR_QTYPE_OTHER,         // RRtype = 105: L32
-    QR_QTYPE_OTHER,         // RRtype = 106: L64
-    QR_QTYPE_OTHER,         // RRtype = 107: LP 
-    QR_QTYPE_OTHER,         // RRtype = 108: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 109: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 110: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 111: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 112: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 113: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 114: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 115: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 116: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 117: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 118: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 119: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 120: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 121: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 122: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 123: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 124: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 125: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 126: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 127: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 128: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 129: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 130: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 131: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 132: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 133: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 134: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 135: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 136: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 137: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 138: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 139: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 140: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 141: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 142: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 143: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 144: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 145: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 146: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 147: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 148: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 149: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 150: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 151: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 152: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 153: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 154: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 155: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 156: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 157: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 158: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 159: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 160: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 161: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 162: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 163: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 164: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 165: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 166: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 167: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 168: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 169: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 170: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 171: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 172: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 173: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 174: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 175: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 176: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 177: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 178: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 179: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 180: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 181: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 182: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 183: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 184: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 185: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 186: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 187: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 188: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 189: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 190: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 191: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 192: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 193: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 194: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 195: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 196: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 197: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 198: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 199: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 200: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 201: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 202: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 203: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 204: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 205: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 206: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 207: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 208: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 209: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 210: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 211: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 212: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 213: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 214: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 215: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 216: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 217: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 218: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 219: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 220: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 221: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 222: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 223: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 224: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 225: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 226: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 227: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 228: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 229: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 230: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 231: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 232: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 233: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 234: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 235: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 236: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 237: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 238: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 239: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 240: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 241: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 242: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 243: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 244: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 245: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 246: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 247: (Unassigned)
-    QR_QTYPE_OTHER,         // RRtype = 248: (Unassigned)
-    QR_QTYPE_TKEY,          // RRtype = 249: TKEY
-    QR_QTYPE_TSIG,          // RRtype = 250: TSIG
-    QR_QTYPE_IXFR,          // RRtype = 251: IXFR
-    QR_QTYPE_AXFR,          // RRtype = 252: AXFR
-    QR_QTYPE_MAILB,         // RRtype = 253: MAILB
-    QR_QTYPE_MAILA,         // RRtype = 254: MAILA
-    QR_QTYPE_OTHER,         // RRtype = 255: for All records
-    QR_QTYPE_URI,           // RRtype = 256: URI
-    QR_QTYPE_CAA            // RRtype = 257: CAA
-};
-const int QRRCodeToQRCounterType[23] = {
-    QR_RCODE_NOERROR,       // Rcode =  0: NoError
-    QR_RCODE_FORMERR,       // Rcode =  1: FormErr
-    QR_RCODE_SERVFAIL,      // Rcode =  2: ServFail
-    QR_RCODE_NXDOMAIN,      // Rcode =  3: NXDomain
-    QR_RCODE_NOTIMP,        // Rcode =  4: NotImp
-    QR_RCODE_REFUSED,       // Rcode =  5: Refused
-    QR_RCODE_YXDOMAIN,      // Rcode =  6: YXDomain
-    QR_RCODE_YXRRSET,       // Rcode =  7: YXRRSet
-    QR_RCODE_NXRRSET,       // Rcode =  8: NXRRSet
-    QR_RCODE_NOTAUTH,       // Rcode =  9: NotAuth
-    QR_RCODE_NOTZONE,       // Rcode = 10: NotZone
-    QR_RCODE_OTHER,         // Rcode = 11: (Unassigned)
-    QR_RCODE_OTHER,         // Rcode = 12: (Unassigned)
-    QR_RCODE_OTHER,         // Rcode = 13: (Unassigned)
-    QR_RCODE_OTHER,         // Rcode = 14: (Unassigned)
-    QR_RCODE_OTHER,         // Rcode = 15: (Unassigned)
-    QR_RCODE_BADSIGVERS,    // Rcode = 16: BADVERS, BADSIG
-    QR_RCODE_BADKEY,        // Rcode = 17: BADKEY
-    QR_RCODE_BADTIME,       // Rcode = 18: BADTIME
-    QR_RCODE_BADMODE,       // Rcode = 19: BADMODE
-    QR_RCODE_BADNAME,       // Rcode = 20: BADNAME
-    QR_RCODE_BADALG,        // Rcode = 21: BADALG
-    QR_RCODE_BADTRUNC       // Rcode = 22: BADTRUNC
-};
-
-} // anonymous namespace
-
-#endif // __STATISTICS_ITEMS_H
-
-// Local Variables:
-// mode: c++
-// End:

+ 53 - 0
src/bin/auth/statistics_items.h.pre

@@ -0,0 +1,53 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __STATISTICS_ITEMS_H
+#define __STATISTICS_ITEMS_H 1
+
+/// This file declares a set of statistics items in Auth module for internal
+/// use. This file is intended to be included in statistics.cc and unittests.
+
+namespace isc {
+namespace auth {
+namespace statistics {
+
+struct CounterSpec {
+    /// \brief name Name of this node. This appears in the spec file.
+    const char* const name;
+    /// \brief sub_counters If this is a branch node, sub_counters points to
+    ///                     CounterSpec which contains child nodes. Otherwise,
+    ///                     for leaf nodes, sub_counters is NULL.
+    const struct CounterSpec* const sub_counters;
+    /// \brief counter_id If this is a leaf node, counter_id is an enumerator
+    ///                   of this item. Otherwise, for branch nodes, counter_id
+    ///                   is NOT_ITEM.
+    const int counter_id;
+};
+
+// ### STATISTICS ITEMS DECLARATION ###
+
+extern const int opcode_to_msgcounter[];
+extern const size_t num_opcode_to_msgcounter;
+extern const int rcode_to_msgcounter[];
+extern const size_t num_rcode_to_msgcounter;
+
+} // namespace statistics
+} // namespace auth
+} // namespace isc
+
+#endif // __STATISTICS_ITEMS_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 48 - 0
src/bin/auth/statistics_msg_items.def

@@ -0,0 +1,48 @@
+request	msg_counter_request		Request statistics	=
+	v4		MSG_REQUEST_IPV4	Number of IPv4 requests received by the b10-auth server.
+	v6		MSG_REQUEST_IPV6	Number of IPv6 requests received by the b10-auth server.
+	edns0		MSG_REQUEST_EDNS0	Number of requests with EDNS0 received by the b10-auth server.
+	badednsver	MSG_REQUEST_BADEDNSVER	Number of requests with unsupported EDNS version received by the b10-auth server.
+	tsig		MSG_REQUEST_TSIG	Number of requests with TSIG received by the b10-auth server.
+	sig0		MSG_REQUEST_SIG0	Number of requests with SIG(0) received by the b10-auth server; currently not implemented in BIND 10.
+	badsig		MSG_REQUEST_BADSIG	Number of requests with invalid TSIG or SIG(0) signature received by the b10-auth server.
+	udp		MSG_REQUEST_UDP		Number of UDP requests received by the b10-auth server.
+	tcp		MSG_REQUEST_TCP		Number of TCP requests received by the b10-auth server.
+	dnssec_ok	MSG_REQUEST_DNSSEC_OK	Number of requests with "DNSSEC OK" (DO) bit was set received by the b10-auth server.
+	;
+opcode	msg_counter_opcode		OpCode statistics	=
+	query		MSG_OPCODE_QUERY	Number of OpCode=Query requests received by the b10-auth server.
+	iquery		MSG_OPCODE_IQUERY	Number of OpCode=IQuery requests received by the b10-auth server.
+	status		MSG_OPCODE_STATUS	Number of OpCode=Status requests received by the b10-auth server.
+	notify		MSG_OPCODE_NOTIFY	Number of OpCode=Notify requests received by the b10-auth server.
+	update		MSG_OPCODE_UPDATE	Number of OpCode=Update requests received by the b10-auth server.
+	other		MSG_OPCODE_OTHER	Number of requests in other OpCode received by the b10-auth server.
+	;
+responses	MSG_RESPONSE			Number of responses sent by the b10-auth server.
+response	msg_counter_response	Response statistics	=
+	truncated	MSG_RESPONSE_TRUNCATED	Number of truncated responses sent by the b10-auth server.
+	edns0		MSG_RESPONSE_EDNS0	Number of responses with EDNS0 sent by the b10-auth server.
+	tsig		MSG_RESPONSE_TSIG	Number of responses with TSIG sent by the b10-auth server.
+	sig0		MSG_RESPONSE_SIG0	Number of responses with SIG(0) sent by the b10-auth server; currently not implemented in BIND 10.
+	;
+qrysuccess	MSG_QRYSUCCESS			Number of queries received by the b10-auth server resulted in rcode = NoError and the number of answer RR >= 1.
+qryauthans	MSG_QRYAUTHANS			Number of queries received by the b10-auth server resulted in authoritative answer.
+qrynoauthans	MSG_QRYNOAUTHANS		Number of queries received by the b10-auth server resulted in non-authoritative answer.
+qryreferral	MSG_QRYREFERRAL			Number of queries received by the b10-auth server resulted in referral answer.
+qrynxrrset	MSG_QRYNXRRSET			Number of queries received by the b10-auth server resulted in NoError and AA bit is set in the response, but the number of answer RR == 0.
+authqryrej	MSG_QRYREJECT			Number of authoritative queries rejected by the b10-auth server.
+rcode		msg_counter_rcode	Rcode statistics	=
+	noerror		MSG_RCODE_NOERROR	Number of requests received by the b10-auth server resulted in RCODE = 0 (NoError).
+	formerr		MSG_RCODE_FORMERR	Number of requests received by the b10-auth server resulted in RCODE = 1 (FormErr).
+	servfail	MSG_RCODE_SERVFAIL	Number of requests received by the b10-auth server resulted in RCODE = 2 (ServFail).
+	nxdomain	MSG_RCODE_NXDOMAIN	Number of requests received by the b10-auth server resulted in RCODE = 3 (NXDomain).
+	notimp		MSG_RCODE_NOTIMP	Number of requests received by the b10-auth server resulted in RCODE = 4 (NotImp).
+	refused		MSG_RCODE_REFUSED	Number of requests received by the b10-auth server resulted in RCODE = 5 (Refused).
+	yxdomain	MSG_RCODE_YXDOMAIN	Number of requests received by the b10-auth server resulted in RCODE = 6 (YXDomain).
+	yxrrset		MSG_RCODE_YXRRSET	Number of requests received by the b10-auth server resulted in RCODE = 7 (YXRRSet).
+	nxrrset		MSG_RCODE_NXRRSET	Number of requests received by the b10-auth server resulted in RCODE = 8 (NXRRSet).
+	notauth		MSG_RCODE_NOTAUTH	Number of requests received by the b10-auth server resulted in RCODE = 9 (NotAuth).
+	notzone		MSG_RCODE_NOTZONE	Number of requests received by the b10-auth server resulted in RCODE = 10 (NotZone).
+	badvers		MSG_RCODE_BADVERS	Number of requests received by the b10-auth server resulted in RCODE = 16 (BADVERS).
+	other		MSG_RCODE_OTHER		Number of requests received by the b10-auth server resulted in other RCODEs.
+	;

+ 1 - 0
src/bin/auth/tests/.gitignore

@@ -1,3 +1,4 @@
 /run_unittests
 /example_base_inc.cc
 /example_nsec3_inc.cc
+/statistics_unittest.cc

+ 9 - 2
src/bin/auth/tests/Makefile.am

@@ -34,6 +34,9 @@ TESTS_ENVIRONMENT = \
 TESTS =
 if HAVE_GTEST
 
+# auto-generated by statistics_items.py
+BUILT_SOURCES = statistics_unittest.cc
+
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
@@ -45,13 +48,13 @@ run_unittests_SOURCES += ../common.h ../common.cc
 run_unittests_SOURCES += ../statistics.h ../statistics.cc ../statistics_items.h
 run_unittests_SOURCES += ../datasrc_config.h ../datasrc_config.cc
 run_unittests_SOURCES += datasrc_util.h datasrc_util.cc
+run_unittests_SOURCES += statistics_util.h statistics_util.cc
 run_unittests_SOURCES += auth_srv_unittest.cc
 run_unittests_SOURCES += config_unittest.cc
 run_unittests_SOURCES += config_syntax_unittest.cc
 run_unittests_SOURCES += command_unittest.cc
 run_unittests_SOURCES += common_unittest.cc
 run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += statistics_unittest.cc
 run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
 run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
 run_unittests_SOURCES += datasrc_clients_mgr_unittest.cc
@@ -59,6 +62,7 @@ run_unittests_SOURCES += datasrc_config_unittest.cc
 run_unittests_SOURCES += run_unittests.cc
 
 nodist_run_unittests_SOURCES = ../auth_messages.h ../auth_messages.cc
+nodist_run_unittests_SOURCES += statistics_unittest.cc
 
 run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
@@ -83,7 +87,7 @@ run_unittests_LDADD += $(SQLITE_LIBS)
 
 # The following are definitions for auto-generating test data for query
 # tests.
-BUILT_SOURCES = example_base_inc.cc example_nsec3_inc.cc
+BUILT_SOURCES += example_base_inc.cc example_nsec3_inc.cc
 BUILT_SOURCES += testdata/example-base.sqlite3
 BUILT_SOURCES += testdata/example-nsec3.sqlite3
 
@@ -114,8 +118,11 @@ testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone testdata/example-com
 		-c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
 		example.com testdata/example-nsec3.zone
 
+EXTRA_DIST += gen-statisticsitems_test.py
+
 check-local:
 	B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
+	$(PYTHON) $(srcdir)/gen-statisticsitems_test.py $(top_builddir)/src/bin/auth/b10-auth.xml
 
 noinst_PROGRAMS = run_unittests
 

+ 310 - 167
src/bin/auth/tests/auth_srv_unittest.cc

@@ -29,7 +29,6 @@
 #include <server_common/portconfig.h>
 #include <server_common/keyring.h>
 
-#include <datasrc/memory_datasrc.h>
 #include <datasrc/client_list.h>
 #include <auth/auth_srv.h>
 #include <auth/command.h>
@@ -46,6 +45,8 @@
 #include <testutils/portconfig.h>
 #include <testutils/socket_request.h>
 
+#include "statistics_util.h"
+
 #include <gtest/gtest.h>
 
 #include <boost/lexical_cast.hpp>
@@ -74,6 +75,7 @@ using namespace isc::asiodns;
 using namespace isc::asiolink;
 using namespace isc::testutils;
 using namespace isc::server_common::portconfig;
+using namespace isc::auth::unittest;
 using isc::datasrc::memory::ZoneTableSegment;
 using isc::UnitTestUtil;
 using boost::scoped_ptr;
@@ -91,7 +93,8 @@ const char* const STATIC_DSRC_FILE = DSRC_DIR "/static.zone";
 
 // This is a configuration that uses the in-memory data source containing
 // a signed example zone.
-const char* const CONFIG_INMEMORY_EXAMPLE = TEST_DATA_DIR "/rfc5155-example.zone.signed";
+const char* const CONFIG_INMEMORY_EXAMPLE =
+    TEST_DATA_DIR "/rfc5155-example.zone.signed";
 
 // shortcut commonly used in tests
 typedef boost::shared_ptr<ConfigurableClientList> ListPtr;
@@ -109,6 +112,7 @@ protected:
         server.setDNSService(dnss_);
         server.setXfrinSession(&notify_session);
         server.createDDNSForwarder();
+        checkCountersAreInitialized();
     }
 
     ~AuthSrvTest() {
@@ -125,7 +129,8 @@ protected:
 
     // Helper for checking Rcode statistic counters;
     // Checks for one specific Rcode statistics counter value
-    void checkRcodeCounter(const std::string& rcode_name, const int rcode_value,
+    void checkRcodeCounter(const std::string& rcode_name,
+                           const int rcode_value,
                            const int expected_value) const
     {
             EXPECT_EQ(expected_value, rcode_value) <<
@@ -134,38 +139,26 @@ protected:
                       rcode_value;
     }
 
-    // Checks whether all Rcode counters are set to zero
-    void checkAllRcodeCountersZero() const {
-        // with checking NOERROR == 0 and the others are 0
-        checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 0);
-    }
-
     // Checks whether all Rcode counters are set to zero except the given
     // rcode (it is checked to be set to 'value')
     void checkAllRcodeCountersZeroExcept(const Rcode& rcode, int value) const {
         std::string target_rcode_name = rcode.toText();
         std::transform(target_rcode_name.begin(), target_rcode_name.end(),
                        target_rcode_name.begin(), ::tolower);
-        // rcode 16 is registered as both BADVERS and BADSIG
-        if (target_rcode_name == "badvers") {
-            target_rcode_name = "badsigvers";
-        }
 
         const std::map<std::string, ConstElementPtr>
-            stats_map(server.getStatistics()->mapValue());
+            stats_map(server.getStatistics()->get("zones")->get("_SERVER_")->
+                      get("rcode")->mapValue());
 
-        const std::string rcode_prefix("rcode.");
         for (std::map<std::string, ConstElementPtr>::const_iterator
                  i = stats_map.begin(), e = stats_map.end();
              i != e;
              ++i)
         {
-            if (i->first.compare(0, rcode_prefix.size(), rcode_prefix) == 0) {
-                if (i->first.compare(rcode_prefix + target_rcode_name) == 0) {
-                    checkRcodeCounter(i->first, i->second->intValue(), value);
-                } else {
-                    checkRcodeCounter(i->first, i->second->intValue(), 0);
-                }
+            if (i->first.compare(target_rcode_name) == 0) {
+                checkRcodeCounter(i->first, i->second->intValue(), value);
+            } else {
+                checkRcodeCounter(i->first, i->second->intValue(), 0);
             }
         }
     }
@@ -200,6 +193,15 @@ protected:
                               &dnsserv);
     }
 
+    // Check if the counters exist and are initialized to 0.
+    void
+    checkCountersAreInitialized() {
+        const std::map<std::string, int> expect;
+        ConstElementPtr stats = server.getStatistics()->
+            get("zones")->get("_SERVER_");
+        checkStatisticsCounters(stats, expect);
+    }
+
     MockDNSService dnss_;
     MockXfroutClient xfrout;
     MockSocketSessionForwarder ddns_forwarder;
@@ -242,29 +244,6 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
                 renderer.getLength());
 }
 
-// Check if the item has expected value.
-// Before reading the item, check the item exists.
-void
-expectCounterItem(ConstElementPtr stats,
-                  const std::string& item, const int expected) {
-    ConstElementPtr value(Element::create(0));
-    if (item == "queries.udp" || item == "queries.tcp" || expected != 0) {
-        // if the value of the item is not zero, the item exists and has
-        // expected value
-        // item "queries.udp" and "queries.tcp" exists whether the value
-        // is zero or nonzero
-        ASSERT_TRUE(stats->find(item, value)) << "    Item: " << item;
-        // Get the value of the item with another method because of API bug
-        // (ticket #2302)
-        value = stats->find(item);
-        EXPECT_EQ(expected, value->intValue()) << "    Item: " << item;
-    } else {
-        // otherwise the item does not exist
-        ASSERT_FALSE(stats->find(item, value)) << "    Item: " << item <<
-            std::endl << "   Value: " << value->intValue();
-    }
-}
-
 // We did not configure any client lists. Therefore it should be REFUSED
 TEST_F(AuthSrvTest, noClientList) {
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
@@ -277,6 +256,18 @@ TEST_F(AuthSrvTest, noClientList) {
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::REFUSED(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["qrynoauthans"] = 1;
+    expect["authqryrej"] = 1;
+    expect["rcode.refused"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Unsupported requests.  Should result in NOTIMP.
@@ -296,32 +287,74 @@ TEST_F(AuthSrvTest, multiQuestion) {
 // dropped.
 TEST_F(AuthSrvTest, shortMessage) {
     shortMessage();
-    checkAllRcodeCountersZero();
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Response messages.  Must be silently dropped, whether it's a valid response
 // or malformed or could otherwise cause a protocol error.
 TEST_F(AuthSrvTest, response) {
+    // isc::testutils::SrvTestBase::response() processes 3 messages.
     response();
-    checkAllRcodeCountersZero();
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 3;
+    expect["request.udp"] = 3;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Query with a broken question
 TEST_F(AuthSrvTest, shortQuestion) {
     shortQuestion();
-    checkAllRcodeCountersZeroExcept(Rcode::FORMERR(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.formerr"] = 1;
+    expect["qrynoauthans"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Query with a broken answer section
 TEST_F(AuthSrvTest, shortAnswer) {
     shortAnswer();
-    checkAllRcodeCountersZeroExcept(Rcode::FORMERR(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.formerr"] = 1;
+    expect["qrynoauthans"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Query with unsupported version of EDNS.
 TEST_F(AuthSrvTest, ednsBadVers) {
     ednsBadVers();
-    checkAllRcodeCountersZeroExcept(Rcode::BADVERS(), 1);
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.badednsver"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.badvers"] = 1;
+    expect["qrynoauthans"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 TEST_F(AuthSrvTest, AXFROverUDP) {
@@ -340,7 +373,14 @@ TEST_F(AuthSrvTest, AXFRSuccess) {
                           &dnsserv);
     EXPECT_FALSE(dnsserv.hasAnswer());
     EXPECT_TRUE(xfrout.isConnected());
-    checkAllRcodeCountersZero();
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tcp"] = 1;
+    expect["opcode.query"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Give the server a signed request, but don't give it the key. It will
@@ -374,7 +414,20 @@ TEST_F(AuthSrvTest, TSIGSignedBadKey) {
     EXPECT_EQ(0, tsig->getRdata().getMACSize()) <<
         "It should be unsigned with this error";
 
+    // check Rcode counters and TSIG counters
     checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tsig"] = 1;
+    expect["request.badsig"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["response.tsig"] = 1;
+    expect["rcode.notauth"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Give the server a signed request, but signed by a different key
@@ -409,7 +462,18 @@ TEST_F(AuthSrvTest, TSIGBadSig) {
     EXPECT_EQ(0, tsig->getRdata().getMACSize()) <<
         "It should be unsigned with this error";
 
-    checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tsig"] = 1;
+    expect["request.badsig"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["response.tsig"] = 1;
+    expect["rcode.notauth"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Give the server a signed unsupported request with a bad signature.
@@ -446,13 +510,19 @@ TEST_F(AuthSrvTest, TSIGCheckFirst) {
     EXPECT_EQ(TSIGError::BAD_SIG_CODE, tsig->getRdata().getError());
     EXPECT_EQ(0, tsig->getRdata().getMACSize()) <<
         "It should be unsigned with this error";
-    // TSIG should have failed, and so the per opcode counter shouldn't be
-    // incremented.
-    ConstElementPtr stats = server.getStatistics();
-    expectCounterItem(stats, "opcode.normal", 0);
-    expectCounterItem(stats, "opcode.other", 0);
 
-    checkAllRcodeCountersZeroExcept(Rcode::NOTAUTH(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tsig"] = 1;
+    expect["request.badsig"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.other"] = 1;
+    expect["responses"] = 1;
+    expect["response.tsig"] = 1;
+    expect["rcode.notauth"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 TEST_F(AuthSrvTest, AXFRConnectFail) {
@@ -590,7 +660,8 @@ TEST_F(AuthSrvTest, notify) {
     // external module.  Check them.
     EXPECT_EQ("Zonemgr", notify_session.getMessageDest());
     EXPECT_EQ("notify",
-              notify_session.getSentMessage()->get("command")->get(0)->stringValue());
+              notify_session.getSentMessage()->get("command")->get(0)->
+                  stringValue());
     ConstElementPtr notify_args =
         notify_session.getSentMessage()->get("command")->get(1);
     EXPECT_EQ("example.com.", notify_args->get("zone_name")->stringValue());
@@ -608,7 +679,15 @@ TEST_F(AuthSrvTest, notify) {
     EXPECT_EQ(RRClass::IN(), question->getClass());
     EXPECT_EQ(RRType::SOA(), question->getType());
 
-    checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.notify"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.noerror"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 TEST_F(AuthSrvTest, notifyForCHClass) {
@@ -627,6 +706,16 @@ TEST_F(AuthSrvTest, notifyForCHClass) {
     ConstElementPtr notify_args =
         notify_session.getSentMessage()->get("command")->get(1);
     EXPECT_EQ("CH", notify_args->get("zone_class")->stringValue());
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.notify"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.noerror"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 TEST_F(AuthSrvTest, notifyEmptyQuestion) {
@@ -642,6 +731,16 @@ TEST_F(AuthSrvTest, notifyEmptyQuestion) {
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::FORMERR(),
                 Opcode::NOTIFY().getCode(), QR_FLAG, 0, 0, 0, 0);
+
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.notify"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.formerr"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 TEST_F(AuthSrvTest, notifyMultiQuestions) {
@@ -855,6 +954,19 @@ TEST_F(AuthSrvTest, TSIGSigned) {
         "The server signed the response, but it doesn't seem to be valid";
 
     checkAllRcodeCountersZeroExcept(Rcode::NOERROR(), 1);
+    ConstElementPtr stats_after = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tsig"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["response.tsig"] = 1;
+    expect["qrysuccess"] = 1;
+    expect["qryauthans"] = 1;
+    expect["rcode.noerror"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Same test emulating the UDPServer class behavior (defined in libasiolink).
@@ -955,8 +1067,8 @@ TEST_F(AuthSrvTest, updateConfig) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     EXPECT_TRUE(dnsserv.hasAnswer());
-    headerCheck(*parse_message, default_qid, Rcode::NOERROR(), opcode.getCode(),
-                QR_FLAG | AA_FLAG, 1, 1, 1, 0);
+    headerCheck(*parse_message, default_qid, Rcode::NOERROR(),
+                opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
 #ifdef USE_STATIC_LINK
@@ -976,6 +1088,18 @@ TEST_F(AuthSrvTest, datasourceFail) {
     EXPECT_TRUE(dnsserv.hasAnswer());
     headerCheck(*parse_message, default_qid, Rcode::SERVFAIL(),
                 opcode.getCode(), QR_FLAG, 1, 0, 0, 0);
+
+    checkAllRcodeCountersZeroExcept(Rcode::SERVFAIL(), 1);
+    ConstElementPtr stats = server.getStatistics()->get("zones")->
+        get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["qrynoauthans"] = 1;
+    expect["rcode.servfail"] = 1;
+    checkStatisticsCounters(stats, expect);
 }
 
 #ifdef USE_STATIC_LINK
@@ -1084,14 +1208,39 @@ TEST_F(AuthSrvTest,
                 opcode.getCode(), QR_FLAG | AA_FLAG, 1, 1, 1, 0);
 }
 
+#ifdef USE_STATIC_LINK
+TEST_F(AuthSrvTest, DISABLED_queryCounterTruncTest) {
+#else
+TEST_F(AuthSrvTest, queryCounterTruncTest) {
+#endif
+    // use CONFIG_TESTDB for large-rdata.example.com.
+    updateDatabase(server, CONFIG_TESTDB);
+
+    // Create UDP message and process.
+    // large-rdata.example.com. TXT; expect it exceeds 512 octet
+    UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
+                                       default_qid,
+                                       Name("large-rdata.example.com."),
+                                       RRClass::IN(), RRType::TXT());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
+
+    ConstElementPtr stats_after = server.getStatistics()->
+        get("zones")->get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["response.truncated"] = 1;
+    expect["qrysuccess"] = 1;
+    expect["qryauthans"] = 1;
+    expect["rcode.noerror"] = 1;
+    checkStatisticsCounters(stats_after, expect);
+}
 // Submit UDP normal query and check query counter
 TEST_F(AuthSrvTest, queryCounterUDPNormal) {
-    // The counters should be initialized to 0.
-    ConstElementPtr stats_init = server.getStatistics();
-    expectCounterItem(stats_init, "queries.udp", 0);
-    expectCounterItem(stats_init, "queries.tcp", 0);
-    expectCounterItem(stats_init, "opcode.query", 0);
-    expectCounterItem(stats_init, "rcode.refused", 0);
     // Create UDP message and process.
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("example.com"),
@@ -1099,25 +1248,50 @@ TEST_F(AuthSrvTest, queryCounterUDPNormal) {
     createRequestPacket(request_message, IPPROTO_UDP);
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
-    // After processing the UDP query, these counters should be incremented:
-    //   queries.udp, opcode.query, rcode.refused
-    // and these counters should not be incremented:
-    //   queries.tcp
-    ConstElementPtr stats_after = server.getStatistics();
-    expectCounterItem(stats_after, "queries.udp", 1);
-    expectCounterItem(stats_after, "queries.tcp", 0);
-    expectCounterItem(stats_after, "opcode.query", 1);
-    expectCounterItem(stats_after, "rcode.refused", 1);
+
+    ConstElementPtr stats_after = server.getStatistics()->
+        get("zones")->get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["qrynoauthans"] = 1;
+    expect["authqryrej"] = 1;
+    expect["rcode.refused"] = 1;
+    checkStatisticsCounters(stats_after, expect);
+}
+
+// Submit UDP normal query with DNSSEC and check query counter
+TEST_F(AuthSrvTest, queryCounterUDPNormalWithDNSSEC) {
+    // Create UDP message and process.
+    UnitTestUtil::createDNSSECRequestMessage(request_message, Opcode::QUERY(),
+                                             default_qid, Name("example.com"),
+                                             RRClass::IN(), RRType::NS());
+    createRequestPacket(request_message, IPPROTO_UDP);
+    server.processMessage(*io_message, *parse_message, *response_obuffer,
+                          &dnsserv);
+
+    ConstElementPtr stats_after = server.getStatistics()->
+        get("zones")->get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.edns0"] = 1;
+    expect["request.udp"] = 1;
+    expect["request.dnssec_ok"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["qrynoauthans"] = 1;
+    expect["authqryrej"] = 1;
+    expect["rcode.refused"] = 1;
+    // XXX: with the current implementation, EDNS0 is omitted in
+    // makeErrorMessage.
+    expect["response.edns0"] = 0;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Submit TCP normal query and check query counter
 TEST_F(AuthSrvTest, queryCounterTCPNormal) {
-    // The counters should be initialized to 0.
-    ConstElementPtr stats_init = server.getStatistics();
-    expectCounterItem(stats_init, "queries.udp", 0);
-    expectCounterItem(stats_init, "queries.tcp", 0);
-    expectCounterItem(stats_init, "opcode.query", 0);
-    expectCounterItem(stats_init, "rcode.refused", 0);
     // Create TCP message and process.
     UnitTestUtil::createRequestMessage(request_message, Opcode::QUERY(),
                                        default_qid, Name("example.com"),
@@ -1125,24 +1299,22 @@ TEST_F(AuthSrvTest, queryCounterTCPNormal) {
     createRequestPacket(request_message, IPPROTO_TCP);
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
-    // After processing the TCP query, these counters should be incremented:
-    //   queries.tcp, opcode.query, rcode.refused
-    // and these counters should not be incremented:
-    //   queries.udp
-    ConstElementPtr stats_after = server.getStatistics();
-    expectCounterItem(stats_after, "queries.udp", 0);
-    expectCounterItem(stats_after, "queries.tcp", 1);
-    expectCounterItem(stats_after, "opcode.query", 1);
-    expectCounterItem(stats_after, "rcode.refused", 1);
+
+    ConstElementPtr stats_after = server.getStatistics()->
+        get("zones")->get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tcp"] = 1;
+    expect["opcode.query"] = 1;
+    expect["responses"] = 1;
+    expect["qrynoauthans"] = 1;
+    expect["authqryrej"] = 1;
+    expect["rcode.refused"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Submit TCP AXFR query and check query counter
 TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
-    // The counters should be initialized to 0.
-    ConstElementPtr stats_init = server.getStatistics();
-    expectCounterItem(stats_init, "queries.udp", 0);
-    expectCounterItem(stats_init, "queries.tcp", 0);
-    expectCounterItem(stats_init, "opcode.query", 0);
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                          Name("example.com"), RRClass::IN(), RRType::AXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
@@ -1151,24 +1323,18 @@ TEST_F(AuthSrvTest, queryCounterTCPAXFR) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     EXPECT_FALSE(dnsserv.hasAnswer());
-    // After processing the TCP AXFR query, these counters should be
-    // incremented:
-    //   queries.tcp, opcode.query
-    // and these counters should not be incremented:
-    //   queries.udp
-    ConstElementPtr stats_after = server.getStatistics();
-    expectCounterItem(stats_after, "queries.udp", 0);
-    expectCounterItem(stats_after, "queries.tcp", 1);
-    expectCounterItem(stats_after, "opcode.query", 1);
+
+    ConstElementPtr stats_after = server.getStatistics()->
+        get("zones")->get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tcp"] = 1;
+    expect["opcode.query"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 // Submit TCP IXFR query and check query counter
 TEST_F(AuthSrvTest, queryCounterTCPIXFR) {
-    // The counters should be initialized to 0.
-    ConstElementPtr stats_init = server.getStatistics();
-    expectCounterItem(stats_init, "queries.udp", 0);
-    expectCounterItem(stats_init, "queries.tcp", 0);
-    expectCounterItem(stats_init, "opcode.query", 0);
     UnitTestUtil::createRequestMessage(request_message, opcode, default_qid,
                          Name("example.com"), RRClass::IN(), RRType::IXFR());
     createRequestPacket(request_message, IPPROTO_TCP);
@@ -1177,27 +1343,38 @@ TEST_F(AuthSrvTest, queryCounterTCPIXFR) {
     server.processMessage(*io_message, *parse_message, *response_obuffer,
                           &dnsserv);
     EXPECT_FALSE(dnsserv.hasAnswer());
-    // After processing the TCP IXFR query, these counters should be
-    // incremented:
-    //   queries.tcp, opcode.query
-    // and these counters should not be incremented:
-    //   queries.udp
-    ConstElementPtr stats_after = server.getStatistics();
-    expectCounterItem(stats_after, "queries.udp", 0);
-    expectCounterItem(stats_after, "queries.tcp", 1);
-    expectCounterItem(stats_after, "opcode.query", 1);
+
+    ConstElementPtr stats_after = server.getStatistics()->
+        get("zones")->get("_SERVER_");
+    std::map<std::string, int> expect;
+    expect["request.v4"] = 1;
+    expect["request.tcp"] = 1;
+    expect["opcode.query"] = 1;
+    checkStatisticsCounters(stats_after, expect);
 }
 
 TEST_F(AuthSrvTest, queryCounterOpcodes) {
-    // Check for 0..2, 3(=other), 4..5
-    // The counter should be initialized to 0.
-    for (int i = 0; i < 6; ++i) {
-        // The counter should be initialized to 0.
-        expectCounterItem(server.getStatistics(),
-                          std::string("opcode.") +
-                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
-                                                  QR_OPCODE_QUERY].name,
-                          0);
+    int other_expected = 0;
+    for (int i = 0; i < isc::auth::statistics::num_opcode_to_msgcounter; ++i) {
+        std::string item_name;
+        int expected;
+        if (isc::auth::statistics::opcode_to_msgcounter[i] ==
+                isc::auth::statistics::MSG_OPCODE_OTHER)
+        {
+            item_name = "OTHER";
+            other_expected += i + 1;
+            expected = other_expected;
+        } else {
+            item_name = Opcode(i).toText();
+            expected = i + 1;
+        }
+        std::transform(item_name.begin(), item_name.end(), item_name.begin(),
+                       ::tolower);
+
+        // The counter should be initialized to expected value.
+        EXPECT_EQ(expected - (i + 1),
+                  server.getStatistics()->get("zones")->get("_SERVER_")->
+                  get("opcode")->get(item_name)->intValue());
 
         // For each possible opcode, create a request message and send it
         UnitTestUtil::createRequestMessage(request_message, Opcode(i),
@@ -1215,45 +1392,11 @@ TEST_F(AuthSrvTest, queryCounterOpcodes) {
         }
 
         // Confirm the counter.
-        expectCounterItem(server.getStatistics(),
-                          std::string("opcode.") +
-                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
-                                                  QR_OPCODE_QUERY].name,
-                          i + 1);
-    }
-    // Check for 6..15
-    // they are treated as the 'other' opcode
-    // the 'other' opcode counter is 4 at this point
-    int expected = 4;
-    for (int i = 6; i < 16; ++i) {
-        // The counter should be initialized to 0.
-        expectCounterItem(server.getStatistics(),
-                          std::string("opcode.") +
-                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
-                                              QR_OPCODE_QUERY].name,
-                          expected);
-
-        // For each possible opcode, create a request message and send it
-        UnitTestUtil::createRequestMessage(request_message, Opcode(i),
-                                           default_qid, Name("example.com"),
-                                           RRClass::IN(), RRType::NS());
-        createRequestPacket(request_message, IPPROTO_UDP);
-
-        // "send" the request once
-        parse_message->clear(Message::PARSE);
-        server.processMessage(*io_message, *parse_message,
-                              *response_obuffer,
-                              &dnsserv);
-
-        // the 'other' opcode counter should be incremented
-        ++expected;
-
-        // Confirm the counter.
-        expectCounterItem(server.getStatistics(),
-                          std::string("opcode.") +
-                              QRCounterOpcode[QROpCodeToQRCounterType[i] -
-                                              QR_OPCODE_QUERY].name,
-                          expected);
+        // This test only checks for opcodes; some part of the other items
+        // depends on the opcode.
+        EXPECT_EQ(expected,
+                  server.getStatistics()->get("zones")->get("_SERVER_")->
+                  get("opcode")->get(item_name)->intValue());
     }
 }
 

+ 0 - 2
src/bin/auth/tests/command_unittest.cc

@@ -29,8 +29,6 @@
 
 #include <config/ccsession.h>
 
-#include <datasrc/memory_datasrc.h>
-
 #include <asiolink/asiolink.h>
 
 #include <util/unittests/mock_socketsession.h>

+ 1 - 2
src/bin/auth/tests/config_unittest.cc

@@ -22,7 +22,6 @@
 #include <cc/data.h>
 
 #include <datasrc/data_source.h>
-#include <datasrc/memory_datasrc.h>
 
 #include <xfr/xfrout_client.h>
 
@@ -130,7 +129,7 @@ TEST_F(AuthConfigTest, invalidListenAddressConfig) {
     isc::testutils::portconfig::invalidListenAddressConfig(server);
 }
 
-// Try setting addresses trough config
+// Try setting addresses through config
 TEST_F(AuthConfigTest, listenAddressConfig) {
     isc::testutils::portconfig::listenAddressConfig(server);
 

+ 2 - 1
src/bin/auth/tests/datasrc_clients_builder_unittest.cc

@@ -328,7 +328,8 @@ TEST_F(DataSrcClientsBuilderTest,
 {
     // Prepare the database first
     const std::string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
-    std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
+    std::stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n"
+                         "example.org. 3600 IN NS ns1.example.org.\n");
     createSQLite3DB(rrclass, Name("example.org"), test_db.c_str(), ss);
     // This describes the data source in the configuration
     const ConstElementPtr config(Element::fromJSON("{"

+ 90 - 0
src/bin/auth/tests/gen-statisticsitems_test.py

@@ -0,0 +1,90 @@
+# Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""\
+This script checks output of gen-statisticsitems.py.
+
+This script checks XML file. Spec file, C++ code and header files syntax is
+checked in the other unittests or system tests.
+"""
+
+import os
+import sys
+from xml.etree import ElementTree
+
+"""\
+User-defined exception for parse error. It is thrown if a file is not
+formatted as expected.
+"""
+class ParseError(Exception):
+    pass
+
+"""\
+Test XML file.
+
+It should have <refsect1> which has <title>STATISTICS DATA</title>.
+Inside the section, it should have one or more <varlistentry> of each item
+inside <variablelist>.
+Each <varlistentry> should have <term> for item name and <simpara> inside
+<listitem> for item description.
+
+Example:
+    <refsect1>
+        <title>STATISTICS DATA</title>
+        <variablelist>
+        <varlistentry>
+          <term>item1</term>
+          <listitem><simpara>statistics item</simpara></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>item2</term>
+          <listitem><simpara>another statistics item</simpara></listitem>
+        </varlistentry>
+        </variablelist>
+    </refsect1>
+"""
+def test_xml_file(xmlfilepath):
+    xmltree = ElementTree.parse(xmlfilepath)
+    root = xmltree.getroot()
+    # find <refsect1> which has <title> of 'STATISTICS DATA'
+    stats_node = [t for t in root.findall('./refsect1')
+            if t.find('./title').text == 'STATISTICS DATA']
+    if not stats_node:
+        raise ParseError('Statistics data section does not exist')
+    # find all <varlistentry> inside <variablelist>
+    entries = stats_node[0].find('./variablelist').findall('./varlistentry')
+    if not entries:
+        raise ParseError('<varlistentry> does not exist inside section')
+    for entry in entries:
+        # find <term> for item name
+        name = entry.find('./term')
+        if name is None or name.text == '':
+            raise ParseError('<term> for item name does not exist')
+        # find <simpara> inside <listitem> for item description
+        description = entry.find('./listitem/simpara')
+        if description is None or description.text == '':
+            raise ParseError('<listitem> nor <simpara> for item description'
+                             ' does not exist')
+    return
+
+if __name__ == "__main__":
+    xmlfilepath = sys.argv[1]
+    try:
+        test_xml_file(xmlfilepath)
+    except ImportError:
+        # pyexpat library is required for ElementTree.parse() but it is
+        # missing in some environment. Just skip this test.
+        print ("Required library is missing, skipping this test.")
+        print ("Detailed information:")
+        print (sys.exc_info()[1])

+ 165 - 105
src/bin/auth/tests/query_unittest.cc

@@ -30,7 +30,7 @@
 #include <dns/rrtype.h>
 #include <dns/rdataclass.h>
 
-#include <datasrc/memory_datasrc.h>
+#include <datasrc/client.h>
 #include <datasrc/client_list.h>
 
 #include <auth/query.h>
@@ -90,6 +90,10 @@ private:
 #include <auth/tests/example_base_inc.cc>
 #include <auth/tests/example_nsec3_inc.cc>
 
+// This SOA is used in negative responses; its RRTTL is set to SOA's MINTTL
+const char* const soa_minttl_txt =
+    "example.com. 0 IN SOA . . 1 0 0 0 0\n";
+
 // This is used only in one pathological test case.
 const char* const zone_ds_txt =
     "example.com. 3600 IN DS 57855 5 1 "
@@ -213,6 +217,13 @@ public:
             "t644ebqk9bibcna874givr6joj62mlhv";
         hash_map_[Name("www1.uwild.example.com")] =
             "q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
+
+        // For empty-non-terminal derived from insecure delegation (we don't
+        // need a hash for the delegation point itself for that test).  the
+        // hash for empty name is the same as that for unsigned-delegation
+        // above, as the case is similar to that.
+        hash_map_[Name("empty.example.com")] =
+            "q81r598950igr1eqvc60aedlq66425b5"; // a bit larger than H(www)
     }
     virtual string calculate(const Name& name) const {
         const NSEC3HashMap::const_iterator found = hash_map_.find(name);
@@ -258,8 +269,6 @@ public:
 // to child zones are identified by the existence of non origin NS records.
 // Another special name is "dname.example.com".  Query names under this name
 // will result in DNAME.
-// This mock zone doesn't handle empty non terminal nodes (if we need to test
-// such cases find() should have specialized code for it).
 class MockZoneFinder : public ZoneFinder {
 public:
     MockZoneFinder() :
@@ -822,6 +831,68 @@ createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
     }
 }
 
+class MockClient : public DataSourceClient {
+public:
+    virtual FindResult findZone(const isc::dns::Name& origin) const {
+        const Name r_origin(origin.reverse());
+        std::map<Name, ZoneFinderPtr>::const_iterator it =
+            zone_finders_.lower_bound(r_origin);
+
+        if (it != zone_finders_.end()) {
+            const NameComparisonResult result =
+                origin.compare((it->first).reverse());
+            if (result.getRelation() == NameComparisonResult::EQUAL) {
+                return (FindResult(result::SUCCESS, it->second));
+            } else if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
+                return (FindResult(result::PARTIALMATCH, it->second));
+            }
+        }
+
+        // If it is at the beginning of the map, then the name was not
+        // found (we have already handled the element the iterator
+        // points to).
+        if (it == zone_finders_.begin()) {
+            return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+        }
+
+        // Check if the previous element is a partial match.
+        --it;
+        const NameComparisonResult result =
+            origin.compare((it->first).reverse());
+        if (result.getRelation() == NameComparisonResult::SUBDOMAIN) {
+            return (FindResult(result::PARTIALMATCH, it->second));
+        }
+
+        return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
+    }
+
+    virtual ZoneUpdaterPtr getUpdater(const isc::dns::Name&, bool, bool) const {
+        isc_throw(isc::NotImplemented,
+                  "Updater isn't supported in the MockClient");
+    }
+
+    virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+    getJournalReader(const isc::dns::Name&, uint32_t, uint32_t) const {
+        isc_throw(isc::NotImplemented,
+                  "Journaling isn't supported in the MockClient");
+    }
+
+    result::Result addZone(ZoneFinderPtr finder) {
+        // Use the reverse of the name as the key, so we can quickly
+        // find partial matches in the map.
+        zone_finders_[finder->getOrigin().reverse()] = finder;
+        return (result::SUCCESS);
+    }
+
+private:
+    // Note that because we no longer have the old RBTree, and the new
+    // in-memory DomainTree is not useful as it returns const nodes, we
+    // use a std::map instead. In this map, the key is a name stored in
+    // reverse order of labels to aid in finding partial matches
+    // quickly.
+    std::map<Name, ZoneFinderPtr> zone_finders_;
+};
+
 class QueryTest : public ::testing::TestWithParam<DataSrcType> {
 protected:
     QueryTest() :
@@ -847,7 +918,7 @@ protected:
         response.setOpcode(Opcode::QUERY());
         // create and add a matching zone.
         mock_finder = new MockZoneFinder();
-        memory_client.addZone(ZoneFinderPtr(mock_finder));
+        mock_client.addZone(ZoneFinderPtr(mock_finder));
     }
 
     virtual void SetUp() {
@@ -861,7 +932,7 @@ protected:
         // doesn't happen for derived test class.  This also ensures the
         // data source clients are configured after setting NSEC3 hash in case
         // there's dependency.
-        list_ = createDataSrcClientList(GetParam(), memory_client);
+        list_ = createDataSrcClientList(GetParam(), mock_client);
     }
 
     virtual void TearDown() {
@@ -987,11 +1058,7 @@ private:
 
 protected:
     MockZoneFinder* mock_finder;
-    // We use InMemoryClient here. We could have some kind of mock client
-    // here, but historically, the Query supported only InMemoryClient
-    // (originally named MemoryDataSrc) and was tested with it, so we keep
-    // it like this for now.
-    InMemoryClient memory_client;
+    MockClient mock_client;
 
     boost::shared_ptr<ClientList> list_;
     const Name qname;
@@ -1031,7 +1098,7 @@ class QueryTestForMockOnly : public QueryTest {
 protected:
     // Override SetUp() to avoid parameterized setup
     virtual void SetUp() {
-        list_ = createDataSrcClientList(MOCK, memory_client);
+        list_ = createDataSrcClientList(MOCK, mock_client);
     }
 };
 
@@ -1075,8 +1142,8 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
 TEST_P(QueryTest, noZone) {
     // There's no zone in the memory datasource.  So the response should have
     // REFUSED.
-    InMemoryClient empty_memory_client;
-    SingletonList empty_list(empty_memory_client);
+    MockClient empty_mock_client;
+    SingletonList empty_list(empty_mock_client);
     EXPECT_NO_THROW(query.process(empty_list, qname, qtype,
                                   response));
     EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
@@ -1158,12 +1225,6 @@ TEST_P(QueryTest, apexNSMatch) {
 
 // test type any query logic
 TEST_P(QueryTest, exactAnyMatch) {
-    // This is an in-memory specific bug (#2585), until it's fixed we
-    // tentatively skip the test for in-memory
-    if (GetParam() == INMEMORY) {
-        return;
-    }
-
     // find match rrset, omit additional data which has already been provided
     // in the answer section from the additional.
     EXPECT_NO_THROW(query.process(*list_, Name("noglue.example.com"),
@@ -1207,7 +1268,7 @@ TEST_P(QueryTest, nodomainANY) {
     EXPECT_NO_THROW(query.process(*list_, Name("nxdomain.example.com"),
                                   RRType::ANY(), response));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
-                  NULL, soa_txt, NULL, mock_finder->getOrigin());
+                  NULL, soa_minttl_txt, NULL, mock_finder->getOrigin());
 }
 
 // This tests that when we need to look up Zone's apex NS records for
@@ -1345,7 +1406,7 @@ TEST_P(QueryTest, nxdomain) {
                                   Name("nxdomain.example.com"), qtype,
                                   response));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
-                  NULL, soa_txt, NULL, mock_finder->getOrigin());
+                  NULL, soa_minttl_txt, NULL, mock_finder->getOrigin());
 }
 
 TEST_P(QueryTest, nxdomainWithNSEC) {
@@ -1356,8 +1417,8 @@ TEST_P(QueryTest, nxdomainWithNSEC) {
                                   Name("nxdomain.example.com"), qtype,
                                   response, true));
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
-                  NULL, (string(soa_txt) +
-                         string("example.com. 3600 IN RRSIG ") +
+                  NULL, (string(soa_minttl_txt) +
+                         string("example.com. 0 IN RRSIG ") +
                          getCommonRRSIGText("SOA") + "\n" +
                          string(nsec_nxdomain_txt) + "\n" +
                          string("noglue.example.com. 3600 IN RRSIG ") +
@@ -1369,49 +1430,36 @@ TEST_P(QueryTest, nxdomainWithNSEC) {
 }
 
 TEST_P(QueryTest, nxdomainWithNSEC2) {
-    // there seems to be a bug in the SQLite3 (or database in general) data
-    // source and this doesn't work (Trac #2586).
-    if (GetParam() == SQLITE3) {
-        return;
-    }
-
     // See comments about no_txt.  In this case the best possible wildcard
     // is derived from the next domain of the NSEC that proves NXDOMAIN, and
     // the NSEC to provide the non existence of wildcard is different from
     // the first NSEC.
-    query.process(*list_, Name("(.no.example.com"), qtype, response,
+    query.process(*list_, Name("!.no.example.com"), qtype, response,
                   true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
-                  NULL, (string(soa_txt) +
-                         string("example.com. 3600 IN RRSIG ") +
+                  NULL, (string(soa_minttl_txt) +
+                         string("example.com. 0 IN RRSIG ") +
                          getCommonRRSIGText("SOA") + "\n" +
                          string(nsec_mx_txt) + "\n" +
                          string("mx.example.com. 3600 IN RRSIG ") +
                          getCommonRRSIGText("NSEC") + "\n" +
                          string(nsec_no_txt) + "\n" +
-                         string(").no.example.com. 3600 IN RRSIG ") +
+                         string("&.no.example.com. 3600 IN RRSIG ") +
                          getCommonRRSIGText("NSEC")).c_str(),
                   NULL, mock_finder->getOrigin());
 }
 
 TEST_P(QueryTest, nxdomainWithNSECDuplicate) {
-    // there seems to be a bug in the SQLite3 (or database in general) data
-    // source and this doesn't work.  This is probably the same type of bug
-    // as nxdomainWithNSEC2 (Trac #2586).
-    if (GetParam() == SQLITE3) {
-        return;
-    }
-
     // See comments about nz_txt.  In this case we only need one NSEC,
     // which proves both NXDOMAIN and the non existence of wildcard.
     query.process(*list_, Name("nx.no.example.com"), qtype, response,
                   true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
-                  NULL, (string(soa_txt) +
-                         string("example.com. 3600 IN RRSIG ") +
+                  NULL, (string(soa_minttl_txt) +
+                         string("example.com. 0 IN RRSIG ") +
                          getCommonRRSIGText("SOA") + "\n" +
                          string(nsec_no_txt) + "\n" +
-                         string(").no.example.com. 3600 IN RRSIG ") +
+                         string("&.no.example.com. 3600 IN RRSIG ") +
                          getCommonRRSIGText("NSEC")).c_str(),
                   NULL, mock_finder->getOrigin());
 }
@@ -1474,8 +1522,8 @@ TEST_F(QueryTestForMockOnly, nxdomainBadNSEC5) {
     query.process(*list_, Name("nxdomain.example.com"), qtype,
                   response, true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
-                  NULL, (string(soa_txt) +
-                         string("example.com. 3600 IN RRSIG ") +
+                  NULL, (string(soa_minttl_txt) +
+                         string("example.com. 0 IN RRSIG ") +
                          getCommonRRSIGText("SOA") + "\n" +
                          string(nsec_nxdomain_txt) + "\n" +
                          string("noglue.example.com. 3600 IN RRSIG ") +
@@ -1503,7 +1551,7 @@ TEST_P(QueryTest, nxrrset) {
                                   RRType::TXT(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
-                  NULL, soa_txt, NULL, mock_finder->getOrigin());
+                  NULL, soa_minttl_txt, NULL, mock_finder->getOrigin());
 }
 
 TEST_P(QueryTest, nxrrsetWithNSEC) {
@@ -1513,7 +1561,8 @@ TEST_P(QueryTest, nxrrsetWithNSEC) {
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec_www_txt) + "\n" +
                    string("www.example.com. 3600 IN RRSIG ") +
@@ -1524,7 +1573,7 @@ TEST_P(QueryTest, nxrrsetWithNSEC) {
 TEST_P(QueryTest, emptyNameWithNSEC) {
     // Empty non terminal with DNSSEC proof.  This is one of the cases of
     // Section 3.1.3.2 of RFC4035.
-    // mx.example.com. NSEC ).no.example.com. proves no.example.com. is a
+    // mx.example.com. NSEC &.no.example.com. proves no.example.com. is a
     // non empty terminal node.  Note that it also implicitly proves there
     // should be no closer wildcard match (because the empty name is an
     // exact match), so we only need one NSEC.
@@ -1534,7 +1583,8 @@ TEST_P(QueryTest, emptyNameWithNSEC) {
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec_mx_txt) + "\n" +
                    string("mx.example.com. 3600 IN RRSIG ") +
@@ -1550,7 +1600,8 @@ TEST_P(QueryTest, nxrrsetWithoutNSEC) {
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n").c_str(),
                   NULL, mock_finder->getOrigin());
 }
@@ -1693,12 +1744,6 @@ TEST_F(QueryTestForMockOnly, badWildcardProof3) {
 }
 
 TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
-    // This is an in-memory specific bug (#2585), until it's fixed we
-    // tentatively skip the test for in-memory
-    if (GetParam() == INMEMORY) {
-        return;
-    }
-
     // NXRRSET on WILDCARD with DNSSEC proof.  We should have SOA, NSEC that
     // proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
     // which proves both NXDOMAIN and the non existence RRSETs of wildcard.
@@ -1706,7 +1751,8 @@ TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec_wild_txt) +
                    string("*.wild.example.com. 3600 IN RRSIG ") +
@@ -1715,12 +1761,6 @@ TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
 }
 
 TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
-    // This is an in-memory specific bug (#2585), until it's fixed we
-    // tentatively skip the test for in-memory
-    if (GetParam() == INMEMORY) {
-        return;
-    }
-
     // WILDCARD + NXRRSET with DNSSEC proof.  We should have SOA, NSEC that
     // proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
     // one proves NXDOMAIN and the other proves non existence RRSETs of
@@ -1729,7 +1769,8 @@ TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
                   RRType::TXT(), response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec_wild_txt_nxrrset) +
                    string("*.uwild.example.com. 3600 IN RRSIG ") +
@@ -1753,7 +1794,8 @@ TEST_P(QueryTest, wildcardNxrrsetWithNSEC3) {
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
                   // SOA + its RRSIG
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    // NSEC3 for the closest encloser + its RRSIG
                    string(nsec3_uwild_txt) +
@@ -1816,7 +1858,8 @@ TEST_P(QueryTest, wildcardEmptyWithNSEC) {
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec_empty_prev_txt) +
                    string("t.example.com. 3600 IN RRSIG ") +
@@ -2043,7 +2086,7 @@ TEST_P(QueryTest, DNAME_NX_RRSET) {
                     RRType::TXT(), response));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
-        NULL, soa_txt, NULL, mock_finder->getOrigin());
+        NULL, soa_minttl_txt, NULL, mock_finder->getOrigin());
 }
 
 /*
@@ -2273,8 +2316,8 @@ TEST_F(QueryTestForMockOnly, dsAboveDelegation) {
     // simple addition.  For now we test it for mock only.
 
     // Pretending to have authority for the child zone, too.
-    memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
-                                            Name("delegation.example.com"))));
+    mock_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
+                                           Name("delegation.example.com"))));
 
     // The following will succeed only if the search goes to the parent
     // zone, not the child one we added above.
@@ -2296,8 +2339,8 @@ TEST_P(QueryTest, dsAboveDelegationNoData) {
     // Similar to the previous case, but the query is for an unsigned zone
     // (which doesn't have a DS at the parent).  The response should be a
     // "no data" error.  The query should still be handled at the parent.
-    memory_client.addZone(ZoneFinderPtr(
-                              new AlternateZoneFinder(
+    mock_client.addZone(ZoneFinderPtr(
+                             new AlternateZoneFinder(
                                   Name("unsigned-delegation.example.com"))));
 
     // The following will succeed only if the search goes to the parent
@@ -2307,8 +2350,8 @@ TEST_P(QueryTest, dsAboveDelegationNoData) {
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) +
-                   string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(unsigned_delegation_nsec_txt) +
                    "unsigned-delegation.example.com. 3600 IN RRSIG " +
@@ -2324,7 +2367,8 @@ TEST_P(QueryTest, dsBelowDelegation) {
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec_apex_txt) + "\n" +
                    string("example.com. 3600 IN RRSIG ") +
@@ -2342,7 +2386,8 @@ TEST_P(QueryTest, dsBelowDelegationWithDS) {
                                   RRType::DS(), response, true));
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA")).c_str(), NULL,
                   mock_finder->getOrigin());
 }
@@ -2379,12 +2424,13 @@ TEST_F(QueryTestForMockOnly, dsAtGrandParentAndChild) {
 
     // Pretending to have authority for the child zone, too.
     const Name childname("grand.delegation.example.com");
-    memory_client.addZone(ZoneFinderPtr(
-                              new AlternateZoneFinder(childname)));
+    mock_client.addZone(ZoneFinderPtr(
+                             new AlternateZoneFinder(childname)));
     query.process(*list_, childname, RRType::DS(), response, true);
+    // Note that RR TTL of SOA and its RRSIG are set to SOA MINTTL, 0
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
-                   childname.toText() + " 3600 IN RRSIG " +
+                  (childname.toText() + " 0 IN SOA . . 0 0 0 0 0\n" +
+                   childname.toText() + " 0 IN RRSIG " +
                    getCommonRRSIGText("SOA") + "\n" +
                    childname.toText() + " 3600 IN NSEC " +
                    childname.toText() + " SOA NSEC RRSIG\n" +
@@ -2400,13 +2446,14 @@ TEST_F(QueryTestForMockOnly, dsAtRoot) {
     // won't be simple addition.  For now we test it for mock only.
 
     // Pretend to be a root server.
-    memory_client.addZone(ZoneFinderPtr(
-                              new AlternateZoneFinder(Name::ROOT_NAME())));
+    mock_client.addZone(ZoneFinderPtr(
+                             new AlternateZoneFinder(Name::ROOT_NAME())));
     query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
                   true);
+    // Note that RR TTL of SOA and its RRSIG are set to SOA MINTTL, 0
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(". 3600 IN SOA . . 0 0 0 0 0\n") +
-                   ". 3600 IN RRSIG " + getCommonRRSIGText("SOA") + "\n" +
+                  (string(". 0 IN SOA . . 0 0 0 0 0\n") +
+                   ". 0 IN RRSIG " + getCommonRRSIGText("SOA") + "\n" +
                    ". 3600 IN NSEC " + ". SOA NSEC RRSIG\n" +
                    ". 3600 IN RRSIG " +
                    getCommonRRSIGText("NSEC")).c_str(), NULL);
@@ -2419,9 +2466,8 @@ TEST_F(QueryTestForMockOnly, dsAtRootWithDS) {
     // We could setup the additional zone for other data sources, but it
     // won't be simple addition.  For now we test it for mock only.
 
-    memory_client.addZone(ZoneFinderPtr(
-                              new AlternateZoneFinder(Name::ROOT_NAME(),
-                                                      true)));
+    mock_client.addZone(ZoneFinderPtr(
+                             new AlternateZoneFinder(Name::ROOT_NAME(), true)));
     query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
                   true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
@@ -2443,7 +2489,8 @@ TEST_P(QueryTest, nxrrsetWithNSEC3) {
                   response, true);
 
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec3_www_txt) + "\n" +
                    nsec3_hash_.calculate(Name("www.example.com.")) +
@@ -2452,21 +2499,32 @@ TEST_P(QueryTest, nxrrsetWithNSEC3) {
                   NULL, mock_finder->getOrigin());
 }
 
-// Check the exception is correctly raised when the NSEC3 thing isn't in the
-// zone
-TEST_F(QueryTestForMockOnly, nxrrsetMissingNSEC3) {
-    // This is a broken data source scenario; works only with mock.
-
-    mock_finder->setNSEC3Flag(true);
-    // We just need it to return false for "matched". This indicates
-    // there's no exact match for NSEC3 on www.example.com.
-    ZoneFinder::FindNSEC3Result nsec3(false, 0, ConstRRsetPtr(),
-                                      ConstRRsetPtr());
-    mock_finder->setNSEC3Result(&nsec3);
+TEST_P(QueryTest, nxrrsetDerivedFromOptOutNSEC3) {
+    // In this test we emulate the situation where an empty non-terminal name
+    // is derived from insecure delegation and covered by an opt-out NSEC3.
+    // In the actual test data the covering NSEC3 really has the opt-out
+    // bit set, although the implementation doesn't check it anyway.
+    enableNSEC3(rrsets_to_add_);
+    query.process(*list_, Name("empty.example.com"), RRType::TXT(), response,
+                  true);
 
-    EXPECT_THROW(query.process(*list_, Name("www.example.com"),
-                               RRType::TXT(), response, true),
-                 Query::BadNSEC3);
+    // The closest provable encloser is the origin name (example.com.), and
+    // the next closer is the empty name itself, which is expected to be
+    // covered by an opt-out NSEC3 RR.  The response should contain these 2
+    // NSEC3s.
+    responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
+                   getCommonRRSIGText("SOA") + "\n" +
+                   string(nsec3_apex_txt) + "\n" +
+                   nsec3_hash_.calculate(Name("example.com.")) +
+                   ".example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("NSEC3") + "\n" +
+                   string(nsec3_www_txt) + "\n" +
+                   nsec3_hash_.calculate(Name("www.example.com.")) +
+                   ".example.com. 3600 IN RRSIG " +
+                   getCommonRRSIGText("NSEC3") + "\n").c_str(),
+                  NULL, mock_finder->getOrigin());
 }
 
 TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
@@ -2478,7 +2536,8 @@ TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
     query.process(*list_, Name("unsigned-delegation.example.com."),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(unsigned_delegation_nsec3_txt) + "\n" +
                    nsec3_hash_.calculate(
@@ -2500,7 +2559,8 @@ TEST_P(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
     query.process(*list_, Name("unsigned-delegation-optout.example.com."),
                   RRType::DS(), response, true);
     responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
-                  (string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    string(nsec3_apex_txt) + "\n" +
                    nsec3_hash_.calculate(Name("example.com.")) +
@@ -2528,8 +2588,8 @@ TEST_P(QueryTest, nxdomainWithNSEC3Proof) {
                   response, true);
     responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
                   // SOA + its RRSIG
-                  (string(soa_txt) +
-                   string("example.com. 3600 IN RRSIG ") +
+                  (string(soa_minttl_txt) +
+                   string("example.com. 0 IN RRSIG ") +
                    getCommonRRSIGText("SOA") + "\n" +
                    // NSEC3 for the closest encloser + its RRSIG
                    string(nsec3_apex_txt) + "\n" +

+ 0 - 123
src/bin/auth/tests/statistics_unittest.cc

@@ -1,123 +0,0 @@
-// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-
-#include <string>
-
-#include <gtest/gtest.h>
-
-#include <boost/bind.hpp>
-
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-
-#include <cc/data.h>
-#include <cc/session.h>
-
-#include <auth/statistics.h>
-#include <auth/statistics_items.h>
-
-#include <dns/tests/unittest_util.h>
-
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-
-using namespace std;
-using namespace isc::cc;
-using namespace isc::dns;
-using namespace isc::data;
-using isc::auth::statistics::Counters;
-using isc::auth::statistics::QRAttributes;
-
-namespace {
-
-class CountersTest : public ::testing::Test {
-protected:
-    CountersTest() : counters() {}
-    ~CountersTest() {}
-    Counters counters;
-};
-
-// Test if the values of the counters are all zero except for the items
-// specified in except_for.
-void
-checkCountersAllZeroExcept(const isc::data::ConstElementPtr counters,
-                           const std::set<std::string>& except_for) {
-    std::map<std::string, ConstElementPtr> stats_map = counters->mapValue();
-
-    for (std::map<std::string, ConstElementPtr>::const_iterator
-            i = stats_map.begin(), e = stats_map.end();
-            i != e;
-            ++i)
-    {
-        int expect = 0;
-        if (except_for.count(i->first) != 0) {
-            expect = 1;
-        }
-        EXPECT_EQ(expect, i->second->intValue()) << "Expected counter "
-            << i->first << " = " << expect << ", actual: "
-            << i->second->intValue();
-    }
-}
-
-TEST_F(CountersTest, incrementNormalQuery) {
-    Message response(Message::RENDER);
-    QRAttributes qrattrs;
-    std::set<std::string> expect_nonzero;
-
-    expect_nonzero.clear();
-    checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero);
-
-    qrattrs.setQueryIPVersion(AF_INET6);
-    qrattrs.setQueryTransportProtocol(IPPROTO_UDP);
-    qrattrs.setQueryOpCode(Opcode::QUERY_CODE);
-    qrattrs.setQueryEDNS(true, false);
-    qrattrs.setQueryDO(true);
-    qrattrs.answerWasSent();
-
-    response.setRcode(Rcode::REFUSED());
-    response.addQuestion(Question(Name("example.com"),
-                                  RRClass::IN(), RRType::AAAA()));
-
-    counters.inc(qrattrs, response);
-
-    expect_nonzero.clear();
-    expect_nonzero.insert("opcode.query");
-    expect_nonzero.insert("queries.udp");
-    expect_nonzero.insert("rcode.refused");
-    checkCountersAllZeroExcept(counters.getStatistics(), expect_nonzero);
-}
-
-int
-countTreeElements(const struct CounterTypeTree* tree) {
-    int count = 0;
-    for (int i = 0; tree[i].name != NULL; ++i) {
-        if (tree[i].sub_tree == NULL) {
-            ++count;
-        } else {
-            count += countTreeElements(tree[i].sub_tree);
-        }
-    }
-    return count;
-}
-
-TEST(StatisticsItemsTest, QRItemNamesCheck) {
-    EXPECT_EQ(QR_COUNTER_TYPES, countTreeElements(QRCounterTree));
-}
-
-}

+ 714 - 0
src/bin/auth/tests/statistics_unittest.cc.pre

@@ -0,0 +1,714 @@
+// Copyright (C) 2010  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+#include <dns/rrttl.h>
+
+#include <cc/data.h>
+
+#include <auth/statistics.h>
+#include <auth/statistics_items.h>
+
+#include <dns/tests/unittest_util.h>
+
+#include "statistics_util.h"
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::auth::statistics;
+using namespace isc::auth::unittest;
+
+namespace {
+
+// ### STATISTICS ITEMS DEFINITION ###
+
+class CountersTest : public ::testing::Test {
+protected:
+    CountersTest() : counters() {}
+    ~CountersTest() {}
+    Counters counters;
+};
+
+void
+buildSkeletonMessage(MessageAttributes& msgattrs) {
+    msgattrs.setRequestIPVersion(AF_INET);
+    msgattrs.setRequestTransportProtocol(IPPROTO_UDP);
+    msgattrs.setRequestOpCode(Opcode::QUERY());
+    msgattrs.setRequestEDNS0(true);
+    msgattrs.setRequestDO(true);
+}
+
+TEST_F(CountersTest, invalidParameterForSetRequestIPVersion) {
+    MessageAttributes msgattrs;
+
+    // It should not throw if the parameter is AF_INET or AF_INET6.
+    EXPECT_NO_THROW(msgattrs.setRequestIPVersion(AF_INET));
+    EXPECT_NO_THROW(msgattrs.setRequestIPVersion(AF_INET6));
+
+    // It should throw isc::InvalidParameter if the parameter is not AF_INET
+    // nor AF_INET6.
+    EXPECT_THROW(msgattrs.setRequestIPVersion(AF_UNIX), isc::InvalidParameter);
+}
+
+TEST_F(CountersTest, invalidParameterForSetRequestTransportProtocol) {
+    MessageAttributes msgattrs;
+
+    // It should not throw if the parameter is IPPROTO_UDP or IPPROTO_TCP.
+    EXPECT_NO_THROW(msgattrs.setRequestTransportProtocol(IPPROTO_UDP));
+    EXPECT_NO_THROW(msgattrs.setRequestTransportProtocol(IPPROTO_TCP));
+
+    // It should throw isc::InvalidParameter if the parameter is not
+    // IPPROTO_UDP nor IPPROTO_TCP.
+    EXPECT_THROW(msgattrs.setRequestTransportProtocol(IPPROTO_IP),
+                 isc::InvalidParameter);
+}
+
+TEST_F(CountersTest, invalidOperationForGetRequestOpCode) {
+    MessageAttributes msgattrs;
+
+    // getRequestOpCode() should return boost::none when called before
+    // opcode is set with setRequestOpCode().
+    EXPECT_FALSE(msgattrs.getRequestOpCode());
+
+    msgattrs.setRequestOpCode(Opcode::QUERY());
+    // getRequestOpCode() should be Opcode::QUERY.
+    EXPECT_EQ(Opcode::QUERY(), msgattrs.getRequestOpCode().get());
+}
+
+TEST_F(CountersTest, invalidParameterForSetRequestTSIG) {
+    MessageAttributes msgattrs;
+
+    // These patterns should not throw:
+    //      request signature  badsig
+    //     --------------------------
+    //      (none)             false
+    //      TSIG               false
+    //      TSIG               true
+    EXPECT_NO_THROW(msgattrs.setRequestTSIG(false, false));
+    EXPECT_NO_THROW(msgattrs.setRequestTSIG(true, false));
+    EXPECT_NO_THROW(msgattrs.setRequestTSIG(true, true));
+
+    // It should throw isc::InvalidParameter if a message is not signed but
+    // badsig is true
+    EXPECT_THROW(msgattrs.setRequestTSIG(false, true), isc::InvalidParameter);
+}
+
+TEST_F(CountersTest, incrementResponse) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test response counters are incremented only if responded == true.
+    for (int i = 0; i < 2; ++i) {
+        const bool responded = i & 1;
+
+        buildSkeletonMessage(msgattrs);
+
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        counters.inc(msgattrs, response, responded);
+
+        expect.clear();
+        expect["opcode.query"] = i+1;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = i+1;
+        expect["request.badednsver"] = 0;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = responded ? 1 : 0;
+        expect["qrynoauthans"] = responded ? 1 : 0;
+        expect["rcode.refused"] = responded ? 1 : 0;
+        expect["authqryrej"] = responded ? 1 : 0;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementProtocolType) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test these patterns:
+    //      af     proto
+    //     -----------------
+    //      ipv6   tcp
+    //      ipv4   tcp
+    //      ipv6   udp
+    //      ipv4   udp
+    int count_v4 = 0, count_v6 = 0, count_udp = 0, count_tcp = 0;
+    for (int i = 0; i < 4; ++i) {
+        const int af = i & 1 ? AF_INET : AF_INET6;
+        const int proto = i & 2 ? IPPROTO_UDP : IPPROTO_TCP;
+
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestIPVersion(af);
+        msgattrs.setRequestTransportProtocol(proto);
+
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        counters.inc(msgattrs, response, true);
+
+        if (af == AF_INET) {
+            ++count_v4;
+        } else {
+            ++count_v6;
+        }
+        if (proto == IPPROTO_UDP) {
+            ++count_udp;
+        } else {
+            ++count_tcp;
+        }
+
+        expect.clear();
+        expect["opcode.query"] = i+1;
+        expect["request.v4"] = count_v4;
+        expect["request.v6"] = count_v6;
+        expect["request.udp"] = count_udp;
+        expect["request.tcp"] = count_tcp;
+        expect["request.edns0"] = i+1;
+        expect["request.badednsver"] = 0;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = i+1;
+        expect["qrynoauthans"] = i+1;
+        expect["rcode.refused"] = i+1;
+        expect["authqryrej"] = i+1;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementDO) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test these patterns:
+    //     DNSSEC OK
+    //    -----------
+    //     false
+    //     true
+    for (int i = 0; i < 2; ++i) {
+        const bool is_dnssec_ok = i & 1;
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestDO(is_dnssec_ok);
+
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        counters.inc(msgattrs, response, true);
+
+        expect.clear();
+        expect["opcode.query"] = i+1;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = i+1;
+        expect["request.badednsver"] = 0;
+        expect["request.dnssec_ok"] = i & 1;
+        expect["responses"] = i+1;
+        expect["qrynoauthans"] = i+1;
+        expect["rcode.refused"] = i+1;
+        expect["authqryrej"] = i+1;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementEDNS) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test these patterns:
+    //     request edns0   response edns0
+    //    --------------------------------
+    //     false           true
+    //     true            false
+    //
+    // They can't be both true since edns0 and badednsver are exclusive.
+    int count_req_edns0 = 0, count_res_edns0 = 0;
+    for (int i = 0; i < 2; ++i) {
+        const bool is_edns0 = i & 1;
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestEDNS0(is_edns0);
+
+        if (!is_edns0) {
+            ConstEDNSPtr edns = EDNSPtr(new EDNS(0));
+            response.setEDNS(edns);
+        } else {
+            response.setEDNS(EDNSPtr());
+        }
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        counters.inc(msgattrs, response, true);
+
+        if (is_edns0) {
+            ++count_req_edns0;
+        } else {
+            ++count_res_edns0;
+        }
+
+        expect.clear();
+        expect["opcode.query"] = i+1;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = count_req_edns0;
+        expect["response.edns0"] = count_res_edns0;
+        expect["request.badednsver"] = 0;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = i+1;
+        expect["qrynoauthans"] = i+1;
+        expect["rcode.refused"] = i+1;
+        expect["authqryrej"] = i+1;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementTSIG) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test these patterns:
+    //      request signature  badsig   response signature
+    //     -----------------------------------------------
+    //      (none)             false    (none)
+    //      TSIG               false    TSIG
+    //      TSIG               true     (none)
+    //
+    // badsig can't be true if the message does not have signature.
+    int count_req_tsig = 0, count_res_tsig = 0, count_badsig = 0;
+    for (int i = 0; i < 3; ++i) {
+        const bool is_req_tsig = (i == 2) ? true : (i & 1) != 0;
+        const bool is_res_tsig = (i & 1) != 0;
+        const bool is_badsig = (i & 2) != 0;
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestTSIG(is_req_tsig, is_badsig);
+        msgattrs.setResponseTSIG(is_res_tsig);
+
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        // don't increment response counters if signature is bad
+        counters.inc(msgattrs, response, !is_badsig);
+
+        if (is_req_tsig) {
+            ++count_req_tsig;
+        }
+        if (is_res_tsig) {
+            ++count_res_tsig;
+        }
+        if (is_badsig) {
+            ++count_badsig;
+        }
+
+        expect.clear();
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["opcode.query"] = i+1;
+        expect["request.edns0"] = i+1 - count_badsig;
+        expect["request.badednsver"] = 0;
+        expect["request.dnssec_ok"] = i+1 - count_badsig;
+        expect["request.tsig"] = count_req_tsig;
+        expect["response.tsig"] = count_res_tsig;
+        expect["request.sig0"] = 0;
+        expect["request.badsig"] = count_badsig;
+        expect["responses"] = i+1 - count_badsig;
+        expect["qrynoauthans"] = i+1 - count_badsig;
+        expect["rcode.refused"] = i+1 - count_badsig;
+        expect["authqryrej"] = i+1 - count_badsig;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementOpcode) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test all opcodes (QUERY..RESERVED15)
+    int count_all = 0, count_opcode_other = 0;
+    for (uint8_t i = Opcode::QUERY().getCode(),
+                 e = Opcode::RESERVED15().getCode();
+         i <= e;
+         ++i)
+    {
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestOpCode(Opcode(i));
+        msgattrs.setRequestTSIG(false, false);
+
+        response.setRcode(Rcode::REFUSED());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        for (uint8_t j = 0; j < i; ++j) {
+            // count up i times for i-th opcode to identify counters
+            counters.inc(msgattrs, response, true);
+            ++count_all;
+        }
+
+        expect.clear();
+        expect["request.v4"] = count_all;
+        expect["request.udp"] = count_all;
+        expect["request.edns0"] = count_all;
+        expect["request.badednsver"] = 0;
+        expect["request.dnssec_ok"] = count_all;
+        expect["request.tsig"] = 0;
+        expect["request.sig0"] = 0;
+        expect["request.badsig"] = 0;
+        expect["responses"] = count_all;
+        expect["rcode.refused"] = count_all;
+        if (opcode_to_msgcounter[i] == MSG_OPCODE_OTHER) {
+            count_opcode_other += i;
+        }
+        for (uint8_t j = 0; j <= i; ++j) {
+            if (opcode_to_msgcounter[j] == MSG_OPCODE_OTHER) {
+                expect["opcode.other"] = count_opcode_other;
+            } else {
+                std::string code_text = Opcode(j).toText();
+                std::transform(code_text.begin(), code_text.end(),
+                               code_text.begin(), ::tolower);
+                expect["opcode."+code_text] = j;
+            }
+        }
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementRcode) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test all rcodes (NOERROR..BADVERS)
+    int count_all = 0, count_rcode_other = 0, count_ednsbadver = 0;
+    for (uint16_t i = Rcode::NOERROR().getCode(),
+                  e = Rcode::BADVERS().getCode();
+         i <= e;
+         ++i)
+    {
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestOpCode(Opcode::IQUERY());
+        msgattrs.setRequestTSIG(false, false);
+
+        response.setRcode(Rcode(i));
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::AAAA()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        for (uint16_t j = 0; j < i; ++j) {
+            // count up i times for i-th rcode to identify counters
+            counters.inc(msgattrs, response, true);
+            ++count_all;
+        }
+
+        expect.clear();
+        expect["opcode.iquery"] = count_all;
+        expect["request.v4"] = count_all;
+        expect["request.udp"] = count_all;
+        expect["request.edns0"] = count_all;
+        expect["request.dnssec_ok"] = count_all;
+        expect["request.tsig"] = 0;
+        expect["request.sig0"] = 0;
+        expect["request.badsig"] = 0;
+        expect["responses"] = count_all;
+        if (rcode_to_msgcounter[i] == MSG_RCODE_OTHER) {
+            count_rcode_other += i;
+        }
+        // "request.badednsver" counts for Rcode == BADVERS
+        if (rcode_to_msgcounter[i] == MSG_RCODE_BADVERS) {
+            count_ednsbadver += i;
+        }
+        expect["request.badednsver"] = count_ednsbadver;
+        for (uint16_t j = 0; j <= i; ++j) {
+            if (rcode_to_msgcounter[j] == MSG_RCODE_OTHER) {
+                expect["rcode.other"] = count_rcode_other;
+            } else {
+                std::string code_text = Rcode(j).toText();
+                std::transform(code_text.begin(), code_text.end(),
+                               code_text.begin(), ::tolower);
+                expect["rcode."+code_text] = j;
+            }
+        }
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementTruncated) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Test these patterns:
+    //      truncated
+    //     -----------
+    //      false
+    //      true
+    int count_truncated = 0;
+    for (int i = 0; i < 2; ++i) {
+        const bool is_truncated = i & 1;
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestOpCode(Opcode::IQUERY());
+        msgattrs.setRequestTSIG(false, false);
+        msgattrs.setResponseTruncated(is_truncated);
+
+        response.setRcode(Rcode::SERVFAIL());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::TXT()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+        counters.inc(msgattrs, response, true);
+
+        if (is_truncated) {
+            ++count_truncated;
+        }
+
+        expect.clear();
+        expect["opcode.iquery"] = i+1;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = i+1;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = i+1;
+        expect["rcode.servfail"] = i+1;
+        expect["response.truncated"] = count_truncated;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementQryAuthAnsAndNoAuthAns) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Opcode = QUERY, ANCOUNT = 0 (don't care), Rcode = SERVFAIL (don't care)
+    // Test these patterns:
+    //      AA flag
+    //     -----------------------
+    //      false -> QryNoAuthAns
+    //      true  -> QryAuthAns
+    int count_authans = 0, count_noauthans = 0;
+    for (int i = 0; i < 2; ++i) {
+        const bool is_aa_set = i & 1;
+        buildSkeletonMessage(msgattrs);
+        msgattrs.setRequestTSIG(false, false);
+
+        response.setRcode(Rcode::SERVFAIL());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::TXT()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+        if (is_aa_set) {
+            response.setHeaderFlag(Message::HEADERFLAG_AA);
+            ++count_authans;
+        } else {
+            ++count_noauthans;
+        }
+
+        counters.inc(msgattrs, response, true);
+
+        expect.clear();
+        expect["opcode.query"] = i+1;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = i+1;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = i+1;
+        expect["rcode.servfail"] = i+1;
+        expect["qryauthans"] = count_authans;
+        expect["qrynoauthans"] = count_noauthans;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementQrySuccess) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Opcode = QUERY, Rcode = NOERROR, ANCOUNT > 0
+    msgattrs.setRequestIPVersion(AF_INET);
+    msgattrs.setRequestTransportProtocol(IPPROTO_UDP);
+    msgattrs.setRequestOpCode(Opcode::QUERY());
+    msgattrs.setRequestEDNS0(true);
+    msgattrs.setRequestDO(true);
+    msgattrs.setRequestTSIG(false, false);
+
+    response.setRcode(Rcode::NOERROR());
+    response.addQuestion(Question(Name("example.com"),
+                                  RRClass::IN(), RRType::TXT()));
+    RRsetPtr answer_rrset(new RRset(Name("example.com"),
+                                    RRClass::IN(), RRType::TXT(),
+                                    RRTTL(3600)));
+    answer_rrset->addRdata(rdata::createRdata(RRType::TXT(),
+                                              RRClass::IN(),
+                                              "Answer"));
+    response.addRRset(Message::SECTION_ANSWER, answer_rrset);
+    response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+    counters.inc(msgattrs, response, true);
+
+    expect.clear();
+    expect["opcode.query"] = 1;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["request.edns0"] = 1;
+    expect["request.dnssec_ok"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.noerror"] = 1;
+    expect["qrysuccess"] = 1;
+    // noauthans is also incremented
+    expect["qrynoauthans"] = 1;
+    checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                            expect);
+}
+
+TEST_F(CountersTest, incrementQryReferralAndNxrrset) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Opcode = QUERY, Rcode = NOERROR, ANCOUNT = 0
+    // Test these patterns:
+    //      AA flag
+    //     ----------------------
+    //      false -> QryReferral
+    //      true  -> QryNxrrset
+    int count_referral = 0, count_nxrrset = 0;
+    for (int i = 0; i < 2; ++i) {
+        const bool is_aa_set = i & 1;
+        msgattrs.setRequestIPVersion(AF_INET);
+        msgattrs.setRequestTransportProtocol(IPPROTO_UDP);
+        msgattrs.setRequestOpCode(Opcode::QUERY());
+        msgattrs.setRequestEDNS0(true);
+        msgattrs.setRequestDO(true);
+        msgattrs.setRequestTSIG(false, false);
+
+        response.setRcode(Rcode::NOERROR());
+        response.addQuestion(Question(Name("example.com"),
+                                      RRClass::IN(), RRType::TXT()));
+        response.setHeaderFlag(Message::HEADERFLAG_QR);
+        if (is_aa_set) {
+            response.setHeaderFlag(Message::HEADERFLAG_AA);
+            ++count_nxrrset;
+        } else {
+            ++count_referral;
+        }
+
+        counters.inc(msgattrs, response, true);
+
+        expect.clear();
+        expect["opcode.query"] = i+1;
+        expect["request.v4"] = i+1;
+        expect["request.udp"] = i+1;
+        expect["request.edns0"] = i+1;
+        expect["request.dnssec_ok"] = i+1;
+        expect["responses"] = i+1;
+        expect["rcode.noerror"] = i+1;
+        expect["qrynxrrset"] = count_nxrrset;
+        expect["qryreferral"] = count_referral;
+        // qryauthans or qrynoauthans is also incremented
+        expect["qryauthans"] = count_nxrrset;
+        expect["qrynoauthans"] = count_referral;
+        checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                                expect);
+    }
+}
+
+TEST_F(CountersTest, incrementAuthQryRej) {
+    Message response(Message::RENDER);
+    MessageAttributes msgattrs;
+    std::map<std::string, int> expect;
+
+    // Opcode = QUERY, Rcode = REFUSED, ANCOUNT = 0 (don't care)
+    msgattrs.setRequestIPVersion(AF_INET);
+    msgattrs.setRequestTransportProtocol(IPPROTO_UDP);
+    msgattrs.setRequestOpCode(Opcode::QUERY());
+    msgattrs.setRequestEDNS0(true);
+    msgattrs.setRequestDO(true);
+    msgattrs.setRequestTSIG(false, false);
+
+    response.setRcode(Rcode::REFUSED());
+    response.addQuestion(Question(Name("example.com"),
+                                  RRClass::IN(), RRType::TXT()));
+    response.setHeaderFlag(Message::HEADERFLAG_QR);
+
+    counters.inc(msgattrs, response, true);
+
+    expect.clear();
+    expect["opcode.query"] = 1;
+    expect["request.v4"] = 1;
+    expect["request.udp"] = 1;
+    expect["request.edns0"] = 1;
+    expect["request.dnssec_ok"] = 1;
+    expect["responses"] = 1;
+    expect["rcode.refused"] = 1;
+    expect["authqryrej"] = 1;
+    // noauthans is also incremented since AA bit is not set
+    expect["qrynoauthans"] = 1;
+    checkStatisticsCounters(counters.get()->get("zones")->get("_SERVER_"),
+                            expect);
+}
+
+int
+countTreeElements(const struct CounterSpec* tree) {
+    int count = 0;
+    for (int i = 0; tree[i].name != NULL; ++i) {
+        if (tree[i].sub_counters == NULL) {
+            ++count;
+        } else {
+            count += countTreeElements(tree[i].sub_counters);
+        }
+    }
+    return (count);
+}
+
+TEST(StatisticsItemsTest, MSGItemNamesCheck) {
+    EXPECT_EQ(MSG_COUNTER_TYPES, countTreeElements(msg_counter_tree));
+}
+
+}

+ 75 - 0
src/bin/auth/tests/statistics_util.cc

@@ -0,0 +1,75 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "statistics_util.h"
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <map>
+
+namespace {
+void
+flatten(std::map<std::string, int>& flat_map, const std::string& prefix,
+        const isc::data::ConstElementPtr map_element)
+{
+    std::map<std::string, isc::data::ConstElementPtr> map =
+        map_element->mapValue();
+    for (std::map<std::string, isc::data::ConstElementPtr>::const_iterator
+             i = map.begin(), e = map.end();
+         i != e;
+         ++i)
+    {
+        switch (i->second->getType()) {
+            case isc::data::Element::map:
+                flatten(flat_map, i->first + ".", i->second);
+                break;
+            case isc::data::Element::integer:
+                flat_map[prefix + i->first] = i->second->intValue();
+                break;
+            default:
+                FAIL() << "Element Parse Error";
+        }
+    }
+}
+}
+
+namespace isc {
+namespace auth {
+namespace unittest {
+
+void
+checkStatisticsCounters(const isc::data::ConstElementPtr counters,
+                        const std::map<std::string, int>& expect)
+{
+    std::map<std::string, int> stats_map;
+    flatten(stats_map, "", counters);
+
+    for (std::map<std::string, int>::const_iterator
+            i = stats_map.begin(), e = stats_map.end();
+            i != e;
+            ++i)
+    {
+        const int value =
+            expect.find(i->first) == expect.end() ?
+                0 : expect.find(i->first)->second;
+        EXPECT_EQ(value, i->second) << "Expected counter "
+            << i->first << " = " << value << ", actual: "
+            << i->second;
+    }
+}
+
+} // end of unittest
+} // end of auth
+} // end of isc

+ 38 - 0
src/bin/auth/tests/statistics_util.h

@@ -0,0 +1,38 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef __AUTH_STATISTICS_UTIL_H
+#define __AUTH_STATISTICS_UTIL_H 1
+
+#include <cc/data.h>
+
+namespace isc {
+namespace auth {
+namespace unittest {
+
+// Test if the counters has expected values specified in expect and the others
+// are zero.
+void
+checkStatisticsCounters(const isc::data::ConstElementPtr counters,
+                        const std::map<std::string, int>& expect);
+
+} // end of unittest
+} // end of auth
+} // end of isc
+
+#endif  // __AUTH_STATISTICS_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:

+ 0 - 1
src/bin/auth/tests/testdata/Makefile.am

@@ -23,7 +23,6 @@ EXTRA_DIST += simpleresponse_fromWire.spec
 EXTRA_DIST += spec.spec
 
 EXTRA_DIST += example.com
-EXTRA_DIST += example.zone
 EXTRA_DIST += example.sqlite3
 
 EXTRA_DIST += example-base-inc.zone example-nsec3-inc.zone

+ 12 - 7
src/bin/auth/tests/testdata/example-base-inc.zone

@@ -150,32 +150,32 @@ t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 e
 ;; the best possible wildcard is below the "next domain" of the NSEC RR that
 ;; proves the NXDOMAIN, i.e.,
 ;; mx.example.com. (exist)
-;; (.no.example.com. (qname, NXDOMAIN)
-;; ).no.example.com. (exist)
+;; !.no.example.com. (qname, NXDOMAIN)
+;; &.no.example.com. (exist)
 ;; *.no.example.com. (best possible wildcard, not exist)
 ;var=no_txt
-\).no.example.com. 3600 IN AAAA 2001:db8::53
+&.no.example.com. 3600 IN AAAA 2001:db8::53
 ;; NSEC records.
 ;var=nsec_apex_txt
 example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
 ;var=
 example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
 ;var=nsec_mx_txt
-mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
+mx.example.com. 3600 IN NSEC &.no.example.com. MX NSEC RRSIG
 
 ;var=
 mx.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
 
 ;var=nsec_no_txt
-\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
+&.no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
 
 ;var=
-\).no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+&.no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
 
 ;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
 ;; non existence of wildcard.  The following records will be used for that
 ;; test.
-;; ).no.example.com. (exist, whose NSEC proves everything)
+;; &.no.example.com. (exist, whose NSEC proves everything)
 ;; *.no.example.com. (best possible wildcard, not exist)
 ;; nx.no.example.com. (NXDOMAIN)
 ;; nz.no.example.com. (exist)
@@ -234,3 +234,8 @@ bad-delegation.example.com. 3600 IN NS ns.example.net.
 ;; or NSEC3 that proves it.
 ;var=nosec_delegation_txt
 nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
+
+;; Setup for emulating insecure delegation that contain an empty name.
+;; the delegation itself isn't expected to be used directly in tests.
+;var=
+delegation.empty.example.com. 3600 IN NS ns.delegation.empty.example

+ 1 - 1
src/bin/auth/tests/testdata/example-nsec3-inc.zone

@@ -1,4 +1,4 @@
-;; See query_testzone_data.txt for general notes.
+;; See example-base-inc.zone for general notes.
 
 ;; NSEC3PARAM.  This is needed for database-based data source to
 ;; signal the zone is NSEC3-signed

+ 4 - 2
src/bin/bind10/.gitignore

@@ -1,4 +1,6 @@
-/bind10
-/bind10_src.py
 /run_bind10.sh
+/bind10
 /bind10.8
+/b10-init
+/b10-init.8
+/init.py

+ 18 - 14
src/bin/bind10/Makefile.am

@@ -1,29 +1,33 @@
 SUBDIRS = . tests
 
 sbin_SCRIPTS = bind10
-CLEANFILES = bind10 bind10_src.pyc
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/bind10_messages.py
-CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/bind10_messages.pyc
+pkglibexec_SCRIPTS = b10-init
+CLEANFILES = b10-init b10-init.pyc init.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.pyc
 
 pkglibexecdir = $(libexecdir)/@PACKAGE@
 
-nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/bind10_messages.py
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py
 pylogmessagedir = $(pyexecdir)/isc/log_messages/
 
 noinst_SCRIPTS = run_bind10.sh
 
 bind10dir = $(pkgdatadir)
-bind10_DATA = bob.spec
-EXTRA_DIST = bob.spec
+bind10_DATA = init.spec
+EXTRA_DIST = init.spec bind10.in
 
-man_MANS = bind10.8
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST += $(man_MANS) bind10.xml bind10_messages.mes
+man_MANS = b10-init.8 bind10.8
+DISTCLEANFILES = $(man_MANS) bind10
+EXTRA_DIST += $(man_MANS) b10-init.xml bind10.xml init_messages.mes
 
 if GENERATE_DOCS
 
 bind10.8: bind10.xml
-	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/bind10.xml 
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/bind10.xml
+
+b10-init.8: b10-init.xml
+	@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-init.xml
 
 #dist-local-check-mans-enabled:
 #	@if grep "Man generation disabled" $(man_MANS) >/dev/null; then $(RM) $(man_MANS); fi
@@ -40,15 +44,15 @@ $(man_MANS):
 
 endif
 
-$(PYTHON_LOGMSGPKG_DIR)/work/bind10_messages.py : bind10_messages.mes
+$(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py : init_messages.mes
 	$(top_builddir)/src/lib/log/compiler/message \
-	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/bind10_messages.mes
+	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/init_messages.mes
 
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
-bind10: bind10_src.py $(PYTHON_LOGMSGPKG_DIR)/work/bind10_messages.py
+b10-init: init.py $(PYTHON_LOGMSGPKG_DIR)/work/init_messages.py
 	$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
 	       -e "s|@@LIBDIR@@|$(libdir)|" \
-	       -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bind10_src.py >$@
+	       -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" init.py >$@
 	chmod a+x $@
 
 pytest:

+ 3 - 2
src/bin/bind10/README

@@ -1,11 +1,12 @@
-This directory contains the source for the "Boss of Bind" program.
+This directory contains the source for the "b10-init" program, as well as
+the "bind10" script that runs it.
 
 Files:
   Makefile.am      - build information
   README           - this file
   TODO             - remaining development tasks for this program
   bind10.py.in     - used to make bind10.py with proper Python paths
-  bob.spec         - defines the options and commands
+  init.spec        - defines the options and commands
   run_bind10.sh.in - use to make run_bind10.sh with proper Python paths
 
 The "tests" directory contains unit tests for the application.

+ 519 - 0
src/bin/bind10/b10-init.xml

@@ -0,0 +1,519 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+               "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+	       [<!ENTITY mdash "&#8212;">]>
+<!--
+ - Copyright (C) 2010-2012  Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+  <refentryinfo>
+    <date>February 5, 2013</date>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>b10-init</refentrytitle>
+    <manvolnum>8</manvolnum>
+    <refmiscinfo>BIND10</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>b10-init</refname>
+    <refpurpose>BIND 10 Init process</refpurpose>
+  </refnamediv>
+
+  <docinfo>
+    <copyright>
+      <year>2010-2013</year>
+      <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+    </copyright>
+  </docinfo>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>b10-init</command>
+      <arg><option>-c <replaceable>config-filename</replaceable></option></arg>
+      <arg><option>-i</option></arg>
+      <arg><option>-m <replaceable>file</replaceable></option></arg>
+      <arg><option>-p <replaceable>data_path</replaceable></option></arg>
+      <arg><option>-u <replaceable>user</replaceable></option></arg>
+      <arg><option>-v</option></arg>
+      <arg><option>-w <replaceable>wait_time</replaceable></option></arg>
+      <arg><option>--clear-config</option></arg>
+      <arg><option>--cmdctl-port</option> <replaceable>port</replaceable></arg>
+      <arg><option>--config-file</option> <replaceable>config-filename</replaceable></arg>
+      <arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
+      <arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
+      <arg><option>--no-kill</option></arg>
+      <arg><option>--pid-file</option> <replaceable>filename</replaceable></arg>
+      <arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
+      <arg><option>--user <replaceable>user</replaceable></option></arg>
+      <arg><option>--verbose</option></arg>
+      <arg><option>--wait <replaceable>wait_time</replaceable></option></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>DESCRIPTION</title>
+
+    <para>The <command>b10-init</command> daemon starts up other
+    BIND 10 required daemons.  It handles restarting of exiting
+    programs and also the shutdown of all managed daemons.</para>
+
+<!-- TODO: list what it starts here? -->
+
+<!-- TODO
+    <para>The configuration of the <command>b10-init</command> daemon
+    is defined in the TODO configuration file, as described in the
+    <citerefentry><refentrytitle>TODO</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+    manual page.</para>
+-->
+
+  </refsect1>
+
+  <refsect1>
+    <title>ARGUMENTS</title>
+
+    <para>The arguments are as follows:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>
+          <option>-c</option> <replaceable>config-filename</replaceable>,
+          <option>--config-file</option> <replaceable>config-filename</replaceable>
+        </term>
+        <listitem>
+          <para>The configuration filename to use. Can be either absolute or
+          relative to data path. In case it is absolute, value of data path is
+          not considered.
+          Defaults to <filename>b10-config.db</filename>.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>--clear-config</option>
+        </term>
+        <listitem>
+	  <para>
+	    This will create a backup of the existing configuration
+	    file, remove it and start
+	    <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+            with the default configuration.
+	    The name of the backup file can be found in the logs
+	    (<varname>CFGMGR_BACKED_UP_CONFIG_FILE</varname>).
+	    (It will append a number to the backup filename if a
+	    previous backup file exists.)
+
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>--cmdctl-port</option> <replaceable>port</replaceable>
+        </term>
+        <listitem>
+	  <para>The <command>b10-cmdctl</command> daemon will listen
+	    on this port.
+	    (See
+	    <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+            for the default.)
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term>
+          <option>-p</option> <replaceable>directory</replaceable>,
+          <option>--data-path</option> <replaceable>directory</replaceable>
+        </term>
+        <listitem>
+          <para>The path where BIND 10 programs look for various data files.
+	  Currently only
+	  <citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+	  uses it to locate the configuration file, but the usage
+	  might be extended for other programs and other types of
+	  files.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-m</option> <replaceable>file</replaceable>,
+           <option>--msgq-socket-file</option> <replaceable>file</replaceable></term>
+
+        <listitem>
+          <para>The UNIX domain socket file for the
+	    <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+            daemon to use.
+            The default is
+            <filename>/usr/local/var/bind10/msg_socket</filename>.
+<!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
+           </para>
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-i</option>, <option>--no-kill</option></term>
+        <listitem>
+	  <para>When this option is passed, <command>b10-init</command>
+	  does not send SIGTERM and SIGKILL signals to modules during
+	  shutdown. (This option was introduced for use during
+	  testing.)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
+<!-- TODO: example more detail. -->
+        <listitem>
+          <para>The username for <command>b10-init</command> to run as.
+            <command>b10-init</command> must be initially ran as the
+            root user to use this option.
+            The default is to run as the current user.</para>
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--pid-file</option> <replaceable>filename</replaceable></term>
+        <listitem>
+          <para>If defined, the PID of the <command>b10-init</command> is stored
+             in this file.
+          </para>
+         </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--pretty-name <replaceable>name</replaceable></option></term>
+
+        <listitem>
+          <para>The name this process should have in tools like
+          <command>ps</command> or <command>top</command>. This
+          is handy if you have multiple versions/installations
+          of <command>b10-init</command>.
+<!-- TODO: only supported with setproctitle feature
+The default is the basename of ARG 0.
+-->
+</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-v</option>, <option>--verbose</option></term>
+        <listitem>
+	  <para>Display more about what is going on for
+	  <command>b10-init</command> and its child processes.</para>
+<!-- TODO: not true about all children yet -->
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>-w</option> <replaceable>wait_time</replaceable>, <option>--wait</option> <replaceable>wait_time</replaceable></term>
+        <listitem>
+	  <para>Sets the amount of time that BIND 10 will wait for
+	  the configuration manager (a key component of BIND 10)
+	  to initialize itself before abandoning the start up and
+	  terminating with an error.  The
+	  <replaceable>wait_time</replaceable> is specified in
+	  seconds and has a default value of 10.
+          </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+<!--
+TODO: configuration section
+-->
+
+  <refsect1>
+    <title>CONFIGURATION AND COMMANDS</title>
+
+    <para>
+      The configuration provides settings for components for
+      <command>b10-init</command> to manage under
+      <varname>/Init/components/</varname>.
+      The default elements are:
+    </para>
+
+    <itemizedlist>
+
+      <listitem>
+        <para> <varname>/Init/components/b10-cmdctl</varname> </para>
+      </listitem>
+
+      <listitem>
+        <para> <varname>/Init/components/b10-stats</varname> </para>
+      </listitem>
+
+    </itemizedlist>
+
+    <para>
+      (Note that the startup of <command>b10-sockcreator</command>,
+      <command>b10-cfgmgr</command>, and <command>b10-msgq</command>
+      is not configurable. They are hardcoded and <command>b10-init</command>
+      will not run without them.)
+    </para>
+
+    <para>
+      The named sets for components contain the following settings:
+    </para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><varname>address</varname></term>
+        <listitem>
+	  <para>The name used for communicating to it on the message
+	  bus.</para>
+<!-- NOTE: vorner said:
+These can be null, because the components are special ones, and
+the special class there already knows the address. It is (I hope)
+explained in the guide. I'd like to get rid of the special components
+sometime and I'd like it to teach to guess the address.
+-->
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>kind</varname></term>
+        <listitem>
+          <para>
+            This defines how required a component is.
+            The possible settings for <varname>kind</varname> are:
+            <varname>core</varname> (system won't start if it won't
+            start and <command>b10-init</command> will shutdown if
+            a <quote>core</quote> component crashes),
+            <varname>dispensable</varname> (<command>b10-init</command>
+            will restart failing component),
+            and
+	    <varname>needed</varname> (<command>b10-init</command>
+	    will shutdown if component won't initially start, but
+	    if crashes later, it will attempt to restart).
+            This setting is required.
+<!-- TODO: formatting -->
+          </para>
+        </listitem>
+      </varlistentry>
+
+<!--
+TODO: currently not used
+      <varlistentry>
+        <term> <varname>params</varname> </term>
+        <listitem>
+          <para>
+list
+</para>
+        </listitem>
+      </varlistentry>
+-->
+
+      <varlistentry>
+        <term> <varname>priority</varname> </term>
+        <listitem>
+          <para>This is an integer. <command>b10-init</command>
+            will start the components with largest priority numbers first.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+          <term> <varname>process</varname> </term>
+        <listitem>
+          <para>This is the filename of the executable to be started.
+            If not defined, then <command>b10-init</command> will
+            use the component name instead.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+          <term> <varname>special</varname> </term>
+        <listitem>
+          <para>
+            This defines if the component is started a special, hardcoded
+            way.
+<!--
+TODO: document this ... but maybe some of these will be removed
+once we get rid of some using switches for components?
+
+auth
+cfgmgr
+cmdctl
+msgq
+resolver
+sockcreator
+xfrin
+-->
+
+</para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+
+<!-- TODO: formating -->
+    <para>
+      The <varname>Init</varname> configuration commands are:
+    </para>
+
+<!-- TODO -->
+<!--
+    <para>
+      <command>drop_socket</command>
+      This is an internal command and not exposed to the administrator.
+    </para>
+-->
+
+<!-- TODO -->
+<!--
+    <para>
+      <command>get_socket</command>
+      This is an internal command and not exposed to the administrator.
+    </para>
+-->
+
+    <para>
+      <command>getstats</command> tells <command>b10-init</command>
+      to send its statistics data to the <command>b10-stats</command>
+      daemon.
+      This is an internal command and not exposed to the administrator.
+<!-- not defined in spec -->
+    </para>
+
+    <para>
+      <command>ping</command> is used to check the connection with the
+      <command>b10-init</command> daemon.
+      It returns the text <quote>pong</quote>.
+    </para>
+
+    <para>
+      <command>show_processes</command> lists the current processes
+      managed by <command>b10-init</command>.
+      The output is an array in JSON format containing the process
+      ID, the name for each and the address name used on each message bus.
+<!-- TODO: what is name? -->
+<!-- TODO: change to JSON object format? -->
+<!-- TODO: ticket #1406 -->
+    </para>
+
+    <para>
+      <command>shutdown</command> tells <command>b10-init</command>
+      to shutdown the BIND 10 servers.
+      It will tell each process it manages to shutdown and, when
+      complete, <command>b10-init</command> will exit.
+    </para>
+
+  </refsect1>
+
+  <refsect1>
+    <title>STATISTICS DATA</title>
+
+    <para>
+      The statistics data collected by the <command>b10-stats</command>
+      daemon for <quote>Init</quote> include:
+    </para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term>boot_time</term>
+        <listitem><para>
+          The date and time that the <command>b10-init</command>
+          process started.
+          This is represented in ISO 8601 format.
+        </para></listitem>
+      </varlistentry>
+
+    </variablelist>
+
+  </refsect1>
+
+  <refsect1>
+    <title>FILES</title>
+    <para><filename>sockcreator-XXXXXX/sockcreator</filename>
+    &mdash;
+    the Unix Domain socket located in a temporary file directory for
+    <command>b10-sockcreator</command>
+<!--    <citerefentry><refentrytitle>b10-sockcreator</refentrytitle><manvolnum>8</manvolnum></citerefentry> -->
+    communication.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>SEE ALSO</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-xfrout</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-zonemgr</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-stats-httpd</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citetitle>BIND 10 Guide</citetitle>.
+    </para>
+  </refsect1>
+<!-- <citerefentry>
+        <refentrytitle>b10-sockcreator</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>, -->
+
+  <refsect1 id='history'><title>HISTORY</title>
+    <para>The development of <command>b10-init</command>
+      was started in October 2009.
+      It was renamed and its configuration identifier changed
+      in February 2013.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>AUTHORS</title>
+    <para>
+      The <command>b10-init</command>
+      daemon was initially designed by Shane Kerr of ISC.
+    </para>
+  </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->

+ 11 - 0
src/bin/bind10/bind10.in

@@ -0,0 +1,11 @@
+#!/bin/sh
+
+# We use this wrapper script both for production and in-source tests; in
+# the latter case B10_FROM_BUILD environment is expected to be defined.
+if test -n "${B10_FROM_BUILD}"; then
+	exec ${B10_FROM_BUILD}/src/bin/bind10/b10-init $*
+else
+	prefix=@prefix@
+	exec_prefix=@exec_prefix@
+	exec @libexecdir@/@PACKAGE@/b10-init $*
+fi

+ 13 - 448
src/bin/bind10/bind10.xml

@@ -2,7 +2,7 @@
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
 	       [<!ENTITY mdash "&#8212;">]>
 <!--
- - Copyright (C) 2010-2012  Internet Systems Consortium, Inc. ("ISC")
+ - 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
@@ -20,7 +20,7 @@
 <refentry>
 
   <refentryinfo>
-    <date>April 12, 2012</date>
+    <date>February 5, 2013</date>
   </refentryinfo>
 
   <refmeta>
@@ -31,12 +31,12 @@
 
   <refnamediv>
     <refname>bind10</refname>
-    <refpurpose>BIND 10 boss process</refpurpose>
+    <refpurpose>BIND 10 start script</refpurpose>
   </refnamediv>
 
   <docinfo>
     <copyright>
-      <year>2010-2012</year>
+      <year>2013</year>
       <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
     </copyright>
   </docinfo>
@@ -44,468 +44,33 @@
   <refsynopsisdiv>
     <cmdsynopsis>
       <command>bind10</command>
-      <arg><option>-c <replaceable>config-filename</replaceable></option></arg>
-      <arg><option>-i</option></arg>
-      <arg><option>-m <replaceable>file</replaceable></option></arg>
-      <arg><option>-p <replaceable>data_path</replaceable></option></arg>
-      <arg><option>-u <replaceable>user</replaceable></option></arg>
-      <arg><option>-v</option></arg>
-      <arg><option>-w <replaceable>wait_time</replaceable></option></arg>
-      <arg><option>--clear-config</option></arg>
-      <arg><option>--cmdctl-port</option> <replaceable>port</replaceable></arg>
-      <arg><option>--config-file</option> <replaceable>config-filename</replaceable></arg>
-      <arg><option>--data-path</option> <replaceable>directory</replaceable></arg>
-      <arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
-      <arg><option>--no-kill</option></arg>
-      <arg><option>--pid-file</option> <replaceable>filename</replaceable></arg>
-      <arg><option>--pretty-name <replaceable>name</replaceable></option></arg>
-      <arg><option>--user <replaceable>user</replaceable></option></arg>
-      <arg><option>--verbose</option></arg>
-      <arg><option>--wait <replaceable>wait_time</replaceable></option></arg>
+      <arg><option>options</option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 
   <refsect1>
     <title>DESCRIPTION</title>
 
-    <para>The <command>bind10</command> daemon starts up other
-    BIND 10 required daemons.  It handles restarting of exiting
-    programs and also the shutdown of all managed daemons.</para>
+    <para>The <command>bind10</command> script is a simple wrapper that
+    starts BIND 10 by running the <command>b10-init</command> daemon. All
+    options passed to <command>bind10</command> are directly passed on to
+    <command>b10-init</command>.</para>
 
-<!-- TODO: list what it starts here? -->
-
-<!-- TODO
-    <para>The configuration of the <command>bind10</command> daemon
-    is defined in the TODO configuration file, as described in the
-    <citerefentry><refentrytitle>TODO</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-    manual page.</para>
--->
-
-  </refsect1>
-
-  <refsect1>
-    <title>ARGUMENTS</title>
-
-    <para>The arguments are as follows:</para>
-
-    <variablelist>
-
-      <varlistentry>
-        <term>
-          <option>-c</option> <replaceable>config-filename</replaceable>,
-          <option>--config-file</option> <replaceable>config-filename</replaceable>
-        </term>
-        <listitem>
-          <para>The configuration filename to use. Can be either absolute or
-          relative to data path. In case it is absolute, value of data path is
-          not considered.
-          Defaults to <filename>b10-config.db</filename>.</para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          <option>--clear-config</option>
-        </term>
-        <listitem>
-	  <para>
-	    This will create a backup of the existing configuration
-	    file, remove it and start
-	    <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
-            with the default configuration.
-	    The name of the backup file can be found in the logs
-	    (<varname>CFGMGR_BACKED_UP_CONFIG_FILE</varname>).
-	    (It will append a number to the backup filename if a
-	    previous backup file exists.)
-
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          <option>--cmdctl-port</option> <replaceable>port</replaceable>
-        </term>
-        <listitem>
-	  <para>The <command>b10-cmdctl</command> daemon will listen
-	    on this port.
-	    (See
-	    <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
-            for the default.)
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term>
-          <option>-p</option> <replaceable>directory</replaceable>,
-          <option>--data-path</option> <replaceable>directory</replaceable>
-        </term>
-        <listitem>
-          <para>The path where BIND 10 programs look for various data files.
-	  Currently only
-	  <citerefentry><refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-	  uses it to locate the configuration file, but the usage
-	  might be extended for other programs and other types of
-	  files.</para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-m</option> <replaceable>file</replaceable>,
-           <option>--msgq-socket-file</option> <replaceable>file</replaceable></term>
-
-        <listitem>
-          <para>The UNIX domain socket file for the
-	    <citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-            daemon to use.
-            The default is
-            <filename>/usr/local/var/bind10/msg_socket</filename>.
-<!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
-           </para>
-         </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-i</option>, <option>--no-kill</option></term>
-        <listitem>
-	  <para>When this option is passed, <command>bind10</command>
-	  does not send SIGTERM and SIGKILL signals to modules during
-	  shutdown. (This option was introduced for use during
-	  testing.)</para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-u</option> <replaceable>user</replaceable>, <option>--user</option> <replaceable>name</replaceable></term>
-<!-- TODO: example more detail. -->
-        <listitem>
-          <para>The username for <command>bind10</command> to run as.
-            <command>bind10</command> must be initially ran as the
-            root user to use this option.
-            The default is to run as the current user.</para>
-         </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>--pid-file</option> <replaceable>filename</replaceable></term>
-        <listitem>
-          <para>If defined, the PID of the <command>bind10</command> is stored
-             in this file.
-          </para>
-         </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>--pretty-name <replaceable>name</replaceable></option></term>
-
-        <listitem>
-          <para>The name this process should have in tools like
-          <command>ps</command> or <command>top</command>. This
-          is handy if you have multiple versions/installations
-          of <command>bind10</command>.
-<!-- TODO: only supported with setproctitle feature
-The default is the basename of ARG 0.
--->
-</para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-v</option>, <option>--verbose</option></term>
-        <listitem>
-	  <para>Display more about what is going on for
-	  <command>bind10</command> and its child processes.</para>
-<!-- TODO: not true about all children yet -->
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><option>-w</option> <replaceable>wait_time</replaceable>, <option>--wait</option> <replaceable>wait_time</replaceable></term>
-        <listitem>
-	  <para>Sets the amount of time that BIND 10 will wait for
-	  the configuration manager (a key component of BIND 10)
-	  to initialize itself before abandoning the start up and
-	  terminating with an error.  The
-	  <replaceable>wait_time</replaceable> is specified in
-	  seconds and has a default value of 10.
-          </para>
-        </listitem>
-      </varlistentry>
-
-    </variablelist>
-  </refsect1>
-
-<!--
-TODO: configuration section
--->
-
-  <refsect1>
-    <title>CONFIGURATION AND COMMANDS</title>
-
-    <para>
-      The configuration provides settings for components for
-      <command>bind10</command> to manage under
-      <varname>/Boss/components/</varname>.
-      The default elements are:
-    </para>
-
-    <itemizedlist>
-
-      <listitem>
-        <para> <varname>/Boss/components/b10-cmdctl</varname> </para>
-      </listitem>
-
-      <listitem>
-        <para> <varname>/Boss/components/b10-stats</varname> </para>
-      </listitem>
-
-    </itemizedlist>
-
-    <para>
-      (Note that the startup of <command>b10-sockcreator</command>,
-      <command>b10-cfgmgr</command>, and <command>b10-msgq</command>
-      is not configurable. They are hardcoded and <command>bind10</command>
-      will not run without them.)
-    </para>
-
-    <para>
-      The named sets for components contain the following settings:
-    </para>
-
-    <variablelist>
-
-      <varlistentry>
-        <term><varname>address</varname></term>
-        <listitem>
-	  <para>The name used for communicating to it on the message
-	  bus.</para>
-<!-- NOTE: vorner said:
-These can be null, because the components are special ones, and
-the special class there already knows the address. It is (I hope)
-explained in the guide. I'd like to get rid of the special components
-sometime and I'd like it to teach to guess the address.
--->
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-        <term><varname>kind</varname></term>
-        <listitem>
-          <para>
-            This defines how required a component is.
-            The possible settings for <varname>kind</varname> are:
-            <varname>core</varname> (system won't start if it won't
-            start and <command>bind10</command> will shutdown if
-            a <quote>core</quote> component crashes),
-            <varname>dispensable</varname> (<command>bind10</command>
-            will restart failing component),
-            and
-	    <varname>needed</varname> (<command>bind10</command>
-	    will shutdown if component won't initially start, but
-	    if crashes later, it will attempt to restart).
-            This setting is required.
-<!-- TODO: formatting -->
-          </para>
-        </listitem>
-      </varlistentry>
-
-<!--
-TODO: currently not used
-      <varlistentry>
-        <term> <varname>params</varname> </term>
-        <listitem>
-          <para>
-list
-</para>
-        </listitem>
-      </varlistentry>
--->
-
-      <varlistentry>
-        <term> <varname>priority</varname> </term>
-        <listitem>
-          <para>This is an integer. <command>bind10</command>
-            will start the components with largest priority numbers first.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-          <term> <varname>process</varname> </term>
-        <listitem>
-          <para>This is the filename of the executable to be started.
-            If not defined, then <command>bind10</command> will
-            use the component name instead.
-          </para>
-        </listitem>
-      </varlistentry>
-
-      <varlistentry>
-          <term> <varname>special</varname> </term>
-        <listitem>
-          <para>
-            This defines if the component is started a special, hardcoded
-            way.
-<!--
-TODO: document this ... but maybe some of these will be removed
-once we get rid of some using switches for components?
-
-auth
-cfgmgr
-cmdctl
-msgq
-resolver
-sockcreator
-xfrin
--->
-
-</para>
-        </listitem>
-      </varlistentry>
-
-    </variablelist>
-
-<!-- TODO: formating -->
-    <para>
-      The <varname>Boss</varname> configuration commands are:
-    </para>
-<!-- TODO: let's just let bind10 be known as bind10 and not Boss -->
-
-<!-- TODO -->
-<!--
-    <para>
-      <command>drop_socket</command>
-      This is an internal command and not exposed to the administrator.
-    </para>
--->
-
-<!-- TODO -->
-<!--
-    <para>
-      <command>get_socket</command>
-      This is an internal command and not exposed to the administrator.
-    </para>
--->
-
-    <para>
-      <command>getstats</command> tells <command>bind10</command>
-      to send its statistics data to the <command>b10-stats</command>
-      daemon.
-      This is an internal command and not exposed to the administrator.
-<!-- not defined in spec -->
-    </para>
-
-    <para>
-      <command>ping</command> is used to check the connection with the
-      <command>bind10</command> daemon.
-      It returns the text <quote>pong</quote>.
-    </para>
-
-    <para>
-      <command>show_processes</command> lists the current processes
-      managed by <command>bind10</command>.
-      The output is an array in JSON format containing the process
-      ID, the name for each and the address name used on each message bus.
-<!-- TODO: what is name? -->
-<!-- TODO: change to JSON object format? -->
-<!-- TODO: ticket #1406 -->
-    </para>
-
-    <para>
-      <command>shutdown</command> tells <command>bind10</command>
-      to shutdown the BIND 10 servers.
-      It will tell each process it manages to shutdown and, when
-      complete, <command>bind10</command> will exit.
-    </para>
-
-  </refsect1>
-
-  <refsect1>
-    <title>STATISTICS DATA</title>
-
-    <para>
-      The statistics data collected by the <command>b10-stats</command>
-      daemon for <quote>Boss</quote> include:
-    </para>
-
-    <variablelist>
-
-      <varlistentry>
-        <term>boot_time</term>
-        <listitem><para>
-          The date and time that the <command>bind10</command>
-          process started.
-          This is represented in ISO 8601 format.
-        </para></listitem>
-      </varlistentry>
-
-    </variablelist>
-
-  </refsect1>
-
-  <refsect1>
-    <title>FILES</title>
-    <para><filename>sockcreator-XXXXXX/sockcreator</filename>
-    &mdash;
-    the Unix Domain socket located in a temporary file directory for
-    <command>b10-sockcreator</command>
-<!--    <citerefentry><refentrytitle>b10-sockcreator</refentrytitle><manvolnum>8</manvolnum></citerefentry> -->
-    communication.
-    </para>
   </refsect1>
 
   <refsect1>
     <title>SEE ALSO</title>
     <para>
       <citerefentry>
-        <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-xfrout</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-zonemgr</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
-        <refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citetitle>BIND 10 Guide</citetitle>.
     </para>
   </refsect1>
-<!-- <citerefentry>
-        <refentrytitle>b10-sockcreator</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>, -->
 
   <refsect1 id='history'><title>HISTORY</title>
-    <para>The development of <command>bind10</command>
-    was started in October 2009.</para>
-  </refsect1>
-
-  <refsect1>
-    <title>AUTHORS</title>
-    <para>
-      The <command>bind10</command>
-      daemon was initially designed by Shane Kerr of ISC.
+    <para>The <command>bind10</command> script was added in February 2013.
     </para>
   </refsect1>
-</refentry><!--
- - Local variables:
- - mode: sgml
- - End:
--->
+
+</refentry>

+ 14 - 14
src/bin/bind10/creatorapi.txt

@@ -1,7 +1,7 @@
 Socket creator API
 ==================
 
-This API is between Boss and other modules to allow them requesting of sockets.
+This API is between Init and other modules to allow them requesting of sockets.
 For simplicity, we will use the socket creator for all (even non-privileged)
 ports for now, but we should have some function where we can abstract it later.
 
@@ -25,12 +25,12 @@ It seems we are stuck with current msgq for a while and there's a chance the
 new replacement will not be able to send sockets inbound. So, we need another
 channel.
 
-The boss will create a unix-domain socket and listen on it. When something
+b10-init will create a unix-domain socket and listen on it. When something
 requests a socket over the command channel and the socket is created, some kind
 of token is returned to the application (which will represent the future
 socket). The application then connects to the unix-domain socket, sends the
-token over the connection (so Boss will know which socket to send there, in case
-multiple applications ask for sockets simultaneously) and Boss sends the socket
+token over the connection (so Init will know which socket to send there, in case
+multiple applications ask for sockets simultaneously) and Init sends the socket
 in return.
 
 In theory, we could send the requests directly over the unix-domain
@@ -48,8 +48,8 @@ socket, but it has two disadvantages:
 
 Caching of sockets
 ------------------
-To allow sending the same socket to multiple application, the Boss process will
-hold a cache. Each socket that is created and sent is kept open in Boss and
+To allow sending the same socket to multiple application, the Init process will
+hold a cache. Each socket that is created and sent is kept open in Init and
 preserved there as well. A reference count is kept with each of them.
 
 When another application asks for the same socket, it is simply sent from the
@@ -60,14 +60,14 @@ command channel), the reference count can be decreased without problems. But
 when the application terminates or crashes, we need to decrease it as well.
 There's a problem, since we don't know which command channel connection (eg.
 lname) belongs to which PID. Furthermore, the applications don't need to be
-started by boss.
+started by b10-init.
 
 There are two possibilities:
 * Let the msgq send messages about disconnected clients (eg. group message to
   some name). This one is better if we want to migrate to dbus, since dbus
   already has this capability as well as sending the sockets inbound (at least it
   seems so on unix) and we could get rid of the unix-domain socket completely.
-* Keep the unix-domain connections open forever. Boss can remember which socket
+* Keep the unix-domain connections open forever. Init can remember which socket
   was sent to which connection and when the connection closes (because the
   application crashed), it can drop all the references on the sockets. This
   seems easier to implement.
@@ -75,12 +75,12 @@ There are two possibilities:
 The commands
 ------------
 * Command to release a socket. This one would have single parameter, the token
-  used to get the socket. After this, boss would decrease its reference count
-  and if it drops to zero, close its own copy of the socket. This should be used
-  when the module stops using the socket (and after closes it). The
-  library could remember the file-descriptor to token mapping (for
-  common applications that don't request the same socket multiple
-  times in parallel).
+  used to get the socket. After this, b10-init would decrease its reference
+  count and if it drops to zero, close its own copy of the socket. This
+  should be used when the module stops using the socket (and after closes
+  it). The library could remember the file-descriptor to token mapping (for
+  common applications that don't request the same socket multiple times in
+  parallel).
 * Command to request a socket. It would have parameters to specify which socket
   (IP address, address family, port) and how to allow sharing. Sharing would be
   one of:

+ 51 - 46
src/bin/bind10/bind10_src.py.in

@@ -16,7 +16,7 @@
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 """
-This file implements the Boss of Bind (BoB, or bob) program.
+This file implements the b10-init program.
 
 Its purpose is to start up the BIND 10 system, and then manage the
 processes, by starting and stopping processes, plus restarting
@@ -30,7 +30,7 @@ The Python subprocess module is used for starting processes, but
 because this is not efficient for managing groups of processes,
 SIGCHLD signals are caught and processed using the signal module.
 
-Most of the logic is contained in the BoB class. However, since Python
+Most of the logic is contained in the Init class. However, since Python
 requires that signal processing happen in the main thread, we do
 signal handling outside of that class, in the code running for
 __main__.
@@ -43,11 +43,14 @@ import os
 # from a directory relative to that, otherwise we use the ones
 # installed on the system
 if "B10_FROM_SOURCE" in os.environ:
-    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + "/src/bin/bind10/bob.spec"
+    SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] +\
+                        "/src/bin/bind10/init.spec"
 else:
     PREFIX = "@prefix@"
     DATAROOTDIR = "@datarootdir@"
-    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+    SPECFILE_LOCATION = "@datadir@/@PACKAGE@/init.spec"\
+                         .replace("${datarootdir}", DATAROOTDIR)\
+                         .replace("${prefix}", PREFIX)
 
 import subprocess
 import signal
@@ -69,15 +72,16 @@ import isc.cc
 import isc.util.process
 import isc.net.parse
 import isc.log
-from isc.log_messages.bind10_messages import *
+import isc.config
+from isc.log_messages.init_messages import *
 import isc.bind10.component
 import isc.bind10.special_component
 import isc.bind10.socket_cache
 import libutil_io_python
 import tempfile
 
-isc.log.init("b10-boss", buffer=True)
-logger = isc.log.Logger("boss")
+isc.log.init("b10-init", buffer=True)
+logger = isc.log.Logger("init")
 
 # Pending system-wide debug level definitions, the ones we
 # use here are hardcoded for now
@@ -93,14 +97,14 @@ CREATOR_SOCKET_ERROR = 2
 CREATOR_SHARE_ERROR = 3
 
 # Assign this process some longer name
-isc.util.process.rename(sys.argv[0])
+isc.util.process.rename()
 
 # This is the version that gets displayed to the user.
 # The VERSION string consists of the module name, the module version
 # number, and the overall BIND 10 version number (set in configure.ac).
 VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
 
-# This is for boot_time of Boss
+# This is for boot_time of Init
 _BASETIME = time.gmtime()
 
 # Detailed error message commonly used on startup failure, possibly due to
@@ -147,7 +151,7 @@ class ProcessInfo:
         """Function used before running a program that needs to run as a
         different user."""
         # First, put us into a separate process group so we don't get
-        # SIGINT signals on Ctrl-C (the boss will shut everthing down by
+        # SIGINT signals on Ctrl-C (b10-init will shut everthing down by
         # other means).
         os.setpgrp()
 
@@ -161,7 +165,7 @@ class ProcessInfo:
         else:
             spawn_stderr = None
         # Environment variables for the child process will be a copy of those
-        # of the boss process with any additional specific variables given
+        # of the b10-init process with any additional specific variables given
         # on construction (self.env).
         spawn_env = copy.deepcopy(os.environ)
         spawn_env.update(self.env)
@@ -187,18 +191,18 @@ class CChannelConnectError(Exception): pass
 
 class ProcessStartError(Exception): pass
 
-class BoB:
-    """Boss of BIND class."""
+class Init:
+    """Init of BIND class."""
 
     def __init__(self, msgq_socket_file=None, data_path=None,
                  config_filename=None, clear_config=False,
                  verbose=False, nokill=False, setuid=None, setgid=None,
                  username=None, cmdctl_port=None, wait_time=10):
         """
-            Initialize the Boss of BIND. This is a singleton (only one can run).
+            Initialize the Init of BIND. This is a singleton (only one can run).
 
             The msgq_socket_file specifies the UNIX domain socket file that the
-            msgq process listens on.  If verbose is True, then the boss reports
+            msgq process listens on.  If verbose is True, then b10-init reports
             what it is doing.
 
             Data path and config filename are passed through to config manager
@@ -207,7 +211,7 @@ class BoB:
             The cmdctl_port is passed to cmdctl and specify on which port it
             should listen.
 
-            wait_time controls the amount of time (in seconds) that Boss waits
+            wait_time controls the amount of time (in seconds) that Init waits
             for selected processes to initialize before continuing with the
             initialization.  Currently this is only the configuration manager.
         """
@@ -287,7 +291,7 @@ class BoB:
         for comp in self.__core_components:
             if comp in comps:
                 raise Exception(comp + " is core component managed by " +
-                                "bind10 boss, do not set it")
+                                "b10-init, do not set it")
             comps[comp] = self.__core_components[comp]
         # Update the configuration
         self._component_configurator.reconfigure(comps)
@@ -313,7 +317,7 @@ class BoB:
                 # We use one-shot logger after setuid here.  This will
                 # detect any permission issue regarding logging due to the
                 # result of setuid at the earliest opportunity.
-                isc.log.Logger("boss").info(BIND10_SETUID, self.__uid)
+                isc.log.Logger("b10-init").info(BIND10_SETUID, self.__uid)
         except Exception as ex:
             raise ChangeUserError('failed to change user: ' + str(ex))
 
@@ -390,7 +394,7 @@ class BoB:
 
     def _read_bind10_config(self):
         """
-            Reads the parameters associated with the BoB module itself.
+            Reads the parameters associated with the Init module itself.
 
             This means the list of components we should start now.
 
@@ -398,7 +402,7 @@ class BoB:
             it stays because of historical reasons and because the tests
             replace the method sometimes.
         """
-        logger.info(BIND10_READING_BOSS_CONFIGURATION)
+        logger.info(BIND10_READING_INIT_CONFIGURATION)
 
         config_data = self.ccs.get_full_config()
         self.__propagate_component_config(config_data['components'])
@@ -439,7 +443,7 @@ class BoB:
 
     def process_running(self, msg, who):
         """
-            Some processes return a message to the Boss after they have
+            Some processes return a message to the Init after they have
             started to indicate that they are running.  The form of the
             message is a dictionary with contents {"running:", "<process>"}.
             This method checks the passed message and returns True if the
@@ -491,6 +495,8 @@ class BoB:
 
             # if we have been trying for "a while" give up
             if (time.time() - cc_connect_start) > self.msgq_timeout:
+                if msgq_proc.process:
+                    msgq_proc.process.kill()
                 logger.error(BIND10_CONNECTING_TO_CC_FAIL)
                 raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
 
@@ -503,7 +509,7 @@ class BoB:
         # Subscribe to the message queue.  The only messages we expect to receive
         # on this channel are once relating to process startup.
         if self.cc_session is not None:
-            self.cc_session.group_subscribe("Boss")
+            self.cc_session.group_subscribe("Init")
 
         return msgq_proc
 
@@ -576,7 +582,7 @@ class BoB:
 
     def register_process(self, pid, component):
         """
-        Put another process into boss to watch over it.  When the process
+        Put another process into b10-init to watch over it.  When the process
         dies, the component.failed() is called with the exit code.
 
         It is expected the info is a isc.bind10.component.BaseComponent
@@ -659,7 +665,7 @@ class BoB:
         # inside the configurator.
         self.start_ccsession(self.c_channel_env)
 
-        # Extract the parameters associated with Bob.  This can only be
+        # Extract the parameters associated with Init.  This can only be
         # done after the CC Session is started.  Note that the logging
         # configuration may override the "-v" switch set on the command line.
         self._read_bind10_config()
@@ -668,7 +674,7 @@ class BoB:
 
     def startup(self):
         """
-            Start the BoB instance.
+            Start the Init instance.
 
             Returns None if successful, otherwise an string describing the
             problem.
@@ -718,7 +724,7 @@ class BoB:
 
     def component_shutdown(self, exitcode=0):
         """
-        Stop the Boss instance from a components' request. The exitcode
+        Stop the Init instance from a components' request. The exitcode
         indicates the desired exit code.
 
         If we did not start yet, it raises an exception, which is meant
@@ -735,11 +741,11 @@ class BoB:
             self.runnable = False
 
     def shutdown(self):
-        """Stop the BoB instance."""
+        """Stop the Init instance."""
         logger.info(BIND10_SHUTDOWN)
         # If ccsession is still there, inform rest of the system this module
         # is stopping. Since everything will be stopped shortly, this is not
-        # really necessary, but this is done to reflect that boss is also
+        # really necessary, but this is done to reflect that b10-init is also
         # 'just' a module.
         self.ccs.send_stopping()
 
@@ -941,7 +947,7 @@ class BoB:
 
     def set_creator(self, creator):
         """
-        Registeres a socket creator into the boss. The socket creator is not
+        Registeres a socket creator into the b10-init. The socket creator is not
         used directly, but through a cache. The cache is created in this
         method.
 
@@ -1076,7 +1082,7 @@ class BoB:
 
 # global variables, needed for signal handlers
 options = None
-boss_of_bind = None
+b10_init = None
 
 def reaper(signal_number, stack_frame):
     """A child process has died (SIGCHLD received)."""
@@ -1097,10 +1103,10 @@ def get_signame(signal_number):
 def fatal_signal(signal_number, stack_frame):
     """We need to exit (SIGINT or SIGTERM received)."""
     global options
-    global boss_of_bind
+    global b10_init
     logger.info(BIND10_RECEIVED_SIGNAL, get_signame(signal_number))
     signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-    boss_of_bind.runnable = False
+    b10_init.runnable = False
 
 def process_rename(option, opt_str, value, parser):
     """Function that renames the process if it is requested by a option."""
@@ -1222,7 +1228,7 @@ def remove_lock_files():
 
 def main():
     global options
-    global boss_of_bind
+    global b10_init
     # Enforce line buffering on stdout, even when not a TTY
     sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
 
@@ -1284,33 +1290,32 @@ def main():
     signal.signal(signal.SIGPIPE, signal.SIG_IGN)
 
     try:
-        # Go bob!
-        boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
-                           options.config_file, options.clear_config,
-                           options.verbose, options.nokill,
-                           setuid, setgid, username, options.cmdctl_port,
-                           options.wait_time)
-        startup_result = boss_of_bind.startup()
+        b10_init = Init(options.msgq_socket_file, options.data_path,
+                        options.config_file, options.clear_config,
+                        options.verbose, options.nokill,
+                        setuid, setgid, username, options.cmdctl_port,
+                        options.wait_time)
+        startup_result = b10_init.startup()
         if startup_result:
             logger.fatal(BIND10_STARTUP_ERROR, startup_result)
             sys.exit(1)
-        boss_of_bind.init_socket_srv()
+        b10_init.init_socket_srv()
         logger.info(BIND10_STARTUP_COMPLETE)
         dump_pid(options.pid_file)
 
         # Let it run
-        boss_of_bind.run(wakeup_pipe[0])
+        b10_init.run(wakeup_pipe[0])
 
         # shutdown
         signal.signal(signal.SIGCHLD, signal.SIG_DFL)
-        boss_of_bind.shutdown()
+        b10_init.shutdown()
     finally:
         # Clean up the filesystem
         unlink_pid_file(options.pid_file)
         remove_lock_files()
-        if boss_of_bind is not None:
-            boss_of_bind.remove_socket_srv()
-    sys.exit(boss_of_bind.exitcode)
+        if b10_init is not None:
+            b10_init.remove_socket_srv()
+    sys.exit(b10_init.exitcode)
 
 if __name__ == "__main__":
     main()

+ 3 - 3
src/bin/bind10/bob.spec

@@ -1,7 +1,7 @@
 {
   "module_spec": {
-    "module_name": "Boss",
-    "module_description": "Master process",
+    "module_name": "Init",
+    "module_description": "Init process",
     "config_data": [
       {
         "item_name": "components",
@@ -66,7 +66,7 @@
       },
       {
         "command_name": "ping",
-        "command_description": "Ping the boss process",
+        "command_description": "Ping the b10-init process",
         "command_args": []
       },
       {

+ 55 - 55
src/bin/bind10/bind10_messages.mes

@@ -16,12 +16,12 @@
 # of the xfrin messages python module.
 
 % BIND10_CHECK_MSGQ_ALREADY_RUNNING checking if msgq is already running
-The boss process is starting up and will now check if the message bus
+The b10-init process is starting up and will now check if the message bus
 daemon is already running. If so, it will not be able to start, as it
 needs a dedicated message bus.
 
 % BIND10_COMPONENT_FAILED component %1 (pid %2) failed: %3
-The process terminated, but the bind10 boss didn't expect it to, which means
+The process terminated, but b10-init didn't expect it to, which means
 it must have failed.
 
 % BIND10_COMPONENT_RESTART component %1 is about to restart
@@ -30,7 +30,7 @@ as flawless service as possible, but it should be investigated what happened,
 as it could happen again.
 
 % BIND10_COMPONENT_START component %1 is starting
-The named component is about to be started by the boss process.
+The named component is about to be started by the b10-init process.
 
 % BIND10_COMPONENT_START_EXCEPTION component %1 failed to start: %2
 An exception (mentioned in the message) happened during the startup of the
@@ -38,7 +38,7 @@ named component. The componet is not considered started and further actions
 will be taken about it.
 
 % BIND10_COMPONENT_STOP component %1 is being stopped
-A component is about to be asked to stop willingly by the boss.
+A component is about to be asked to stop willingly by the b10-init.
 
 % BIND10_COMPONENT_UNSATISFIED component %1 is required to run and failed
 A component failed for some reason (see previous messages). It is either a core
@@ -59,31 +59,31 @@ will be propagated.
 A different configuration of which components should be running is being
 installed. All components that are no longer needed will be stopped and
 newly introduced ones started. This happens at startup, when the configuration
-is read the first time, or when an operator changes configuration of the boss.
+is read the first time, or when an operator changes configuration of the b10-init.
 
 % BIND10_CONFIGURATOR_RUN running plan of %1 tasks
 A debug message. The configurator is about to execute a plan of actions it
 computed previously.
 
 % BIND10_CONFIGURATOR_START bind10 component configurator is starting up
-The part that cares about starting and stopping the right component from the
-boss process is starting up. This happens only once at the startup of the
-boss process. It will start the basic set of processes now (the ones boss
-needs to read the configuration), the rest will be started after the
-configuration is known.
+The part that cares about starting and stopping the right component from
+the b10-init process is starting up. This happens only once at the startup
+of the b10-init process. It will start the basic set of processes now (the
+ones b10-init needs to read the configuration), the rest will be started
+after the configuration is known.
 
 % BIND10_CONFIGURATOR_STOP bind10 component configurator is shutting down
-The part that cares about starting and stopping processes in the boss is
+The part that cares about starting and stopping processes in the b10-init is
 shutting down. All started components will be shut down now (more precisely,
 asked to terminate by their own, if they fail to comply, other parts of
-the boss process will try to force them).
+the b10-init process will try to force them).
 
 % BIND10_CONFIGURATOR_TASK performing task %1 on %2
 A debug message. The configurator is about to perform one task of the plan it
 is currently executing on the named component.
 
 % BIND10_CONNECTING_TO_CC_FAIL failed to connect to configuration/command channel; try -v to see output from msgq
-The boss process tried to connect to the communication channel for
+The b10-init process tried to connect to the communication channel for
 commands and configuration updates during initialization, but it
 failed.  This is a fatal startup error, and process will soon
 terminate after some cleanup.  There can be several reasons for the
@@ -98,15 +98,15 @@ you specify the -u option to change process users, the directory must
 be writable for that user.
 
 % BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the boss module specified
-statistics data which is invalid for the boss specification file.
+An error was encountered when the b10-init module specified
+statistics data which is invalid for the b10-init specification file.
 
 % BIND10_INVALID_USER invalid user: %1
-The boss process was started with the -u option, to drop root privileges
+The b10-init process was started with the -u option, to drop root privileges
 and continue running as the specified user, but the user is unknown.
 
 % BIND10_KILLING_ALL_PROCESSES killing all started processes
-The boss module was not able to start every process it needed to start
+The b10-init module was not able to start every process it needed to start
 during startup, and will now kill the processes that did get started.
 
 % BIND10_LOST_SOCKET_CONSUMER consumer %1 of sockets disconnected, considering all its sockets closed
@@ -136,28 +136,28 @@ This indicates a process started previously terminated. The process id
 and component owning the process are indicated, as well as the exit code.
 This doesn't distinguish if the process was supposed to terminate or not.
 
-% BIND10_READING_BOSS_CONFIGURATION reading boss configuration
-The boss process is starting up, and will now process the initial
+% BIND10_READING_INIT_CONFIGURATION reading b10-init configuration
+The b10-init process is starting up, and will now process the initial
 configuration, as received from the configuration manager.
 
 % BIND10_RECEIVED_COMMAND received command: %1
-The boss module received a command and shall now process it. The command
+The b10-init module received a command and shall now process it. The command
 is printed.
 
 % BIND10_RECEIVED_NEW_CONFIGURATION received new configuration: %1
-The boss module received a configuration update and is going to apply
+The b10-init module received a configuration update and is going to apply
 it now. The new configuration is printed.
 
 % BIND10_RECEIVED_SIGNAL received signal %1
-The boss module received the given signal.
+The b10-init module received the given signal.
 
 % BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
-The boss module tried to restart a component after it failed (crashed)
-unexpectedly, but the boss then found that the component had been removed
+The b10-init module tried to restart a component after it failed (crashed)
+unexpectedly, but the b10-init then found that the component had been removed
 from its local configuration of components to run.  This is an unusual
 situation but can happen if the administrator removes the component from
 the configuration after the component's crash and before the restart time.
-The boss module simply skipped restarting that module, and the whole system
+The b10-init module simply skipped restarting that module, and the whole system
 went back to the expected state (except that the crash itself is likely
 to be a bug).
 
@@ -175,51 +175,51 @@ should not happen under normal circumstances and is considered fatal,
 so BIND 10 will now shut down. The specific error is printed.
 
 % BIND10_SEND_SIGKILL sending SIGKILL to %1 (PID %2)
-The boss module is sending a SIGKILL signal to the given process.
+The b10-init module is sending a SIGKILL signal to the given process.
 
 % BIND10_SEND_SIGNAL_FAIL sending %1 to %2 (PID %3) failed: %4
-The boss module sent a single (either SIGTERM or SIGKILL) to a process,
+The b10-init module sent a single (either SIGTERM or SIGKILL) to a process,
 but it failed due to some system level error.  There are two major cases:
-the target process has already terminated but the boss module had sent
+the target process has already terminated but the b10-init module had sent
 the signal before it noticed the termination.  In this case an error
 message should indicate something like "no such process".  This can be
-safely ignored.  The other case is that the boss module doesn't have
+safely ignored.  The other case is that the b10-init module doesn't have
 the privilege to send a signal to the process.  It can typically
-happen when the boss module started as a privileged process, spawned a
+happen when the b10-init module started as a privileged process, spawned a
 subprocess, and then dropped the privilege.  It includes the case for
-the socket creator when the boss process runs with the -u command line
-option.  In this case, the boss module simply gives up to terminate
+the socket creator when the b10-init process runs with the -u command line
+option.  In this case, the b10-init module simply gives up to terminate
 the process explicitly because it's unlikely to succeed by keeping
 sending the signal.  Although the socket creator is implemented so
-that it will terminate automatically when the boss process exits
+that it will terminate automatically when the b10-init process exits
 (and that should be the case for any other future process running with
 a higher privilege), but it's recommended to check if there's any
 remaining BIND 10 process if this message is logged.  For all other
-cases, the boss module will keep sending the signal until it confirms
+cases, the b10-init module will keep sending the signal until it confirms
 all child processes terminate.  Although unlikely, this could prevent
-the boss module from exiting, just keeping sending the signals.  So,
+the b10-init module from exiting, just keeping sending the signals.  So,
 again, it's advisable to check if it really terminates when this
 message is logged.
 
 % BIND10_SEND_SIGTERM sending SIGTERM to %1 (PID %2)
-The boss module is sending a SIGTERM signal to the given process.
+The b10-init module is sending a SIGTERM signal to the given process.
 
 % BIND10_SETGID setting GID to %1
-The boss switches the process group ID to the given value.  This happens
+The b10-init switches the process group ID to the given value.  This happens
 when BIND 10 starts with the -u option, and the group ID will be set to
 that of the specified user.
 
 % BIND10_SETUID setting UID to %1
-The boss switches the user it runs as to the given UID.
+The b10-init switches the user it runs as to the given UID.
 
 % BIND10_SHUTDOWN stopping the server
-The boss process received a command or signal telling it to shut down.
+The b10-init process received a command or signal telling it to shut down.
 It will send a shutdown command to each process. The processes that do
 not shut down will then receive a SIGTERM signal. If that doesn't work,
 it shall send SIGKILL signals to the processes still alive.
 
 % BIND10_SHUTDOWN_COMPLETE all processes ended, shutdown complete
-All child processes have been stopped, and the boss process will now
+All child processes have been stopped, and the b10-init process will now
 stop itself.
 
 % BIND10_SOCKCREATOR_BAD_CAUSE unknown error cause from socket creator: %1
@@ -227,7 +227,7 @@ The socket creator reported an error when creating a socket. But the function
 which failed is unknown (not one of 'S' for socket or 'B' for bind).
 
 % BIND10_SOCKCREATOR_BAD_RESPONSE unknown response for socket request: %1
-The boss requested a socket from the creator, but the answer is unknown. This
+The b10-init requested a socket from the creator, but the answer is unknown. This
 looks like a programmer error.
 
 % BIND10_SOCKCREATOR_EOF eof while expecting data from socket creator
@@ -235,7 +235,7 @@ There should be more data from the socket creator, but it closed the socket.
 It probably crashed.
 
 % BIND10_SOCKCREATOR_INIT initializing socket creator parser
-The boss module initializes routines for parsing the socket creator
+The b10-init module initializes routines for parsing the socket creator
 protocol.
 
 % BIND10_SOCKCREATOR_KILL killing the socket creator
@@ -243,7 +243,7 @@ The socket creator is being terminated the aggressive way, by sending it
 sigkill. This should not happen usually.
 
 % BIND10_SOCKCREATOR_TERMINATE terminating socket creator
-The boss module sends a request to terminate to the socket creator.
+The b10-init module sends a request to terminate to the socket creator.
 
 % BIND10_SOCKCREATOR_TRANSPORT_ERROR transport error when talking to the socket creator: %1
 Either sending or receiving data from the socket creator failed with the given
@@ -259,7 +259,7 @@ The socket creator failed to create the requested socket. It failed on the
 indicated OS API function with given error.
 
 % BIND10_SOCKET_GET requesting socket [%1]:%2 of type %3 from the creator
-The boss forwards a request for a socket to the socket creator.
+The b10-init forwards a request for a socket to the socket creator.
 
 % BIND10_STARTED_CC started configuration/command session
 Debug message given when BIND 10 has successfully started the object that
@@ -279,14 +279,14 @@ Informational message given when BIND 10 is starting the session object
 that handles configuration and commands.
 
 % BIND10_STARTING_PROCESS starting process %1
-The boss module is starting the given process.
+The b10-init module is starting the given process.
 
 % BIND10_STARTING_PROCESS_PORT starting process %1 (to listen on port %2)
-The boss module is starting the given process, which will listen on the
+The b10-init module is starting the given process, which will listen on the
 given port number.
 
 % BIND10_STARTING_PROCESS_PORT_ADDRESS starting process %1 (to listen on %2#%3)
-The boss module is starting the given process, which will listen on the
+The b10-init module is starting the given process, which will listen on the
 given address and port number (written as <address>#<port>).
 
 % BIND10_STARTUP_COMPLETE BIND 10 started
@@ -298,30 +298,30 @@ shown, and BIND10 will now shut down.
 
 % BIND10_STARTUP_UNEXPECTED_MESSAGE unrecognised startup message %1
 During the startup process, a number of messages are exchanged between the
-Boss process and the processes it starts.  This error is output when a
-message received by the Boss process is recognised as being of the
+Init process and the processes it starts.  This error is output when a
+message received by the Init process is recognised as being of the
 correct format but is unexpected.  It may be that processes are starting
 of sequence.
 
 % BIND10_STARTUP_UNRECOGNISED_MESSAGE unrecognised startup message %1
 During the startup process, a number of messages are exchanged between the
-Boss process and the processes it starts.  This error is output when a
-message received by the Boss process is not recognised.
+Init process and the processes it starts.  This error is output when a
+message received by the Init process is not recognised.
 
 % BIND10_STOP_PROCESS asking %1 to shut down
-The boss module is sending a shutdown command to the given module over
+The b10-init module is sending a shutdown command to the given module over
 the message channel.
 
 % BIND10_UNKNOWN_CHILD_PROCESS_ENDED unknown child pid %1 exited
 An unknown child process has exited. The PID is printed, but no further
-action will be taken by the boss process.
+action will be taken by the b10-init process.
 
 % BIND10_WAIT_CFGMGR waiting for configuration manager process to initialize
 The configuration manager process is so critical to operation of BIND 10
-that after starting it, the Boss module will wait for it to initialize
+that after starting it, the Init module will wait for it to initialize
 itself before continuing.  This debug message is produced during the
 wait and may be output zero or more times depending on how long it takes
-the configuration manager to start up.  The total length of time Boss
+the configuration manager to start up.  The total length of time Init
 will wait for the configuration manager before reporting an error is
 set with the command line --wait switch, which has a default value of
 ten seconds.

+ 2 - 2
src/bin/bind10/run_bind10.sh.in

@@ -23,7 +23,7 @@ BIND10_PATH=@abs_top_builddir@/src/bin/bind10
 PATH=@abs_top_builddir@/src/bin/msgq:@abs_top_builddir@/src/bin/auth:@abs_top_builddir@/src/bin/resolver:@abs_top_builddir@/src/bin/cfgmgr:@abs_top_builddir@/src/bin/cmdctl:@abs_top_builddir@/src/bin/stats:@abs_top_builddir@/src/bin/xfrin:@abs_top_builddir@/src/bin/xfrout:@abs_top_builddir@/src/bin/zonemgr:@abs_top_builddir@/src/bin/ddns:@abs_top_builddir@/src/bin/dhcp6:@abs_top_builddir@/src/bin/sockcreator:$PATH
 export PATH
 
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -45,5 +45,5 @@ export B10_FROM_BUILD
 BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
 export BIND10_MSGQ_SOCKET_FILE
 
-exec ${PYTHON_EXEC} -O ${BIND10_PATH}/bind10 "$@"
+exec ${BIND10_PATH}/b10-init "$@"
 

+ 1 - 1
src/bin/bind10/tests/.gitignore

@@ -1 +1 @@
-/bind10_test.py
+/init_test.py

+ 1 - 1
src/bin/bind10/tests/Makefile.am

@@ -1,7 +1,7 @@
 PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
 #PYTESTS = args_test.py bind10_test.py
 # NOTE: this has a generated test found in the builddir
-PYTESTS = bind10_test.py
+PYTESTS = init_test.py
 noinst_SCRIPTS = $(PYTESTS)
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 65 - 50
src/bin/bind10/tests/args_test.py

@@ -1,5 +1,20 @@
+# Copyright (C) 2010,2013  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
 """
-This program tests the boss process to make sure that it runs while
+This program tests the b10-init process to make sure that it runs while
 dropping permissions. It must be run as a user that can set permission.
 """
 import unittest
@@ -17,69 +32,69 @@ SUID_USER="shane"
 BIND10_EXE="../run_bind10.sh"
 TIMEOUT=3
 
-class TestBossArgs(unittest.TestCase):
-    def _waitForString(self, bob, s):
+class TestInitArgs(unittest.TestCase):
+    def _waitForString(self, init, s):
         found_string = False
         start_time = time.time()
         while time.time() < start_time + TIMEOUT:
-            (r,w,x) = select.select((bob.stdout,), (), (), TIMEOUT) 
-            if bob.stdout in r:
-                s = bob.stdout.readline()
+            (r,w,x) = select.select((init.stdout,), (), (), TIMEOUT)
+            if init.stdout in r:
+                s = init.stdout.readline()
                 if s == '':
                     break
-                if s.startswith(s): 
+                if s.startswith(s):
                     found_string = True
                     break
         return found_string
 
     def testNoArgs(self):
         """Run bind10 without any arguments"""
-        bob = subprocess.Popen(args=(BIND10_EXE,),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        started_ok = self._waitForString(bob, '[bind10] BIND 10 started')
+        init = subprocess.Popen(args=(BIND10_EXE,),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        started_ok = self._waitForString(init, '[bind10] BIND 10 started')
         time.sleep(0.1)
-        bob.terminate()
-        bob.wait()
+        init.terminate()
+        init.wait()
         self.assertTrue(started_ok)
 
     def testBadOption(self):
         """Run bind10 with a bogus option"""
-        bob = subprocess.Popen(args=(BIND10_EXE, "--badoption"),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        failed = self._waitForString(bob, 'bind10: error: no such option: --badoption')
+        init = subprocess.Popen(args=(BIND10_EXE, "--badoption"),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        failed = self._waitForString(init, 'bind10: error: no such option: --badoption')
         time.sleep(0.1)
-        bob.terminate()
-        self.assertTrue(bob.wait() == 2)
+        init.terminate()
+        self.assertTrue(init.wait() == 2)
         self.assertTrue(failed)
 
     def testArgument(self):
         """Run bind10 with an argument (this is not allowed)"""
-        bob = subprocess.Popen(args=(BIND10_EXE, "argument"),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        failed = self._waitForString(bob, 'Usage: bind10 [options]')
+        init = subprocess.Popen(args=(BIND10_EXE, "argument"),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        failed = self._waitForString(init, 'Usage: bind10 [options]')
         time.sleep(0.1)
-        bob.terminate()
-        self.assertTrue(bob.wait() == 1)
+        init.terminate()
+        self.assertTrue(init.wait() == 1)
         self.assertTrue(failed)
 
     def testBadUser(self):
         """Run bind10 with a bogus user"""
-        bob = subprocess.Popen(args=(BIND10_EXE, "-u", "bogus_user"),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        failed = self._waitForString(bob, "bind10: invalid user: 'bogus_user'")
+        init = subprocess.Popen(args=(BIND10_EXE, "-u", "bogus_user"),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        failed = self._waitForString(init, "bind10: invalid user: 'bogus_user'")
         time.sleep(0.1)
-        bob.terminate()
-        self.assertTrue(bob.wait() == 1)
+        init.terminate()
+        self.assertTrue(init.wait() == 1)
         self.assertTrue(failed)
 
     def testBadUid(self):
         """Run bind10 with a bogus user ID"""
-        bob = subprocess.Popen(args=(BIND10_EXE, "-u", "999999999"),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        failed = self._waitForString(bob, "bind10: invalid user: '999999999'")
+        init = subprocess.Popen(args=(BIND10_EXE, "-u", "999999999"),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        failed = self._waitForString(init, "bind10: invalid user: '999999999'")
         time.sleep(0.1)
-        bob.terminate()
-        self.assertTrue(bob.wait() == 1)
+        init.terminate()
+        self.assertTrue(init.wait() == 1)
         self.assertTrue(failed)
 
     def testFailSetUser(self):
@@ -90,12 +105,12 @@ class TestBossArgs(unittest.TestCase):
         if os.getuid() == 0:
             self.skipTest("test must not be run as root (uid is 0)")
         # XXX: we depend on the "nobody" user
-        bob = subprocess.Popen(args=(BIND10_EXE, "-u", "nobody"),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        failed = self._waitForString(bob, "[bind10] Error on startup: Unable to start b10-msgq; Unable to change to user nobody")
+        init = subprocess.Popen(args=(BIND10_EXE, "-u", "nobody"),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        failed = self._waitForString(init, "[bind10] Error on startup: Unable to start b10-msgq; Unable to change to user nobody")
         time.sleep(0.1)
-        bob.terminate()
-        self.assertTrue(bob.wait() == 1)
+        init.terminate()
+        self.assertTrue(init.wait() == 1)
         self.assertTrue(failed)
 
     def testSetUser(self):
@@ -108,9 +123,9 @@ class TestBossArgs(unittest.TestCase):
         if os.geteuid() != 0:
             self.skipTest("test must run as root (euid is not 0)")
 
-        bob = subprocess.Popen(args=(BIND10_EXE, "-u", SUID_USER),
-                               stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        started_ok = self._waitForString(bob, '[bind10] BIND 10 started')
+        init = subprocess.Popen(args=(BIND10_EXE, "-u", SUID_USER),
+                                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+        started_ok = self._waitForString(init, '[bind10] BIND 10 started')
         self.assertTrue(started_ok)
         ps = subprocess.Popen(args=("ps", "axo", "user,pid"),
                               stdout=subprocess.PIPE)
@@ -120,22 +135,22 @@ class TestBossArgs(unittest.TestCase):
             s = ps.stdout.readline()
             if s == '': break
             (user, pid) = s.split()
-            if int(pid) == bob.pid:
+            if int(pid) == init.pid:
                 ps_user = user.decode()
                 break
         self.assertTrue(ps_user is not None)
         self.assertTrue(ps_user == SUID_USER)
         time.sleep(0.1)
-        bob.terminate()
-        x = bob.wait()
-        self.assertTrue(bob.wait() == 0)
+        init.terminate()
+        x = init.wait()
+        self.assertTrue(init.wait() == 0)
 
     def testPrettyName(self):
         """Try the --pretty-name option."""
-        CMD_PRETTY_NAME = b'bob-name-test'
-        bob = subprocess.Popen(args=(BIND10_EXE, '--pretty-name',
+        CMD_PRETTY_NAME = b'init-name-test'
+        init = subprocess.Popen(args=(BIND10_EXE, '--pretty-name',
             CMD_PRETTY_NAME), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
-        started_ok = self._waitForString(bob, '[bind10] BIND 10 started')
+        started_ok = self._waitForString(init, '[bind10] BIND 10 started')
         self.assertTrue(started_ok)
         ps = subprocess.Popen(args=("ps", "axo", "pid,comm"),
                               stdout=subprocess.PIPE)
@@ -145,13 +160,13 @@ class TestBossArgs(unittest.TestCase):
             s = ps.stdout.readline()
             if s == '': break
             (pid,comm) = s.split(None, 1)
-            if int(pid) == bob.pid:
+            if int(pid) == init.pid:
                 command = comm
                 break
         self.assertEqual(command, CMD_PRETTY_NAME + b'\n')
         time.sleep(0.1)
-        bob.terminate()
-        bob.wait()
+        init.terminate()
+        init.wait()
 
 if __name__ == '__main__':
     unittest.main()

Fichier diff supprimé car celui-ci est trop grand
+ 541 - 537
src/bin/bind10/tests/bind10_test.py.in


+ 108 - 69
src/bin/bindctl/bindcmd.py

@@ -25,7 +25,7 @@ from bindctl.moduleinfo import *
 from bindctl.cmdparse import BindCmdParser
 from bindctl import command_sets
 from xml.dom import minidom
-import isc
+import isc.config
 import isc.cc.data
 import http.client
 import json
@@ -39,6 +39,7 @@ import csv
 import pwd
 import getpass
 import copy
+import errno
 
 try:
     from collections import OrderedDict
@@ -123,6 +124,11 @@ class BindCmdInterpreter(Cmd):
             self.csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir + \
                 os.sep + '.bind10' + os.sep
 
+    def _print(self, *args):
+        '''Simple wrapper around calls to print that can be overridden in
+           unit tests.'''
+        print(*args)
+
     def _get_session_id(self):
         '''Generate one session id for the connection. '''
         rand = os.urandom(16)
@@ -150,19 +156,19 @@ WARNING: Python readline module isn't available, so the command line editor
                 return 1
 
             self.cmdloop()
-            print('\nExit from bindctl')
+            self._print('\nExit from bindctl')
             return 0
         except FailToLogin as err:
             # error already printed when this was raised, ignoring
             return 1
         except KeyboardInterrupt:
-            print('\nExit from bindctl')
+            self._print('\nExit from bindctl')
             return 0
         except socket.error as err:
-            print('Failed to send request, the connection is closed')
+            self._print('Failed to send request, the connection is closed')
             return 1
         except http.client.CannotSendRequest:
-            print('Can not send request, the connection is busy')
+            self._print('Can not send request, the connection is busy')
             return 1
 
     def _get_saved_user_info(self, dir, file_name):
@@ -181,7 +187,8 @@ WARNING: Python readline module isn't available, so the command line editor
             for row in users_info:
                 users.append([row[0], row[1]])
         except (IOError, IndexError) as err:
-            print("Error reading saved username and password from %s%s: %s" % (dir, file_name, err))
+            self._print("Error reading saved username and password "
+                        "from %s%s: %s" % (dir, file_name, err))
         finally:
             if csvfile:
                 csvfile.close()
@@ -201,12 +208,48 @@ WARNING: Python readline module isn't available, so the command line editor
             writer.writerow([username, passwd])
             csvfile.close()
         except IOError as err:
-            print("Error saving user information:", err)
-            print("user info file name: %s%s" % (dir, file_name))
+            self._print("Error saving user information:", err)
+            self._print("user info file name: %s%s" % (dir, file_name))
             return False
 
         return True
 
+    def __print_check_ssl_msg(self):
+        self._print("Please check the logs of b10-cmdctl, there may "
+                    "be a problem accepting SSL connections, such "
+                    "as a permission problem on the server "
+                    "certificate file.")
+
+    def _try_login(self, username, password):
+        '''
+        Attempts to log in to cmdctl by sending a POST with
+        the given username and password.
+        On success of the POST (mind, not the login, only the network
+        operation), returns a tuple (response, data).
+        On failure, raises a FailToLogin exception, and prints some
+        information on the failure.
+        This call is essentially 'private', but made 'protected' for
+        easier testing.
+        '''
+        param = {'username': username, 'password' : password}
+        try:
+            response = self.send_POST('/login', param)
+            data = response.read().decode()
+            # return here (will raise error after try block)
+            return (response, data)
+        except ssl.SSLError as err:
+            self._print("SSL error while sending login information: ", err)
+            if err.errno == ssl.SSL_ERROR_EOF:
+                self.__print_check_ssl_msg()
+        except socket.error as err:
+            self._print("Socket error while sending login information: ", err)
+            # An SSL setup error can also bubble up as a plain CONNRESET...
+            # (on some systems it usually does)
+            if err.errno == errno.ECONNRESET:
+                self.__print_check_ssl_msg()
+            pass
+        raise FailToLogin()
+
     def login_to_cmdctl(self):
         '''Login to cmdctl with the username and password given by
         the user. After the login is sucessful, the username and
@@ -217,41 +260,30 @@ WARNING: Python readline module isn't available, so the command line editor
         # Look at existing username/password combinations and try to log in
         users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
         for row in users:
-            param = {'username': row[0], 'password' : row[1]}
-            try:
-                response = self.send_POST('/login', param)
-                data = response.read().decode()
-            except socket.error as err:
-                print("Socket error while sending login information:", err)
-                raise FailToLogin()
+            response, data = self._try_login(row[0], row[1])
 
             if response.status == http.client.OK:
                 # Is interactive?
                 if sys.stdin.isatty():
-                    print(data + ' login as ' + row[0])
+                    self._print(data + ' login as ' + row[0])
                 return True
 
         # No valid logins were found, prompt the user for a username/password
         count = 0
-        print('No stored password file found, please see sections '
+        self._print('No stored password file found, please see sections '
               '"Configuration specification for b10-cmdctl" and "bindctl '
               'command-line options" of the BIND 10 guide.')
         while True:
             count = count + 1
             if count > 3:
-                print("Too many authentication failures")
+                self._print("Too many authentication failures")
                 return False
 
             username = input("Username: ")
             passwd = getpass.getpass()
-            param = {'username': username, 'password' : passwd}
-            try:
-                response = self.send_POST('/login', param)
-                data = response.read().decode()
-                print(data)
-            except socket.error as err:
-                print("Socket error while sending login information:", err)
-                raise FailToLogin()
+
+            response, data = self._try_login(username, passwd)
+            self._print(data)
 
             if response.status == http.client.OK:
                 self._save_user_info(username, passwd, self.csv_file_dir,
@@ -449,25 +481,26 @@ WARNING: Python readline module isn't available, so the command line editor
         pass
 
     def do_help(self, name):
-        print(CONST_BINDCTL_HELP)
+        self._print(CONST_BINDCTL_HELP)
         for k in self.modules.values():
             n = k.get_name()
             if len(n) >= CONST_BINDCTL_HELP_INDENT_WIDTH:
-                print("    %s" % n)
-                print(textwrap.fill(k.get_desc(),
-                      initial_indent="            ",
-                      subsequent_indent="    " +
-                      " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
-                      width=70))
+                self._print("    %s" % n)
+                self._print(textwrap.fill(k.get_desc(),
+                            initial_indent="            ",
+                            subsequent_indent="    " +
+                            " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+                            width=70))
             else:
-                print(textwrap.fill("%s%s%s" %
-                    (k.get_name(),
-                     " "*(CONST_BINDCTL_HELP_INDENT_WIDTH - len(k.get_name())),
-                     k.get_desc()),
-                    initial_indent="    ",
-                    subsequent_indent="    " +
-                    " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
-                    width=70))
+                self._print(textwrap.fill("%s%s%s" %
+                            (k.get_name(),
+                            " "*(CONST_BINDCTL_HELP_INDENT_WIDTH -
+                                 len(k.get_name())),
+                            k.get_desc()),
+                            initial_indent="    ",
+                            subsequent_indent="    " +
+                            " " * CONST_BINDCTL_HELP_INDENT_WIDTH,
+                            width=70))
 
     def onecmd(self, line):
         if line == 'EOF' or line.lower() == "quit":
@@ -642,20 +675,20 @@ WARNING: Python readline module isn't available, so the command line editor
             self._validate_cmd(cmd)
             self._handle_cmd(cmd)
         except (IOError, http.client.HTTPException) as err:
-            print('Error: ', err)
+            self._print('Error: ', err)
         except BindCtlException as err:
-            print("Error! ", err)
+            self._print("Error! ", err)
             self._print_correct_usage(err)
         except isc.cc.data.DataTypeError as err:
-            print("Error! ", err)
+            self._print("Error! ", err)
         except isc.cc.data.DataTypeError as dte:
-            print("Error: " + str(dte))
+            self._print("Error: " + str(dte))
         except isc.cc.data.DataNotFoundError as dnfe:
-            print("Error: " + str(dnfe))
+            self._print("Error: " + str(dnfe))
         except isc.cc.data.DataAlreadyPresentError as dape:
-            print("Error: " + str(dape))
+            self._print("Error: " + str(dape))
         except KeyError as ke:
-            print("Error: missing " + str(ke))
+            self._print("Error: missing " + str(ke))
 
     def _print_correct_usage(self, ept):
         if isinstance(ept, CmdUnknownModuleSyntaxError):
@@ -704,7 +737,8 @@ WARNING: Python readline module isn't available, so the command line editor
             module_name = identifier.split('/')[1]
             if module_name != "" and (self.config_data is None or \
                not self.config_data.have_specification(module_name)):
-                print("Error: Module '" + module_name + "' unknown or not running")
+                self._print("Error: Module '" + module_name +
+                            "' unknown or not running")
                 return
 
         if cmd.command == "show":
@@ -718,7 +752,9 @@ WARNING: Python readline module isn't available, so the command line editor
                     #identifier
                     identifier += cmd.params['argument']
                 else:
-                    print("Error: unknown argument " + cmd.params['argument'] + ", or multiple identifiers given")
+                    self._print("Error: unknown argument " +
+                                cmd.params['argument'] +
+                                ", or multiple identifiers given")
                     return
             values = self.config_data.get_value_maps(identifier, show_all)
             for value_map in values:
@@ -746,13 +782,14 @@ WARNING: Python readline module isn't available, so the command line editor
                     line += "(default)"
                 if value_map['modified']:
                     line += "(modified)"
-                print(line)
+                self._print(line)
         elif cmd.command == "show_json":
             if identifier == "":
-                print("Need at least the module to show the configuration in JSON format")
+                self._print("Need at least the module to show the "
+                            "configuration in JSON format")
             else:
                 data, default = self.config_data.get_value(identifier)
-                print(json.dumps(data))
+                self._print(json.dumps(data))
         elif cmd.command == "add":
             self.config_data.add_value(identifier,
                                        cmd.params.get('value_or_name'),
@@ -764,7 +801,7 @@ WARNING: Python readline module isn't available, so the command line editor
                 self.config_data.remove_value(identifier, None)
         elif cmd.command == "set":
             if 'identifier' not in cmd.params:
-                print("Error: missing identifier or value")
+                self._print("Error: missing identifier or value")
             else:
                 parsed_value = None
                 try:
@@ -781,9 +818,9 @@ WARNING: Python readline module isn't available, so the command line editor
             try:
                 self.config_data.commit()
             except isc.config.ModuleCCSessionError as mcse:
-                print(str(mcse))
+                self._print(str(mcse))
         elif cmd.command == "diff":
-            print(self.config_data.get_local_changes())
+            self._print(self.config_data.get_local_changes())
         elif cmd.command == "go":
             self.go(identifier)
 
@@ -803,7 +840,7 @@ WARNING: Python readline module isn't available, so the command line editor
         # check if exists, if not, revert and error
         v,d = self.config_data.get_value(new_location)
         if v is None:
-            print("Error: " + identifier + " not found")
+            self._print("Error: " + identifier + " not found")
             return
 
         self.location = new_location
@@ -818,7 +855,7 @@ WARNING: Python readline module isn't available, so the command line editor
                 with open(command.params['filename']) as command_file:
                     commands = command_file.readlines()
             except IOError as ioe:
-                print("Error: " + str(ioe))
+                self._print("Error: " + str(ioe))
                 return
         elif command_sets.has_command_set(command.command):
             commands = command_sets.get_commands(command.command)
@@ -836,7 +873,7 @@ WARNING: Python readline module isn't available, so the command line editor
     def __show_execute_commands(self, commands):
         '''Prints the command list without executing them'''
         for line in commands:
-            print(line.strip())
+            self._print(line.strip())
 
     def __apply_execute_commands(self, commands):
         '''Applies the configuration commands from the given iterator.
@@ -857,18 +894,19 @@ WARNING: Python readline module isn't available, so the command line editor
             for line in commands:
                 line = line.strip()
                 if verbose:
-                    print(line)
+                    self._print(line)
                 if line.startswith('#') or len(line) == 0:
                     continue
                 elif line.startswith('!'):
                     if re.match('^!echo ', line, re.I) and len(line) > 6:
-                        print(line[6:])
+                        self._print(line[6:])
                     elif re.match('^!verbose\s+on\s*$', line, re.I):
                         verbose = True
                     elif re.match('^!verbose\s+off$', line, re.I):
                         verbose = False
                     else:
-                        print("Warning: ignoring unknown directive: " + line)
+                        self._print("Warning: ignoring unknown directive: " +
+                                    line)
                 else:
                     cmd = BindCmdParser(line)
                     self._validate_cmd(cmd)
@@ -879,12 +917,12 @@ WARNING: Python readline module isn't available, so the command line editor
                 isc.cc.data.DataNotFoundError,
                 isc.cc.data.DataAlreadyPresentError,
                 KeyError) as err:
-            print('Error: ', err)
-            print()
-            print('Depending on the contents of the script, and which')
-            print('commands it has called, there can be committed and')
-            print('local changes. It is advised to check your settings,')
-            print('and revert local changes with "config revert".')
+            self._print('Error: ', err)
+            self._print()
+            self._print('Depending on the contents of the script, and which')
+            self._print('commands it has called, there can be committed and')
+            self._print('local changes. It is advised to check your settings')
+            self._print(', and revert local changes with "config revert".')
 
     def apply_cmd(self, cmd):
         '''Handles a general module command'''
@@ -898,6 +936,7 @@ WARNING: Python readline module isn't available, so the command line editor
         # The reply is a string containing JSON data,
         # parse it, then prettyprint
         if data != "" and data != "{}":
-            print(json.dumps(json.loads(data), sort_keys=True, indent=4))
+            self._print(json.dumps(json.loads(data), sort_keys=True,
+                                   indent=4))
 
 

+ 2 - 5
src/bin/bindctl/bindctl.xml

@@ -218,7 +218,7 @@
        <command>config</command> for Configuration commands.
 <!-- TODO: or is config from the cfgmgr module? -->
        Additional modules may be available, such as
-       <command>Boss</command>, <command>Xfrin</command>, and
+       <command>Init</command>, <command>Xfrin</command>, and
        <command>Auth</command>.
      </para>
 
@@ -305,16 +305,13 @@ configuration location.
     <title>SEE ALSO</title>
     <para>
       <citerefentry>
-        <refentrytitle>b10-auth</refentrytitle><manvolnum>8</manvolnum>
-      </citerefentry>,
-      <citerefentry>
         <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
         <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
-        <refentrytitle>b10-xfrin</refentrytitle><manvolnum>8</manvolnum>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>

+ 1 - 1
src/bin/bindctl/bindctl_main.py.in

@@ -34,7 +34,7 @@ isc.util.process.rename()
 # number, and the overall BIND 10 version number (set in configure.ac).
 VERSION = "bindctl 20110217 (BIND 10 @PACKAGE_VERSION@)"
 
-DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Boss/start_auth', 'Recurse/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
+DEFAULT_IDENTIFIER_DESC = "The identifier specifies the config item. Child elements are separated with the '/' character. List indices can be specified with '[i]', where i is an integer specifying the index, starting with 0. Examples: 'Init/start_auth', 'Auth/listen_on[0]/address'. If no identifier is given, shows the item at the current location."
 
 def prepare_config_commands(tool):
     '''Prepare fixed commands for local configuration editing'''

+ 12 - 12
src/bin/bindctl/command_sets.py

@@ -35,21 +35,21 @@ command_sets = {
         'commands':
             [
             '!echo adding Authoritative server component',
-            'config add /Boss/components b10-auth',
-            'config set /Boss/components/b10-auth/kind needed',
-            'config set /Boss/components/b10-auth/special auth',
+            'config add /Init/components b10-auth',
+            'config set /Init/components/b10-auth/kind needed',
+            'config set /Init/components/b10-auth/special auth',
             '!echo adding Xfrin component',
-            'config add /Boss/components b10-xfrin',
-            'config set /Boss/components/b10-xfrin/address Xfrin',
-            'config set /Boss/components/b10-xfrin/kind dispensable',
+            'config add /Init/components b10-xfrin',
+            'config set /Init/components/b10-xfrin/address Xfrin',
+            'config set /Init/components/b10-xfrin/kind dispensable',
             '!echo adding Xfrout component',
-            'config add /Boss/components b10-xfrout',
-            'config set /Boss/components/b10-xfrout/address Xfrout',
-            'config set /Boss/components/b10-xfrout/kind dispensable',
+            'config add /Init/components b10-xfrout',
+            'config set /Init/components/b10-xfrout/address Xfrout',
+            'config set /Init/components/b10-xfrout/kind dispensable',
             '!echo adding Zone Manager component',
-            'config add /Boss/components b10-zonemgr',
-            'config set /Boss/components/b10-zonemgr/address Zonemgr',
-            'config set /Boss/components/b10-zonemgr/kind dispensable',
+            'config add /Init/components b10-zonemgr',
+            'config set /Init/components/b10-zonemgr/address Zonemgr',
+            'config set /Init/components/b10-zonemgr/kind dispensable',
             '!echo Components added. Please enter "config commit" to',
             '!echo finalize initial setup and run the components.'
             ]

+ 15 - 0
src/bin/bindctl/mycollections.py

@@ -1,3 +1,18 @@
+# Copyright (C) 2010  Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM 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.
+
 from collections import MutableMapping
 
 class OrderedDict(dict, MutableMapping):

+ 1 - 1
src/bin/bindctl/run_bindctl.sh.in

@@ -20,7 +20,7 @@ export PYTHON_EXEC
 
 BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
 
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 114 - 11
src/bin/bindctl/tests/bindctl_test.py

@@ -18,11 +18,14 @@ import unittest
 import isc.cc.data
 import os
 import io
+import errno
 import sys
 import socket
+import ssl
 import http.client
 import pwd
 import getpass
+import re
 from optparse import OptionParser
 from isc.config.config_data import ConfigData, MultiConfigData
 from isc.config.module_spec import ModuleSpec
@@ -335,6 +338,8 @@ class TestConfigCommands(unittest.TestCase):
         self.tool.add_module_info(mod_info)
         self.tool.config_data = FakeCCSession()
         self.stdout_backup = sys.stdout
+        self.printed_messages = []
+        self.tool._print = self.store_print
 
     def test_precmd(self):
         def update_all_modules_info():
@@ -347,6 +352,111 @@ class TestConfigCommands(unittest.TestCase):
         precmd('EOF')
         self.assertRaises(socket.error, precmd, 'continue')
 
+    def store_print(self, *args):
+        '''Method to override _print in BindCmdInterpreter.
+           Instead of printing the values, appends the argument tuple
+           to the list in self.printed_messages'''
+        self.printed_messages.append(" ".join(map(str, args)))
+
+    def __check_printed_message(self, expected_message, printed_message):
+        self.assertIsNotNone(re.match(expected_message, printed_message),
+                             "Printed message '" + printed_message +
+                             "' does not match '" + expected_message + "'")
+
+    def __check_printed_messages(self, expected_messages):
+        '''Helper test function to check the printed messages against a list
+           of regexps'''
+        self.assertEqual(len(expected_messages), len(self.printed_messages))
+        for _ in map(self.__check_printed_message,
+                     expected_messages,
+                     self.printed_messages):
+            pass
+
+    def test_try_login(self):
+        # Make sure __try_login raises the correct exception
+        # upon failure of either send_POST or the read() on the
+        # response
+
+        orig_send_POST = self.tool.send_POST
+        expected_printed_messages = []
+        try:
+            def send_POST_raiseImmediately(self, params):
+                raise socket.error("test error")
+
+            self.tool.send_POST = send_POST_raiseImmediately
+            self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
+            expected_printed_messages.append(
+                'Socket error while sending login information:  test error')
+            self.__check_printed_messages(expected_printed_messages)
+
+            def create_send_POST_raiseOnRead(exception):
+                '''Create a replacement send_POST() method that raises
+                   the given exception when read() is called on the value
+                   returned from send_POST()'''
+                def send_POST_raiseOnRead(self, params):
+                    class MyResponse:
+                        def read(self):
+                            raise exception
+                    return MyResponse()
+                return send_POST_raiseOnRead
+
+            # basic socket error
+            self.tool.send_POST =\
+                create_send_POST_raiseOnRead(socket.error("read error"))
+            self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
+            expected_printed_messages.append(
+                'Socket error while sending login information:  read error')
+            self.__check_printed_messages(expected_printed_messages)
+
+            # connection reset
+            exc = socket.error("connection reset")
+            exc.errno = errno.ECONNRESET
+            self.tool.send_POST =\
+                create_send_POST_raiseOnRead(exc)
+            self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
+            expected_printed_messages.append(
+                'Socket error while sending login information:  '
+                'connection reset')
+            expected_printed_messages.append(
+                'Please check the logs of b10-cmdctl, there may be a '
+                'problem accepting SSL connections, such as a permission '
+                'problem on the server certificate file.'
+            )
+            self.__check_printed_messages(expected_printed_messages)
+
+            # 'normal' SSL error
+            exc = ssl.SSLError()
+            self.tool.send_POST =\
+                create_send_POST_raiseOnRead(exc)
+            self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
+            expected_printed_messages.append(
+                'SSL error while sending login information:  .*')
+            self.__check_printed_messages(expected_printed_messages)
+
+            # 'EOF' SSL error
+            exc = ssl.SSLError()
+            exc.errno = ssl.SSL_ERROR_EOF
+            self.tool.send_POST =\
+                create_send_POST_raiseOnRead(exc)
+            self.assertRaises(FailToLogin, self.tool._try_login, "foo", "bar")
+            expected_printed_messages.append(
+                'SSL error while sending login information: .*')
+            expected_printed_messages.append(
+                'Please check the logs of b10-cmdctl, there may be a '
+                'problem accepting SSL connections, such as a permission '
+                'problem on the server certificate file.'
+            )
+            self.__check_printed_messages(expected_printed_messages)
+
+            # any other exception should be passed through
+            self.tool.send_POST =\
+                create_send_POST_raiseOnRead(ImportError())
+            self.assertRaises(ImportError, self.tool._try_login, "foo", "bar")
+            self.__check_printed_messages(expected_printed_messages)
+
+        finally:
+            self.tool.send_POST = orig_send_POST
+
     def test_run(self):
         def login_to_cmdctl():
             return True
@@ -360,29 +470,22 @@ class TestConfigCommands(unittest.TestCase):
         self.tool.conn.sock = FakeSocket()
         self.tool.conn.sock.close()
 
-        # validate log message for socket.err
-        socket_err_output = io.StringIO()
-        sys.stdout = socket_err_output
         self.assertEqual(1, self.tool.run())
 
         # First few lines may be some kind of heading, or a warning that
         # Python readline is unavailable, so we do a sub-string check.
         self.assertIn("Failed to send request, the connection is closed",
-                      socket_err_output.getvalue())
-
-        socket_err_output.close()
+                      self.printed_messages)
+        self.assertEqual(1, len(self.printed_messages))
 
         # validate log message for http.client.CannotSendRequest
-        cannot_send_output = io.StringIO()
-        sys.stdout = cannot_send_output
         self.assertEqual(1, self.tool.run())
 
         # First few lines may be some kind of heading, or a warning that
         # Python readline is unavailable, so we do a sub-string check.
         self.assertIn("Can not send request, the connection is busy",
-                      cannot_send_output.getvalue())
-
-        cannot_send_output.close()
+                      self.printed_messages)
+        self.assertEqual(2, len(self.printed_messages))
 
     def test_apply_cfg_command_int(self):
         self.tool.location = '/'

+ 1 - 1
src/bin/cfgmgr/b10-cfgmgr.py.in

@@ -115,7 +115,7 @@ def main():
         cm.read_config()
         for ppath in PLUGIN_PATHS:
             load_plugins(ppath, cm)
-        cm.notify_boss()
+        cm.notify_b10_init()
         cm.run()
     except SessionError as se:
         logger.fatal(CFGMGR_CC_SESSION_ERROR, se)

+ 8 - 2
src/bin/cfgmgr/b10-cfgmgr.xml

@@ -65,7 +65,7 @@
       The <command>bindctl</command> can be used to talk to this
       configuration manager via a <command>b10-cmdctl</command> connection.
     </para>
-     
+
 <!-- TODO: briefly explain why both msqg channel and cmdctl communication -->
     <para>
       This daemon communicates over a <command>b10-msgq</command> C-Channel
@@ -148,7 +148,13 @@
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
-        <refentrytitle>msgq</refentrytitle><manvolnum>8</manvolnum>
+        <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>.
+      <citerefentry>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
+        <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>.
     </para>
   </refsect1>

+ 4 - 4
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in

@@ -27,7 +27,7 @@ class MyConfigManager:
     def __init__(self, path, filename, session=None, rename_config_file=False):
         self._path = path
         self.read_config_called = False
-        self.notify_boss_called = False
+        self.notify_b10_init_called = False
         self.run_called = False
         self.write_config_called = False
         self.rename_config_called = False
@@ -37,8 +37,8 @@ class MyConfigManager:
     def read_config(self):
         self.read_config_called = True
 
-    def notify_boss(self):
-        self.notify_boss_called = True
+    def notify_b10_init(self):
+        self.notify_b10_init_called = True
 
     def run(self):
         self.run_called = True
@@ -89,7 +89,7 @@ class TestConfigManagerStartup(unittest.TestCase):
         b.load_plugins = orig_load
 
         self.assertTrue(b.cm.read_config_called)
-        self.assertTrue(b.cm.notify_boss_called)
+        self.assertTrue(b.cm.notify_b10_init_called)
         self.assertTrue(b.cm.run_called)
         self.assertTrue(self.loaded_plugins)
         # if there are no changes, config is not written

+ 6 - 11
src/bin/cmdctl/Makefile.am

@@ -11,17 +11,12 @@ pylogmessagedir = $(pyexecdir)/isc/log_messages/
 
 b10_cmdctldir = $(pkgdatadir)
 
-# NOTE: this will overwrite on install
-# So these generic copies are placed in share/bind10 instead of to etc
-# Admin or packageer will need to put into place manually.
+USERSFILES = cmdctl-accounts.csv
+CERTFILES = cmdctl-keyfile.pem cmdctl-certfile.pem
 
-CMDCTL_CONFIGURATIONS = cmdctl-accounts.csv
-CMDCTL_CONFIGURATIONS += cmdctl-keyfile.pem cmdctl-certfile.pem
+b10_cmdctl_DATA = cmdctl.spec
 
-b10_cmdctl_DATA = $(CMDCTL_CONFIGURATIONS)
-b10_cmdctl_DATA += cmdctl.spec
-
-EXTRA_DIST = $(CMDCTL_CONFIGURATIONS)
+EXTRA_DIST = $(USERSFILES)
 
 CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
 CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
@@ -55,7 +50,7 @@ $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py : cmdctl_messages.mes
 	-d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/cmdctl_messages.mes
 
 # this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
-b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
+b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py $(CERTFILES)
 	$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
 	chmod a+x $@
 
@@ -76,7 +71,7 @@ if INSTALL_CONFIGURATIONS
 # because these file will contain sensitive information.
 install-data-local:
 	$(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
-	for f in $(CMDCTL_CONFIGURATIONS) ; do	\
+	for f in $(CERTFILES) ; do	\
 	  if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then	\
 	    ${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ;	\
 	  fi ;	\

+ 3 - 0
src/bin/cmdctl/b10-certgen.xml

@@ -172,6 +172,9 @@
       <citerefentry>
         <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
+      <citerefentry>
+        <refentrytitle>bindctl</refentrytitle><manvolnum>1</manvolnum>
+      </citerefentry>,
       <citetitle>BIND 10 Guide</citetitle>.
     </para>
   </refsect1>

+ 5 - 2
src/bin/cmdctl/b10-cmdctl.xml

@@ -78,7 +78,7 @@
 
   <refsect1>
     <title>OPTIONS</title>
-    
+
     <para>The arguments are as follows:</para>
 
     <variablelist>
@@ -175,7 +175,7 @@
       <command>shutdown</command> exits <command>b10-cmdctl</command>.
       This has an optional <varname>pid</varname> argument to
       select the process ID to stop.
-      (Note that the BIND 10 boss process may restart this service
+      (Note that the b10-init process may restart this service
       if configured.)
     </para>
 
@@ -209,6 +209,9 @@
         <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>

+ 42 - 22
src/bin/cmdctl/cmdctl.py.in

@@ -82,6 +82,18 @@ SPECFILE_LOCATION = SPECFILE_PATH + os.sep + "cmdctl.spec"
 class CmdctlException(Exception):
     pass
 
+def check_file(file_name):
+    # TODO: Check contents of certificate file
+    if not os.path.exists(file_name):
+        raise CmdctlException("'%s' does not exist" % file_name)
+
+    if not os.path.isfile(file_name):
+        raise CmdctlException("'%s' is not a file" % file_name)
+
+    if not os.access(file_name, os.R_OK):
+        raise CmdctlException("'%s' is not readable" % file_name)
+
+
 class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
     '''https connection request handler.
     Currently only GET and POST are supported.  '''
@@ -153,7 +165,6 @@ class SecureHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
         self.end_headers()
         self.wfile.write(json.dumps(reply).encode())
 
-
     def _handle_login(self):
         if self._is_user_logged_in():
             return http.client.OK, ["user has already login"]
@@ -278,12 +289,14 @@ class CommandControl():
             if key == 'version':
                 continue
             elif key in ['key_file', 'cert_file']:
-                #TODO, only check whether the file exist,
-                # further check need to be done: eg. whether
-                # the private/certificate is valid.
+                # TODO: we only check whether the file exist, is a
+                # file, and is readable; but further check need to be done:
+                # eg. whether the private/certificate is valid.
                 path = new_config[key]
-                if not os.path.exists(path):
-                    errstr = "the file doesn't exist: " + path
+                try:
+                    check_file(path)
+                except CmdctlException as cce:
+                    errstr = str(cce)
             elif key == 'accounts_file':
                 errstr = self._accounts_file_check(new_config[key])
             else:
@@ -326,7 +339,7 @@ class CommandControl():
                     self.modules_spec[args[0]] = args[1]
 
         elif command == ccsession.COMMAND_SHUTDOWN:
-            #When cmdctl get 'shutdown' command from boss,
+            #When cmdctl get 'shutdown' command from b10-init,
             #shutdown the outer httpserver.
             self._module_cc.send_stopping()
             self._httpserver.shutdown()
@@ -416,8 +429,13 @@ class CommandControl():
             # Process the command sent to cmdctl directly.
             answer = self.command_handler(command_name, params)
         else:
+            # FIXME: Due to the fact that we use a separate session
+            # from the module one (due to threads and blocking), and
+            # because the plain cc session does not have the high-level
+            # rpc-call method, we use the low-level way and create the
+            # command ourselves.
             msg = ccsession.create_command(command_name, params)
-            seq = self._cc.group_sendmsg(msg, module_name)
+            seq = self._cc.group_sendmsg(msg, module_name, want_answer=True)
             logger.debug(DBG_CMDCTL_MESSAGING, CMDCTL_COMMAND_SENT,
                          command_name, module_name)
             #TODO, it may be blocked, msqg need to add a new interface waiting in timeout.
@@ -524,27 +542,27 @@ class SecureHTTPServer(socketserver_mixin.NoPollMixIn,
         self.user_sessions[session_id] = time.time()
 
     def _check_key_and_cert(self, key, cert):
-        # TODO, check the content of key/certificate file
-        if not os.path.exists(key):
-            raise CmdctlException("key file '%s' doesn't exist " % key)
-
-        if not os.path.exists(cert):
-            raise CmdctlException("certificate file '%s' doesn't exist " % cert)
+        check_file(key)
+        check_file(cert);
 
     def _wrap_socket_in_ssl_context(self, sock, key, cert):
         try:
             self._check_key_and_cert(key, cert)
             ssl_sock = ssl.wrap_socket(sock,
-                                      server_side = True,
-                                      certfile = cert,
-                                      keyfile = key,
-                                      ssl_version = ssl.PROTOCOL_SSLv23)
+                                       server_side=True,
+                                       certfile=cert,
+                                       keyfile=key,
+                                       ssl_version=ssl.PROTOCOL_SSLv23)
+            # Return here (if control leaves this blocks it will raise an
+            # error)
             return ssl_sock
-        except (ssl.SSLError, CmdctlException) as err :
+        except ssl.SSLError as err:
             logger.error(CMDCTL_SSL_SETUP_FAILURE_USER_DENIED, err)
-            self.close_request(sock)
-            # raise socket error to finish the request
-            raise socket.error
+        except (CmdctlException, IOError) as cce:
+            logger.error(CMDCTL_SSL_SETUP_FAILURE_READING_CERT, cce)
+        self.close_request(sock)
+        # raise socket error to finish the request
+        raise socket.error
 
     def get_request(self):
         '''Get client request socket and wrap it in SSL context. '''
@@ -637,4 +655,6 @@ if __name__ == '__main__':
     if httpd:
         httpd.shutdown()
 
+    logger.info(CMDCTL_EXITING)
+
     sys.exit(result)

+ 10 - 0
src/bin/cmdctl/cmdctl_messages.mes

@@ -43,6 +43,9 @@ specific error is printed in the message.
 This debug message indicates that the given command has been sent to
 the given module.
 
+% CMDCTL_EXITING exiting
+The b10-cmdctl daemon is exiting.
+
 % CMDCTL_NO_SUCH_USER username not found in user database: %1
 A login attempt was made to b10-cmdctl, but the username was not known.
 Users can be added with the tool b10-cmdctl-usermgr.
@@ -58,6 +61,13 @@ with the tool b10-cmdctl-usermgr.
 This debug message indicates that the given command is being sent to
 the given module.
 
+% CMDCTL_SSL_SETUP_FAILURE_READING_CERT failed to read certificate or key: %1
+The b10-cmdctl daemon is unable to read either the certificate file or
+the private key file, and is therefore unable to accept any SSL connections.
+The specific error is printed in the message.
+The administrator should solve the issue with the files, or recreate them
+with the b10-certgen tool.
+
 % CMDCTL_SSL_SETUP_FAILURE_USER_DENIED failed to create an SSL connection (user denied): %1
 The user was denied because the SSL connection could not successfully
 be set up. The specific error is given in the log message. Possible

+ 1 - 1
src/bin/cmdctl/run_b10-cmdctl.sh.in

@@ -19,7 +19,7 @@ PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@}
 export PYTHON_EXEC
 
 CMD_CTRLD_PATH=@abs_top_builddir@/src/bin/cmdctl
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/xfr/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/python/isc/config:@abs_top_builddir@/src/lib/python/isc/acl/.libs:@abs_top_builddir@/src/lib/python/isc/datasrc/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 1 - 1
src/bin/cmdctl/tests/Makefile.am

@@ -25,7 +25,7 @@ endif
 	echo Running test: $$pytest ; \
 	$(LIBRARY_PATH_PLACEHOLDER) \
 	PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/cmdctl \
-	CMDCTL_SPEC_PATH=$(abs_top_builddir)/src/bin/cmdctl \
+	CMDCTL_BUILD_PATH=$(abs_top_builddir)/src/bin/cmdctl \
 	CMDCTL_SRC_PATH=$(abs_top_srcdir)/src/bin/cmdctl \
 	B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir) \
 	$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \

+ 115 - 45
src/bin/cmdctl/tests/cmdctl_test.py

@@ -17,17 +17,18 @@
 import unittest
 import socket
 import tempfile
+import stat
 import sys
 from cmdctl import *
 import isc.log
 
-SPEC_FILE_PATH = '..' + os.sep
-if 'CMDCTL_SPEC_PATH' in os.environ:
-    SPEC_FILE_PATH = os.environ['CMDCTL_SPEC_PATH'] + os.sep
+assert 'CMDCTL_SRC_PATH' in os.environ,\
+       "Please run this test with 'make check'"
+SRC_FILE_PATH = os.environ['CMDCTL_SRC_PATH'] + os.sep
 
-SRC_FILE_PATH = '..' + os.sep
-if 'CMDCTL_SRC_PATH' in os.environ:
-    SRC_FILE_PATH = os.environ['CMDCTL_SRC_PATH'] + os.sep
+assert 'CMDCTL_BUILD_PATH' in os.environ,\
+       "Please run this test with 'make check'"
+BUILD_FILE_PATH = os.environ['CMDCTL_BUILD_PATH'] + os.sep
 
 # Rewrite the class for unittest.
 class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
@@ -36,7 +37,7 @@ class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
 
     def send_response(self, rcode):
         self.rcode = rcode
-    
+
     def end_headers(self):
         pass
 
@@ -51,13 +52,13 @@ class MySecureHTTPRequestHandler(SecureHTTPRequestHandler):
         super().do_POST()
         self.wfile.close()
         os.remove('tmp.file')
-    
+
 
 class FakeSecureHTTPServer(SecureHTTPServer):
     def __init__(self):
         self.user_sessions = {}
         self.cmdctl = FakeCommandControlForTestRequestHandler()
-        self._verbose = True 
+        self._verbose = True
         self._user_infos = {}
         self.idle_timeout = 1200
         self._lock = threading.Lock()
@@ -71,6 +72,17 @@ class FakeCommandControlForTestRequestHandler(CommandControl):
     def send_command(self, mod, cmd, param):
         return 0, {}
 
+# context to temporarily make a file unreadable
+class UnreadableFile:
+    def __init__(self, file_name):
+        self.file_name = file_name
+        self.orig_mode = os.stat(file_name).st_mode
+
+    def __enter__(self):
+        os.chmod(self.file_name, self.orig_mode & ~stat.S_IRUSR)
+
+    def __exit__(self, type, value, traceback):
+        os.chmod(self.file_name, self.orig_mode)
 
 class TestSecureHTTPRequestHandler(unittest.TestCase):
     def setUp(self):
@@ -97,7 +109,7 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
         self.handler.path = '/abc'
         mod, cmd = self.handler._parse_request_path()
         self.assertTrue((mod == 'abc') and (cmd == None))
-        
+
         self.handler.path = '/abc/edf'
         mod, cmd = self.handler._parse_request_path()
         self.assertTrue((mod == 'abc') and (cmd == 'edf'))
@@ -125,20 +137,20 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
 
     def test_do_GET(self):
         self.handler.do_GET()
-        self.assertEqual(self.handler.rcode, http.client.BAD_REQUEST)    
-        
+        self.assertEqual(self.handler.rcode, http.client.BAD_REQUEST)
+
     def test_do_GET_1(self):
         self.handler.headers['cookie'] = 12345
         self.handler.do_GET()
-        self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED)    
+        self.assertEqual(self.handler.rcode, http.client.UNAUTHORIZED)
 
     def test_do_GET_2(self):
         self.handler.headers['cookie'] = 12345
         self.handler.server.user_sessions[12345] = time.time() + 1000000
         self.handler.path = '/how/are'
         self.handler.do_GET()
-        self.assertEqual(self.handler.rcode, http.client.NO_CONTENT)    
-    
+        self.assertEqual(self.handler.rcode, http.client.NO_CONTENT)
+
     def test_do_GET_3(self):
         self.handler.headers['cookie'] = 12346
         self.handler.server.user_sessions[12346] = time.time() + 1000000
@@ -146,8 +158,8 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
         for path in path_vec:
             self.handler.path = '/' + path
             self.handler.do_GET()
-            self.assertEqual(self.handler.rcode, http.client.OK)    
-    
+            self.assertEqual(self.handler.rcode, http.client.OK)
+
     def test_user_logged_in(self):
         self.handler.server.user_sessions = {}
         self.handler.session_id = 12345
@@ -243,8 +255,8 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
         self.assertEqual(http.client.BAD_REQUEST, rcode)
 
     def _gen_module_spec(self):
-        spec = { 'commands': [ 
-                  { 'command_name' :'command', 
+        spec = { 'commands': [
+                  { 'command_name' :'command',
                     'command_args': [ {
                             'item_name' : 'param1',
                             'item_type' : 'integer',
@@ -253,9 +265,9 @@ class TestSecureHTTPRequestHandler(unittest.TestCase):
                            } ],
                     'command_description' : 'cmd description'
                   }
-                ] 
+                ]
                }
-        
+
         return spec
 
     def test_handle_post_request_2(self):
@@ -290,13 +302,13 @@ class MyCommandControl(CommandControl):
         return {}
 
     def _setup_session(self):
-        spec_file = SPEC_FILE_PATH + 'cmdctl.spec'
+        spec_file = BUILD_FILE_PATH + 'cmdctl.spec'
         module_spec = isc.config.module_spec_from_file(spec_file)
         config = isc.config.config_data.ConfigData(module_spec)
         self._module_name = 'Cmdctl'
         self._cmdctl_config_data = config.get_full_config()
 
-    def _handle_msg_from_msgq(self): 
+    def _handle_msg_from_msgq(self):
         pass
 
 class TestCommandControl(unittest.TestCase):
@@ -305,7 +317,7 @@ class TestCommandControl(unittest.TestCase):
         self.old_stdout = sys.stdout
         sys.stdout = open(os.devnull, 'w')
         self.cmdctl = MyCommandControl(None, True)
-   
+
     def tearDown(self):
         sys.stdout.close()
         sys.stdout = self.old_stdout
@@ -320,7 +332,7 @@ class TestCommandControl(unittest.TestCase):
         old_env = os.environ
         if 'B10_FROM_SOURCE' in os.environ:
             del os.environ['B10_FROM_SOURCE']
-        self.cmdctl.get_cmdctl_config_data() 
+        self.cmdctl.get_cmdctl_config_data()
         self._check_config(self.cmdctl)
         os.environ = old_env
 
@@ -328,7 +340,7 @@ class TestCommandControl(unittest.TestCase):
         os.environ['B10_FROM_SOURCE'] = '../'
         self._check_config(self.cmdctl)
         os.environ = old_env
-    
+
     def test_parse_command_result(self):
         self.assertEqual({}, self.cmdctl._parse_command_result(1, {'error' : 1}))
         self.assertEqual({'a': 1}, self.cmdctl._parse_command_result(0, {'a' : 1}))
@@ -391,13 +403,13 @@ class TestCommandControl(unittest.TestCase):
         os.environ = old_env
 
         answer = self.cmdctl.config_handler({'key_file': '/user/non-exist_folder'})
-        self._check_answer(answer, 1, "the file doesn't exist: /user/non-exist_folder")
+        self._check_answer(answer, 1, "'/user/non-exist_folder' does not exist")
 
         answer = self.cmdctl.config_handler({'cert_file': '/user/non-exist_folder'})
-        self._check_answer(answer, 1, "the file doesn't exist: /user/non-exist_folder")
+        self._check_answer(answer, 1, "'/user/non-exist_folder' does not exist")
 
         answer = self.cmdctl.config_handler({'accounts_file': '/user/non-exist_folder'})
-        self._check_answer(answer, 1, 
+        self._check_answer(answer, 1,
                 "Invalid accounts file: [Errno 2] No such file or directory: '/user/non-exist_folder'")
 
         # Test with invalid accounts file
@@ -409,7 +421,7 @@ class TestCommandControl(unittest.TestCase):
         answer = self.cmdctl.config_handler({'accounts_file': file_name})
         self._check_answer(answer, 1, "Invalid accounts file: list index out of range")
         os.remove(file_name)
-    
+
     def test_send_command(self):
         rcode, value = self.cmdctl.send_command('Cmdctl', 'print_settings', None)
         self.assertEqual(rcode, 0)
@@ -424,7 +436,7 @@ class TestSecureHTTPServer(unittest.TestCase):
         self.old_stderr = sys.stderr
         sys.stdout = open(os.devnull, 'w')
         sys.stderr = sys.stdout
-        self.server = MySecureHTTPServer(('localhost', 8080), 
+        self.server = MySecureHTTPServer(('localhost', 8080),
                                          MySecureHTTPRequestHandler,
                                          MyCommandControl, verbose=True)
 
@@ -458,32 +470,90 @@ class TestSecureHTTPServer(unittest.TestCase):
         self.assertEqual(1, len(self.server._user_infos))
         self.assertTrue('root' in self.server._user_infos)
 
+    def test_check_file(self):
+        # Just some file that we know exists
+        file_name = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+        check_file(file_name)
+        with UnreadableFile(file_name):
+            self.assertRaises(CmdctlException, check_file, file_name)
+        self.assertRaises(CmdctlException, check_file, '/local/not-exist')
+        self.assertRaises(CmdctlException, check_file, '/')
+
+
     def test_check_key_and_cert(self):
+        keyfile = BUILD_FILE_PATH + 'cmdctl-keyfile.pem'
+        certfile = BUILD_FILE_PATH + 'cmdctl-certfile.pem'
+
+        # no exists
+        self.assertRaises(CmdctlException, self.server._check_key_and_cert,
+                          keyfile, '/local/not-exist')
+        self.assertRaises(CmdctlException, self.server._check_key_and_cert,
+                         '/local/not-exist', certfile)
+
+        # not a file
+        self.assertRaises(CmdctlException, self.server._check_key_and_cert,
+                          keyfile, '/')
         self.assertRaises(CmdctlException, self.server._check_key_and_cert,
-                         '/local/not-exist', 'cmdctl-keyfile.pem')
+                         '/', certfile)
 
-        self.server._check_key_and_cert(SRC_FILE_PATH + 'cmdctl-keyfile.pem',
-                                        SRC_FILE_PATH + 'cmdctl-certfile.pem')
+        # no read permission
+        with UnreadableFile(certfile):
+            self.assertRaises(CmdctlException,
+                              self.server._check_key_and_cert,
+                              keyfile, certfile)
+
+        with UnreadableFile(keyfile):
+            self.assertRaises(CmdctlException,
+                              self.server._check_key_and_cert,
+                              keyfile, certfile)
+
+        # All OK (also happens to check the context code above works)
+        self.server._check_key_and_cert(keyfile, certfile)
 
     def test_wrap_sock_in_ssl_context(self):
         sock = socket.socket()
-        self.assertRaises(socket.error, 
+
+        # Bad files should result in a socket.error raised by our own
+        # code in the basic file checks
+        self.assertRaises(socket.error,
                           self.server._wrap_socket_in_ssl_context,
-                          sock, 
-                          '../cmdctl-keyfile',
-                          '../cmdctl-certfile')
+                          sock,
+                          'no_such_file', 'no_such_file')
 
+        # Using a non-certificate file would cause an SSLError, which
+        # is caught by our code which then raises a basic socket.error
+        self.assertRaises(socket.error,
+                          self.server._wrap_socket_in_ssl_context,
+                          sock,
+                          BUILD_FILE_PATH + 'cmdctl.py',
+                          BUILD_FILE_PATH + 'cmdctl-certfile.pem')
+
+        # Should succeed
         sock1 = socket.socket()
-        self.server._wrap_socket_in_ssl_context(sock1, 
-                          SRC_FILE_PATH + 'cmdctl-keyfile.pem',
-                          SRC_FILE_PATH + 'cmdctl-certfile.pem')
+        ssl_sock = self.server._wrap_socket_in_ssl_context(sock1,
+                                   BUILD_FILE_PATH + 'cmdctl-keyfile.pem',
+                                   BUILD_FILE_PATH + 'cmdctl-certfile.pem')
+        self.assertTrue(isinstance(ssl_sock, ssl.SSLSocket))
+
+        # wrap_socket can also raise IOError, which should be caught and
+        # handled like the other errors.
+        # Force this by temporarily disabling our own file checks
+        orig_check_func = self.server._check_key_and_cert
+        try:
+            self.server._check_key_and_cert = lambda x,y: None
+            self.assertRaises(socket.error,
+                              self.server._wrap_socket_in_ssl_context,
+                              sock,
+                              'no_such_file', 'no_such_file')
+        finally:
+            self.server._check_key_and_cert = orig_check_func
 
 class TestFuncNotInClass(unittest.TestCase):
     def test_check_port(self):
-        self.assertRaises(OptionValueError, check_port, None, 'port', -1, None)        
-        self.assertRaises(OptionValueError, check_port, None, 'port', 65536, None)        
-        self.assertRaises(OptionValueError, check_addr, None, 'ipstr', 'a.b.d', None)        
-        self.assertRaises(OptionValueError, check_addr, None, 'ipstr', '1::0:a.b', None)        
+        self.assertRaises(OptionValueError, check_port, None, 'port', -1, None)
+        self.assertRaises(OptionValueError, check_port, None, 'port', 65536, None)
+        self.assertRaises(OptionValueError, check_addr, None, 'ipstr', 'a.b.d', None)
+        self.assertRaises(OptionValueError, check_addr, None, 'ipstr', '1::0:a.b', None)
 
 
 if __name__== "__main__":

+ 1 - 1
src/bin/dbutil/b10-dbutil.xml

@@ -93,7 +93,7 @@
       file.  It has the same name, with ".backup" appended to it.  If a
       file of that name already exists, the file will have the suffix
       ".backup-1".  If that exists, the file will be suffixed ".backup-2",
-      and so on). Exit status is 0 if the upgrade is either succesful or
+      and so on). Exit status is 0 if the upgrade is either successful or
       aborted by the user, and non-zero if there is an error.
     </para>
 

+ 7 - 1
src/bin/dbutil/dbutil.py.in

@@ -200,10 +200,16 @@ UPGRADES = [
             "CREATE INDEX nsec3_byhash_and_rdtype ON nsec3 " +
                 "(hash, rdtype)"
         ]
+    },
+
+    {'from': (2, 1), 'to': (2, 2),
+        'statements': [
+            "CREATE INDEX records_byrname_and_rdtype ON records (rname, rdtype)"
+        ]
     }
 
 # To extend this, leave the above statements in place and add another
-# dictionary to the list.  The "from" version should be (2, 1), the "to"
+# dictionary to the list.  The "from" version should be (2, 2), the "to"
 # version whatever the version the update is to, and the SQL statements are
 # the statements required to perform the upgrade.  This way, the upgrade
 # program will be able to upgrade both a V1.0 and a V2.0 database.

+ 4 - 1
src/bin/dbutil/run_dbutil.sh.in

@@ -20,7 +20,10 @@ export PYTHON_EXEC
 
 DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
 
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module.  #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python/isc/cc:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
 export PYTHONPATH
 
 # If necessary (rare cases), explicitly specify paths to dynamic libraries

+ 70 - 32
src/bin/dbutil/tests/dbutil_test.sh.in

@@ -165,7 +165,7 @@ upgrade_ok_test() {
     if [ $? -eq 0 ]
     then
         # Compare schema with the reference
-        get_schema $testdata/v2_1.sqlite3
+        get_schema $testdata/v2_2.sqlite3
         expected_schema=$db_schema
         get_schema $tempfile
         actual_schema=$db_schema
@@ -177,7 +177,7 @@ upgrade_ok_test() {
         fi
 
         # Check the version is set correctly
-        check_version $tempfile "V2.1"
+        check_version $tempfile "V2.2"
 
         # Check that a backup was made
         check_backup $1 $2
@@ -303,26 +303,32 @@ check_version_fail() {
 
 rm -f $tempfile $backupfile
 
-# Test 1 - check that the utility fails if the database does not exist
-echo "1.1. Non-existent database - check"
+# This is the section number that is echoed during tests. It is
+# incremented when each section is run.
+sec=0
+
+# Test: check that the utility fails if the database does not exist
+sec=`expr $sec + 1`
+echo $sec".1. Non-existent database - check"
 ${SHELL} ../run_dbutil.sh --check $tempfile
 failzero $?
 check_no_backup $tempfile $backupfile
 
-echo "1.2. Non-existent database - upgrade"
+echo $sec".2. Non-existent database - upgrade"
 ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
 failzero $?
 check_no_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
 
-# Test 2 - should fail to check an empty file and fail to upgrade it
-echo "2.1. Database is an empty file - check"
+# Test: should fail to check an empty file and fail to upgrade it
+sec=`expr $sec + 1`
+echo $sec".1. Database is an empty file - check"
 touch $tempfile
 check_version_fail $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-echo "2.2. Database is an empty file - upgrade"
+echo $sec".2. Database is an empty file - upgrade"
 touch $tempfile
 ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
 failzero $?
@@ -330,13 +336,13 @@ failzero $?
 check_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-
-echo "3.1. Database is not an SQLite file - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is not an SQLite file - check"
 echo "This is not an sqlite3 database" > $tempfile
 check_version_fail $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-echo "3.2. Database is not an SQLite file - upgrade"
+echo $sec".2. Database is not an SQLite file - upgrade"
 echo "This is not an sqlite3 database" > $tempfile
 ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile
 failzero $?
@@ -345,81 +351,113 @@ check_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "4.1. Database is an SQLite3 file without the schema table - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is an SQLite3 file without the schema table - check"
 check_version_fail $testdata/no_schema.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
-echo "4.1. Database is an SQLite3 file without the schema table - upgrade"
+echo $sec".1. Database is an SQLite3 file without the schema table - upgrade"
 upgrade_fail_test $testdata/no_schema.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "5.1. Database is an old V1 database - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is an old V1 database - check"
 check_version $testdata/old_v1.sqlite3 "V1.0"
 check_no_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-echo "5.2. Database is an old V1 database - upgrade"
+echo $sec".2. Database is an old V1 database - upgrade"
 upgrade_ok_test $testdata/old_v1.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "6.1. Database is new V1 database - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is new V1 database - check"
 check_version $testdata/new_v1.sqlite3 "V1.0"
 check_no_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-echo "6.2. Database is a new V1 database - upgrade"
+echo $sec".2. Database is a new V1 database - upgrade"
 upgrade_ok_test $testdata/new_v1.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "7.1. Database is V2.0 database - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is V2.0 database - check"
 check_version $testdata/v2_0.sqlite3 "V2.0"
 check_no_backup $tempfile $backupfile
 rm -f $tempfile $backupfile
 
-echo "7.2. Database is a V2.0 database - upgrade"
+echo $sec".2. Database is a V2.0 database - upgrade"
 upgrade_ok_test $testdata/v2_0.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "8.1. Database is V2.0 database with empty schema table - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is V2.1 database - check"
+check_version $testdata/v2_1.sqlite3 "V2.1"
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo $sec".2. Database is a V2.1 database - upgrade"
+upgrade_ok_test $testdata/v2_1.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+sec=`expr $sec + 1`
+echo $sec".1. Database is V2.2 database - check"
+check_version $testdata/v2_2.sqlite3 "V2.2"
+check_no_backup $tempfile $backupfile
+rm -f $tempfile $backupfile
+
+echo $sec".2. Database is a V2.2 database - upgrade"
+upgrade_ok_test $testdata/v2_2.sqlite3 $backupfile
+rm -f $tempfile $backupfile
+
+
+sec=`expr $sec + 1`
+echo $sec".1. Database is V2.0 database with empty schema table - check"
 check_version_fail $testdata/empty_version.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
-echo "8.2. Database is V2.0 database with empty schema table - upgrade"
+echo $sec".2. Database is V2.0 database with empty schema table - upgrade"
 upgrade_fail_test $testdata/empty_version.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "9.1. Database is V2.0 database with over-full schema table - check"
+sec=`expr $sec + 1`
+echo $sec".1. Database is V2.0 database with over-full schema table - check"
 check_version_fail $testdata/too_many_version.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
-echo "9.2. Database is V2.0 database with over-full schema table - upgrade"
+echo $sec".2. Database is V2.0 database with over-full schema table - upgrade"
 upgrade_fail_test $testdata/too_many_version.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "10.0. Upgrade corrupt database"
+sec=`expr $sec + 1`
+echo $sec". Upgrade corrupt database"
 upgrade_fail_test $testdata/corrupt.sqlite3 $backupfile
 rm -f $tempfile $backupfile
 
 
-echo "11. Record count test"
+sec=`expr $sec + 1`
+echo $sec". Record count test"
 record_count_test $testdata/new_v1.sqlite3
 rm -f $tempfile $backupfile
 
 
-echo "12. Backup file already exists"
+sec=`expr $sec + 1`
+echo $sec". Backup file already exists"
 touch $backupfile
 touch ${backupfile}-1
 upgrade_ok_test $testdata/v2_0.sqlite3 ${backupfile}-2
 rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2
 
 
-echo "13.1 Command-line errors"
+sec=`expr $sec + 1`
+echo $sec".1 Command-line errors"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 ${SHELL} ../run_dbutil.sh $tempfile
 failzero $?
@@ -437,22 +475,22 @@ ${SHELL} ../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile
 failzero $?
 rm -f $tempfile $backupfile
 
-echo "13.2 verbose flag"
+echo $sec".2 verbose flag"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 ${SHELL} ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile
 passzero $?
 rm -f $tempfile $backupfile
 
-echo "13.3 Interactive prompt - yes"
+echo $sec".3 Interactive prompt - yes"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 ${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
 Yes
 .
 passzero $?
-check_version $tempfile "V2.1"
+check_version $tempfile "V2.2"
 rm -f $tempfile $backupfile
 
-echo "13.4 Interactive prompt - no"
+echo $sec".4 Interactive prompt - no"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 ${SHELL} ../run_dbutil.sh --upgrade $tempfile << .
 no
@@ -462,7 +500,7 @@ diff $testdata/old_v1.sqlite3 $tempfile > /dev/null
 passzero $?
 rm -f $tempfile $backupfile
 
-echo "13.5 quiet flag"
+echo $sec".5 quiet flag"
 copy_file $testdata/old_v1.sqlite3 $tempfile
 ${SHELL} ../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep .
 failzero $?

+ 1 - 0
src/bin/dbutil/tests/testdata/Makefile.am

@@ -11,3 +11,4 @@ EXTRA_DIST += README
 EXTRA_DIST += too_many_version.sqlite3
 EXTRA_DIST += v2_0.sqlite3
 EXTRA_DIST += v2_1.sqlite3
+EXTRA_DIST += v2_2.sqlite3

BIN
src/bin/dbutil/tests/testdata/v2_2.sqlite3


+ 7 - 4
src/bin/ddns/b10-ddns.xml

@@ -56,8 +56,8 @@
     <para>The <command>b10-ddns</command> daemon provides the BIND 10
       Dynamic Update (DDNS) service, as specified in RFC 2136.
       Normally it is started by the
-      <citerefentry><refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum></citerefentry>
-      boss process.
+      <citerefentry><refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+      process.
     </para>
 
     <para>
@@ -119,7 +119,7 @@
         <listitem>
           <para>
             This value is ignored at this moment, but is provided for
-            compatibility with the <command>bind10</command> Boss process.
+            compatibility with the <command>b10-init</command> process.
           </para>
         </listitem>
       </varlistentry>
@@ -154,7 +154,7 @@
       <command>shutdown</command> exits <command>b10-ddns</command>.
       This has an optional <varname>pid</varname> argument to
       select the process ID to stop.
-      (Note that the BIND 10 boss process may restart this service
+      (Note that the b10-init process may restart this service
       if configured.)
     </para>
 
@@ -171,6 +171,9 @@
         <refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>,
       <citerefentry>

+ 28 - 41
src/bin/ddns/ddns.py.in

@@ -32,7 +32,8 @@ import isc.util.cio.socketsession
 import isc.server_common.tsig_keyring
 from isc.server_common.dns_tcp import DNSTCPContext
 from isc.datasrc import DataSourceClient
-from isc.server_common.auth_command import auth_loadzone_command
+from isc.server_common.auth_command import AUTH_LOADZONE_COMMAND, \
+    auth_loadzone_params
 import select
 import time
 import errno
@@ -134,7 +135,7 @@ def get_datasrc_client(cc_session):
     function will simply be removed.
 
     '''
-    HARDCODED_DATASRC_CLASS = RRClass.IN()
+    HARDCODED_DATASRC_CLASS = RRClass.IN
     file, is_default = cc_session.get_remote_config_value("Auth",
                                                           "database_file")
     # See xfrout.py:get_db_file() for this trick:
@@ -469,7 +470,7 @@ class DDNSServer:
             self.__request_msg.clear(Message.PARSE)
             # specify PRESERVE_ORDER as we need to handle each RR separately.
             self.__request_msg.from_wire(req_data, Message.PRESERVE_ORDER)
-            if self.__request_msg.get_opcode() != Opcode.UPDATE():
+            if self.__request_msg.get_opcode() != Opcode.UPDATE:
                 raise self.InternalError('Update request has unexpected '
                                          'opcode: ' +
                                          str(self.__request_msg.get_opcode()))
@@ -536,7 +537,7 @@ class DDNSServer:
                 else:
                     tcp_ctx.close()
         except socket.error as ex:
-            logger.warn(DDNS_RESPONSE_SOCKET_ERROR, ClientFormatter(dest), ex)
+            logger.warn(DDNS_RESPONSE_SOCKET_SEND_FAILED, ClientFormatter(dest), ex)
             return False
 
         return True
@@ -544,42 +545,38 @@ class DDNSServer:
     def __notify_start_forwarder(self):
         '''Notify auth that DDNS Update messages can now be forwarded'''
         try:
-            seq = self._cc._session.group_sendmsg(create_command(
-                    "start_ddns_forwarder"), AUTH_MODULE_NAME)
-            answer, _ = self._cc._session.group_recvmsg(False, seq)
-            rcode, error_msg = parse_answer(answer)
-            if rcode != 0:
-                logger.error(DDNS_START_FORWARDER_ERROR, error_msg)
-        except (SessionTimeout, SessionError, ProtocolError) as ex:
+            self._cc.rpc_call("start_ddns_forwarder", AUTH_MODULE_NAME)
+        except (SessionTimeout, SessionError, ProtocolError,
+                RPCRecipientMissing) as ex:
             logger.error(DDNS_START_FORWARDER_FAIL, ex)
+        except RPCError as e:
+            logger.error(DDNS_START_FORWARDER_ERROR, e)
 
     def __notify_stop_forwarder(self):
         '''Notify auth that DDNS Update messages should no longer be forwarded.
 
         '''
         try:
-            seq = self._cc._session.group_sendmsg(create_command(
-                    "stop_ddns_forwarder"), AUTH_MODULE_NAME)
-            answer, _ = self._cc._session.group_recvmsg(False, seq)
-            rcode, error_msg = parse_answer(answer)
-            if rcode != 0:
-                logger.error(DDNS_STOP_FORWARDER_ERROR, error_msg)
-        except (SessionTimeout, SessionError, ProtocolError) as ex:
+            self._cc.rpc_call("stop_ddns_forwarder", AUTH_MODULE_NAME)
+        except (SessionTimeout, SessionError, ProtocolError,
+                RPCRecipientMissing) as ex:
             logger.error(DDNS_STOP_FORWARDER_FAIL, ex)
+        except RPCError as e:
+            logger.error(DDNS_STOP_FORWARDER_ERROR, e)
 
     def __notify_auth(self, zname, zclass):
         '''Notify auth of the update, if necessary.'''
-        msg = auth_loadzone_command(self._cc, zname, zclass)
-        if msg is not None:
-            self.__notify_update(AUTH_MODULE_NAME, msg, zname, zclass)
+        self.__notify_update(AUTH_MODULE_NAME, AUTH_LOADZONE_COMMAND,
+                             auth_loadzone_params(zname, zclass), zname,
+                             zclass)
 
     def __notify_xfrout(self, zname, zclass):
         '''Notify xfrout of the update.'''
         param = {'zone_name': zname.to_text(), 'zone_class': zclass.to_text()}
-        msg = create_command('notify', param)
-        self.__notify_update(XFROUT_MODULE_NAME, msg, zname, zclass)
+        self.__notify_update(XFROUT_MODULE_NAME, 'notify', param, zname,
+                             zclass)
 
-    def __notify_update(self, modname, msg, zname, zclass):
+    def __notify_update(self, modname, command, params, zname, zclass):
         '''Notify other module of the update.
 
         Note that we use blocking communication here.  While the internal
@@ -590,27 +587,17 @@ class DDNSServer:
         For a longer term we'll need to switch to asynchronous communication,
         but for now we rely on the blocking operation.
 
-        Note also that we directly refer to the "protected" member of
-        ccsession (_cc._session) rather than creating a separate channel.
-        It's probably not the best practice, but hopefully we can introduce
-        a cleaner way when we support asynchronous communication.
-        At the moment we prefer the brevity with the use of internal channel
-        of the cc session.
-
         '''
         try:
-            seq = self._cc._session.group_sendmsg(msg, modname)
-            answer, _ = self._cc._session.group_recvmsg(False, seq)
-            rcode, error_msg = parse_answer(answer)
-        except (SessionTimeout, SessionError, ProtocolError) as ex:
-            rcode = 1
-            error_msg = str(ex)
-        if rcode == 0:
+            # FIXME? Is really rpc_call the correct one? What if there are more
+            # than one recipient of the given kind? What if none? We need to
+            # think of some kind of notification/broadcast mechanism.
+            self._cc.rpc_call(command, modname, params=params)
             logger.debug(TRACE_BASIC, DDNS_UPDATE_NOTIFY, modname,
                          ZoneFormatter(zname, zclass))
-        else:
+        except (SessionTimeout, SessionError, ProtocolError, RPCError) as ex:
             logger.error(DDNS_UPDATE_NOTIFY_FAIL, modname,
-                         ZoneFormatter(zname, zclass), error_msg)
+                         ZoneFormatter(zname, zclass), ex)
 
     def handle_session(self, fileno):
         """Handle incoming session on the socket with given fileno.
@@ -683,7 +670,7 @@ class DDNSServer:
                 result = ctx[0].send_ready()
                 if result != DNSTCPContext.SENDING:
                     if result == DNSTCPContext.CLOSED:
-                        logger.warn(DDNS_RESPONSE_TCP_SOCKET_ERROR,
+                        logger.warn(DDNS_RESPONSE_TCP_SOCKET_SEND_FAILED,
                                     ClientFormatter(ctx[1]))
                     ctx[0].close()
                     del self._tcp_ctxs[fileno]

+ 19 - 29
src/bin/ddns/ddns_messages.mes

@@ -69,7 +69,7 @@ it's just a timing issue.  The number of total failed attempts is also
 logged.  If it reaches an internal threshold b10-ddns considers it a
 fatal error and terminates.  Even in that case, if b10-ddns is
 configured as a "dispensable" component (which is the default), the
-parent bind10 process will restart it, and there will be another
+parent ("init") process will restart it, and there will be another
 chance of getting the remote configuration successfully.  These are
 not the optimal behavior, but it's believed to be sufficient in
 practice (there would normally be no failure in the first place).  If
@@ -134,12 +134,12 @@ appropriate ACL configuration or some lower layer filtering.  The
 number of existing TCP clients are shown in the log, which should be
 identical to the current quota.
 
-% DDNS_RESPONSE_SOCKET_ERROR failed to send update response to %1: %2
+% DDNS_RESPONSE_SOCKET_SEND_FAILED failed to send update response to %1: %2
 Network I/O error happens in sending an update response.  The
 client's address that caused the error and error details are also
 logged.
 
-% DDNS_RESPONSE_TCP_SOCKET_ERROR failed to complete sending update response to %1 over TCP
+% DDNS_RESPONSE_TCP_SOCKET_SEND_FAILED failed to complete sending update response to %1 over TCP
 b10-ddns had tried to send an update response over TCP, and it hadn't
 been completed at that time, and a followup attempt to complete the
 send operation failed due to some network I/O error.  While a network
@@ -253,29 +253,19 @@ notify messages to secondary servers.
 b10-ddns has made updates to a zone based on an update request and
 tried to notify an external component of the updates, but the
 notification fails.  One possible cause of this is that the external
-component is not really running and it times out in waiting for the
-response, although it will be less likely to happen in practice
-because these components will normally be configured to run when the
-server provides the authoritative DNS service; ddns is rather optional
-among them.  If this happens, however, it will suspend b10-ddns for a
-few seconds during which it cannot handle new requests (some may be
-delayed, some may be dropped, depending on the volume of the incoming
-requests).  This is obviously bad, and if this error happens due to
-this reason, the administrator should make sure the component in
-question should be configured to run.  For a longer term, b10-ddns
-should be more robust about this case such as by making this
-notification asynchronously and/or detecting the existence of the
-external components to avoid hopeless notification in the first place.
-Severity of this error for the receiving components depends on the
-type of the component.  If it's b10-xfrout, this means DNS notify
-messages won't be sent to secondary servers of the zone.  It's
-suboptimal, but not necessarily critical as the secondary servers will
-try to check the zone's status periodically.  If it's b10-auth and the
-notification was needed to have it reload the corresponding zone, it's
-more serious because b10-auth won't be able to serve the new version
-of the zone unless some explicit recovery action is taken.  So the
-administrator needs to examine this message and takes an appropriate
-action.  In either case, this notification is generally expected to
-succeed; so the fact it fails itself means there's something wrong in
-the BIND 10 system, and it would be advisable to check other log
-messages.
+component is not really running, although it will be less likely to
+happen in practice because these components will normally be
+configured to run when the server provides the authoritative DNS
+service; ddns is rather optional among them. Severity of this error
+for the receiving components depends on the type of the component.  If
+it's b10-xfrout, this means DNS notify messages won't be sent to
+secondary servers of the zone.  It's suboptimal, but not necessarily
+critical as the secondary servers will try to check the zone's status
+periodically.  If it's b10-auth and the notification was needed to
+have it reload the corresponding zone, it's more serious because
+b10-auth won't be able to serve the new version of the zone unless
+some explicit recovery action is taken.  So the administrator needs to
+examine this message and takes an appropriate action.  In either case,
+this notification is generally expected to succeed; so the fact it
+fails itself means there's something wrong in the BIND 10 system, and
+it would be advisable to check other log messages.

+ 20 - 19
src/bin/ddns/tests/ddns_test.py

@@ -39,9 +39,9 @@ TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
 READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
 TEST_ZONE_NAME = Name('example.org')
 TEST_ZONE_NAME_STR = TEST_ZONE_NAME.to_text()
-UPDATE_RRTYPE = RRType.SOA()
+UPDATE_RRTYPE = RRType.SOA
 TEST_QID = 5353                 # arbitrary chosen
-TEST_RRCLASS = RRClass.IN()
+TEST_RRCLASS = RRClass.IN
 TEST_RRCLASS_STR = TEST_RRCLASS.to_text()
 TEST_SERVER6 = ('2001:db8::53', 53, 0, 0)
 TEST_CLIENT6 = ('2001:db8::1', 53000, 0, 0)
@@ -169,9 +169,9 @@ class FakeUpdateSession:
         self.__msg.make_response()
         self.__msg.clear_section(SECTION_ZONE)
         if self.__faked_result == UPDATE_SUCCESS:
-            self.__msg.set_rcode(Rcode.NOERROR())
+            self.__msg.set_rcode(Rcode.NOERROR)
         else:
-            self.__msg.set_rcode(Rcode.REFUSED())
+            self.__msg.set_rcode(Rcode.REFUSED)
         return self.__msg
 
 class FakeKeyringModule:
@@ -191,7 +191,7 @@ class FakeKeyringModule:
         '''Simply return the predefined TSIG keyring unconditionally.'''
         return TEST_TSIG_KEYRING
 
-class MyCCSession(isc.config.ConfigData):
+class MyCCSession(isc.config.ModuleCCSession):
     '''Fake session with minimal interface compliance.'''
 
     # faked CC sequence used in group_send/recvmsg
@@ -276,7 +276,8 @@ class MyCCSession(isc.config.ConfigData):
                     'secondary_zones')
                 return seczone_default, True
 
-    def group_sendmsg(self, msg, group):
+    def group_sendmsg(self, msg, group, instance='*', to='*',
+                      want_answer=False):
         # remember the passed parameter, and return dummy sequence
         self._sent_msg.append((msg, group))
         if self._sendmsg_exception is not None:
@@ -478,7 +479,7 @@ class TestDDNSServer(unittest.TestCase):
         # By default (in our faked config) it should be derived from the
         # test data source
         rrclass, datasrc_client = self.ddns_server._datasrc_info
-        self.assertEqual(RRClass.IN(), rrclass)
+        self.assertEqual(RRClass.IN, rrclass)
         self.assertEqual(DataSourceClient.SUCCESS,
                          datasrc_client.find_zone(Name('example.org'))[0])
 
@@ -491,7 +492,7 @@ class TestDDNSServer(unittest.TestCase):
             {'database_file': './notexistentdir/somedb.sqlite3'}
         self.__cc_session.add_remote_config_by_name('Auth')
         rrclass, datasrc_client = self.ddns_server._datasrc_info
-        self.assertEqual(RRClass.IN(), rrclass)
+        self.assertEqual(RRClass.IN, rrclass)
         self.assertRaises(isc.datasrc.Error,
                           datasrc_client.find_zone, Name('example.org'))
 
@@ -887,12 +888,12 @@ class TestDDNSServer(unittest.TestCase):
         self.__select_answer = ([], [10], [])
         self.assertRaises(KeyError, self.ddns_server.run)
 
-def create_msg(opcode=Opcode.UPDATE(), zones=[TEST_ZONE_RECORD], prereq=[],
+def create_msg(opcode=Opcode.UPDATE, zones=[TEST_ZONE_RECORD], prereq=[],
                tsigctx=None):
     msg = Message(Message.RENDER)
     msg.set_qid(TEST_QID)
     msg.set_opcode(opcode)
-    msg.set_rcode(Rcode.NOERROR())
+    msg.set_rcode(Rcode.NOERROR)
     for z in zones:
         msg.add_question(z)
     for p in prereq:
@@ -936,7 +937,7 @@ class TestDDNSSession(unittest.TestCase):
         return FakeUpdateSession(req_message, client_addr, zone_config,
                                  self.__faked_result)
 
-    def check_update_response(self, resp_wire, expected_rcode=Rcode.NOERROR(),
+    def check_update_response(self, resp_wire, expected_rcode=Rcode.NOERROR,
                               tsig_ctx=None, tcp=False):
         '''Check if given wire data are valid form of update response.
 
@@ -963,7 +964,7 @@ class TestDDNSSession(unittest.TestCase):
             self.assertNotEqual(None, tsig_record)
             self.assertEqual(TSIGError.NOERROR,
                              tsig_ctx.verify(tsig_record, resp_wire))
-        self.assertEqual(Opcode.UPDATE(), msg.get_opcode())
+        self.assertEqual(Opcode.UPDATE, msg.get_opcode())
         self.assertEqual(expected_rcode, msg.get_rcode())
         self.assertEqual(TEST_QID, msg.get_qid())
         for section in [SECTION_ZONE, SECTION_PREREQUISITE, SECTION_UPDATE]:
@@ -977,7 +978,7 @@ class TestDDNSSession(unittest.TestCase):
         server_addr = TEST_SERVER6 if ipv6 else TEST_SERVER4
         client_addr = TEST_CLIENT6 if ipv6 else TEST_CLIENT4
         tsig = TSIGContext(tsig_key) if tsig_key is not None else None
-        rcode = Rcode.NOERROR() if result == UPDATE_SUCCESS else Rcode.REFUSED()
+        rcode = Rcode.NOERROR if result == UPDATE_SUCCESS else Rcode.REFUSED
         has_response = (result != UPDATE_DROP)
 
         self.assertEqual(has_response,
@@ -1015,7 +1016,7 @@ class TestDDNSSession(unittest.TestCase):
 
         # Opcode is not UPDATE
         self.assertFalse(self.server.handle_request(
-                (self.__sock, None, None, create_msg(opcode=Opcode.QUERY()))))
+                (self.__sock, None, None, create_msg(opcode=Opcode.QUERY))))
         self.assertEqual((None, None), (s._sent_data, s._sent_addr))
 
         # TSIG verification error.  We use UPDATE_DROP to signal check_session
@@ -1031,7 +1032,7 @@ class TestDDNSSession(unittest.TestCase):
                                                      TEST_CLIENT6,
                                                      create_msg())))
         # this check ensures sendto() was really attempted.
-        self.check_update_response(self.__sock._sent_data, Rcode.NOERROR())
+        self.check_update_response(self.__sock._sent_data, Rcode.NOERROR)
 
     def test_tcp_request(self):
         # A simple case using TCP: all resopnse data are sent out at once.
@@ -1040,7 +1041,7 @@ class TestDDNSSession(unittest.TestCase):
         self.assertTrue(self.server.handle_request((s, TEST_SERVER6,
                                                     TEST_CLIENT6,
                                                     create_msg())))
-        self.check_update_response(s._sent_data, Rcode.NOERROR(), tcp=True)
+        self.check_update_response(s._sent_data, Rcode.NOERROR, tcp=True)
         # In the current implementation, the socket should be closed
         # immedidately after a successful send.
         self.assertEqual(1, s._close_called)
@@ -1071,7 +1072,7 @@ class TestDDNSSession(unittest.TestCase):
         s.make_send_ready()
         self.assertEqual(DNSTCPContext.SEND_DONE,
                          self.server._tcp_ctxs[s.fileno()][0].send_ready())
-        self.check_update_response(s._sent_data, Rcode.NOERROR(), tcp=True)
+        self.check_update_response(s._sent_data, Rcode.NOERROR, tcp=True)
 
     def test_tcp_request_error(self):
         # initial send() on the TCP socket will fail.  The request handling
@@ -1127,9 +1128,9 @@ class TestDDNSSession(unittest.TestCase):
         self.__faked_result = UPDATE_DROP
         # Put the same RR twice in the prerequisite section.  We should see
         # them as separate RRs.
-        dummy_record = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS(),
+        dummy_record = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.NS,
                              RRTTL(0))
-        dummy_record.add_rdata(Rdata(RRType.NS(), TEST_RRCLASS, "ns.example"))
+        dummy_record.add_rdata(Rdata(RRType.NS, TEST_RRCLASS, "ns.example."))
         self.server.handle_request((self.__sock, TEST_SERVER6, TEST_CLIENT6,
                                     create_msg(prereq=[dummy_record,
                                                        dummy_record])))

+ 4 - 6
src/bin/dhcp4/Makefile.am

@@ -5,6 +5,10 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/bin -I$(top_builddir)/src/bin
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some Boost headers when compiling with clang
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
 
 if USE_STATIC_LINK
 AM_LDFLAGS = -static
@@ -51,12 +55,6 @@ b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
 nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
 EXTRA_DIST += dhcp4_messages.mes
 
-if USE_CLANGPP
-# Disable unused parameter warning caused by some of the
-# Boost headers when compiling with clang.
-b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
-endif
-
 b10_dhcp4_LDADD  = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
 b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la

+ 3 - 0
src/bin/dhcp4/b10-dhcp4.xml

@@ -79,6 +79,9 @@
     <title>SEE ALSO</title>
     <para>
       <citerefentry>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>.
     </para>

+ 144 - 42
src/bin/dhcp4/config_parser.cc

@@ -18,6 +18,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/option_space_container.h>
 #include <util/encode/hex.h>
@@ -49,9 +50,6 @@ typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
 typedef boost::shared_ptr<StringParser> StringParserPtr;
 typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
 
-/// @brief auxiliary type used for storing element name and its parser
-typedef pair<string, ConstElementPtr> ConfigPair;
-
 /// @brief a factory method that will create a parser for a given element name
 typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
 
@@ -689,7 +687,7 @@ public:
     virtual void commit() {
         if (options_ == NULL) {
             isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
-                      "commiting option data.");
+                      "committing option data.");
         } else  if (!option_descriptor_.option) {
             // Before we can commit the new option should be configured. If it is not
             // than somebody must have called commit() before build().
@@ -745,30 +743,34 @@ private:
     void createOption() {
         // Option code is held in the uint32_t storage but is supposed to
         // be uint16_t value. We need to check that value in the configuration
-        // does not exceed range of uint16_t and is not zero.
+        // does not exceed range of uint8_t and is not zero.
         uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
         if (option_code == 0) {
-            isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
-                      << " be equal to zero. Option code '0' is reserved in"
-                      << " DHCPv4.");
-        } else if (option_code > std::numeric_limits<uint16_t>::max()) {
-            isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
-                      << " exceed " << std::numeric_limits<uint16_t>::max());
+            isc_throw(DhcpConfigError, "option code must not be zero."
+                      << " Option code '0' is reserved in DHCPv4.");
+        } else if (option_code > std::numeric_limits<uint8_t>::max()) {
+            isc_throw(DhcpConfigError, "invalid option code '" << option_code
+                      << "', it must not exceed '"
+                      << std::numeric_limits<uint8_t>::max() << "'");
         }
         // Check that the option name has been specified, is non-empty and does not
         // contain spaces.
-        // @todo possibly some more restrictions apply here?
         std::string option_name = getParam<std::string>("name", string_values_);
         if (option_name.empty()) {
-            isc_throw(DhcpConfigError, "Parser error: option name must not be"
-                      << " empty");
+            isc_throw(DhcpConfigError, "name of the option with code '"
+                      << option_code << "' is empty");
         } else if (option_name.find(" ") != std::string::npos) {
-            isc_throw(DhcpConfigError, "Parser error: option name must not contain"
-                      << " spaces");
+            isc_throw(DhcpConfigError, "invalid option name '" << option_name
+                      << "', space character is not allowed");
         }
 
         std::string option_space = getParam<std::string>("space", string_values_);
-        /// @todo Validate option space once #2313 is merged.
+        if (!OptionSpace::validateName(option_space)) {
+            isc_throw(DhcpConfigError, "invalid option space name '"
+                      << option_space << "' specified for option '"
+                      << option_name << "' (code '" << option_code
+                      << "')");
+        }
 
         OptionDefinitionPtr def;
         if (option_space == "dhcp4" &&
@@ -822,7 +824,7 @@ private:
             try {
                 util::encode::decodeHex(option_data, binary);
             } catch (...) {
-                isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+                isc_throw(DhcpConfigError, "option data is not a valid"
                           << " string of hexadecimal digits: " << option_data);
             }
         }
@@ -857,7 +859,7 @@ private:
             // definition of option value makes sense.
             if (def->getName() != option_name) {
                 isc_throw(DhcpConfigError, "specified option name '"
-                          << option_name << " does not match the "
+                          << option_name << "' does not match the "
                           << "option definition: '" << option_space
                           << "." << def->getName() << "'");
             }
@@ -877,6 +879,7 @@ private:
                           << ", code: " << option_code << "): "
                           << ex.what());
             }
+
         }
         // All went good, so we can set the option space name.
         option_space_ = option_space;
@@ -964,13 +967,13 @@ public:
         return (new OptionDataListParser(param_name));
     }
 
+    /// Pointer to options instances storage.
+    OptionStorage* options_;
     /// Intermediate option storage. This storage is used by
     /// lower level parsers to add new options.  Values held
     /// in this storage are assigned to main storage (options_)
     /// if overall parsing was successful.
     OptionStorage local_options_;
-    /// Pointer to options instances storage.
-    OptionStorage* options_;
     /// Collection of parsers;
     ParserCollection parsers_;
 };
@@ -978,7 +981,7 @@ public:
 /// @brief Parser for a single option definition.
 ///
 /// This parser creates an instance of a single option definition.
-class OptionDefParser: DhcpConfigParser {
+class OptionDefParser : public DhcpConfigParser {
 public:
 
     /// @brief Constructor.
@@ -1005,7 +1008,8 @@ public:
             std::string entry(param.first);
             ParserPtr parser;
             if (entry == "name" || entry == "type" ||
-                entry == "record-types" || entry == "space") {
+                entry == "record-types" || entry == "space" ||
+                entry == "encapsulate") {
                 StringParserPtr
                     str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
                 if (str_parser) {
@@ -1055,8 +1059,8 @@ public:
 
     /// @brief Stores the parsed option definition in a storage.
     void commit() {
-        // @todo validate option space name once 2313 is merged.
-        if (storage_ && option_definition_) {
+        if (storage_ && option_definition_ &&
+            OptionSpace::validateName(option_space_name_)) {
             storage_->addItem(option_definition_, option_space_name_);
         }
     }
@@ -1078,11 +1082,10 @@ private:
     void createOptionDef() {
         // Get the option space name and validate it.
         std::string space = getParam<std::string>("space", string_values_);
-        // @todo uncomment the code below when the #2313 is merged.
-        /*        if (!OptionSpace::validateName()) {
+        if (!OptionSpace::validateName(space)) {
             isc_throw(DhcpConfigError, "invalid option space name '"
                       << space << "'");
-                      } */
+        }
 
         // Get other parameters that are needed to create the
         // option definition.
@@ -1090,9 +1093,35 @@ private:
         uint32_t code = getParam<uint32_t>("code", uint32_values_);
         std::string type = getParam<std::string>("type", string_values_);
         bool array_type = getParam<bool>("array", boolean_values_);
+        std::string encapsulates = getParam<std::string>("encapsulate",
+                                                         string_values_);
 
-        OptionDefinitionPtr def(new OptionDefinition(name, code,
-                                                     type, array_type));
+        // Create option definition.
+        OptionDefinitionPtr def;
+        // We need to check if user has set encapsulated option space
+        // name. If so, different constructor will be used.
+        if (!encapsulates.empty()) {
+            // Arrays can't be used together with sub-options.
+            if (array_type) {
+                isc_throw(DhcpConfigError, "option '" << space << "."
+                          << "name" << "', comprising an array of data"
+                          << " fields may not encapsulate any option space");
+
+            } else if (encapsulates == space) {
+                isc_throw(DhcpConfigError, "option must not encapsulate"
+                          << " an option space it belongs to: '"
+                          << space << "." << name << "' is set to"
+                          << " encapsulate '" << space << "'");
+
+            } else {
+                def.reset(new OptionDefinition(name, code, type,
+                                               encapsulates.c_str()));
+            }
+
+        } else {
+            def.reset(new OptionDefinition(name, code, type, array_type));
+
+        }
         // The record-types field may carry a list of comma separated names
         // of data types that form a record.
         std::string record_types = getParam<std::string>("record-types",
@@ -1110,7 +1139,7 @@ private:
                 }
             } catch (const Exception& ex) {
                 isc_throw(DhcpConfigError, "invalid record type values"
-                          << " specified for the option  definition: "
+                          << " specified for the option definition: "
                           << ex.what());
             }
         }
@@ -1332,6 +1361,63 @@ private:
         return (false);
     }
 
+    /// @brief Append sub-options to an option.
+    ///
+    /// @param option_space a name of the encapsulated option space.
+    /// @param option option instance to append sub-options to.
+    void appendSubOptions(const std::string& option_space, OptionPtr& option) {
+        // Only non-NULL options are stored in option container.
+        // If this option pointer is NULL this is a serious error.
+        assert(option);
+
+        OptionDefinitionPtr def;
+        if (option_space == "dhcp4" &&
+            LibDHCP::isStandardOption(Option::V4, option->getType())) {
+            def = LibDHCP::getOptionDef(Option::V4, option->getType());
+            // Definitions for some of the standard options hasn't been
+            // implemented so it is ok to leave here.
+            if (!def) {
+                return;
+            }
+        } else {
+            const OptionDefContainerPtr defs =
+                option_def_intermediate.getItems(option_space);
+            const OptionDefContainerTypeIndex& idx = defs->get<1>();
+            const OptionDefContainerTypeRange& range =
+                idx.equal_range(option->getType());
+            // There is no definition so we have to leave.
+            if (std::distance(range.first, range.second) == 0) {
+                return;
+            }
+
+            def = *range.first;
+
+            // If the definition exists, it must be non-NULL.
+            // Otherwise it is a programming error.
+            assert(def);
+        }
+
+        // We need to get option definition for the particular option space
+        // and code. This definition holds the information whether our
+        // option encapsulates any option space.
+        // Get the encapsulated option space name.
+        std::string encapsulated_space = def->getEncapsulatedSpace();
+        // If option space name is empty it means that our option does not
+        // encapsulate any option space (does not include sub-options).
+        if (!encapsulated_space.empty()) {
+            // Get the sub-options that belong to the encapsulated
+            // option space.
+            const Subnet::OptionContainerPtr sub_opts =
+                option_defaults.getItems(encapsulated_space);
+            // Append sub-options to the option.
+            BOOST_FOREACH(Subnet::OptionDescriptor desc, *sub_opts) {
+                if (desc.option) {
+                    option->addOption(desc.option);
+                }
+            }
+        }
+    }
+
     /// @brief Create a new subnet using a data from child parsers.
     ///
     /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
@@ -1407,6 +1493,8 @@ private:
                     LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
                         .arg(desc.option->getType()).arg(addr.toText());
                 }
+                // Add sub-options (if any).
+                appendSubOptions(option_space, desc.option);
                 // In any case, we add the option to the subnet.
                 subnet_->addOption(desc.option, false, option_space);
             }
@@ -1434,6 +1522,9 @@ private:
                 Subnet::OptionDescriptor existing_desc =
                     subnet_->getOptionDescriptor(option_space, desc.option->getType());
                 if (!existing_desc.option) {
+                    // Add sub-options (if any).
+                    appendSubOptions(option_space, desc.option);
+
                     subnet_->addOption(desc.option, false, option_space);
                 }
             }
@@ -1607,6 +1698,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
     factories["option-data"] = OptionDataListParser::factory;
     factories["option-def"] = OptionDefListParser::factory;
     factories["version"] = StringParser::factory;
+    factories["lease-database"] = DbAccessParser::factory;
 
     FactoryMap::iterator f = factories.find(config_id);
     if (f == factories.end()) {
@@ -1661,13 +1753,18 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
     // rollback informs whether error occured and original data
     // have to be restored to global storages.
     bool rollback = false;
-
+    // config_pair holds the details of the current parser when iterating over
+    // the parsers.  It is declared outside the loops so in case of an error,
+    // the name of the failing parser can be retrieved in the "catch" clause.
+    ConfigPair config_pair;
     try {
         // Make parsers grouping.
         const std::map<std::string, ConstElementPtr>& values_map =
             config_set->mapValue();
-        BOOST_FOREACH(ConfigPair config_pair, values_map) {
+        BOOST_FOREACH(config_pair, values_map) {
             ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
+                      .arg(config_pair.first);
             if (config_pair.first == "subnet4") {
                 subnet_parser = parser;
 
@@ -1702,16 +1799,19 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
         }
 
     } catch (const isc::Exception& ex) {
-        answer =
-            isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+        LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
+                  .arg(config_pair.first).arg(ex.what());
+        answer = isc::config::createAnswer(1,
+                     string("Configuration parsing failed: ") + ex.what());
 
         // An error occured, so make sure that we restore original data.
         rollback = true;
 
     } catch (...) {
         // for things like bad_cast in boost::lexical_cast
-        answer =
-            isc::config::createAnswer(1, string("Configuration parsing failed"));
+        LOG_ERROR(dhcp4_logger, DHCP4_PARSER_EXCEPTION).arg(config_pair.first);
+        answer = isc::config::createAnswer(1,
+                     string("Configuration parsing failed"));
 
         // An error occured, so make sure that we restore original data.
         rollback = true;
@@ -1728,14 +1828,16 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
             }
         }
         catch (const isc::Exception& ex) {
-            answer =
-                isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+            LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
+            answer = isc::config::createAnswer(2,
+                         string("Configuration commit failed: ") + ex.what());
             rollback = true;
 
         } catch (...) {
             // for things like bad_cast in boost::lexical_cast
-            answer =
-                isc::config::createAnswer(2, string("Configuration commit failed"));
+            LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_EXCEPTION);
+            answer = isc::config::createAnswer(2,
+                         string("Configuration commit failed"));
             rollback = true;
 
         }
@@ -1753,7 +1855,7 @@ configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
     LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
 
     // Everything was fine. Configuration is successful.
-    answer = isc::config::createAnswer(0, "Configuration commited.");
+    answer = isc::config::createAnswer(0, "Configuration committed.");
     return (answer);
 }
 

+ 60 - 10
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -47,18 +47,61 @@ namespace dhcp {
 ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
 
 ConstElementPtr
+ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
+    // This configuration handler is intended to be used only
+    // when the initial configuration comes in. To receive this
+    // configuration a pointer to this handler must be passed
+    // using ModuleCCSession constructor. This constructor will
+    // invoke the handler and will store the configuration for
+    // the configuration session when the handler returns success.
+    // Since this configuration is partial we just pretend to
+    // parse it and always return success. The function that
+    // initiates the session must get the configuration on its
+    // own using getFullConfig.
+    return (isc::config::createAnswer(0, "Configuration accepted."));
+}
+
+ConstElementPtr
 ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
-    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
-              .arg(new_config->str());
-    if (server_) {
-        return (configureDhcp4Server(*server_, new_config));
+    if (!server_ || !server_->config_session_) {
+        // That should never happen as we install config_handler
+        // after we instantiate the server.
+        ConstElementPtr answer =
+            isc::config::createAnswer(1, "Configuration rejected,"
+                                      " server is during startup/shutdown phase.");
+        return (answer);
     }
 
-    // That should never happen as we install config_handler after we instantiate
-    // the server.
-    ConstElementPtr answer = isc::config::createAnswer(1,
-           "Configuration rejected, server is during startup/shutdown phase.");
-    return (answer);
+    // The configuration passed to this handler function is partial.
+    // In other words, it just includes the values being modified.
+    // In the same time, there are dependencies between various
+    // DHCP configuration parsers. For example: the option value can
+    // be set if the definition of this option is set. If someone removes
+    // an existing option definition then the partial configuration that
+    // removes that definition is triggered while a relevant option value
+    // may remain configured. This eventually results in the DHCP server
+    // configuration being in the inconsistent state.
+    // In order to work around this problem we need to merge the new
+    // configuration with the existing (full) configuration.
+
+    // Let's create a new object that will hold the merged configuration.
+    boost::shared_ptr<MapElement> merged_config(new MapElement());
+    // Let's get the existing configuration.
+    ConstElementPtr full_config = server_->config_session_->getFullConfig();
+    // The full_config and merged_config should be always non-NULL
+    // but to provide some level of exception safety we check that they
+    // really are (in case we go out of memory).
+    if (full_config && merged_config) {
+        merged_config->setValue(full_config->mapValue());
+
+        // Merge an existing and new configuration.
+        isc::data::merge(merged_config, new_config);
+        LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
+            .arg(full_config->str());
+    }
+
+    // Configure the server.
+    return (configureDhcp4Server(*server_, merged_config));
 }
 
 ConstElementPtr
@@ -109,8 +152,15 @@ void ControlledDhcpv4Srv::establishSession() {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
               .arg(specfile);
     cc_session_ = new Session(io_service_.get_io_service());
+    // Create a session with the dummy configuration handler.
+    // Dumy configuration handler is internally invoked by the
+    // constructor and on success the constructor updates
+    // the current session with the configuration that had been
+    // commited in the previous session. If we did not install
+    // the dummy handler, the previous configuration would have
+    // been lost.
     config_session_ = new ModuleCCSession(specfile, *cc_session_,
-                                          NULL,
+                                          dhcp4StubConfigHandler,
                                           dhcp4CommandHandler, false);
     config_session_->start();
 

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

@@ -49,7 +49,7 @@ public:
     /// @brief Establishes msgq session.
     ///
     /// Creates session that will be used to receive commands and updated
-    /// configuration from boss (or indirectly from user via bindctl).
+    /// configuration from cfgmgr (or indirectly from user via bindctl).
     void establishSession();
 
     /// @brief Terminates existing msgq session.
@@ -94,6 +94,27 @@ protected:
     static isc::data::ConstElementPtr
     dhcp4ConfigHandler(isc::data::ConstElementPtr new_config);
 
+    /// @brief A dummy configuration handler that always returns success.
+    ///
+    /// This configuration handler does not perform configuration
+    /// parsing and always returns success. A dummy hanlder should
+    /// be installed using \ref isc::config::ModuleCCSession ctor
+    /// to get the initial configuration. This initial configuration
+    /// comprises values for only those elements that were modified
+    /// the previous session. The \ref dhcp4ConfigHandler can't be
+    /// used to parse the initial configuration because it needs the
+    /// full configuration to satisfy dependencies between the
+    /// various configuration values. Installing the dummy handler
+    /// that guarantees to return success causes initial configuration
+    /// to be stored for the session being created and that it can
+    /// be later accessed with \ref isc::ConfigData::getFullConfig.
+    ///
+    /// @param new_config new configuration.
+    ///
+    /// @return success configuration status.
+    static isc::data::ConstElementPtr
+    dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config);
+
     /// @brief A callback for handling incoming commands.
     ///
     /// @param command textual representation of the command

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

@@ -1,3 +1,17 @@
+// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
 /**
  @page dhcp4 DHCPv4 Server Component
 

+ 48 - 4
src/bin/dhcp4/dhcp4.spec

@@ -55,13 +55,13 @@
           { "item_name": "code",
             "item_type": "integer",
             "item_optional": false,
-            "item_default": 0,
+            "item_default": 0
           },
 
           { "item_name": "type",
             "item_type": "string",
             "item_optional": false,
-            "item_default": "",
+            "item_default": ""
           },
 
           { "item_name": "array",
@@ -70,16 +70,22 @@
             "item_default": False
           },
 
-          { "item_name": "record_types",
+          { "item_name": "record-types",
             "item_type": "string",
             "item_optional": false,
-            "item_default": "",
+            "item_default": ""
           },
 
           { "item_name": "space",
             "item_type": "string",
             "item_optional": false,
             "item_default": ""
+          },
+
+          { "item_name": "encapsulate",
+            "item_type": "string",
+            "item_optional": false,
+            "item_default": ""
           } ]
         }
       },
@@ -125,6 +131,44 @@
         }
       },
 
+      { "item_name": "lease-database",
+        "item_type": "map",
+        "item_optional": false,
+        "item_default": {"type": "memfile"},
+        "map_item_spec": [
+            {
+                "item_name": "type",
+                "item_type": "string",
+                "item_optional": false,
+                "item_default": ""
+            },
+            {
+                "item_name": "name",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": ""
+            },
+            {
+                "item_name": "user",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": ""
+            },
+            {
+                "item_name": "host",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": ""
+            },
+            {
+                "item_name": "password",
+                "item_type": "string",
+                "item_optional": true,
+                "item_default": ""
+            }
+        ]
+      },
+
       { "item_name": "subnet4",
         "item_type": "list",
         "item_optional": false,

+ 67 - 10
src/bin/dhcp4/dhcp4_messages.mes

@@ -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
@@ -26,6 +26,12 @@ to establish a session with the BIND 10 control channel.
 A debug message listing the command (and possible arguments) received
 from the BIND 10 control system by the IPv4 DHCP server.
 
+% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator.  Additional information
+may be provided.
+
 % DHCP4_CONFIG_LOAD_FAIL failed to load configuration: %1
 This critical error message indicates that the initial DHCPv4
 configuration has failed. The server will start, but nothing will be
@@ -40,20 +46,14 @@ This warning message is issued on an attempt to configure multiple options
 with the same option code for a particular subnet. Adding multiple options
 is uncommon for DHCPv4, but is not prohibited.
 
-% DHCP4_CONFIG_UPDATE updated configuration received: %1
-A debug message indicating that the IPv4 DHCP server has received an
-updated configuration from the BIND 10 configuration system.
-
 % DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
 This is a debug message that is issued every time the server receives a
 configuration. That happens at start up and also when a server configuration
 change is committed by the administrator.
 
-% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
-This is an informational message announcing the successful processing of a
-new configuration. It is output during server startup, and when an updated
-configuration is committed by the administrator.  Additional information
-may be provided.
+% DHCP4_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the IPv4 DHCP server has received an
+updated configuration from the BIND 10 configuration system.
 
 % DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
 This informational message is printed every time DHCPv4 server is started
@@ -94,6 +94,11 @@ server is about to open sockets on the specified port.
 The IPv4 DHCP server has received a packet that it is unable to
 interpret. The reason why the packet is invalid is included in the message.
 
+% DHCP4_PACKET_PROCESS_FAIL failed to process packet received from %1: %2
+This is a general catch-all message indicating that the processing of a
+received packet failed.  The reason is given in the message.  The server
+will not send a response but will instead ignore the packet.
+
 % DHCP4_PACKET_RECEIVED %1 (type %2) packet received on interface %3
 A debug message noting that the server has received the specified type of
 packet on the specified interface.  Note that a packet marked as UNKNOWN
@@ -115,6 +120,38 @@ This error is output if the server failed to assemble the data to be
 returned to the client into a valid packet.  The cause is most likely
 to be a programming error: please raise a bug report.
 
+% DHCP4_PARSER_COMMIT_EXCEPTION parser failed to commit changes
+On receipt of message containing details to a change of the IPv4 DHCP
+server configuration, a set of parsers were successfully created, but one
+of them failed to commit its changes due to a low-level system exception
+being raised.  Additional messages may be output indicating the reason.
+
+% DHCP4_PARSER_COMMIT_FAIL parser failed to commit changes: %1
+On receipt of message containing details to a change of the IPv4 DHCP
+server configuration, a set of parsers were successfully created, but
+one of them failed to commit its changes.  The reason for the failure
+is given in the message.
+
+% DHCP4_PARSER_CREATED created parser for configuration element %1
+A debug message output during a configuration update of the IPv4 DHCP
+server, notifying that the parser for the specified configuration element
+has been successfully created.
+
+% DHCP4_PARSER_EXCEPTION failed to create or run parser for configuration element %1
+On receipt of message containing details to a change of its configuration,
+the IPv4 DHCP server failed to create a parser to decode the contents of
+the named configuration element, or the creation succeeded but the parsing
+actions and committal of changes failed.  The message has been output in
+response to a non-BIND 10 exception being raised.  Additional messages
+may give further information.
+
+% DHCP4_PARSER_FAIL failed to create or run parser for configuration element %1: %2
+On receipt of message containing details to a change of its configuration,
+the IPv4 DHCP server failed to create a parser to decode the contents
+of the named configuration element, or the creation succeeded but the
+parsing actions and committal of changes failed.  The reason for the
+failure is given in the message.
+
 % DHCP4_QUERY_DATA received packet type %1, data is <%2>
 A debug message listing the data received from the client.
 
@@ -158,6 +195,26 @@ both clones use the same client-id.
 % DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
 A debug message listing the data returned to the client.
 
+% DHCP4_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to
+read its server identifier and has generated a new one. This server-id
+will be stored in a file and will be read (and used) whenever the server
+is restarted. This is normal behavior when the server is started for the
+first time. If this message is printed every time the server is started,
+please check that the server has sufficient permission to write its
+server-id file and that the file is not corrupt.
+
+% DHCP4_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP4_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier to a file. The most likely cause is is that the server
+does not have permissions to write the server id file.
+
 % DHCP4_SERVER_FAILED server failed: %1
 The IPv4 DHCP server has encountered a fatal error and is terminating.
 The reason for the failure is included in the message.

+ 294 - 65
src/bin/dhcp4/dhcp4_srv.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-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
@@ -17,6 +17,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
 #include <dhcp/pkt4.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
@@ -30,18 +31,31 @@
 #include <dhcpsrv/utils.h>
 #include <dhcpsrv/addr_utilities.h>
 
+#include <boost/algorithm/string/erase.hpp>
+
+#include <iomanip>
+#include <fstream>
+
 using namespace isc;
 using namespace isc::asiolink;
 using namespace isc::dhcp;
 using namespace isc::log;
 using namespace std;
 
+namespace isc {
+namespace dhcp {
+
+/// @brief file name of a server-id file
+///
+/// Server must store its server identifier in persistent storage that must not
+/// change between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// regular IPv4 address, e.g. 192.0.2.1. Server will create it during
+/// first run and then use it afterwards.
+static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
+
 // These are hardcoded parameters. Currently this is a skeleton server that only
 // grants those options and a single, fixed, hardcoded lease.
-const std::string HARDCODED_GATEWAY = "192.0.2.1";
-const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
-const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";
 
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
@@ -56,7 +70,23 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
             IfaceMgr::instance().openSockets4(port);
         }
 
-        setServerID();
+        string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
+        if (loadServerID(srvid_file)) {
+            LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_SERVERID_LOADED)
+                .arg(srvidToString(getServerID()))
+                .arg(srvid_file);
+        } else {
+            generateServerID();
+            LOG_INFO(dhcp4_logger, DHCP4_SERVERID_GENERATED)
+                .arg(srvidToString(getServerID()))
+                .arg(srvid_file);
+
+            if (!writeServerID(srvid_file)) {
+                LOG_WARN(dhcp4_logger, DHCP4_SERVERID_WRITE_FAIL)
+                    .arg(srvid_file);
+            }
+
+        }
 
         // Instantiate LeaseMgr
         LeaseMgrFactory::create(dbconfig);
@@ -80,7 +110,8 @@ Dhcpv4Srv::~Dhcpv4Srv() {
     IfaceMgr::instance().closeSockets();
 }
 
-void Dhcpv4Srv::shutdown() {
+void
+Dhcpv4Srv::shutdown() {
     LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_SHUTDOWN_REQUEST);
     shutdown_ = true;
 }
@@ -118,32 +149,53 @@ Dhcpv4Srv::run() {
             LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_QUERY_DATA)
                       .arg(query->toText());
 
-            switch (query->getType()) {
-            case DHCPDISCOVER:
-                rsp = processDiscover(query);
-                break;
-
-            case DHCPREQUEST:
-                rsp = processRequest(query);
-                break;
-
-            case DHCPRELEASE:
-                processRelease(query);
-                break;
-
-            case DHCPDECLINE:
-                processDecline(query);
-                break;
-
-            case DHCPINFORM:
-                processInform(query);
-                break;
-
-            default:
-                // Only action is to output a message if debug is enabled,
-                // and that will be covered by the debug statement before
-                // the "switch" statement.
-                ;
+            try {
+                switch (query->getType()) {
+                case DHCPDISCOVER:
+                    rsp = processDiscover(query);
+                    break;
+
+                case DHCPREQUEST:
+                    rsp = processRequest(query);
+                    break;
+
+                case DHCPRELEASE:
+                    processRelease(query);
+                    break;
+
+                case DHCPDECLINE:
+                    processDecline(query);
+                    break;
+
+                case DHCPINFORM:
+                    processInform(query);
+                    break;
+
+                default:
+                    // Only action is to output a message if debug is enabled,
+                    // and that is covered by the debug statement before the
+                    // "switch" statement.
+                    ;
+                }
+            } catch (const isc::Exception& e) {
+
+                // Catch-all exception (at least for ones based on the isc
+                // Exception class, which covers more or less all that
+                // are explicitly raised in the BIND 10 code).  Just log
+                // the problem and ignore the packet. (The problem is logged
+                // as a debug message because debug is disabled by default -
+                // it prevents a DDOS attack based on the sending of problem
+                // packets.)
+                if (dhcp4_logger.isDebugEnabled(DBG_DHCP4_BASIC)) {
+                    std::string source = "unknown";
+                    HWAddrPtr hwptr = query->getHWAddr();
+                    if (hwptr) {
+                        source = hwptr->toText();
+                    }
+                    LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
+                              DHCP4_PACKET_PROCESS_FAIL)
+                              .arg(source).arg(e.what());
+                }
             }
 
             if (rsp) {
@@ -176,22 +228,117 @@ Dhcpv4Srv::run() {
                 }
             }
         }
+    }
+
+    return (true);
+}
+
+bool
+Dhcpv4Srv::loadServerID(const std::string& file_name) {
 
-        // TODO add support for config session (see src/bin/auth/main.cc)
-        //      so this daemon can be controlled from bob
+    // load content of the file into a string
+    fstream f(file_name.c_str(), ios::in);
+    if (!f.is_open()) {
+        return (false);
+    }
+
+    string hex_string;
+    f >> hex_string;
+    f.close();
+
+    // remove any spaces
+    boost::algorithm::erase_all(hex_string, " ");
+
+    try {
+        IOAddress addr(hex_string);
+
+        if (!addr.isV4()) {
+            return (false);
+        }
+
+        // Now create server-id option
+        serverid_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, addr));
+
+    } catch(...) {
+        // any kind of malformed input (empty string, IPv6 address, complete
+        // garbate etc.)
+        return (false);
     }
 
     return (true);
 }
 
 void
-Dhcpv4Srv::setServerID() {
-    /// @todo: implement this for real (see ticket #2588)
-    serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
-                                             IOAddress(HARDCODED_SERVER_ID)));
+Dhcpv4Srv::generateServerID() {
+
+    const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+    // Let's find suitable interface.
+    for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+         iface != ifaces.end(); ++iface) {
+
+        // Let's don't use loopback.
+        if (iface->flag_loopback_) {
+            continue;
+        }
+
+        // Let's skip downed interfaces. It is better to use working ones.
+        if (!iface->flag_up_) {
+            continue;
+        }
+
+        const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+
+        for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+             addr != addrs.end(); ++addr) {
+            if (addr->getFamily() != AF_INET) {
+                continue;
+            }
+
+            serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+                                                     *addr));
+            return;
+        }
+
+
+    }
+
+    isc_throw(BadValue, "No suitable interfaces for server-identifier found");
+}
+
+bool
+Dhcpv4Srv::writeServerID(const std::string& file_name) {
+    fstream f(file_name.c_str(), ios::out | ios::trunc);
+    if (!f.good()) {
+        return (false);
+    }
+    f << srvidToString(getServerID());
+    f.close();
+    return (true);
+}
+
+string 
+Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
+    if (!srvid) {
+        isc_throw(BadValue, "NULL pointer passed to srvidToString()");
+    }
+    boost::shared_ptr<Option4AddrLst> generated =
+        boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
+    if (!srvid) {
+        isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
+    }
+
+    Option4AddrLst::AddressContainer addrs = generated->getAddresses();
+    if (addrs.size() != 1) {
+        isc_throw(BadValue, "Malformed option passed to srvidToString(). "
+                  << "Expected to contain a single IPv4 address.");
+    }
+
+    return (addrs[0].toText());
 }
 
-void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+void
+Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     answer->setIface(question->getIface());
     answer->setIndex(question->getIndex());
     answer->setCiaddr(question->getCiaddr());
@@ -220,7 +367,8 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     }
 }
 
-void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
+void
+Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
     OptionPtr opt;
 
     // add Message Type Option (type 53)
@@ -232,22 +380,80 @@ void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
     // more options will be added here later
 }
 
+void
+Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
 
-void Dhcpv4Srv::appendRequestedOptions(Pkt4Ptr& msg) {
-    OptionPtr opt;
+    // Get the subnet relevant for the client. We will need it
+    // to get the options associated with it.
+    Subnet4Ptr subnet = selectSubnet(question);
+    // If we can't find the subnet for the client there is no way
+    // to get the options to be sent to a client. We don't log an
+    // error because it will be logged by the assignLease method
+    // anyway.
+    if (!subnet) {
+        return;
+    }
 
-    // Domain name (type 15)
-    vector<uint8_t> domain(HARDCODED_DOMAIN_NAME.begin(), HARDCODED_DOMAIN_NAME.end());
-    opt = OptionPtr(new Option(Option::V4, DHO_DOMAIN_NAME, domain));
-    msg->addOption(opt);
-    // TODO: Add Option_String class
+    // try to get the 'Parameter Request List' option which holds the
+    // codes of requested options.
+    OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
+        OptionUint8Array>(question->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
+    // If there is no PRL option in the message from the client then
+    // there is nothing to do.
+    if (!option_prl) {
+        return;
+    }
 
-    // DNS servers (type 6)
-    opt = OptionPtr(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS, IOAddress(HARDCODED_DNS_SERVER)));
-    msg->addOption(opt);
+    // Get the codes of requested options.
+    const std::vector<uint8_t>& requested_opts = option_prl->getValues();
+    // For each requested option code get the instance of the option
+    // to be returned to the client.
+    for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
+         opt != requested_opts.end(); ++opt) {
+        Subnet::OptionDescriptor desc =
+            subnet->getOptionDescriptor("dhcp4", *opt);
+        if (desc.option) {
+            msg->addOption(desc.option);
+        }
+    }
 }
 
-void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+void
+Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
+    // Identify options that we always want to send to the
+    // client (if they are configured).
+    static const uint16_t required_options[] = {
+        DHO_SUBNET_MASK,
+        DHO_ROUTERS,
+        DHO_DOMAIN_NAME_SERVERS,
+        DHO_DOMAIN_NAME };
+
+    static size_t required_options_size =
+        sizeof(required_options) / sizeof(required_options[0]);
+
+    // Get the subnet.
+    Subnet4Ptr subnet = selectSubnet(question);
+    if (!subnet) {
+        return;
+    }
+
+    // Try to find all 'required' options in the outgoing
+    // message. Those that are not present will be added.
+    for (int i = 0; i < required_options_size; ++i) {
+        OptionPtr opt = msg->getOption(required_options[i]);
+        if (!opt) {
+            // Check whether option has been configured.
+            Subnet::OptionDescriptor desc =
+                subnet->getOptionDescriptor("dhcp4", required_options[i]);
+            if (desc.option) {
+                msg->addOption(desc.option);
+            }
+        }
+    }
+}
+
+void
+Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
 
     // We need to select a subnet the client is connected in.
     Subnet4Ptr subnet = selectSubnet(question);
@@ -316,10 +522,12 @@ void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
         opt->setUint32(lease->valid_lft_);
         answer->addOption(opt);
 
-        // @todo: include real router information here
         // Router (type 3)
-        opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
-        answer->addOption(opt);
+        Subnet::OptionDescriptor opt_routers =
+            subnet->getOptionDescriptor("dhcp4", DHO_ROUTERS);
+        if (opt_routers.option) {
+            answer->addOption(opt_routers.option);
+        }
 
         // Subnet mask (type 1)
         answer->addOption(getNetmaskOption(subnet));
@@ -343,7 +551,8 @@ void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
     }
 }
 
-OptionPtr Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
+OptionPtr
+Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
     uint32_t netmask = getNetmask4(subnet->get().second);
 
     OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
@@ -352,33 +561,46 @@ OptionPtr Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
     return (opt);
 }
 
-Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
+Pkt4Ptr
+Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
     Pkt4Ptr offer = Pkt4Ptr
         (new Pkt4(DHCPOFFER, discover->getTransid()));
 
     copyDefaultFields(discover, offer);
     appendDefaultOptions(offer, DHCPOFFER);
-    appendRequestedOptions(offer);
+    appendRequestedOptions(discover, offer);
 
     assignLease(discover, offer);
 
+    // There are a few basic options that we always want to
+    // include in the response. If client did not request
+    // them we append them for him.
+    appendBasicOptions(discover, offer);
+
     return (offer);
 }
 
-Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
+Pkt4Ptr
+Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
     Pkt4Ptr ack = Pkt4Ptr
         (new Pkt4(DHCPACK, request->getTransid()));
 
     copyDefaultFields(request, ack);
     appendDefaultOptions(ack, DHCPACK);
-    appendRequestedOptions(ack);
+    appendRequestedOptions(request, ack);
 
     assignLease(request, ack);
 
+    // There are a few basic options that we always want to
+    // include in the response. If client did not request
+    // them we append them for him.
+    appendBasicOptions(request, ack);
+
     return (ack);
 }
 
-void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
+void
+Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 
     // Try to find client-id
     ClientIdPtr client_id;
@@ -446,11 +668,13 @@ void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
 
 }
 
-void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
+void
+Dhcpv4Srv::processDecline(Pkt4Ptr& /* decline */) {
     /// TODO: Implement this.
 }
 
-Pkt4Ptr Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
+Pkt4Ptr
+Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
     /// TODO: Currently implemented echo mode. Implement this for real
     return (inform);
 }
@@ -486,7 +710,8 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
     return (UNKNOWN);
 }
 
-Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+Subnet4Ptr
+Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
 
     // Is this relayed message?
     IOAddress relay = question->getGiaddr();
@@ -501,7 +726,8 @@ Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
     }
 }
 
-void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
+void
+Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
     OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
     switch (serverid) {
     case FORBIDDEN:
@@ -524,3 +750,6 @@ void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
         ;
     }
 }
+
+}   // namespace dhcp
+}   // namespace isc

+ 42 - 3
src/bin/dhcp4/dhcp4_srv.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-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
@@ -173,8 +173,9 @@ protected:
     /// This method assigns options that were requested by client
     /// (sent in PRL) or are enforced by server.
     ///
+    /// @param question DISCOVER or REQUEST message from a client.
     /// @param msg outgoing message (options will be added here)
-    void appendRequestedOptions(Pkt4Ptr& msg);
+    void appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
 
     /// @brief Assigns a lease and appends corresponding options
     ///
@@ -186,6 +187,19 @@ protected:
     /// @param answer OFFER or ACK/NAK message (lease options will be added here)
     void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
 
+    /// @brief Append basic options if they are not present.
+    ///
+    /// This function adds the following basic options if they
+    /// are not yet added to the message:
+    /// - Subnet Mask,
+    /// - Router,
+    /// - Name Server,
+    /// - Domain Name.
+    ///
+    /// @param question DISCOVER or REQUEST message from a client.
+    /// @param msg the message to add options to.
+    void appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
+
     /// @brief Attempts to renew received addresses
     ///
     /// Attempts to renew existing lease. This typically includes finding a lease that
@@ -216,7 +230,32 @@ protected:
     ///
     /// @throws isc::Unexpected Failed to obtain server identifier (i.e. no
     //          previously stored configuration and no network interfaces available)
-    void setServerID();
+    void generateServerID();
+
+    /// @brief attempts to load server-id from a file
+    ///
+    /// Tries to load duid from a text file. If the load is successful,
+    /// it creates server-id option and stores it in serverid_ (to be used
+    /// later by getServerID()).
+    ///
+    /// @param file_name name of the server-id file to load
+    /// @return true if load was successful, false otherwise
+    bool loadServerID(const std::string& file_name);
+
+    /// @brief attempts to write server-id to a file
+    /// Tries to write server-id content (stored in serverid_) to a text file.
+    ///
+    /// @param file_name name of the server-id file to write
+    /// @return true if write was successful, false otherwise
+    bool writeServerID(const std::string& file_name);
+
+    /// @brief converts server-id to text
+    /// Converts content of server-id option to a text representation, e.g.
+    /// "192.0.2.1"
+    ///
+    /// @param opt option that contains server-id
+    /// @return string representation
+    static std::string srvidToString(const OptionPtr& opt);
 
     /// @brief Selects a subnet for a given client's packet.
     ///

+ 4 - 6
src/bin/dhcp4/tests/Makefile.am

@@ -34,6 +34,10 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
 CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some Boost headers when compiling with clang
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
 
 if USE_STATIC_LINK
 AM_LDFLAGS = -static
@@ -56,12 +60,6 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
 dhcp4_unittests_SOURCES += config_parser_unittest.cc
 nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
 
-if USE_CLANGPP
-# Disable unused parameter warning caused by some of the
-# Boost headers when compiling with clang.
-dhcp4_unittests_CXXFLAGS = -Wno-unused-parameter
-endif
-
 dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
 dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
 dhcp4_unittests_LDADD = $(GTEST_LDADD)

+ 476 - 18
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -21,6 +21,8 @@
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/config_parser.h>
 #include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <boost/foreach.hpp>
@@ -61,7 +63,7 @@ public:
         EXPECT_EQ(expected_value, it->second);
     }
 
-    // Checks if config_result (result of DHCP server configuration) has
+    // Checks if the result of DHCP server configuration has
     // expected code (0 for success, other for failures).
     // Also stores result in rcode_ and comment_.
     void checkResult(ConstElementPtr status, int expected_code) {
@@ -345,7 +347,6 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
         "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
-    cout << config << endl;
 
     ElementPtr json = Element::fromJSON(config);
 
@@ -379,7 +380,6 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
         "    \"valid-lifetime\": 4,"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
-    cout << config << endl;
 
     ElementPtr json = Element::fromJSON(config);
 
@@ -408,7 +408,6 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
         "    \"pool\": [ \"192.0.4.0/28\" ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
-    cout << config << endl;
 
     ElementPtr json = Element::fromJSON(config);
 
@@ -433,7 +432,6 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
         "    \"pool\": [ \"192.0.2.128/28\" ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
-    cout << config << endl;
 
     ElementPtr json = Element::fromJSON(config);
 
@@ -461,7 +459,8 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -474,6 +473,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
+    checkResult(status, 0);
 
     // The option definition should now be available in the CfgMgr.
     def = CfgMgr::instance().getOptionDef("isc", 100);
@@ -484,6 +484,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
     EXPECT_EQ(100, def->getCode());
     EXPECT_FALSE(def->getArrayType());
     EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
 }
 
 // The goal of this test is to check whether an option definiiton
@@ -499,7 +500,8 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
         "      \"type\": \"record\","
         "      \"array\": False,"
         "      \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -523,6 +525,7 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
     EXPECT_EQ(100, def->getCode());
     EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
     EXPECT_FALSE(def->getArrayType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
 
     // The option comprises the record of data fields. Verify that all
     // fields are present and they are of the expected types.
@@ -546,7 +549,8 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
         "      \"type\": \"uint32\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
@@ -554,7 +558,8 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -578,6 +583,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
     EXPECT_EQ(100, def1->getCode());
     EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
     EXPECT_FALSE(def1->getArrayType());
+    EXPECT_TRUE(def1->getEncapsulatedSpace().empty());
 
     // Check the second option definition we have created.
     OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
@@ -588,6 +594,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
     EXPECT_EQ(101, def2->getCode());
     EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
     EXPECT_FALSE(def2->getArrayType());
+    EXPECT_TRUE(def2->getEncapsulatedSpace().empty());
 }
 
 // The goal of this test is to verify that the duplicated option
@@ -604,7 +611,8 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
         "      \"type\": \"uint32\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  },"
         "  {"
         "      \"name\": \"foo-2\","
@@ -612,7 +620,8 @@ TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
         "      \"type\": \"ipv4-address\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -640,7 +649,8 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
         "      \"type\": \"uint32\","
         "      \"array\": True,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -664,6 +674,48 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
     EXPECT_EQ(100, def->getCode());
     EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
     EXPECT_TRUE(def->getArrayType());
+    EXPECT_TRUE(def->getEncapsulatedSpace().empty());
+}
+
+// The purpose of this test to verify that encapsulated option
+// space name may be specified.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulate) {
+
+    // Configuration string. Included the encapsulated
+    // option space name.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"sub-opts-space\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Make sure that the particular option definition does not exist.
+    OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_FALSE(def);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // The option definition should now be available in the CfgMgr.
+    def = CfgMgr::instance().getOptionDef("isc", 100);
+    ASSERT_TRUE(def);
+
+    // Check the option data.
+    EXPECT_EQ("foo", def->getName());
+    EXPECT_EQ(100, def->getCode());
+    EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+    EXPECT_FALSE(def->getArrayType());
+    EXPECT_EQ("sub-opts-space", def->getEncapsulatedSpace());
 }
 
 /// The purpose of this test is to verify that the option definition
@@ -678,7 +730,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -703,7 +756,8 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
         "      \"type\": \"sting\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -728,7 +782,62 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
         "      \"type\": \"record\","
         "      \"array\": False,"
         "      \"record-types\": \"uint32,uint8,sting\","
-        "      \"space\": \"isc\""
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the invalid encapsulated
+/// option space name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidEncapsulatedSpace) {
+    // Configuration string. The encapsulated option space
+    // name is invalid (% character is not allowed).
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"invalid%space%name\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
+
+/// The goal of this test is to verify that the encapsulated
+/// option space name can't be specified for the option that
+/// comprises an array of data fields.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulatedSpaceAndArray) {
+    // Configuration string. The encapsulated option space
+    // name is set to non-empty value and the array flag
+    // is set.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": True,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"valid-space-name\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -741,6 +850,31 @@ TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
     checkResult(status, 1);
 }
 
+/// The goal of this test is to verify that the option may not
+/// encapsulate option space it belongs to.
+TEST_F(Dhcp4ParserTest, optionDefEncapsulateOwnSpace) {
+    // Configuration string. Option is set to encapsulate
+    // option space it belongs to.
+    std::string config =
+        "{ \"option-def\": [ {"
+        "      \"name\": \"foo\","
+        "      \"code\": 100,"
+        "      \"type\": \"uint32\","
+        "      \"array\": False,"
+        "      \"record-types\": \"\","
+        "      \"space\": \"isc\","
+        "      \"encapsulate\": \"isc\""
+        "  } ]"
+        "}";
+    ElementPtr json = Element::fromJSON(config);
+
+    // Use the configuration string to create new option definition.
+    ConstElementPtr status;
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    // Expecting parsing error (error code 1).
+    checkResult(status, 1);
+}
 
 /// The purpose of this test is to verify that it is not allowed
 /// to override the standard option (that belongs to dhcp4 option
@@ -759,7 +893,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\""
+        "      \"space\": \"dhcp4\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     ElementPtr json = Element::fromJSON(config);
@@ -794,7 +929,8 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "      \"type\": \"string\","
         "      \"array\": False,"
         "      \"record-types\": \"\","
-        "      \"space\": \"dhcp4\""
+        "      \"space\": \"dhcp4\","
+        "      \"encapsulate\": \"\""
         "  } ]"
         "}";
     json = Element::fromJSON(config);
@@ -907,7 +1043,8 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
         "    \"type\": \"uint32\","
         "    \"array\": False,"
         "    \"record-types\": \"\","
-        "    \"space\": \"isc\""
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
         " } ],"
         "\"subnet4\": [ { "
         "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
@@ -940,6 +1077,166 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
     ASSERT_FALSE(desc3.option);
 }
 
+// The goal of this test is to verify that it is possible to
+// encapsulate option space containing some options with
+// another option. In this test we create base option that
+// encapsulates option space 'isc' that comprises two other
+// options. Also, for all options their definitions are
+// created.
+TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
+
+    // @todo DHCP configurations has many dependencies between
+    // parameters. First of all, configuration for subnet is
+    // inherited from the global values. Thus subnet has to be
+    // configured when all global values have been configured.
+    // Also, an option can encapsulate another option only
+    // if the latter has been configured. For this reason in this
+    // test we created two-stage configuration where first we
+    // created options that belong to encapsulated option space.
+    // In the second stage we add the base option. Also, the Subnet
+    // object is configured in the second stage so it is created
+    // at the very end (when all other parameters are configured).
+
+    // Starting stage 1. Configure sub-options and their definitions.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"isc\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Stage 2. Configure base option and a subnet. Please note that
+    // the configuration from the stage 2 is repeated because BIND
+    // configuration manager sends whole configuration for the lists
+    // where at least one element is being modified or added.
+    config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"base-option\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 222,"
+        "    \"data\": \"11\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"isc\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"isc\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"base-option\","
+        "    \"code\": 222,"
+        "    \"type\": \"uint8\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"dhcp4\","
+        "    \"encapsulate\": \"isc\""
+        "},"
+        "{"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"isc\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}";
+
+
+    json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Get the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+
+    // We should have one option available.
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
+
+    // Get the option.
+    Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("dhcp4", 222);
+    EXPECT_TRUE(desc.option);
+    EXPECT_EQ(222, desc.option->getType());
+
+    // This opton should comprise two sub-options.
+    // One of them is 'foo' with code 1.
+    OptionPtr option_foo = desc.option->getOption(1);
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(1, option_foo->getType());
+
+    // ...another one 'foo2' with code 2.
+    OptionPtr option_foo2 = desc.option->getOption(2);
+    ASSERT_TRUE(option_foo2);
+    EXPECT_EQ(2, option_foo2->getType());
+}
+
 // Goal of this test is to verify options configuration
 // for a single subnet. In particular this test checks
 // that local options configuration overrides global
@@ -1290,4 +1587,165 @@ TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
     checkResult(status, 1);
 }
 
+// The goal of this test is to verify that the standard option can
+// be configured to encapsulate multiple other options.
+TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
+
+    // The configuration is two stage process in this test.
+    // In the first stahe we create definitions of suboptions
+    // that we will add to the base option.
+    // Let's create some dummy options: foo and foo2.
+    string config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " } ]"
+        "}";
+
+    ConstElementPtr status;
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Once the definitions have been added we can configure the
+    // standard option #17. This option comprises an enterprise
+    // number and sub options. By convention (introduced in
+    // std_option_defs.h) option named 'vendor-opts'
+    // encapsulates the option space named 'vendor-opts-space'.
+    // We add our dummy options to this option space and thus
+    // they should be included as sub-options in the 'vendor-opts'
+    // option.
+    config = "{ \"interface\": [ \"all\" ],"
+        "\"rebind-timer\": 2000,"
+        "\"renew-timer\": 1000,"
+        "\"option-data\": [ {"
+        "    \"name\": \"vendor-encapsulated-options\","
+        "    \"space\": \"dhcp4\","
+        "    \"code\": 43,"
+        "    \"data\": \"\","
+        "    \"csv-format\": False"
+        " },"
+        " {"
+        "    \"name\": \"foo\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 1,"
+        "    \"data\": \"1234\","
+        "    \"csv-format\": True"
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"code\": 2,"
+        "    \"data\": \"192.168.2.1\","
+        "    \"csv-format\": True"
+        " } ],"
+        "\"option-def\": [ {"
+        "    \"name\": \"foo\","
+        "    \"code\": 1,"
+        "    \"type\": \"uint32\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " },"
+        " {"
+        "    \"name\": \"foo2\","
+        "    \"code\": 2,"
+        "    \"type\": \"ipv4-address\","
+        "    \"array\": False,"
+        "    \"record-types\": \"\","
+        "    \"space\": \"vendor-encapsulated-options-space\","
+        "    \"encapsulate\": \"\""
+        " } ],"
+        "\"subnet4\": [ { "
+        "    \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+        "    \"subnet\": \"192.0.2.0/24\""
+        " } ]"
+        "}";
+
+
+    json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+    ASSERT_TRUE(status);
+    checkResult(status, 0);
+
+    // Get the subnet.
+    Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+    ASSERT_TRUE(subnet);
+
+    // We should have one option available.
+    Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+    ASSERT_TRUE(options);
+    ASSERT_EQ(1, options->size());
+
+    // Get the option.
+    Subnet::OptionDescriptor desc =
+        subnet->getOptionDescriptor("dhcp4", DHO_VENDOR_ENCAPSULATED_OPTIONS);
+    EXPECT_TRUE(desc.option);
+    EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, desc.option->getType());
+
+    // Option with the code 1 should be added as a sub-option.
+    OptionPtr option_foo = desc.option->getOption(1);
+    ASSERT_TRUE(option_foo);
+    EXPECT_EQ(1, option_foo->getType());
+    // This option comprises a single uint32_t value thus it is
+    // represented by OptionInt<uint32_t> class. Let's get the
+    // object of this type.
+    boost::shared_ptr<OptionInt<uint32_t> > option_foo_uint32 =
+        boost::dynamic_pointer_cast<OptionInt<uint32_t> >(option_foo);
+    ASSERT_TRUE(option_foo_uint32);
+    // Validate the value according to the configuration.
+    EXPECT_EQ(1234, option_foo_uint32->getValue());
+
+    // Option with the code 2 should be added as a sub-option.
+    OptionPtr option_foo2 = desc.option->getOption(2);
+    ASSERT_TRUE(option_foo2);
+    EXPECT_EQ(2, option_foo2->getType());
+    // This option comprises the IPV4 address. Such option is
+    // represented by OptionCustom object.
+    OptionCustomPtr option_foo2_v4 =
+        boost::dynamic_pointer_cast<OptionCustom>(option_foo2);
+    ASSERT_TRUE(option_foo2_v4);
+    // Get the IP address carried by this option and validate it.
+    EXPECT_EQ("192.168.2.1", option_foo2_v4->readAddress().toText());
+
+    // Option with the code 3 should not be added.
+    EXPECT_FALSE(desc.option->getOption(3));
+}
+
+
 };

+ 228 - 14
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -18,6 +18,9 @@
 #include <asiolink/io_address.h>
 #include <dhcp/dhcp4.h>
 #include <dhcp/option.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int_array.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/dhcp4_log.h>
 #include <dhcpsrv/cfgmgr.h>
@@ -49,9 +52,15 @@ public:
     using Dhcpv4Srv::processDecline;
     using Dhcpv4Srv::processInform;
     using Dhcpv4Srv::getServerID;
+    using Dhcpv4Srv::loadServerID;
+    using Dhcpv4Srv::generateServerID;
+    using Dhcpv4Srv::writeServerID;
     using Dhcpv4Srv::sanityCheck;
+    using Dhcpv4Srv::srvidToString;
 };
 
+static const char* SRVID_FILE = "server-id-test.txt";
+
 class Dhcpv4SrvTest : public ::testing::Test {
 public:
 
@@ -67,12 +76,83 @@ public:
 
         CfgMgr::instance().deleteSubnets4();
         CfgMgr::instance().addSubnet4(subnet_);
+
+        // Add Router option.
+        Option4AddrLstPtr opt_routers(new Option4AddrLst(DHO_ROUTERS));
+        opt_routers->setAddress(IOAddress("192.0.2.2"));
+        subnet_->addOption(opt_routers, false, "dhcp4");
+
+        // it's ok if that fails. There should not be such a file anyway
+        unlink(SRVID_FILE);
+    }
+
+    /// @brief Add 'Parameter Request List' option to the packet.
+    ///
+    /// This function PRL option comprising the following option codes:
+    /// - 5 - Name Server
+    /// - 15 - Domain Name
+    /// - 7 - Log Server
+    /// - 8 - Quotes Server
+    /// - 9 - LPR Server
+    ///
+    /// @param pkt packet to add PRL option to.
+    void addPrlOption(Pkt4Ptr& pkt) {
+
+        OptionUint8ArrayPtr option_prl =
+            OptionUint8ArrayPtr(new OptionUint8Array(Option::V4,
+                                                     DHO_DHCP_PARAMETER_REQUEST_LIST));
+
+        // Let's request options that have been configured for the subnet.
+        option_prl->addValue(DHO_DOMAIN_NAME_SERVERS);
+        option_prl->addValue(DHO_DOMAIN_NAME);
+        option_prl->addValue(DHO_LOG_SERVERS);
+        option_prl->addValue(DHO_COOKIE_SERVERS);
+        // Let's also request the option that hasn't been configured. In such
+        // case server should ignore request for this particular option.
+        option_prl->addValue(DHO_LPR_SERVERS);
+        // And add 'Parameter Request List' option into the DISCOVER packet.
+        pkt->addOption(option_prl);
+    }
+
+    /// @brief Configures options being requested in the PRL option.
+    ///
+    /// The lpr-servers option is NOT configured here altough it is
+    /// added to the 'Parameter Request List' option in the
+    /// \ref addPrlOption. When requested option is not configured
+    /// the server should not return it in its rensponse. The goal
+    /// of not configuring the requested option is to verify that
+    /// the server will not return it.
+    void configureRequestedOptions() {
+        // dns-servers
+        Option4AddrLstPtr
+            option_dns_servers(new Option4AddrLst(DHO_DOMAIN_NAME_SERVERS));
+        option_dns_servers->addAddress(IOAddress("192.0.2.1"));
+        option_dns_servers->addAddress(IOAddress("192.0.2.100"));
+        ASSERT_NO_THROW(subnet_->addOption(option_dns_servers, false, "dhcp4"));
+
+        // domain-name
+        OptionDefinition def("domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE);
+        boost::shared_ptr<OptionCustom>
+            option_domain_name(new OptionCustom(def, Option::V4));
+        option_domain_name->writeFqdn("example.com");
+        subnet_->addOption(option_domain_name, false, "dhcp4");
+
+        // log-servers
+        Option4AddrLstPtr option_log_servers(new Option4AddrLst(DHO_LOG_SERVERS));
+        option_log_servers->addAddress(IOAddress("192.0.2.2"));
+        option_log_servers->addAddress(IOAddress("192.0.2.10"));
+        ASSERT_NO_THROW(subnet_->addOption(option_log_servers, false, "dhcp4"));
+
+        // cookie-servers
+        Option4AddrLstPtr option_cookie_servers(new Option4AddrLst(DHO_COOKIE_SERVERS));
+        option_cookie_servers->addAddress(IOAddress("192.0.2.1"));
+        ASSERT_NO_THROW(subnet_->addOption(option_cookie_servers, false, "dhcp4"));
     }
 
     /// @brief checks that the response matches request
     /// @param q query (client's message)
     /// @param a answer (server's message)
-    void MessageCheck(const boost::shared_ptr<Pkt4>& q,
+    void messageCheck(const boost::shared_ptr<Pkt4>& q,
                       const boost::shared_ptr<Pkt4>& a) {
         ASSERT_TRUE(q);
         ASSERT_TRUE(a);
@@ -82,20 +162,40 @@ public:
         EXPECT_EQ(q->getIndex(),  a->getIndex());
         EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
 
-        // Check that bare minimum of required options are there
+        // Check that bare minimum of required options are there.
+        // We don't check options requested by a client. Those
+        // are checked elsewhere.
         EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
         EXPECT_TRUE(a->getOption(DHO_ROUTERS));
         EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
         EXPECT_TRUE(a->getOption(DHO_DHCP_LEASE_TIME));
         EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
-        EXPECT_TRUE(a->getOption(DHO_ROUTERS));
-        EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
-        EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
 
         // Check that something is offered
         EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
     }
 
+    /// @brief Check that requested options are present.
+    ///
+    /// @param pkt packet to be checked.
+    void optionsCheck(const Pkt4Ptr& pkt) {
+        // Check that the requested and configured options are returned
+        // in the ACK message.
+        EXPECT_TRUE(pkt->getOption(DHO_DOMAIN_NAME))
+            << "domain-name not present in the response";
+        EXPECT_TRUE(pkt->getOption(DHO_DOMAIN_NAME_SERVERS))
+            << "dns-servers not present in the response";
+        EXPECT_TRUE(pkt->getOption(DHO_LOG_SERVERS))
+            << "log-servers not present in the response";
+        EXPECT_TRUE(pkt->getOption(DHO_COOKIE_SERVERS))
+            << "cookie-servers not present in the response";
+        // Check that the requested but not configured options are not
+        // returned in the ACK message.
+        EXPECT_FALSE(pkt->getOption(DHO_LPR_SERVERS))
+            << "domain-name present in the response but it is"
+            << " expected not to be present";
+    }
+
     /// @brief generates client-id option
     ///
     /// Generate client-id option of specified length
@@ -134,11 +234,13 @@ public:
     /// Check that address was returned from proper range, that its lease
     /// lifetime is correct, that T1 and T2 are returned properly
     /// @param rsp response to be checked
-    /// @param subnet subnet that should be used to verify assigned address and options
+    /// @param subnet subnet that should be used to verify assigned address
+    ///        and options
     /// @param t1_mandatory is T1 mandatory?
     /// @param t2_mandatory is T2 mandatory?
     void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
-                            bool t1_mandatory = false, bool t2_mandatory = false) {
+                            bool t1_mandatory = false,
+                            bool t2_mandatory = false) {
 
         // Technically inPool implies inRange, but let's be on the safe
         // side and check both.
@@ -168,7 +270,7 @@ public:
         if (opt) {
             EXPECT_EQ(opt->getUint32(), subnet->getT2());
         } else {
-            if (t1_mandatory) {
+            if (t2_mandatory) {
                 ADD_FAILURE() << "Required T2 option missing";
             }
         }
@@ -245,6 +347,9 @@ public:
 
     ~Dhcpv4SrvTest() {
         CfgMgr::instance().deleteSubnets4();
+
+        // Let's clean up if there is such a file.
+        unlink(SRVID_FILE);
     };
 
     /// @brief A subnet used in most tests
@@ -312,6 +417,12 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
     pkt->setHops(3);
     pkt->setRemotePort(DHCP4_SERVER_PORT);
 
+    // We are going to test that certain options are returned
+    // (or not returned) in the OFFER message when requested
+    // using 'Parameter Request List' option. Let's configure
+    // those options that are returned when requested.
+    configureRequestedOptions();
+
     // Should not throw
     EXPECT_NO_THROW(
         offer = srv->processDiscover(pkt);
@@ -325,7 +436,39 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
     // This is relayed message. It should be sent back to relay address.
     EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
 
-    MessageCheck(pkt, offer);
+    messageCheck(pkt, offer);
+
+    // There are some options that are always present in the
+    // message, even if not requested.
+    EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME));
+    EXPECT_TRUE(offer->getOption(DHO_DOMAIN_NAME_SERVERS));
+
+    // We did not request any options so they should not be present
+    // in the OFFER.
+    EXPECT_FALSE(offer->getOption(DHO_LOG_SERVERS));
+    EXPECT_FALSE(offer->getOption(DHO_COOKIE_SERVERS));
+    EXPECT_FALSE(offer->getOption(DHO_LPR_SERVERS));
+
+    // Add 'Parameter Request List' option.
+    addPrlOption(pkt);
+
+    // Now repeat the test but request some options.
+    EXPECT_NO_THROW(
+        offer = srv->processDiscover(pkt);
+    );
+
+    // Should return something
+    ASSERT_TRUE(offer);
+
+    EXPECT_EQ(DHCPOFFER, offer->getType());
+
+    // This is relayed message. It should be sent back to relay address.
+    EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
+
+    messageCheck(pkt, offer);
+
+    // Check that the requested options are returned.
+    optionsCheck(offer);
 
     // Now repeat the test for directly sent message
     pkt->setHops(0);
@@ -345,7 +488,10 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
     // to relay.
     EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
 
-    MessageCheck(pkt, offer);
+    messageCheck(pkt, offer);
+
+    // Check that the requested options are returned.
+    optionsCheck(offer);
 
     delete srv;
 }
@@ -373,6 +519,12 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
     req->setRemoteAddr(IOAddress("192.0.2.56"));
     req->setGiaddr(IOAddress("192.0.2.67"));
 
+    // We are going to test that certain options are returned
+    // in the ACK message when requested using 'Parameter
+    // Request List' option. Let's configure those options that
+    // are returned when requested.
+    configureRequestedOptions();
+
     // Should not throw
     ASSERT_NO_THROW(
         ack = srv->processRequest(req);
@@ -386,7 +538,37 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
     // This is relayed message. It should be sent back to relay address.
     EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
 
-    MessageCheck(req, ack);
+    messageCheck(req, ack);
+
+    // There are some options that are always present in the
+    // message, even if not requested.
+    EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME));
+    EXPECT_TRUE(ack->getOption(DHO_DOMAIN_NAME_SERVERS));
+
+    // We did not request any options so these should not be present
+    // in the ACK.
+    EXPECT_FALSE(ack->getOption(DHO_LOG_SERVERS));
+    EXPECT_FALSE(ack->getOption(DHO_COOKIE_SERVERS));
+    EXPECT_FALSE(ack->getOption(DHO_LPR_SERVERS));
+
+    // Add 'Parameter Request List' option.
+    addPrlOption(req);
+
+    // Repeat the test but request some options.
+    ASSERT_NO_THROW(
+        ack = srv->processRequest(req);
+    );
+
+    // Should return something
+    ASSERT_TRUE(ack);
+
+    EXPECT_EQ(DHCPACK, ack->getType());
+
+    // This is relayed message. It should be sent back to relay address.
+    EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
+
+    // Check that the requested options are returned.
+    optionsCheck(ack);
 
     // Now repeat the test for directly sent message
     req->setHops(0);
@@ -406,7 +588,10 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
     // to relay.
     EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
 
-    MessageCheck(req, ack);
+    messageCheck(req, ack);
+
+    // Check that the requested options are returned.
+    optionsCheck(ack);
 
     delete srv;
 }
@@ -691,7 +876,7 @@ TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
     checkAddressParams(offer2, subnet_);
     checkAddressParams(offer3, subnet_);
 
-    // Check DUIDs
+    // Check server-ids
     checkServerId(offer1, srv->getServerID());
     checkServerId(offer2, srv->getServerID());
     checkServerId(offer3, srv->getServerID());
@@ -878,7 +1063,6 @@ TEST_F(Dhcpv4SrvTest, RenewBasic) {
 
     // let's create a lease and put it in the LeaseMgr
     uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
-    uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
     Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
                               &client_id_->getDuid()[0], client_id_->getDuid().size(),
                               temp_valid, temp_t1, temp_t2, temp_timestamp,
@@ -1126,4 +1310,34 @@ TEST_F(Dhcpv4SrvTest, ReleaseReject) {
     EXPECT_FALSE(l);
 }
 
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv4SrvTest, ServerID) {
+    NakedDhcpv4Srv srv(0);
+
+    string srvid_text = "192.0.2.100";
+    IOAddress srvid(srvid_text);
+
+    fstream file1(SRVID_FILE, ios::out | ios::trunc);
+    file1 << srvid_text;
+    file1.close();
+
+    // Test reading from a file
+    EXPECT_TRUE(srv.loadServerID(SRVID_FILE));
+    ASSERT_TRUE(srv.getServerID());
+    EXPECT_EQ(srvid_text, srv.srvidToString(srv.getServerID()));
+
+    // Now test writing to a file
+    EXPECT_EQ(0, unlink(SRVID_FILE));
+    EXPECT_NO_THROW(srv.writeServerID(SRVID_FILE));
+
+    fstream file2(SRVID_FILE, ios::in);
+    ASSERT_TRUE(file2.good());
+    string text;
+    file2 >> text;
+    file2.close();
+
+    EXPECT_EQ(srvid_text, text);
+}
+
 } // end of anonymous namespace

+ 1 - 1
src/bin/dhcp4/tests/dhcp4_test.py

@@ -13,7 +13,7 @@
 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-from bind10_src import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
+from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
 
 import unittest
 import sys

+ 4 - 6
src/bin/dhcp6/Makefile.am

@@ -6,6 +6,10 @@ AM_CPPFLAGS += -I$(top_srcdir)/src/lib/cc -I$(top_builddir)/src/lib/cc
 AM_CPPFLAGS += $(BOOST_INCLUDES)
 
 AM_CXXFLAGS = $(B10_CXXFLAGS)
+if USE_CLANGPP
+# Disable unused parameter warning caused by some Boost headers when compiling with clang
+AM_CXXFLAGS += -Wno-unused-parameter
+endif
 
 if USE_STATIC_LINK
 AM_LDFLAGS = -static
@@ -53,12 +57,6 @@ b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
 nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
 EXTRA_DIST += dhcp6_messages.mes
 
-if USE_CLANGPP
-# Disable unused parameter warning caused by some of the
-# Boost headers when compiling with clang.
-b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
-endif
-
 b10_dhcp6_LDADD  = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la

+ 3 - 0
src/bin/dhcp6/b10-dhcp6.xml

@@ -79,6 +79,9 @@
     <title>SEE ALSO</title>
     <para>
       <citerefentry>
+        <refentrytitle>b10-init</refentrytitle><manvolnum>8</manvolnum>
+      </citerefentry>,
+      <citerefentry>
         <refentrytitle>bind10</refentrytitle><manvolnum>8</manvolnum>
       </citerefentry>.
     </para>

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


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