Browse Source

Merge branch 'master' into trac2164

Mukund Sivaraman 12 years ago
parent
commit
199af97d47
43 changed files with 3655 additions and 1063 deletions
  1. 1 0
      .gitignore
  2. 9 0
      ChangeLog
  3. 5 3
      doc/devel/02-dhcp.dox
  4. 174 145
      doc/guide/bind10-guide.html
  5. 365 336
      doc/guide/bind10-guide.txt
  6. 290 148
      doc/guide/bind10-guide.xml
  7. 1 1
      doc/guide/bind10-messages.html
  8. 2 1
      src/bin/cfgmgr/Makefile.am
  9. 5 4
      src/bin/ddns/ddns.spec
  10. 1 1
      src/bin/dhcp4/ctrl_dhcp4_srv.cc
  11. 12 7
      src/bin/dhcp4/dhcp4_srv.cc
  12. 35 14
      src/bin/dhcp4/main.cc
  13. 3 3
      src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
  14. 40 5
      src/bin/dhcp4/tests/dhcp4_test.py
  15. 3 0
      src/bin/dhcp6/Makefile.am
  16. 159 0
      src/bin/dhcp6/ctrl_dhcp6_srv.cc
  17. 123 0
      src/bin/dhcp6/ctrl_dhcp6_srv.h
  18. 14 2
      src/bin/dhcp6/dhcp6.spec
  19. 24 16
      src/bin/dhcp6/dhcp6_srv.cc
  20. 3 1
      src/bin/dhcp6/dhcp6_srv.h
  21. 56 49
      src/bin/dhcp6/main.cc
  22. 4 1
      src/bin/dhcp6/tests/Makefile.am
  23. 85 0
      src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
  24. 39 5
      src/bin/dhcp6/tests/dhcp6_test.py
  25. 0 4
      src/bin/loadzone/Makefile.am
  26. 17 31
      src/bin/resolver/resolver.spec.pre.in
  27. 38 29
      src/bin/sysinfo/sysinfo.py.in
  28. 4 2
      src/bin/xfrout/xfrout.spec.pre.in
  29. 9 0
      src/lib/config/tests/testdata/spec40.spec
  30. 104 42
      src/lib/dhcp/iface_mgr.cc
  31. 3 1
      src/lib/dhcp/iface_mgr.h
  32. 1 1
      src/lib/dhcp/pkt6.h
  33. 1 1
      src/lib/dhcp/tests/iface_mgr_unittest.cc
  34. 32 14
      src/lib/python/isc/config/ccsession.py
  35. 13 1
      src/lib/python/isc/config/config_data.py
  36. 100 18
      src/lib/python/isc/config/tests/ccsession_test.py
  37. 97 44
      src/lib/python/isc/sysinfo/sysinfo.py
  38. 191 131
      src/lib/python/isc/sysinfo/tests/sysinfo_test.py
  39. 2 2
      tests/lettuce/features/resolver_basic.feature
  40. 2 0
      tests/tools/perfdhcp/Makefile.am
  41. 1137 0
      tests/tools/perfdhcp/stats_mgr.h
  42. 1 0
      tests/tools/perfdhcp/tests/Makefile.am
  43. 450 0
      tests/tools/perfdhcp/tests/stats_mgr_unittest.cc

+ 1 - 0
.gitignore

@@ -35,3 +35,4 @@ TAGS
 /coverage-cpp-html
 /coverage-cpp-html
 /dns++.pc
 /dns++.pc
 /report.info
 /report.info
+/logger_lockfile

+ 9 - 0
ChangeLog

@@ -1,3 +1,12 @@
+459.	[func]		tomek
+	b10-dhcp6: DHCPv6 server component is now integrated into
+	BIND10 framework. It can be started from BIND10 (using bindctl)
+	and can receive commands. The only supported command for now
+	is 'Dhcp6 shutdown'.
+	b10-dhcp4: Command line-switch '-s' to disable msgq was added.
+	b10-dhcp6: Command line-switch '-s' to disable msgq was added.
+	(Trac #1708, git e0d7c52a71414f4de1361b09d3c70431c96daa3f)
+
 458.	[build]*	jinmei
 458.	[build]*	jinmei
 	BIND 10 now relies on Boost offset_ptr, which caused some new
 	BIND 10 now relies on Boost offset_ptr, which caused some new
 	portability issues.  Such issues are detected at ./configure time.
 	portability issues.  Such issues are detected at ./configure time.

+ 5 - 3
doc/devel/02-dhcp.dox

@@ -72,11 +72,13 @@
  * DHCPv6 server component does not support relayed traffic yet, as
  * DHCPv6 server component does not support relayed traffic yet, as
  * support for relay decapsulation is not implemented yet.
  * support for relay decapsulation is not implemented yet.
  *
  *
- * DHCPv6 server component does not listen to BIND10 message queue.
- *
  * DHCPv6 server component does not use BIND10 logging yet.
  * DHCPv6 server component does not use BIND10 logging yet.
  *
  *
- * DHCPv6 server component is not integrated with boss yet.
+ * @section dhcpv6Session BIND10 message queue integration
+ *
+ * DHCPv4 server component is now integrated with BIND10 message queue.
+ * It follows the same principle as DHCPv4. See \ref dhcpv4Session for
+ * details.
  *
  *
  * @page libdhcp libdhcp++
  * @page libdhcp libdhcp++
  *
  *

File diff suppressed because it is too large
+ 174 - 145
doc/guide/bind10-guide.html


File diff suppressed because it is too large
+ 365 - 336
doc/guide/bind10-guide.txt


+ 290 - 148
doc/guide/bind10-guide.xml

@@ -1302,6 +1302,232 @@ TODO
 
 
   </chapter>
   </chapter>
 
 
+  <chapter id="common">
+    <title>Common configuration elements</title>
+
+    <para>
+      Some things are configured in the same or similar manner across
+      many modules. So we show them here in one place.
+    </para>
+
+    <section id='common-acl'>
+      <title>ACLs</title>
+
+      <para>
+        An ACL, or Access Control List, is a way to describe if a request
+        is allowed or disallowed. The principle is, there's a list of rules.
+        Each rule is a name-value mapping (a dictionary, in the JSON
+        terminology). Each rule must contain exactly one mapping called
+        "action", which describes what should happen if the rule applies.
+        There may be more mappings, calld matches, which describe the
+        conditions under which the rule applies.
+      </para>
+
+      <para>
+        When there's a query, the first rule is examined. If it matches, the
+        action in it is taken. If not, next rule is examined. If there are no
+        more rules to examine, a default action is taken.
+      </para>
+
+      <para>
+        There are three possible "action" values. The "ACCEPT" value means
+        the query is handled. If it is "REJECT", the query is not answered,
+        but a polite error message is sent back (if that makes sense in the
+        context). The "DROP" action acts like a black hole. The query is
+        not answered and no error message is sent.
+      </para>
+
+      <para>
+        If there are multiple matching conditions inside the rule, all of
+        them must be satisfied for the rule to apply. This can be used,
+        for example, to require the query to be signed by a TSIG key and
+        originate from given address.
+      </para>
+
+      <para>
+        This is encoded in form of JSON. Semi-formal description could look
+        something like this. It is described in more details below.
+        <!-- FIXME: Is <screen> really the correct one?-->
+        <screen>ACL := [ RULE, RULE, ... ]
+RULE := { "action": "ACCEPT"|"REJECT"|"DROP", MATCH, MATCH, ... }
+RULE_RAW := { MATCH, MATCH, ... }
+MATCH := FROM_MATCH|KEY_MATCH|NOT_MATCH|OR_MATCH|AND_MATCH|...
+FROM_MATCH := "from": [RANGE, RANGE, RANGE, ...] | RANGE
+RANGE := "&lt;ip range&gt;"
+KEY_MATCH := "key": [KEY, KEY, KEY, ...] | KEY
+KEY := "&lt;key name&gt;"
+NOT_MATCH := "NOT": RULE_RAW
+OR_MATCH := "ANY": [ RULE_RAW, RULE_RAW, ... ]
+AND_MATCH := "ALL": [ RULE_RAW, RULE_RAW, ... ]
+</screen>
+      </para>
+
+      <section>
+        <title>Matching properties</title>
+
+        <para>
+          The first thing you can check against is the source address
+          of request. The name is <varname>from</varname> and the value
+          is a string containing either a single IPv4 or IPv6 address,
+          or a range in the usual slash notation (eg. "192.0.2.0/24").
+        </para>
+
+        <para>
+          The other is TSIG key by which the message was signed. The ACL
+          contains only the name (under the name "key"), the key itself
+          must be stored in the global keyring. This property is applicable only
+          to the DNS context. <!-- TODO: Section for the keyring and link to
+          it.-->
+        </para>
+
+        <para>
+          More properties to match are planned &mdash; the destination
+          address, ports, matches against the packet content.
+        </para>
+      </section>
+
+      <section>
+        <title>More complicated matches</title>
+
+        <para>
+          From time to time, you need to express something more complex
+          than just a single address or key.
+        </para>
+
+        <para>
+          You can specify a list of values instead of single value. Then
+          the property needs to match at least one of the values listed
+          &mdash; so you can say <quote>"from": ["192.0.2.0/24",
+          "2001:db8::/32"]</quote> to match any address in the ranges
+          set aside for documentation. The keys or any future properties
+          will work in a similar way.
+        </para>
+
+        <note>
+          <simpara>
+	    The list form is currently rejected due to an
+	    implementation bug.  There is a plan to fix it relatively
+	    soon, so the syntax is kept here, but note that it won't
+	    work until the bug is fixed.  To keep track of the status
+	    of the issue, see
+	    <ulink url="http://bind10.isc.org/ticket/2191">Trac #2191</ulink>.
+	    Until then, the value must be a single string.
+          </simpara>
+        </note>
+
+        <para>
+          If that is not enough, you can compose the matching conditions
+          to logical expressions. They are called "ANY", "ALL" and "NOT".
+          The "ANY" and "ALL" ones contain lists of subexpressions &mdash;
+          each subexpression is a similar dictionary, just not containing
+          the "action" element. The "NOT" contains single subexpression.
+          Their function should be obvious &mdash; "NOT" matches if and
+          only if the subexpression does not match. The "ALL" matches exactly
+          when each of the subexpressions matches and "ANY" when at least
+          one matches.
+        </para>
+      </section>
+
+      <section>
+        <title>Examples</title>
+
+        <para>
+          All the examples here is just the JSON representing the ACL,
+          nicely formatted and split across lines. They are out of any
+          surrounding context. This is similar to what you'd get from
+          <command>config show_json</command> called on the entry containing
+          the ACL.
+        </para>
+
+        <para>
+          In the first example, the ACL accepts queries from two known hosts.
+          Each host has an IP addresses (both IPv4 and IPv6) and a TSIG
+          key. Other queries are politely rejected. The last entry in the list
+          has no conditions &mdash; making it match any query.
+
+          <screen>[
+  {
+    "from": ["192.0.2.1", "2001:db8::1"],
+    "key": "first.key",
+    "action": "ACCEPT"
+  },
+  {
+    "from": ["192.0.2.2", "2001:db8::2"],
+    "key": "second.key",
+    "action": "ACCEPT"
+  },
+  {
+    "action": "REJECT"
+  }
+]</screen>
+        </para>
+
+        <para>
+          Now we show two ways to accept only the queries from private ranges.
+          This is the same as rejecting anything that is outside.
+
+          <screen>[
+  {
+    "from": [
+      "10.0.0.0/8",
+      "172.16.0.0/12",
+      "192.168.0.0/16",
+      "fc00::/7"
+    ],
+    "action": "ACCEPT"
+  },
+  {
+    "action": "REJECT"
+  }
+]</screen>
+
+          <screen>[
+  {
+    "NOT": {
+       "ANY": [
+         {"from": "10.0.0.0/8"},
+         {"from": "172.16.0.0/12"},
+         {"from": "192.168.0.0/16"},
+         {"from": "fc00::/7"}
+       ]
+    },
+    "action": "REJECT"
+  },
+  {
+    "action": "ACCEPT"
+  }
+]</screen>
+        </para>
+      </section>
+
+      <section>
+        <title>Interaction with <command>bindctl</command></title>
+
+        <para>
+          Currently, <command>bindctl</command> has hard time coping with
+          the variable nature of the ACL syntax. This technical limitation
+          makes it impossible to edit parts of the entries. You need to
+          set the whole entry at once, providing the whole JSON value.
+        </para>
+
+        <para>
+          This limitation is planned to be solved soon at least partially.
+        </para>
+
+        <para>
+          You'd do something like this to create the second example.
+          Note that the whole JSON must be on a single line.
+
+          <screen>&gt; <userinput>config add somewhere/acl</userinput>
+&gt; <userinput>config set somewhere/acl[0] { "from": [ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "fc00::/7" ], "action": "ACCEPT" }</userinput>
+&gt; <userinput>config add somewhere/acl</userinput>
+&gt; <userinput>config set somewhere/acl[1] { "action": "REJECT" }</userinput>
+&gt; <userinput>config commit</userinput></screen>
+        </para>
+      </section>
+    </section>
+  </chapter>
+
   <chapter id="authserver">
   <chapter id="authserver">
     <title>Authoritative Server</title>
     <title>Authoritative Server</title>
 
 
@@ -1611,8 +1837,19 @@ can use various data source backends.
 &gt; <userinput>config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }</userinput>
 &gt; <userinput>config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }</userinput>
 &gt; <userinput>config commit</userinput></screen>
 &gt; <userinput>config commit</userinput></screen>
 
 
-          Unfortunately, due to current technical limitations, the params must
-          be set as one JSON blob, it can't be edited in
+          Initially, a map value has to be set, but this value may be an
+          empty map. After that, key/value pairs can be added with 'config
+          add' and keys can be removed with 'config remove'. The initial
+          value may be an empty map, but it has to be set before zones are
+          added or removed.
+
+          <screen>
+&gt; <userinput>config set data_sources/classes/IN[1]/params {}</userinput>
+&gt; <userinput>config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org</userinput>
+&gt; <userinput>config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com</userinput>
+&gt; <userinput>config remove data_sources/classes/IN[1]/params another.example.org</userinput>
+          </screen>
+
           <command>bindctl</command>. To reload a zone, you the same command
           <command>bindctl</command>. To reload a zone, you the same command
           as above.
           as above.
         </para>
         </para>
@@ -1903,37 +2140,17 @@ http://bind10.isc.org/wiki/ScalableZoneLoadDesign#a7.2UpdatingaZone
       can be used to control accessibility of the outbound zone
       can be used to control accessibility of the outbound zone
       transfer service.
       transfer service.
       By default, <command>b10-xfrout</command> allows any clients to
       By default, <command>b10-xfrout</command> allows any clients to
-      perform zone transfers for any zones:
+      perform zone transfers for any zones.
     </para>
     </para>
 
 
       <screen>&gt; <userinput>config show Xfrout/transfer_acl</userinput>
       <screen>&gt; <userinput>config show Xfrout/transfer_acl</userinput>
 Xfrout/transfer_acl[0]	{"action": "ACCEPT"}	any	(default)</screen>
 Xfrout/transfer_acl[0]	{"action": "ACCEPT"}	any	(default)</screen>
 
 
     <para>
     <para>
-      You can change this to, for example, rejecting all transfer
-      requests by default while allowing requests for the transfer
-      of zone "example.com" from 192.0.2.1 and 2001:db8::1 as follows:
-    </para>
-
-      <screen>&gt; <userinput>config set Xfrout/transfer_acl[0] {"action": "REJECT"}</userinput>
-&gt; <userinput>config add Xfrout/zone_config</userinput>
-&gt; <userinput>config set Xfrout/zone_config[0]/origin "example.com"</userinput>
-&gt; <userinput>config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1"},</userinput>
-<userinput>                                                 {"action": "ACCEPT", "from": "2001:db8::1"}]</userinput>
-&gt; <userinput>config commit</userinput></screen>
-
-    <note><simpara>
-        In the above example the lines
-        for <option>transfer_acl</option> were divided for
-        readability.  In the actual input it must be in a single line.
-    </simpara></note>
-
-    <para>
       If you want to require TSIG in access control, a system wide TSIG
       If you want to require TSIG in access control, a system wide TSIG
       "key ring" must be configured.
       "key ring" must be configured.
-      For example, to change the previous example to allowing requests
-      from 192.0.2.1 signed by a TSIG with a key name of
-      "key.example", you'll need to do this:
+      In this example, we allow client matching both the IP address
+      and key.
     </para>
     </para>
 
 
     <screen>&gt; <userinput>config set tsig_keys/keys ["key.example:&lt;base64-key&gt;"]</userinput>
     <screen>&gt; <userinput>config set tsig_keys/keys ["key.example:&lt;base64-key&gt;"]</userinput>
@@ -1944,6 +2161,11 @@ Xfrout/transfer_acl[0]	{"action": "ACCEPT"}	any	(default)</screen>
       will use the system wide keyring to check
       will use the system wide keyring to check
       TSIGs in the incoming messages and to sign responses.</para>
       TSIGs in the incoming messages and to sign responses.</para>
 
 
+    <para>
+      For further details on ACL configuration, see 
+      <xref linkend="common-acl" />.
+    </para>
+
     <note><simpara>
     <note><simpara>
         The way to specify zone specific configuration (ACLs, etc) is
         The way to specify zone specific configuration (ACLs, etc) is
         likely to be changed.
         likely to be changed.
@@ -2150,29 +2372,7 @@ what is XfroutClient xfr_client??
       </para>
       </para>
 
 
       <para>
       <para>
-        Multiple rules can be specified in the ACL, and an ACL rule
-        can consist of multiple constraints, such as a combination of
-        IP address and TSIG.
-        The following configuration sequence will add a new rule to
-        the ACL created in the above example.  This additional rule
-	allows update requests sent from a client
-        using TSIG key name of "key.example" (different from the
-        key used in the previous example) and has an IPv6 address of ::1.
-      <screen>
-&gt; <userinput>config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "from": "::1", "key": "key.example"}</userinput>
-&gt; <userinput>config show DDNS/zones[0]/update_acl</userinput>
-DDNS/zones[0]/update_acl[0]     {"action": "ACCEPT", "key": "key.example.org"} any (modified)
-DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.example"} any (modified)
-&gt; <userinput>config commit</userinput>
-</screen>
-      (Note the "add" in the first line.  Before this sequence, we
-      have had only entry in <varname>zones[0]/update_acl</varname>.
-      The <command>add</command> command with a value (rule) adds
-      a new entry and sets it to the given rule.
-
-      Due to a limitation of the current implementation, it doesn't
-      work if you first try to just add a new entry and then set it to
-      a given rule.)
+        Full description of ACLs can be found in <xref linkend="common-acl" />.
       </para>
       </para>
 
 
       <note><simpara>
       <note><simpara>
@@ -2188,21 +2388,6 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
       </simpara></note>
       </simpara></note>
 
 
       <para>
       <para>
-	The ACL rules will be checked in the listed order, and the
-	first matching one will apply.
-	If none of the rules matches, the default rule will apply,
-	which is rejecting any requests in the case of
-	<command>b10-ddns</command>.
-      </para>
-<!-- TODO: what are the other defaults? -->
-
-      <para>
-	Other actions than "ACCEPT", namely "REJECT" and "DROP", can be
-	used, too.
-	See <xref linkend="resolverserver"/> about their effects.
-      </para>
-
-      <para>
         Currently update ACL can only control updates per zone basis;
         Currently update ACL can only control updates per zone basis;
         it's not possible to specify access control with higher
         it's not possible to specify access control with higher
         granularity such as for particular domain names or specific
         granularity such as for particular domain names or specific
@@ -2341,59 +2526,32 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
         DNS queries from the localhost (127.0.0.1 and ::1).
         DNS queries from the localhost (127.0.0.1 and ::1).
         The <option>Resolver/query_acl</option> configuration may
         The <option>Resolver/query_acl</option> configuration may
         be used to reject, drop, or allow specific IPs or networks.
         be used to reject, drop, or allow specific IPs or networks.
-        This configuration list is first match.
+        See <xref linkend="common-acl" />.
       </para>
       </para>
 
 
       <para>
       <para>
-        The configuration's <option>action</option> item may be
-        set to <quote>ACCEPT</quote> to allow the incoming query,
-        <quote>REJECT</quote> to respond with a DNS REFUSED return
-        code, or <quote>DROP</quote> to ignore the query without
-        any response (such as a blackhole).  For more information,
-        see the respective debugging messages:  <ulink
-        url="bind10-messages.html#RESOLVER_QUERY_ACCEPTED">RESOLVER_QUERY_ACCEPTED</ulink>,
-        <ulink
-        url="bind10-messages.html#RESOLVER_QUERY_REJECTED">RESOLVER_QUERY_REJECTED</ulink>,
-        and <ulink
-url="bind10-messages.html#RESOLVER_QUERY_DROPPED">RESOLVER_QUERY_DROPPED</ulink>.
-      </para>
-
-      <para>
-        The required configuration's <option>from</option> item is set
-        to an IPv4 or IPv6 address, addresses with an network mask, or to
-        the special lowercase keywords <quote>any6</quote> (for
-        any IPv6 address) or <quote>any4</quote> (for any IPv4
-        address).
-      </para>
-
-<!-- TODO:
-/0 is for any address in that address family
-does that need any address too?
-
-TODO: tsig
--->
-
-      <para>
-        For example to allow the <replaceable>192.168.1.0/24</replaceable>
-        network to use your recursive name server, at the
-        <command>bindctl</command> prompt run:
+	The following session is an example of extending the ACL to also
+	allow queries from 192.0.2.0/24:
+        <screen>
+> <userinput>config show Resolver/query_acl</userinput>
+Resolver/query_acl[0]   {"action": "ACCEPT", "from": "127.0.0.1"}   any (default)
+Resolver/query_acl[1]   {"action": "ACCEPT", "from": "::1"} any (default)
+> <userinput>config add Resolver/query_acl</userinput>
+> <userinput>config set Resolver/query_acl[2] {"action": "ACCEPT", "from": "192.0.2.0/24"}</userinput>
+> <userinput>config add Resolver/query_acl</userinput>
+> <userinput>config show Resolver/query_acl</userinput>
+Resolver/query_acl[0]   {"action": "ACCEPT", "from": "127.0.0.1"}   any (modified)
+Resolver/query_acl[1]   {"action": "ACCEPT", "from": "::1"} any (modified)
+Resolver/query_acl[2]   {"action": "ACCEPT", "from": "192.0.2.0/24"}  any (modified)
+Resolver/query_acl[3]   {"action": "REJECT"}    any (modified)
+> <userinput>config commit</userinput></screen>
+	Note that we didn't set the value of the last final rule
+	(query_acl[3]) -- in the case of resolver, rejecting all queries is
+	the default value of a new rule.  In fact, this rule can even be
+	omitted completely, as the default, when a query falls off the list,
+	is rejection.
       </para>
       </para>
 
 
-      <screen>
-&gt; <userinput>config add Resolver/query_acl</userinput>
-&gt; <userinput>config set Resolver/query_acl[<replaceable>2</replaceable>]/action "ACCEPT"</userinput>
-&gt; <userinput>config set Resolver/query_acl[<replaceable>2</replaceable>]/from "<replaceable>192.168.1.0/24</replaceable>"</userinput>
-&gt; <userinput>config commit</userinput>
-</screen>
-
-     <simpara>(Replace the <quote><replaceable>2</replaceable></quote>
-       as needed; run <quote><userinput>config show
-       Resolver/query_acl</userinput></quote> if needed.)</simpara>
-
-<!-- TODO: check this -->
-      <note><simpara>This prototype access control configuration
-      syntax may be changed.</simpara></note>
-
     </section>
     </section>
 
 
     <section>
     <section>
@@ -2499,7 +2657,7 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
 &gt; <userinput>config commit</userinput></screen></para>
 &gt; <userinput>config commit</userinput></screen></para>
 
 
       <para>
       <para>
-        At start, the server will detect available network interfaces
+        During start-up the server will detect available network interfaces
         and will attempt to open UDP sockets on all interfaces that
         and will attempt to open UDP sockets on all interfaces that
         are up, running, are not loopback, and have IPv4 address
         are up, running, are not loopback, and have IPv4 address
         assigned.
         assigned.
@@ -2509,17 +2667,8 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
         will respond to them with OFFER and ACK, respectively.
         will respond to them with OFFER and ACK, respectively.
 
 
         Since the DHCPv4 server opens privileged ports, it requires root
         Since the DHCPv4 server opens privileged ports, it requires root
-        access. Make sure you run this daemon as root.</para>
-
-        <note>
-          <para>
-            Integration with <command>bind10</command> is
-            planned. Ultimately, <command>b10-dhcp4</command> will not
-            be started directly, but rather via
-            <command>bind10</command>. Please be aware of this planned
-            change.
-          </para>
-        </note>
+        access. Make sure you run this daemon as root.
+      </para>
 
 
     </section>
     </section>
 
 
@@ -2684,22 +2833,25 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
       </para>
       </para>
 
 
       <para>
       <para>
-        The DHCPv6 server is implemented as <command>b10-dhcp6</command>
-        daemon. As it is not configurable yet, it is fully autonomous,
-        that is it does not interact with <command>b10-cfgmgr</command>.
-        To start DHCPv6 server, simply input:
-
-        <screen>
-#<userinput>cd src/bin/dhcp6</userinput>
-#<userinput>./b10-dhcp6</userinput>
-</screen>
+        <command>b10-dhcp6</command> is a BIND10 component and is being
+        run under BIND10 framework. To add a DHCPv6 process to the set of running
+        BIND10 services, you can use following commands in <command>bindctl</command>:
+        <screen>&gt; <userinput>config add Boss/components b10-dhcp6</userinput>
+&gt; <userinput>config set Boss/components/b10-dhcp6/kind dispensable</userinput>
+&gt; <userinput>config commit</userinput></screen>
+      </para>
 
 
-        Depending on your installation, <command>b10-dhcp6</command>
-        binary may reside in src/bin/dhcp6 in your source code
-        directory, in /usr/local/bin/b10-dhcp6 or other directory
-        you specified during compilation.
+       <para>
+         To shutdown running <command>b10-dhcp6</command>, please use the
+         following command:
+         <screen>&gt; <userinput>Dhcp6 shutdown</userinput></screen>
+         or
+         <screen>&gt; <userinput>config remove Boss/components b10-dhcp6</userinput>
+&gt; <userinput>config commit</userinput></screen>
+       </para>
 
 
-        At start, server will detect available network interfaces
+      <para>
+        During start-up the server will detect available network interfaces
         and will attempt to open UDP sockets on all interfaces that
         and will attempt to open UDP sockets on all interfaces that
         are up, running, are not loopback, are multicast-capable, and
         are up, running, are not loopback, are multicast-capable, and
         have IPv6 address assigned.
         have IPv6 address assigned.
@@ -2712,16 +2864,6 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
         access. Make sure you run this daemon as root.
         access. Make sure you run this daemon as root.
       </para>
       </para>
 
 
-        <note>
-          <para>
-            Integration with <command>bind10</command> is
-            planned. Ultimately, <command>b10-dhcp6</command> will not
-            be started directly, but rather via
-            <command>bind10</command>. Please be aware of this planned
-            change.
-          </para>
-        </note>
-
     </section>
     </section>
 
 
     <section id="dhcp6-config">
     <section id="dhcp6-config">
@@ -2735,7 +2877,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
       <para>
       <para>
         At this stage of development, the only way to alter server
         At this stage of development, the only way to alter server
         configuration is to tweak its source code. To do so, please
         configuration is to tweak its source code. To do so, please
-        edit src/bin/dhcp6/dhcp6_srv.cc file and modify following
+        edit src/bin/dhcp6/dhcp6_srv.cc file, modify the following
         parameters and recompile:
         parameters and recompile:
         <screen>
         <screen>
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
 const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";

File diff suppressed because it is too large
+ 1 - 1
doc/guide/bind10-messages.html


+ 2 - 1
src/bin/cfgmgr/Makefile.am

@@ -26,8 +26,9 @@ b10-cfgmgr: b10-cfgmgr.py
 
 
 install-data-local:
 install-data-local:
 	$(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
 	$(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
-# TODO: permissions handled later
 
 
+install-data-hook:
+	-chmod 2770 $(DESTDIR)/@localstatedir@/@PACKAGE@
 
 
 CLEANDIRS = __pycache__
 CLEANDIRS = __pycache__
 
 

+ 5 - 4
src/bin/ddns/ddns.spec

@@ -12,8 +12,8 @@
           "item_type": "map",
           "item_type": "map",
           "item_optional": true,
           "item_optional": true,
           "item_default": {
           "item_default": {
-	    "origin": "",
-	    "class": "IN",
+          "origin": "",
+          "class": "IN",
             "update_acl": []
             "update_acl": []
           },
           },
           "map_item_spec": [
           "map_item_spec": [
@@ -33,11 +33,12 @@
               "item_name": "update_acl",
               "item_name": "update_acl",
               "item_type": "list",
               "item_type": "list",
               "item_optional": false,
               "item_optional": false,
-	      "item_default": [],
+              "item_default": [],
               "list_item_spec": {
               "list_item_spec": {
                 "item_name": "acl_element",
                 "item_name": "acl_element",
                 "item_type": "any",
                 "item_type": "any",
-                "item_optional": true
+                "item_optional": true,
+                "item_default": {"action": "REJECT"}
               }
               }
             }
             }
           ]
           ]

+ 1 - 1
src/bin/dhcp4/ctrl_dhcp4_srv.cc

@@ -86,7 +86,7 @@ void ControlledDhcpv4Srv::establishSession() {
     string specfile;
     string specfile;
     if (getenv("B10_FROM_BUILD")) {
     if (getenv("B10_FROM_BUILD")) {
         specfile = string(getenv("B10_FROM_BUILD")) +
         specfile = string(getenv("B10_FROM_BUILD")) +
-            "/src/bin/auth/dhcp4.spec";
+            "/src/bin/dhcp4/dhcp4.spec";
     } else {
     } else {
         specfile = string(DHCP4_SPECFILE_LOCATION);
         specfile = string(DHCP4_SPECFILE_LOCATION);
     }
     }

+ 12 - 7
src/bin/dhcp4/dhcp4_srv.cc

@@ -37,16 +37,21 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
 Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
     cout << "Initialization: opening sockets on port " << port << endl;
     cout << "Initialization: opening sockets on port " << port << endl;
 
 
-    // first call to instance() will create IfaceMgr (it's a singleton)
-    // it may throw something if things go wrong
-    IfaceMgr::instance();
+    try {
+        // first call to instance() will create IfaceMgr (it's a singleton)
+        // it may throw something if things go wrong
+        IfaceMgr::instance();
 
 
-    /// @todo: instantiate LeaseMgr here once it is imlpemented.
-    IfaceMgr::instance().printIfaces();
+        /// @todo: instantiate LeaseMgr here once it is imlpemented.
 
 
-    IfaceMgr::instance().openSockets4(port);
+        IfaceMgr::instance().openSockets4(port);
 
 
-    setServerID();
+        setServerID();
+    } catch (const std::exception &e) {
+        cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
+        shutdown_ = true;
+        return;
+    }
 
 
     shutdown_ = false;
     shutdown_ = false;
 }
 }

+ 35 - 14
src/bin/dhcp4/main.cc

@@ -14,18 +14,14 @@
 
 
 #include <config.h>
 #include <config.h>
 #include <iostream>
 #include <iostream>
-#include <exceptions/exceptions.h>
 #include <log/dummylog.h>
 #include <log/dummylog.h>
 #include <log/logger_support.h>
 #include <log/logger_support.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
 #include <dhcp4/ctrl_dhcp4_srv.h>
-#include <dhcp4/dhcp4_srv.h>
-#include <dhcp/dhcp4.h>
+#include <boost/lexical_cast.hpp>
 
 
 using namespace std;
 using namespace std;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 
 
-
-
 /// This file contains entry point (main() function) for standard DHCPv4 server
 /// This file contains entry point (main() function) for standard DHCPv4 server
 /// component for BIND10 framework. It parses command-line arguments and
 /// component for BIND10 framework. It parses command-line arguments and
 /// instantiates ControlledDhcpv4Srv class that is responsible for establishing
 /// instantiates ControlledDhcpv4Srv class that is responsible for establishing
@@ -44,7 +40,9 @@ usage() {
     cerr << "Usage:  b10-dhcp4 [-v]"
     cerr << "Usage:  b10-dhcp4 [-v]"
          << endl;
          << endl;
     cerr << "\t-v: verbose output" << endl;
     cerr << "\t-v: verbose output" << endl;
-    cerr << "\t-p number: specify non-standard port number 1-65535 (useful for testing only)" << endl;
+    cerr << "\t-s: stand-alone mode (don't connect to BIND10)" << endl;
+    cerr << "\t-p number: specify non-standard port number 1-65535 "
+         << "(useful for testing only)" << endl;
     exit(EXIT_FAILURE);
     exit(EXIT_FAILURE);
 }
 }
 } // end of anonymous namespace
 } // end of anonymous namespace
@@ -55,16 +53,26 @@ main(int argc, char* argv[]) {
     bool verbose_mode = false; // should server be verbose?
     bool verbose_mode = false; // should server be verbose?
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
                                          // useful for testing only.
                                          // useful for testing only.
+    bool stand_alone = false; // should be connect to BIND10 msgq?
 
 
-    while ((ch = getopt(argc, argv, "vp:")) != -1) {
+    while ((ch = getopt(argc, argv, "vsp:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'v':
         case 'v':
             verbose_mode = true;
             verbose_mode = true;
             isc::log::denabled = true;
             isc::log::denabled = true;
             break;
             break;
+        case 's':
+            stand_alone = true;
+            break;
         case 'p':
         case 'p':
-            port_number = strtol(optarg, NULL, 10);
-            if (port_number == 0) {
+            try {
+                port_number = boost::lexical_cast<int>(optarg);
+            } catch (const boost::bad_lexical_cast &) {
+                cerr << "Failed to parse port number: [" << optarg
+                     << "], 1-65535 allowed." << endl;
+                usage();
+            }
+            if (port_number <= 0 || port_number > 65535) {
                 cerr << "Failed to parse port number: [" << optarg
                 cerr << "Failed to parse port number: [" << optarg
                      << "], 1-65535 allowed." << endl;
                      << "], 1-65535 allowed." << endl;
                 usage();
                 usage();
@@ -82,7 +90,8 @@ main(int argc, char* argv[]) {
                          isc::log::MAX_DEBUG_LEVEL, NULL);
                          isc::log::MAX_DEBUG_LEVEL, NULL);
 
 
     cout << "b10-dhcp4: My pid=" << getpid() << ", binding to port "
     cout << "b10-dhcp4: My pid=" << getpid() << ", binding to port "
-         << port_number << ", verbose " << (verbose_mode?"yes":"no") << endl;
+         << port_number << ", verbose " << (verbose_mode?"yes":"no")
+         << ", stand-alone=" << (stand_alone?"yes":"no") << endl;
 
 
     if (argc - optind > 0) {
     if (argc - optind > 0) {
         usage();
         usage();
@@ -94,11 +103,23 @@ main(int argc, char* argv[]) {
 
 
         cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
         cout << "[b10-dhcp4] Initiating DHCPv4 server operation." << endl;
 
 
-        ControlledDhcpv4Srv* server = new ControlledDhcpv4Srv(port_number);
-        server->run();
-        delete server;
-        server = NULL;
+        /// @todo: pass verbose to the actul server once logging is implemented
+        ControlledDhcpv4Srv server(port_number);
+
+        if (!stand_alone) {
+            try {
+                server.establishSession();
+            } catch (const std::exception& ex) {
+                cerr << "Failed to establish BIND10 session. "
+                    "Running in stand-alone mode:" << ex.what() << endl;
+                // Let's continue. It is useful to have the ability to run
+                // DHCP server in stand-alone mode, e.g. for testing
+            }
+        } else {
+            cout << "Skipping connection to the BIND10 msgq." << endl;
+        }
 
 
+        server.run();
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
         cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
         cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
         ret = EXIT_FAILURE;
         ret = EXIT_FAILURE;

+ 3 - 3
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -64,7 +64,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     ConstElementPtr comment = parseAnswer(rcode, result);
     ConstElementPtr comment = parseAnswer(rcode, result);
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
     EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
 
 
-    // case 1: send shutdown command without any parameters
+    // case 2: send shutdown command without any parameters
     result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
     result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
     EXPECT_EQ(0, rcode); // expect success
@@ -73,7 +73,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     ConstElementPtr x(new isc::data::IntElement(pid));
     ConstElementPtr x(new isc::data::IntElement(pid));
     params->set("pid", x);
     params->set("pid", x);
 
 
-    // case 2: send shutdown command with 1 parameter: pid
+    // case 3: send shutdown command with 1 parameter: pid
     result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
     result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
     comment = parseAnswer(rcode, result);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
     EXPECT_EQ(0, rcode); // expect success

+ 40 - 5
src/bin/dhcp4/tests/dhcp4_test.py

@@ -34,7 +34,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
     def tearDown(self):
     def tearDown(self):
         pass
         pass
 
 
-    def runDhcp4(self, params, wait=1):
+    def runCommand(self, params, wait=1):
         """
         """
         This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
         This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
         """
         """
@@ -127,14 +127,14 @@ class TestDhcpv4Daemon(unittest.TestCase):
         print("Note: Purpose of some of the tests is to check if DHCPv4 server can be started,")
         print("Note: Purpose of some of the tests is to check if DHCPv4 server can be started,")
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
 
 
-        (returncode, output, error) = self.runDhcp4(["../b10-dhcp4", "-v"])
+        (returncode, output, error) = self.runCommand(["../b10-dhcp4", "-v"])
 
 
         self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
         self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
 
 
     def test_portnumber_0(self):
     def test_portnumber_0(self):
         print("Check that specifying port number 0 is not allowed.")
         print("Check that specifying port number 0 is not allowed.")
 
 
-        (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '0'])
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p', '0'])
 
 
         # When invalid port number is specified, return code must not be success
         # When invalid port number is specified, return code must not be success
         self.assertTrue(returncode != 0)
         self.assertTrue(returncode != 0)
@@ -145,7 +145,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
     def test_portnumber_missing(self):
     def test_portnumber_missing(self):
         print("Check that -p option requires a parameter.")
         print("Check that -p option requires a parameter.")
 
 
-        (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p'])
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p'])
 
 
         # When invalid port number is specified, return code must not be success
         # When invalid port number is specified, return code must not be success
         self.assertTrue(returncode != 0)
         self.assertTrue(returncode != 0)
@@ -153,10 +153,32 @@ class TestDhcpv4Daemon(unittest.TestCase):
         # Check that there is an error message about invalid port number printed on stderr
         # Check that there is an error message about invalid port number printed on stderr
         self.assertEqual( str(error).count("option requires an argument"), 1)
         self.assertEqual( str(error).count("option requires an argument"), 1)
 
 
+    def test_portnumber_invalid1(self):
+        print("Check that -p option is check against bogus port number (999999).")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p','999999'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+    def test_portnumber_invalid2(self):
+        print("Check that -p option is check against bogus port number (123garbage).")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p','123garbage'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
     def test_portnumber_nonroot(self):
     def test_portnumber_nonroot(self):
         print("Check that specifying unprivileged port number will work.")
         print("Check that specifying unprivileged port number will work.")
 
 
-        (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '10057'])
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
 
 
         # When invalid port number is specified, return code must not be success
         # When invalid port number is specified, return code must not be success
         # TODO: Temporarily commented out as socket binding on systems that do not have
         # TODO: Temporarily commented out as socket binding on systems that do not have
@@ -166,5 +188,18 @@ class TestDhcpv4Daemon(unittest.TestCase):
         # Check that there is an error message about invalid port number printed on stderr
         # Check that there is an error message about invalid port number printed on stderr
         self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
         self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
 
 
+    def test_skip_msgq(self):
+        print("Check that connection to BIND10 msgq can be disabled.")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
+
+        # When invalid port number is specified, return code must not be success
+        # TODO: Temporarily commented out as socket binding on systems that do not have
+        #       interface detection implemented currently fails.
+        # self.assertTrue(returncode == 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
+
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 3 - 0
src/bin/dhcp6/Makefile.am

@@ -33,6 +33,7 @@ BUILT_SOURCES = spec_config.h
 pkglibexec_PROGRAMS = b10-dhcp6
 pkglibexec_PROGRAMS = b10-dhcp6
 
 
 b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
 b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
+b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the
@@ -44,6 +45,8 @@ b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 
 
 b10_dhcp6dir = $(pkgdatadir)
 b10_dhcp6dir = $(pkgdatadir)
 b10_dhcp6_DATA = dhcp6.spec
 b10_dhcp6_DATA = dhcp6.spec

+ 159 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.cc

@@ -0,0 +1,159 @@
+// 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 <config.h>
+#include <cassert>
+#include <iostream>
+
+#include <cc/session.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <util/buffer.h>
+#include <dhcp6/spec_config.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <dhcp/iface_mgr.h>
+#include <asiolink/asiolink.h>
+
+using namespace std;
+using namespace isc::util;
+using namespace isc::dhcp;
+using namespace isc::util;
+using namespace isc::data;
+using namespace isc::cc;
+using namespace isc::config;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
+
+ConstElementPtr
+ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
+    cout << "b10-dhcp6: Received new config:" << new_config->str() << endl;
+    ConstElementPtr answer = isc::config::createAnswer(0,
+                             "Thank you for sending config.");
+    return (answer);
+}
+
+ConstElementPtr
+ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
+    cout << "b10-dhcp6: Received new command: [" << command << "], args="
+         << args->str() << endl;
+    if (command == "shutdown") {
+        if (ControlledDhcpv6Srv::server_) {
+            ControlledDhcpv6Srv::server_->shutdown();
+        } else {
+            cout << "Server not initialized yet or already shut down." << endl;
+            ConstElementPtr answer = isc::config::createAnswer(1,
+                                     "Shutdown failure.");
+            return (answer);
+        }
+        ConstElementPtr answer = isc::config::createAnswer(0,
+                                 "Shutting down.");
+        return (answer);
+    }
+
+    ConstElementPtr answer = isc::config::createAnswer(1,
+                             "Unrecognized command.");
+
+    return (answer);
+}
+
+void ControlledDhcpv6Srv::sessionReader(void) {
+    // Process one asio event. If there are more events, iface_mgr will call
+    // this callback more than once.
+    if (server_) {
+        server_->io_service_.run_one();
+    }
+}
+
+void ControlledDhcpv6Srv::establishSession() {
+    
+    string specfile;
+    if (getenv("B10_FROM_BUILD")) {
+        specfile = string(getenv("B10_FROM_BUILD")) +
+            "/src/bin/dhcp6/dhcp6.spec";
+    } else {
+        specfile = string(DHCP6_SPECFILE_LOCATION);
+    }
+
+    /// @todo: Check if session is not established already. Throw, if it is.
+    
+    cout << "b10-dhcp6: my specfile is " << specfile << endl;
+    
+    cc_session_ = new Session(io_service_.get_io_service());
+
+    config_session_ = new ModuleCCSession(specfile, *cc_session_,
+                                          dhcp6ConfigHandler,
+                                          dhcp6CommandHandler, false);
+    config_session_->start();
+
+    /// Integrate the asynchronous I/O model of BIND 10 configuration
+    /// control with the "select" model of the DHCP server.  This is
+    /// fully explained in \ref dhcpv6Session.
+    int ctrl_socket = cc_session_->getSocketDesc();
+    cout << "b10-dhcp6: Control session started, socket="
+         << ctrl_socket << endl;
+    IfaceMgr::instance().set_session_socket(ctrl_socket, sessionReader);
+}
+
+void ControlledDhcpv6Srv::disconnectSession() {
+    if (config_session_) {
+        delete config_session_;
+        config_session_ = NULL;
+    }
+    if (cc_session_) {
+        cc_session_->disconnect();
+        delete cc_session_;
+        cc_session_ = NULL;
+    }
+
+    // deregister session socket
+    IfaceMgr::instance().set_session_socket(IfaceMgr::INVALID_SOCKET, NULL);
+}
+
+ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port /*= DHCP6_SERVER_PORT*/)
+    :Dhcpv6Srv(port), cc_session_(NULL), config_session_(NULL) {
+    server_ = this; // remember this instance for use in callback
+}
+
+void ControlledDhcpv6Srv::shutdown() {
+    io_service_.stop(); // Stop ASIO transmissions
+    Dhcpv6Srv::shutdown(); // Initiate DHCPv6 shutdown procedure.
+}
+
+ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
+    disconnectSession();
+
+    server_ = NULL; // forget this instance. There should be no callback anymore
+                    // at this stage anyway.
+}
+
+isc::data::ConstElementPtr
+ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
+                                             isc::data::ConstElementPtr args) {
+    try {
+        return (dhcp6CommandHandler(command_id, args));
+    } catch (const Exception& ex) {
+        ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
+        return (answer);
+    }
+}
+
+
+};
+};

+ 123 - 0
src/bin/dhcp6/ctrl_dhcp6_srv.h

@@ -0,0 +1,123 @@
+// 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 CTRL_DHCPV6_SRV_H
+#define CTRL_DHCPV6_SRV_H
+
+#include <dhcp6/dhcp6_srv.h>
+#include <asiolink/asiolink.h>
+#include <cc/session.h>
+#include <config/ccsession.h>
+#include <cc/data.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Controlled version of the DHCPv6 server
+///
+/// This is a class that is responsible for establishing connection
+/// with msqg (receving commands and configuration). This is an extended
+/// version of Dhcpv6Srv class that is purely a DHCPv6 server, without
+/// external control. ControlledDhcpv6Srv should be used in typical BIND10
+/// (i.e. featuring msgq) environment, while Dhcpv6Srv should be used in
+/// embedded environments.
+///
+/// For detailed explanation or relations between main(), ControlledDhcpv6Srv,
+/// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
+class ControlledDhcpv6Srv : public isc::dhcp::Dhcpv6Srv {
+public:
+
+    /// @brief Constructor
+    ///
+    /// @param port UDP port to be opened for DHCP traffic
+    ControlledDhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT);
+
+    /// @brief Destructor.
+    ~ControlledDhcpv6Srv();
+
+    /// @brief Establishes msgq session.
+    ///
+    /// Creates session that will be used to receive commands and updated
+    /// configuration from boss (or indirectly from user via bindctl).
+    void establishSession();
+
+    /// @brief Terminates existing msgq session.
+    ///
+    /// This method terminates existing session with msgq. After calling
+    /// it, no further messages over msgq (commands or configuration updates)
+    /// may be received.
+    ///
+    /// It is ok to call this method when session is disconnected already.
+    void disconnectSession();
+
+    /// @brief Initiates shutdown procedure for the whole DHCPv6 server.
+    void shutdown();
+
+    /// @brief Session callback, processes received commands.
+    ///
+    /// @param command_id text represenation of the command (e.g. "shutdown")
+    /// @param args optional parameters
+    ///
+    /// @return status of the command
+    static isc::data::ConstElementPtr
+    execDhcpv6ServerCommand(const std::string& command,
+                            isc::data::ConstElementPtr args);
+
+protected:
+    /// @brief Static pointer to the sole instance of the DHCP server.
+    ///
+    /// This is required for config and command handlers to gain access to
+    /// the server
+    static ControlledDhcpv6Srv* server_;
+
+    /// @brief A callback for handling incoming configuration updates.
+    ///
+    /// As pointer to this method is used a callback in ASIO used in
+    /// ModuleCCSession, it has to be static.
+    ///
+    /// @param new_config textual representation of the new configuration
+    ///
+    /// @return status of the config update
+    static isc::data::ConstElementPtr
+    dhcp6ConfigHandler(isc::data::ConstElementPtr new_config);
+
+    /// @brief A callback for handling incoming commands.
+    ///
+    /// @param command textual representation of the command
+    /// @param args parameters of the command
+    ///
+    /// @return status of the processed command
+    static isc::data::ConstElementPtr
+    dhcp6CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
+
+    /// @brief Callback that will be called from iface_mgr when command/config arrives.
+    ///
+    /// This static callback method is called from IfaceMgr::receive6() method,
+    /// when there is a new command or configuration sent over msgq.
+    static void sessionReader(void);
+
+    /// @brief IOService object, used for all ASIO operations.
+    isc::asiolink::IOService io_service_;
+
+    /// @brief Helper session object that represents raw connection to msgq.
+    isc::cc::Session* cc_session_;
+
+    /// @brief Session that receives configuation and commands
+    isc::config::ModuleCCSession* config_session_;
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif

+ 14 - 2
src/bin/dhcp6/dhcp6.spec

@@ -1,6 +1,6 @@
 {
 {
   "module_spec": {
   "module_spec": {
-    "module_name": "dhcp6",
+    "module_name": "Dhcp6",
     "module_description": "DHCPv6 server daemon",
     "module_description": "DHCPv6 server daemon",
     "config_data": [
     "config_data": [
       { "item_name": "interface",
       { "item_name": "interface",
@@ -9,6 +9,18 @@
         "item_default": "eth0"
         "item_default": "eth0"
       }
       }
     ],
     ],
-    "commands": []
+    "commands": [
+        {
+            "command_name": "shutdown",
+            "command_description": "Shuts down DHCPv6 server.",
+            "command_args": [
+                {
+                    "item_name": "pid",
+                    "item_type": "integer",
+                    "item_optional": true
+                }
+            ]
+        }
+    ]
   }
   }
 }
 }

+ 24 - 16
src/bin/dhcp6/dhcp6_srv.cc

@@ -45,25 +45,26 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
     // first call to instance() will create IfaceMgr (it's a singleton)
     // first call to instance() will create IfaceMgr (it's a singleton)
     // it may throw something if things go wrong
     // it may throw something if things go wrong
     try {
     try {
-        IfaceMgr::instance();
-    } catch (const std::exception &e) {
-        cout << "Failed to instantiate InterfaceManager:" << e.what() << ". Aborting." << endl;
-        shutdown = true;
-    }
 
 
-    if (IfaceMgr::instance().countIfaces() == 0) {
-        cout << "Failed to detect any network interfaces. Aborting." << endl;
-        shutdown = true;
-    }
+        if (IfaceMgr::instance().countIfaces() == 0) {
+            cout << "Failed to detect any network interfaces. Aborting." << endl;
+            shutdown_ = true;
+            return;
+        }
+
+        IfaceMgr::instance().openSockets6(port);
 
 
-    // Now try to open IPv6 sockets on detected interfaces.
-    IfaceMgr::instance().openSockets6(port);
+        setServerID();
 
 
-    /// @todo: instantiate LeaseMgr here once it is imlpemented.
+        /// @todo: instantiate LeaseMgr here once it is imlpemented.
 
 
-    setServerID();
+    } catch (const std::exception &e) {
+        cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
+        shutdown_ = true;
+        return;
+    }
 
 
-    shutdown = false;
+    shutdown_ = false;
 }
 }
 
 
 Dhcpv6Srv::~Dhcpv6Srv() {
 Dhcpv6Srv::~Dhcpv6Srv() {
@@ -72,11 +73,18 @@ Dhcpv6Srv::~Dhcpv6Srv() {
     IfaceMgr::instance().closeSockets();
     IfaceMgr::instance().closeSockets();
 }
 }
 
 
+void Dhcpv6Srv::shutdown() {
+    cout << "b10-dhcp6: DHCPv6 server shutdown." << endl;
+    shutdown_ = true;
+}
+
 bool Dhcpv6Srv::run() {
 bool Dhcpv6Srv::run() {
-    while (!shutdown) {
+    while (!shutdown_) {
+        /// @todo: calculate actual timeout once we have lease database
+        int timeout = 1000;
 
 
         // client's message and server's response
         // client's message and server's response
-        Pkt6Ptr query = IfaceMgr::instance().receive6();
+        Pkt6Ptr query = IfaceMgr::instance().receive6(timeout);
         Pkt6Ptr rsp;
         Pkt6Ptr rsp;
 
 
         if (query) {
         if (query) {

+ 3 - 1
src/bin/dhcp6/dhcp6_srv.h

@@ -67,6 +67,8 @@ public:
     ///         critical error.
     ///         critical error.
     bool run();
     bool run();
 
 
+    /// @brief Instructs the server to shut down.
+    void shutdown();
 protected:
 protected:
     /// @brief Processes incoming SOLICIT and returns response.
     /// @brief Processes incoming SOLICIT and returns response.
     ///
     ///
@@ -184,7 +186,7 @@ protected:
 
 
     /// indicates if shutdown is in progress. Setting it to true will
     /// indicates if shutdown is in progress. Setting it to true will
     /// initiate server shutdown procedure.
     /// initiate server shutdown procedure.
-    volatile bool shutdown;
+    volatile bool shutdown_;
 };
 };
 
 
 }; // namespace isc::dhcp
 }; // namespace isc::dhcp

+ 56 - 49
src/bin/dhcp6/main.cc

@@ -13,47 +13,36 @@
 // PERFORMANCE OF THIS SOFTWARE.
 // PERFORMANCE OF THIS SOFTWARE.
 
 
 #include <config.h>
 #include <config.h>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include <cassert>
 #include <iostream>
 #include <iostream>
-
-#include <exceptions/exceptions.h>
-#if 0
-// TODO cc is not used yet. It should be eventually
-#include <cc/session.h>
-#include <config/ccsession.h>
-#endif
-
-#include <util/buffer.h>
 #include <log/dummylog.h>
 #include <log/dummylog.h>
-
-#include <dhcp6/spec_config.h>
-#include "dhcp6/dhcp6_srv.h"
+#include <log/logger_support.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <boost/lexical_cast.hpp>
 
 
 using namespace std;
 using namespace std;
-using namespace isc::util;
-
-using namespace isc;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
 
 
+/// This file contains entry point (main() function) for standard DHCPv6 server
+/// component for BIND10 framework. It parses command-line arguments and
+/// instantiates ControlledDhcpv6Srv class that is responsible for establishing
+/// connection with msgq (receiving commands and configuration) and also
+/// creating Dhcpv6 server object as well.
+///
+/// For detailed explanation or relations between main(), ControlledDhcpv6Srv,
+/// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
+
 namespace {
 namespace {
 
 
-bool verbose_mode = false;
+const char* const DHCP6_NAME = "b10-dhcp6";
 
 
 void
 void
 usage() {
 usage() {
-    cerr << "Usage:  b10-dhcp6 [-v]"
+    cerr << "Usage: b10-dhcp6 [-v]"
          << endl;
          << endl;
     cerr << "\t-v: verbose output" << endl;
     cerr << "\t-v: verbose output" << endl;
-    cerr << "\t-p number: specify non-standard port number 1-65535 (useful for testing only)" << endl;
+    cerr << "\t-s: stand-alone mode (don't connect to BIND10)" << endl;
+    cerr << "\t-p number: specify non-standard port number 1-65535 "
+         << "(useful for testing only)" << endl;
     exit(EXIT_FAILURE);
     exit(EXIT_FAILURE);
 }
 }
 } // end of anonymous namespace
 } // end of anonymous namespace
@@ -63,16 +52,27 @@ main(int argc, char* argv[]) {
     int ch;
     int ch;
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
                                          // useful for testing only.
                                          // useful for testing only.
+    bool verbose_mode = false; // Should server be verbose?
+    bool stand_alone = false; // should be connect to BIND10 msgq?
 
 
-    while ((ch = getopt(argc, argv, "vp:")) != -1) {
+    while ((ch = getopt(argc, argv, "vsp:")) != -1) {
         switch (ch) {
         switch (ch) {
         case 'v':
         case 'v':
             verbose_mode = true;
             verbose_mode = true;
             isc::log::denabled = true;
             isc::log::denabled = true;
             break;
             break;
+        case 's':
+            stand_alone = true;
+            break;
         case 'p':
         case 'p':
-            port_number = strtol(optarg, NULL, 10);
-            if (port_number == 0) {
+            try {
+                port_number = boost::lexical_cast<int>(optarg);
+            } catch (const boost::bad_lexical_cast &) {
+                cerr << "Failed to parse port number: [" << optarg
+                     << "], 1-65535 allowed." << endl;
+                usage();
+            }
+            if (port_number <= 0 || port_number > 65535) {
                 cerr << "Failed to parse port number: [" << optarg
                 cerr << "Failed to parse port number: [" << optarg
                      << "], 1-65535 allowed." << endl;
                      << "], 1-65535 allowed." << endl;
                 usage();
                 usage();
@@ -84,7 +84,14 @@ main(int argc, char* argv[]) {
         }
         }
     }
     }
 
 
-    cout << "My pid=" << getpid() << endl;
+    // Initialize logging.  If verbose, we'll use maximum verbosity.
+    isc::log::initLogger(DHCP6_NAME,
+                         (verbose_mode ? isc::log::DEBUG : isc::log::INFO),
+                         isc::log::MAX_DEBUG_LEVEL, NULL);
+
+    cout << "b10-dhcp6: My pid=" << getpid() << ", binding to port "
+         << port_number << ", verbose " << (verbose_mode?"yes":"no")
+         << ", stand-alone=" << (stand_alone?"yes":"no") << endl;
 
 
     if (argc - optind > 0) {
     if (argc - optind > 0) {
         usage();
         usage();
@@ -92,27 +99,27 @@ main(int argc, char* argv[]) {
 
 
     int ret = EXIT_SUCCESS;
     int ret = EXIT_SUCCESS;
 
 
-    // TODO remainder of auth to dhcp6 code copy. We need to enable this in
-    //      dhcp6 eventually
-#if 0
-    Session* cc_session = NULL;
-    Session* statistics_session = NULL;
-    ModuleCCSession* config_session = NULL;
-#endif
     try {
     try {
-        string specfile;
-        if (getenv("B10_FROM_BUILD")) {
-            specfile = string(getenv("B10_FROM_BUILD")) +
-                "/src/bin/auth/dhcp6.spec";
-        } else {
-            specfile = string(DHCP6_SPECFILE_LOCATION);
-        }
 
 
-        cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
+        cout << "b10-dhcp6: Initiating DHCPv6 server operation." << endl;
 
 
-        Dhcpv6Srv* srv = new Dhcpv6Srv(port_number);
+        /// @todo: pass verbose to the actual server once logging is implemented
+        ControlledDhcpv6Srv server(port_number);
+
+        if (!stand_alone) {
+            try {
+                server.establishSession();
+            } catch (const std::exception& ex) {
+                cerr << "Failed to establish BIND10 session. "
+                    "Running in stand-alone mode:" << ex.what() << endl;
+                // Let's continue. It is useful to have the ability to run 
+                // DHCP server in stand-alone mode, e.g. for testing
+            }
+        } else {
+            cout << "Skipping connection to the BIND10 msgq." << endl;
+        }
 
 
-        srv->run();
+        server.run();
 
 
     } catch (const std::exception& ex) {
     } catch (const std::exception& ex) {
         cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;
         cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;

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

@@ -42,9 +42,10 @@ if HAVE_GTEST
 
 
 TESTS += dhcp6_unittests
 TESTS += dhcp6_unittests
 
 
-dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc
+dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc ../ctrl_dhcp6_srv.cc
 dhcp6_unittests_SOURCES += dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_unittests.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
 dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 
 
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the
@@ -59,6 +60,8 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
 dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
 
 
 endif
 endif
 
 

+ 85 - 0
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -0,0 +1,85 @@
+// 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 <config.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp6/ctrl_dhcp6_srv.h>
+#include <config/ccsession.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
+    // "naked" DHCPv6 server, exposes internal fields
+public:
+    NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
+};
+
+class CtrlDhcpv6SrvTest : public ::testing::Test {
+public:
+    CtrlDhcpv6SrvTest() {
+    }
+
+    ~CtrlDhcpv6SrvTest() {
+    };
+};
+
+TEST_F(CtrlDhcpv6SrvTest, commands) {
+
+    ControlledDhcpv6Srv* srv = NULL;
+    ASSERT_NO_THROW({
+        srv = new ControlledDhcpv6Srv(DHCP6_SERVER_PORT + 10000);
+    });
+
+    // use empty parameters list
+    ElementPtr params(new isc::data::MapElement());
+    int rcode = -1;
+
+    // case 1: send bogus command
+    ConstElementPtr result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("blah", params);
+    ConstElementPtr comment = parseAnswer(rcode, result);
+    EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
+
+    // case 2: send shutdown command without any parameters
+    result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
+    comment = parseAnswer(rcode, result);
+    EXPECT_EQ(0, rcode); // expect success
+
+    const pid_t pid(getpid());
+    ConstElementPtr x(new isc::data::IntElement(pid));
+    params->set("pid", x);
+
+    // case 3: send shutdown command with 1 parameter: pid
+    result = ControlledDhcpv6Srv::execDhcpv6ServerCommand("shutdown", params);
+    comment = parseAnswer(rcode, result);
+    EXPECT_EQ(0, rcode); // expect success
+
+
+    delete srv;
+}
+
+} // end of anonymous namespace

+ 39 - 5
src/bin/dhcp6/tests/dhcp6_test.py

@@ -1,4 +1,4 @@
-# Copyright (C) 2011,2012 Internet Systems Consortium.
+# copyright (C) 2011,2012 Internet Systems Consortium.
 #
 #
 # Permission to use, copy, modify, and distribute this software for any
 # Permission to use, copy, modify, and distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 # purpose with or without fee is hereby granted, provided that the above
@@ -131,7 +131,7 @@ class TestDhcpv6Daemon(unittest.TestCase):
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
         print("      not that is can bind sockets correctly. Please ignore binding errors.")
         (returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
         (returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
 
 
-        self.assertEqual( str(output).count("[b10-dhcp6] Initiating DHCPv6 operation."), 1)
+        self.assertEqual( str(output).count("b10-dhcp6: Initiating DHCPv6 server operation."), 1)
 
 
     def test_portnumber_0(self):
     def test_portnumber_0(self):
         print("Check that specifying port number 0 is not allowed.")
         print("Check that specifying port number 0 is not allowed.")
@@ -155,18 +155,52 @@ class TestDhcpv6Daemon(unittest.TestCase):
         # Check that there is an error message about invalid port number printed on stderr
         # Check that there is an error message about invalid port number printed on stderr
         self.assertEqual( str(error).count("option requires an argument"), 1)
         self.assertEqual( str(error).count("option requires an argument"), 1)
 
 
+    def test_portnumber_invalid1(self):
+        print("Check that -p option is check against bogus port number (999999).")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p','999999'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+    def test_portnumber_invalid2(self):
+        print("Check that -p option is check against bogus port number (123garbage).")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p','123garbage'])
+
+        # When invalid port number is specified, return code must not be success
+        self.assertTrue(returncode != 0)
+
+        # Check that there is an error message about invalid port number printed on stderr
+        self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
     def test_portnumber_nonroot(self):
     def test_portnumber_nonroot(self):
         print("Check that specifying unprivileged port number will work.")
         print("Check that specifying unprivileged port number will work.")
 
 
-        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p', '10057'])
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
+
+        # When invalid port number is specified, return code must not be success
+        # TODO: Temporarily commented out as socket binding on systems that do not have
+        #       interface detection implemented currently fails.
+        # self.assertTrue(returncode == 0)
+
+        self.assertEqual( str(output).count("opening sockets on port 10547"), 1)
+
+    def test_skip_msgq(self):
+        print("Check that connection to BIND10 msgq can be disabled.")
+
+        (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
 
 
         # When invalid port number is specified, return code must not be success
         # When invalid port number is specified, return code must not be success
         # TODO: Temporarily commented out as socket binding on systems that do not have
         # TODO: Temporarily commented out as socket binding on systems that do not have
         #       interface detection implemented currently fails.
         #       interface detection implemented currently fails.
         # self.assertTrue(returncode == 0)
         # self.assertTrue(returncode == 0)
 
 
-        # Check that there is a message on stdout about opening proper port
-        self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
+        self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
+
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
     unittest.main()
     unittest.main()

+ 0 - 4
src/bin/loadzone/Makefile.am

@@ -20,10 +20,6 @@ b10-loadzone: b10-loadzone.py
 	       -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" b10-loadzone.py >$@
 	       -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" b10-loadzone.py >$@
 	chmod a+x $@
 	chmod a+x $@
 
 
-install-data-local:
-	$(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
-# TODO: permissions handled later
-
 EXTRA_DIST += tests/normal/README
 EXTRA_DIST += tests/normal/README
 EXTRA_DIST += tests/normal/dsset-subzone.example.com
 EXTRA_DIST += tests/normal/dsset-subzone.example.com
 EXTRA_DIST += tests/normal/example.com
 EXTRA_DIST += tests/normal/example.com

+ 17 - 31
src/bin/resolver/resolver.spec.pre.in

@@ -116,37 +116,23 @@
       },
       },
       {
       {
         "item_name": "query_acl",
         "item_name": "query_acl",
-	"item_type": "list",
-	"item_optional": false,
-	"item_default": [
-	  {
-	    "action": "ACCEPT",
-	    "from": "127.0.0.1"
-	  },
-	  {
-	    "action": "ACCEPT",
-	    "from": "::1"
-	  }
-	],
-	"list_item_spec": {
-	  "item_name": "rule",
-	  "item_type": "map",
-	  "item_optional": false,
-	  "item_default": {},
-	  "map_item_spec": [
-	    {
-	      "item_name": "action",
-	      "item_type": "string",
-	      "item_optional": false,
-	      "item_default": ""
-	    },
-	    {
-	      "item_name": "from",
-	      "item_type": "string",
-	      "item_optional": false,
-	      "item_default": ""
-	    }
-	  ]
+        "item_type": "list",
+        "item_optional": false,
+        "item_default": [
+          {
+            "action": "ACCEPT",
+            "from": "127.0.0.1"
+          },
+          {
+            "action": "ACCEPT",
+            "from": "::1"
+          }
+        ],
+        "list_item_spec": {
+          "item_name": "rule",
+          "item_type": "any",
+          "item_optional": false,
+          "item_default": {"action": "REJECT"}
         }
         }
       }
       }
     ],
     ],

+ 38 - 29
src/bin/sysinfo/sysinfo.py.in

@@ -32,6 +32,15 @@ def usage():
               file=sys.stderr)
               file=sys.stderr)
     exit(1)
     exit(1)
 
 
+def write_value(out, fmt, call):
+    '''Helper function for standard value writing.
+       Writes the result from the call in the given format to out.
+       Does not write anything if the result of the call is None.
+    '''
+    value = call()
+    if value is not None:
+        out.write(fmt % value)
+
 def main():
 def main():
     try:
     try:
         opts, args = getopt.getopt(sys.argv[1:], "o:h", \
         opts, args = getopt.getopt(sys.argv[1:], "o:h", \
@@ -57,39 +66,39 @@ def main():
 
 
     s = SysInfoFromFactory()
     s = SysInfoFromFactory()
 
 
-    f.write('BIND 10 ShowTech tool\n')
-    f.write('=====================\n')
+    f.write('ISC Sysinfo tool\n')
+    f.write('================\n')
 
 
     f.write('\nCPU\n');
     f.write('\nCPU\n');
-    f.write(' + Number of processors: %d\n' % (s.get_num_processors()))
-    f.write(' + Endianness: %s\n' % (s.get_endianness()))
+    write_value(f, ' + Number of processors: %d\n', s.get_num_processors)
+    write_value(f, ' + Endianness: %s\n', s.get_endianness)
 
 
     f.write('\nPlatform\n');
     f.write('\nPlatform\n');
-    f.write(' + Operating system: %s\n' % (s.get_platform_name()))
-    f.write(' + Distribution: %s\n' % (s.get_platform_distro()))
-    f.write(' + Kernel version: %s\n' % (s.get_platform_version()))
-
-    f.write(' + SMP kernel: ')
-    if s.get_platform_is_smp():
-        f.write('yes')
-    else:
-        f.write('no')
-    f.write('\n')
+    write_value(f, ' + Operating system: %s\n', s.get_platform_name)
+    write_value(f, ' + Distribution: %s\n', s.get_platform_distro)
+    write_value(f, ' + Kernel version: %s\n', s.get_platform_version)
+
+    if s.get_platform_is_smp() is not None:
+        f.write(' + SMP kernel: ')
+        if s.get_platform_is_smp():
+            f.write('yes')
+        else:
+            f.write('no')
+        f.write('\n')
 
 
-    f.write(' + Machine name: %s\n' % (s.get_platform_machine()))
-    f.write(' + Hostname: %s\n' % (s.get_platform_hostname()))
-    f.write(' + Uptime: %d seconds\n' % (s.get_uptime()))
+    write_value(f, ' + Machine name: %s\n', s.get_platform_machine)
+    write_value(f, ' + Hostname: %s\n', s.get_platform_hostname)
+    write_value(f, ' + Uptime: %d seconds\n', s.get_uptime)
 
 
-    l = s.get_loadavg()
-    f.write(' + Loadavg: %f %f %f\n' % (l[0], l[1], l[2]))
+    write_value(f, ' + Loadavg: %f %f %f\n', s.get_loadavg)
 
 
     f.write('\nMemory\n');
     f.write('\nMemory\n');
-    f.write(' + Total: %d bytes\n' % (s.get_mem_total()))
-    f.write(' + Free: %d bytes\n' % (s.get_mem_free()))
-    f.write(' + Cached: %d bytes\n' % (s.get_mem_cached()))
-    f.write(' + Buffers: %d bytes\n' % (s.get_mem_buffers()))
-    f.write(' + Swap total: %d bytes\n' % (s.get_mem_swap_total()))
-    f.write(' + Swap free: %d bytes\n' % (s.get_mem_swap_free()))
+    write_value(f, ' + Total: %d bytes\n', s.get_mem_total)
+    write_value(f, ' + Free: %d bytes\n', s.get_mem_free)
+    write_value(f, ' + Cached: %d bytes\n', s.get_mem_cached)
+    write_value(f, ' + Buffers: %d bytes\n', s.get_mem_buffers)
+    write_value(f, ' + Swap total: %d bytes\n', s.get_mem_swap_total)
+    write_value(f, ' + Swap free: %d bytes\n', s.get_mem_swap_free)
 
 
     f.write('\n\nNetwork\n');
     f.write('\n\nNetwork\n');
     f.write('-------\n\n');
     f.write('-------\n\n');
@@ -97,19 +106,19 @@ def main():
     f.write('Interfaces\n')
     f.write('Interfaces\n')
     f.write('~~~~~~~~~~\n\n')
     f.write('~~~~~~~~~~\n\n')
 
 
-    f.write(s.get_net_interfaces())
+    write_value(f, '%s', s.get_net_interfaces)
 
 
     f.write('\nRouting table\n')
     f.write('\nRouting table\n')
     f.write('~~~~~~~~~~~~~\n\n')
     f.write('~~~~~~~~~~~~~\n\n')
-    f.write(s.get_net_routing_table())
+    write_value(f, '%s', s.get_net_routing_table)
 
 
     f.write('\nStatistics\n')
     f.write('\nStatistics\n')
     f.write('~~~~~~~~~~\n\n')
     f.write('~~~~~~~~~~\n\n')
-    f.write(s.get_net_stats())
+    write_value(f, '%s', s.get_net_stats)
 
 
     f.write('\nConnections\n')
     f.write('\nConnections\n')
     f.write('~~~~~~~~~~~\n\n')
     f.write('~~~~~~~~~~~\n\n')
-    f.write(s.get_net_connections())
+    write_value(f, '%s', s.get_net_connections)
 
 
     try:
     try:
         if os.getuid() != 0:
         if os.getuid() != 0:

+ 4 - 2
src/bin/xfrout/xfrout.spec.pre.in

@@ -17,7 +17,8 @@
          {
          {
              "item_name": "acl_element",
              "item_name": "acl_element",
              "item_type": "any",
              "item_type": "any",
-             "item_optional": true
+             "item_optional": true,
+             "item_default": {"action": "ACCEPT"}
          }
          }
        },
        },
        {
        {
@@ -80,7 +81,8 @@
                    {
                    {
                        "item_name": "acl_element",
                        "item_name": "acl_element",
                        "item_type": "any",
                        "item_type": "any",
-                       "item_optional": true
+                       "item_optional": true,
+                       "item_default": {"action": "ACCEPT"}
                    }
                    }
                }
                }
              ]
              ]

+ 9 - 0
src/lib/config/tests/testdata/spec40.spec

@@ -6,6 +6,15 @@
         "item_type": "any",
         "item_type": "any",
         "item_optional": false,
         "item_optional": false,
         "item_default": "asdf"
         "item_default": "asdf"
+      },
+      { "item_name": "item2",
+        "item_type": "any",
+        "item_optional": true
+      },
+      { "item_name": "item3",
+        "item_type": "any",
+        "item_optional": true,
+        "item_default": null
       }
       }
     ]
     ]
   }
   }

+ 104 - 42
src/lib/dhcp/iface_mgr.cc

@@ -805,13 +805,12 @@ IfaceMgr::receive4(uint32_t timeout) {
 
 
     const SocketInfo* candidate = 0;
     const SocketInfo* candidate = 0;
     IfaceCollection::const_iterator iface;
     IfaceCollection::const_iterator iface;
-
     fd_set sockets;
     fd_set sockets;
-    FD_ZERO(&sockets);
     int maxfd = 0;
     int maxfd = 0;
-
     stringstream names;
     stringstream names;
 
 
+    FD_ZERO(&sockets);
+
     /// @todo: marginal performance optimization. We could create the set once
     /// @todo: marginal performance optimization. We could create the set once
     /// and then use its copy for select(). Please note that select() modifies
     /// and then use its copy for select(). Please note that select() modifies
     /// provided set to indicated which sockets have something to read.
     /// provided set to indicated which sockets have something to read.
@@ -970,9 +969,108 @@ IfaceMgr::receive4(uint32_t timeout) {
     return (pkt);
     return (pkt);
 }
 }
 
 
-Pkt6Ptr IfaceMgr::receive6() {
-    uint8_t buf[RCVBUFSIZE];
+Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
+
+    const SocketInfo* candidate = 0;
+    fd_set sockets;
+    int maxfd = 0;
+    stringstream names;
+
+    FD_ZERO(&sockets);
+
+    /// @todo: marginal performance optimization. We could create the set once
+    /// and then use its copy for select(). Please note that select() modifies
+    /// provided set to indicated which sockets have something to read.
+    IfaceCollection::const_iterator iface;
+    for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
+
+        for (SocketCollection::const_iterator s = iface->sockets_.begin();
+             s != iface->sockets_.end(); ++s) {
+
+            // Only deal with IPv4 addresses.
+            if (s->addr_.getFamily() == AF_INET6) {
+                names << s->sockfd_ << "(" << iface->getName() << ") ";
+
+                // Add this socket to listening set
+                FD_SET(s->sockfd_, &sockets);
+                if (maxfd < s->sockfd_) {
+                    maxfd = s->sockfd_;
+                }
+            }
+        }
+    }
+
+    // if there is session socket registered...
+    if (session_socket_ != INVALID_SOCKET) {
+        // at it to the set as well
+        FD_SET(session_socket_, &sockets);
+        if (maxfd < session_socket_)
+            maxfd = session_socket_;
+        names << session_socket_ << "(session)";
+    }
+
+    cout << "Trying to receive data on sockets:" << names.str()
+         << ".Timeout is " << timeout << " seconds." << endl;
+
+    /// @todo: implement sub-second precision one day
+    struct timeval select_timeout;
+    select_timeout.tv_sec = timeout;
+    select_timeout.tv_usec = 0;
+
+    int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
+
+    if (result == 0) {
+        // nothing received and timeout has been reached
+        return (Pkt6Ptr()); // NULL
+    } else if (result < 0) {
+        cout << "Socket read error: " << strerror(errno) << endl;
+
+        /// @todo: perhaps throw here?
+        return (Pkt6Ptr()); // NULL
+    }
+
+    // Let's find out which socket has the data
+    if ((session_socket_ != INVALID_SOCKET) && (FD_ISSET(session_socket_, &sockets))) {
+        // something received over session socket
+        cout << "BIND10 command or config available over session socket." << endl;
+
+        if (session_callback_) {
+            // in theory we could call io_service.run_one() here, instead of
+            // implementing callback mechanism, but that would introduce
+            // asiolink dependency to libdhcp++ and that is something we want
+            // to avoid (see CPE market and out long term plans for minimalistic
+            // implementations.
+            session_callback_();
+        }
+
+        return (Pkt6Ptr()); // NULL
+    }
+
+    // Let's find out which interface/socket has the data
+    for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
+        for (SocketCollection::const_iterator s = iface->sockets_.begin();
+             s != iface->sockets_.end(); ++s) {
+            if (FD_ISSET(s->sockfd_, &sockets)) {
+                candidate = &(*s);
+                break;
+            }
+        }
+        if (candidate) {
+            break;
+        }
+    }
 
 
+    if (!candidate) {
+        cout << "Received data over unknown socket." << endl;
+        return (Pkt6Ptr()); // NULL
+    }
+
+    cout << "Trying to receive over UDP6 socket " << candidate->sockfd_ << " bound to "
+         << candidate->addr_.toText() << "/port=" << candidate->port_ << " on "
+         << iface->getFullName() << endl;
+
+    // Now we have a socket, let's get some data from it!
+    uint8_t buf[RCVBUFSIZE];
     memset(&control_buf_[0], 0, control_buf_len_);
     memset(&control_buf_[0], 0, control_buf_len_);
     struct sockaddr_in6 from;
     struct sockaddr_in6 from;
     memset(&from, 0, sizeof(from));
     memset(&from, 0, sizeof(from));
@@ -1004,43 +1102,7 @@ Pkt6Ptr IfaceMgr::receive6() {
     m.msg_control = &control_buf_[0];
     m.msg_control = &control_buf_[0];
     m.msg_controllen = control_buf_len_;
     m.msg_controllen = control_buf_len_;
 
 
-    /// TODO: Need to move to select() and pool over
-    /// all available sockets. For now, we just take the
-    /// first interface and use first socket from it.
-    IfaceCollection::const_iterator iface = ifaces_.begin();
-    const SocketInfo* candidate = 0;
-    while (iface != ifaces_.end()) {
-        for (SocketCollection::const_iterator s = iface->sockets_.begin();
-             s != iface->sockets_.end(); ++s) {
-            if (s->addr_.getFamily() != AF_INET6) {
-                continue;
-            }
-            if (s->addr_.getAddress().to_v6().is_multicast()) {
-                candidate = &(*s);
-                break;
-            }
-            if (!candidate) {
-                candidate = &(*s); // it's not multicast, but it's better than nothing
-            }
-        }
-        if (candidate) {
-            break;
-        }
-        ++iface;
-    }
-    if (iface == ifaces_.end()) {
-        isc_throw(Unexpected, "No suitable IPv6 interfaces detected. Can't receive anything.");
-    }
-
-    if (!candidate) {
-        isc_throw(Unexpected, "Interface " << iface->getFullName()
-                  << " does not have any sockets open.");
-    }
-
-    cout << "Trying to receive over UDP6 socket " << candidate->sockfd_ << " bound to "
-         << candidate->addr_.toText() << "/port=" << candidate->port_ << " on "
-         << iface->getFullName() << endl;
-    int result = recvmsg(candidate->sockfd_, &m, 0);
+    result = recvmsg(candidate->sockfd_, &m, 0);
 
 
     struct in6_addr to_addr;
     struct in6_addr to_addr;
     memset(&to_addr, 0, sizeof(to_addr));
     memset(&to_addr, 0, sizeof(to_addr));

+ 3 - 1
src/lib/dhcp/iface_mgr.h

@@ -345,8 +345,10 @@ public:
     /// to not wait infinitely, but rather do something useful
     /// to not wait infinitely, but rather do something useful
     /// (e.g. remove expired leases)
     /// (e.g. remove expired leases)
     ///
     ///
+    /// @param timeout specifies timeout (in seconds)
+    ///
     /// @return Pkt6 object representing received packet (or NULL)
     /// @return Pkt6 object representing received packet (or NULL)
-    Pkt6Ptr receive6();
+    Pkt6Ptr receive6(uint32_t timeout);
 
 
     /// @brief Tries to receive IPv4 packet over open IPv4 sockets.
     /// @brief Tries to receive IPv4 packet over open IPv4 sockets.
     ///
     ///

+ 1 - 1
src/lib/dhcp/pkt6.h

@@ -139,7 +139,7 @@ public:
     /// Returns value of transaction-id field
     /// Returns value of transaction-id field
     ///
     ///
     /// @return transaction-id
     /// @return transaction-id
-    uint32_t getTransid() { return (transid_); };
+    uint32_t getTransid() const { return (transid_); };
 
 
     /// Adds an option to this packet.
     /// Adds an option to this packet.
     ///
     ///

+ 1 - 1
src/lib/dhcp/tests/iface_mgr_unittest.cc

@@ -387,7 +387,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
 
 
     EXPECT_EQ(true, ifacemgr->send(sendPkt));
     EXPECT_EQ(true, ifacemgr->send(sendPkt));
 
 
-    rcvPkt = ifacemgr->receive6();
+    rcvPkt = ifacemgr->receive6(10);
 
 
     ASSERT_TRUE(rcvPkt); // received our own packet
     ASSERT_TRUE(rcvPkt); // received our own packet
 
 

+ 32 - 14
src/lib/python/isc/config/ccsession.py

@@ -144,7 +144,7 @@ class ModuleCCSession(ConfigData):
        module, and one to update the configuration run-time. These
        module, and one to update the configuration run-time. These
        callbacks are called when 'check_command' is called on the
        callbacks are called when 'check_command' is called on the
        ModuleCCSession"""
        ModuleCCSession"""
-       
+
     def __init__(self, spec_file_name, config_handler, command_handler,
     def __init__(self, spec_file_name, config_handler, command_handler,
                  cc_session=None, handle_logging_config=True,
                  cc_session=None, handle_logging_config=True,
                  socket_file = None):
                  socket_file = None):
@@ -178,9 +178,9 @@ class ModuleCCSession(ConfigData):
         """
         """
         module_spec = isc.config.module_spec_from_file(spec_file_name)
         module_spec = isc.config.module_spec_from_file(spec_file_name)
         ConfigData.__init__(self, module_spec)
         ConfigData.__init__(self, module_spec)
-        
+
         self._module_name = module_spec.get_module_name()
         self._module_name = module_spec.get_module_name()
-        
+
         self.set_config_handler(config_handler)
         self.set_config_handler(config_handler)
         self.set_command_handler(command_handler)
         self.set_command_handler(command_handler)
 
 
@@ -248,7 +248,7 @@ class ModuleCCSession(ConfigData):
            returns nothing.
            returns nothing.
            It calls check_command_without_recvmsg()
            It calls check_command_without_recvmsg()
            to parse the received message.
            to parse the received message.
-           
+
            If nonblock is True, it just checks if there's a command
            If nonblock is True, it just checks if there's a command
            and does nothing if there isn't. If nonblock is False, it
            and does nothing if there isn't. If nonblock is False, it
            waits until it arrives. It temporarily sets timeout to infinity,
            waits until it arrives. It temporarily sets timeout to infinity,
@@ -265,7 +265,7 @@ class ModuleCCSession(ConfigData):
         """Parse the given message to see if there is a command or a
         """Parse the given message to see if there is a command or a
            configuration update. Calls the corresponding handler
            configuration update. Calls the corresponding handler
            functions if present. Responds on the channel if the
            functions if present. Responds on the channel if the
-           handler returns a message.""" 
+           handler returns a message."""
         # should we default to an answer? success-by-default? unhandled error?
         # should we default to an answer? success-by-default? unhandled error?
         if msg is not None and not 'result' in msg:
         if msg is not None and not 'result' in msg:
             answer = None
             answer = None
@@ -314,7 +314,7 @@ class ModuleCCSession(ConfigData):
                 answer = create_answer(1, str(exc))
                 answer = create_answer(1, str(exc))
             if answer:
             if answer:
                 self._session.group_reply(env, answer)
                 self._session.group_reply(env, answer)
-    
+
     def set_config_handler(self, config_handler):
     def set_config_handler(self, config_handler):
         """Set the config handler for this module. The handler is a
         """Set the config handler for this module. The handler is a
            function that takes the full configuration and handles it.
            function that takes the full configuration and handles it.
@@ -521,7 +521,7 @@ class UIModuleCCSession(MultiConfigData):
         if not cur_list:
         if not cur_list:
             cur_list = []
             cur_list = []
 
 
-        if value is None:
+        if value is None and "list_item_spec" in module_spec:
             if "item_default" in module_spec["list_item_spec"]:
             if "item_default" in module_spec["list_item_spec"]:
                 value = module_spec["list_item_spec"]["item_default"]
                 value = module_spec["list_item_spec"]["item_default"]
 
 
@@ -572,8 +572,14 @@ class UIModuleCCSession(MultiConfigData):
         if module_spec is None:
         if module_spec is None:
             raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier))
             raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier))
 
 
+        # for type any, we determine the 'type' by what value is set
+        # (which would be either list or dict)
+        cur_value, _ = self.get_value(identifier)
+        type_any = module_spec['item_type'] == 'any'
+
         # the specified element must be a list or a named_set
         # the specified element must be a list or a named_set
-        if 'list_item_spec' in module_spec:
+        if 'list_item_spec' in module_spec or\
+           (type_any and type(cur_value) == list):
             value = None
             value = None
             # in lists, we might get the value with spaces, making it
             # in lists, we might get the value with spaces, making it
             # the third argument. In that case we interpret both as
             # the third argument. In that case we interpret both as
@@ -583,11 +589,12 @@ class UIModuleCCSession(MultiConfigData):
                     value_str += set_value_str
                     value_str += set_value_str
                 value = isc.cc.data.parse_value_str(value_str)
                 value = isc.cc.data.parse_value_str(value_str)
             self._add_value_to_list(identifier, value, module_spec)
             self._add_value_to_list(identifier, value, module_spec)
-        elif 'named_set_item_spec' in module_spec:
+        elif 'named_set_item_spec' in module_spec or\
+           (type_any and type(cur_value) == dict):
             item_name = None
             item_name = None
             item_value = None
             item_value = None
             if value_str is not None:
             if value_str is not None:
-                item_name =  isc.cc.data.parse_value_str(value_str)
+                item_name = value_str
             if set_value_str is not None:
             if set_value_str is not None:
                 item_value = isc.cc.data.parse_value_str(set_value_str)
                 item_value = isc.cc.data.parse_value_str(set_value_str)
             else:
             else:
@@ -643,12 +650,23 @@ class UIModuleCCSession(MultiConfigData):
         if value_str is not None:
         if value_str is not None:
             value = isc.cc.data.parse_value_str(value_str)
             value = isc.cc.data.parse_value_str(value_str)
 
 
-        if 'list_item_spec' in module_spec:
-            if value is not None:
+        # for type any, we determine the 'type' by what value is set
+        # (which would be either list or dict)
+        cur_value, _ = self.get_value(identifier)
+        type_any = module_spec['item_type'] == 'any'
+
+        # there's two forms of 'remove from list'; the remove-value-from-list
+        # form, and the 'remove-by-index' form. We can recognize the second
+        # case by value is None
+        if 'list_item_spec' in module_spec or\
+           (type_any and type(cur_value) == list) or\
+           value is None:
+            if not type_any and value is not None:
                 isc.config.config_data.check_type(module_spec['list_item_spec'], value)
                 isc.config.config_data.check_type(module_spec['list_item_spec'], value)
             self._remove_value_from_list(identifier, value)
             self._remove_value_from_list(identifier, value)
-        elif 'named_set_item_spec' in module_spec:
-            self._remove_value_from_named_set(identifier, value)
+        elif 'named_set_item_spec' in module_spec or\
+           (type_any and type(cur_value) == dict):
+            self._remove_value_from_named_set(identifier, value_str)
         else:
         else:
             raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named_set")
             raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named_set")
 
 

+ 13 - 1
src/lib/python/isc/config/config_data.py

@@ -204,6 +204,9 @@ def find_spec_part(element, identifier, strict_identifier = True):
     # always want the 'full' spec of the item
     # always want the 'full' spec of the item
     for id_part in id_parts[:-1]:
     for id_part in id_parts[:-1]:
         cur_el = _find_spec_part_single(cur_el, id_part)
         cur_el = _find_spec_part_single(cur_el, id_part)
+        # As soon as we find 'any', return that
+        if cur_el["item_type"] == "any":
+            return cur_el
         if strict_identifier and spec_part_is_list(cur_el) and\
         if strict_identifier and spec_part_is_list(cur_el) and\
            not isc.cc.data.identifier_has_list_index(id_part):
            not isc.cc.data.identifier_has_list_index(id_part):
             raise isc.cc.data.DataNotFoundError(id_part +
             raise isc.cc.data.DataNotFoundError(id_part +
@@ -553,7 +556,6 @@ class MultiConfigData:
             if 'item_default' in spec:
             if 'item_default' in spec:
                 # one special case, named_set
                 # one special case, named_set
                 if spec['item_type'] == 'named_set':
                 if spec['item_type'] == 'named_set':
-                    print("is " + id_part + " in named set?")
                     return spec['item_default']
                     return spec['item_default']
                 else:
                 else:
                     return spec['item_default']
                     return spec['item_default']
@@ -582,6 +584,14 @@ class MultiConfigData:
             value = self.get_default_value(identifier)
             value = self.get_default_value(identifier)
             if value is not None:
             if value is not None:
                 return value, self.DEFAULT
                 return value, self.DEFAULT
+            else:
+                # get_default_value returns None for both
+                # the cases where there is no default, and where
+                # it is set to null, so we need to catch the latter
+                spec_part = self.find_spec_part(identifier)
+                if spec_part and 'item_default' in spec_part and\
+                   spec_part['item_default'] is None:
+                    return None, self.DEFAULT
         return None, self.NONE
         return None, self.NONE
 
 
     def _append_value_item(self, result, spec_part, identifier, all, first = False):
     def _append_value_item(self, result, spec_part, identifier, all, first = False):
@@ -742,6 +752,8 @@ class MultiConfigData:
                 # list
                 # list
                 cur_list = cur_value
                 cur_list = cur_value
                 for list_index in list_indices:
                 for list_index in list_indices:
+                    if type(cur_list) != list:
+                        raise isc.cc.data.DataTypeError(id + " is not a list")
                     if list_index >= len(cur_list):
                     if list_index >= len(cur_list):
                         raise isc.cc.data.DataNotFoundError("No item " +
                         raise isc.cc.data.DataNotFoundError("No item " +
                                   str(list_index) + " in " + id_part)
                                   str(list_index) + " in " + id_part)

+ 100 - 18
src/lib/python/isc/config/tests/ccsession_test.py

@@ -33,7 +33,7 @@ class TestHelperFunctions(unittest.TestCase):
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [] })
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [] })
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 'not_an_rcode' ] })
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 'not_an_rcode' ] })
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 1, 2 ] })
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 1, 2 ] })
-        
+
         rcode, val = parse_answer({ 'result': [ 0 ] })
         rcode, val = parse_answer({ 'result': [ 0 ] })
         self.assertEqual(0, rcode)
         self.assertEqual(0, rcode)
         self.assertEqual(None, val)
         self.assertEqual(None, val)
@@ -107,7 +107,7 @@ class TestModuleCCSession(unittest.TestCase):
 
 
     def spec_file(self, file):
     def spec_file(self, file):
         return self.data_path + os.sep + file
         return self.data_path + os.sep + file
-        
+
     def create_session(self, spec_file_name, config_handler = None,
     def create_session(self, spec_file_name, config_handler = None,
                        command_handler = None, cc_session = None):
                        command_handler = None, cc_session = None):
         return ModuleCCSession(self.spec_file(spec_file_name),
         return ModuleCCSession(self.spec_file(spec_file_name),
@@ -335,7 +335,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [1, 'No config_data specification']},
         self.assertEqual({'result': [1, 'No config_data specification']},
                          fake_session.get_message('Spec1', None))
                          fake_session.get_message('Spec1', None))
-        
+
     def test_check_command3(self):
     def test_check_command3(self):
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()
         mccs = self.create_session("spec2.spec", None, None, fake_session)
         mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -348,7 +348,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [0]},
         self.assertEqual({'result': [0]},
                          fake_session.get_message('Spec2', None))
                          fake_session.get_message('Spec2', None))
-        
+
     def test_check_command4(self):
     def test_check_command4(self):
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()
         mccs = self.create_session("spec2.spec", None, None, fake_session)
         mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -361,7 +361,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [1, 'aaa should be an integer']},
         self.assertEqual({'result': [1, 'aaa should be an integer']},
                          fake_session.get_message('Spec2', None))
                          fake_session.get_message('Spec2', None))
-        
+
     def test_check_command5(self):
     def test_check_command5(self):
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()
         mccs = self.create_session("spec2.spec", None, None, fake_session)
         mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -374,7 +374,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [1, 'aaa should be an integer']},
         self.assertEqual({'result': [1, 'aaa should be an integer']},
                          fake_session.get_message('Spec2', None))
                          fake_session.get_message('Spec2', None))
-        
+
     def test_check_command6(self):
     def test_check_command6(self):
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()
         mccs = self.create_session("spec2.spec", None, None, fake_session)
         mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -460,7 +460,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [1, 'No config_data specification']},
         self.assertEqual({'result': [1, 'No config_data specification']},
                          fake_session.get_message('Spec1', None))
                          fake_session.get_message('Spec1', None))
- 
+
     def test_check_command_without_recvmsg2(self):
     def test_check_command_without_recvmsg2(self):
         "copied from test_check_command3"
         "copied from test_check_command3"
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()
@@ -474,7 +474,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [0]},
         self.assertEqual({'result': [0]},
                           fake_session.get_message('Spec2', None))
                           fake_session.get_message('Spec2', None))
- 
+
     def test_check_command_without_recvmsg3(self):
     def test_check_command_without_recvmsg3(self):
         "copied from test_check_command7"
         "copied from test_check_command7"
         fake_session = FakeModuleCCSession()
         fake_session = FakeModuleCCSession()
@@ -487,7 +487,7 @@ class TestModuleCCSession(unittest.TestCase):
         mccs.check_command_without_recvmsg(cmd, env)
         mccs.check_command_without_recvmsg(cmd, env)
         self.assertEqual({'result': [0]},
         self.assertEqual({'result': [0]},
                          fake_session.get_message('Spec2', None))
                          fake_session.get_message('Spec2', None))
- 
+
     def test_check_command_block_timeout(self):
     def test_check_command_block_timeout(self):
         """Check it works if session has timeout and it sets it back."""
         """Check it works if session has timeout and it sets it back."""
         def cmd_check(mccs, session):
         def cmd_check(mccs, session):
@@ -893,22 +893,22 @@ class fakeUIConn():
 
 
     def set_get_answer(self, name, answer):
     def set_get_answer(self, name, answer):
         self.get_answers[name] = answer
         self.get_answers[name] = answer
-    
+
     def set_post_answer(self, name, answer):
     def set_post_answer(self, name, answer):
         self.post_answers[name] = answer
         self.post_answers[name] = answer
-    
+
     def send_GET(self, name, arg = None):
     def send_GET(self, name, arg = None):
         if name in self.get_answers:
         if name in self.get_answers:
             return self.get_answers[name]
             return self.get_answers[name]
         else:
         else:
             return {}
             return {}
-    
+
     def send_POST(self, name, arg = None):
     def send_POST(self, name, arg = None):
         if name in self.post_answers:
         if name in self.post_answers:
             return self.post_answers[name]
             return self.post_answers[name]
         else:
         else:
             return fakeAnswer()
             return fakeAnswer()
-    
+
 
 
 class TestUIModuleCCSession(unittest.TestCase):
 class TestUIModuleCCSession(unittest.TestCase):
     def setUp(self):
     def setUp(self):
@@ -919,9 +919,9 @@ class TestUIModuleCCSession(unittest.TestCase):
 
 
     def spec_file(self, file):
     def spec_file(self, file):
         return self.data_path + os.sep + file
         return self.data_path + os.sep + file
-        
-    def create_uccs2(self, fake_conn):
-        module_spec = isc.config.module_spec_from_file(self.spec_file("spec2.spec"))
+
+    def create_uccs(self, fake_conn, specfile="spec2.spec"):
+        module_spec = isc.config.module_spec_from_file(self.spec_file(specfile))
         fake_conn.set_get_answer('/module_spec', { module_spec.get_module_name(): module_spec.get_full_spec()})
         fake_conn.set_get_answer('/module_spec', { module_spec.get_module_name(): module_spec.get_full_spec()})
         fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
         fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
         return UIModuleCCSession(fake_conn)
         return UIModuleCCSession(fake_conn)
@@ -989,7 +989,7 @@ class TestUIModuleCCSession(unittest.TestCase):
 
 
     def test_add_remove_value(self):
     def test_add_remove_value(self):
         fake_conn = fakeUIConn()
         fake_conn = fakeUIConn()
-        uccs = self.create_uccs2(fake_conn)
+        uccs = self.create_uccs(fake_conn)
 
 
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, 1, "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, 1, "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "no_such_item", "a")
         self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "no_such_item", "a")
@@ -1020,6 +1020,88 @@ class TestUIModuleCCSession(unittest.TestCase):
         self.assertRaises(isc.cc.data.DataTypeError,
         self.assertRaises(isc.cc.data.DataTypeError,
                           uccs.remove_value, "Spec2/item5", None)
                           uccs.remove_value, "Spec2/item5", None)
 
 
+    # Check that the difference between no default and default = null
+    # is recognized
+    def test_default_null(self):
+        fake_conn = fakeUIConn()
+        uccs = self.create_uccs(fake_conn, "spec40.spec")
+        (value, status) = uccs.get_value("/Spec40/item2")
+        self.assertIsNone(value)
+        self.assertEqual(uccs.NONE, status)
+        (value, status) = uccs.get_value("/Spec40/item3")
+        self.assertIsNone(value)
+        self.assertEqual(uccs.DEFAULT, status)
+
+    # Test adding and removing values for type = any
+    def test_add_remove_value_any(self):
+        fake_conn = fakeUIConn()
+        uccs = self.create_uccs(fake_conn, "spec40.spec")
+
+        # Test item set of basic types
+        items = [ 1234, "foo", True, False ]
+        items_as_str = [ '1234', 'foo', 'true', 'false' ]
+
+        def test_fails():
+            self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo")
+            self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo", "bar")
+            self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec40/item1", "foo")
+            self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec40/item1[0]", None)
+
+        # A few helper functions to perform a number of tests
+        # (to repeat the same test for nested data)
+        def check_list(identifier):
+            for item in items_as_str:
+                uccs.add_value(identifier, item)
+            self.assertEqual((items, 1), uccs.get_value(identifier))
+
+            # Removing from list should work in both ways
+            uccs.remove_value(identifier, "foo")
+            uccs.remove_value(identifier + "[1]", None)
+            self.assertEqual(([1234, False], 1), uccs.get_value(identifier))
+
+            # As should item indexing
+            self.assertEqual((1234, 1), uccs.get_value(identifier + "[0]"))
+            self.assertEqual((False, 1), uccs.get_value(identifier + "[1]"))
+
+        def check_named_set(identifier):
+            for item in items_as_str:
+                # use string version as key as well
+                uccs.add_value(identifier, item, item)
+
+            self.assertEqual((1234, 1), uccs.get_value(identifier + "/1234"))
+            self.assertEqual((True, 1), uccs.get_value(identifier + "/true"))
+
+            for item in items_as_str:
+                # use string version as key as well
+                uccs.remove_value(identifier, item)
+
+
+        # should fail when set to value of primitive type
+        for item in items:
+            uccs.set_value("Spec40/item1", item)
+            test_fails()
+
+        # When set to list, add and remove should work, and its elements
+        # should be considered of type 'any' themselves.
+        uccs.set_value("Spec40/item1", [])
+        check_list("Spec40/item1")
+
+        # When set to dict, it should have the behaviour of a named set
+        uccs.set_value("Spec40/item1", {})
+        check_named_set("Spec40/item1")
+
+        # And, or course, we may need nesting.
+        uccs.set_value("Spec40/item1", { "foo": {}, "bar": [] })
+        check_named_set("Spec40/item1/foo")
+        check_list("Spec40/item1/bar")
+        uccs.set_value("Spec40/item1", [ {}, [] ] )
+        check_named_set("Spec40/item1[0]")
+        check_list("Spec40/item1[1]")
+        uccs.set_value("Spec40/item1", [[[[[[]]]]]] )
+        check_list("Spec40/item1[0][0][0][0][0]")
+        uccs.set_value("Spec40/item1", { 'a': { 'a': { 'a': {} } } } )
+        check_named_set("Spec40/item1/a/a/a")
+
     def test_add_dup_value(self):
     def test_add_dup_value(self):
         fake_conn = fakeUIConn()
         fake_conn = fakeUIConn()
         uccs = self.create_uccs_listtest(fake_conn)
         uccs = self.create_uccs_listtest(fake_conn)
@@ -1101,7 +1183,7 @@ class TestUIModuleCCSession(unittest.TestCase):
 
 
     def test_commit(self):
     def test_commit(self):
         fake_conn = fakeUIConn()
         fake_conn = fakeUIConn()
-        uccs = self.create_uccs2(fake_conn)
+        uccs = self.create_uccs(fake_conn)
         uccs.commit()
         uccs.commit()
         uccs._local_changes = {'Spec2': {'item5': [ 'a' ]}}
         uccs._local_changes = {'Spec2': {'item5': [ 'a' ]}}
         uccs.commit()
         uccs.commit()

+ 97 - 44
src/lib/python/isc/sysinfo/sysinfo.py

@@ -25,27 +25,31 @@ import time
 
 
 class SysInfo:
 class SysInfo:
     def __init__(self):
     def __init__(self):
-        self._num_processors = -1
+        self._num_processors = None
         self._endianness = 'Unknown'
         self._endianness = 'Unknown'
         self._hostname = ''
         self._hostname = ''
         self._platform_name = 'Unknown'
         self._platform_name = 'Unknown'
         self._platform_version = 'Unknown'
         self._platform_version = 'Unknown'
         self._platform_machine = 'Unknown'
         self._platform_machine = 'Unknown'
-        self._platform_is_smp = False
-        self._uptime = -1
-        self._loadavg = [-1.0, -1.0, -1.0]
-        self._mem_total = -1
-        self._mem_free = -1
-        self._mem_cached = -1
-        self._mem_buffers = -1
-        self._mem_swap_total = -1
-        self._mem_swap_free = -1
-        self._platform_distro = 'Unknown'
+        self._platform_is_smp = None
+        self._uptime = None
+        self._loadavg = None
+        self._mem_total = None
+        self._mem_free = None
+        self._mem_swap_total = None
+        self._mem_swap_free = None
         self._net_interfaces = 'Unknown\n'
         self._net_interfaces = 'Unknown\n'
         self._net_routing_table = 'Unknown\n'
         self._net_routing_table = 'Unknown\n'
         self._net_stats = 'Unknown\n'
         self._net_stats = 'Unknown\n'
         self._net_connections = 'Unknown\n'
         self._net_connections = 'Unknown\n'
 
 
+        # The following are Linux speicific, and should eventually be removed
+        # from this level; for now we simply default to None (so they won't
+        # be printed)
+        self._platform_distro = None
+        self._mem_cached = None
+        self._mem_buffers = None
+
     def get_num_processors(self):
     def get_num_processors(self):
         """Returns the number of processors. This is the number of
         """Returns the number of processors. This is the number of
         hyperthreads when hyper-threading is enabled.
         hyperthreads when hyper-threading is enabled.
@@ -77,7 +81,12 @@ class SysInfo:
         return self._platform_is_smp
         return self._platform_is_smp
 
 
     def get_platform_distro(self):
     def get_platform_distro(self):
-        """Returns the name of the OS distribution in use."""
+        """Returns the name of the OS distribution in use.
+
+        Note: the concept of 'distribution' is Linux specific.  This shouldn't
+        be at this level.
+
+        """
         return self._platform_distro
         return self._platform_distro
 
 
     def get_uptime(self):
     def get_uptime(self):
@@ -164,7 +173,7 @@ class SysInfoLinux(SysInfoPOSIX):
         with open('/proc/loadavg') as f:
         with open('/proc/loadavg') as f:
             l = f.read().strip().split(' ')
             l = f.read().strip().split(' ')
             if len(l) >= 3:
             if len(l) >= 3:
-                self._loadavg = [float(l[0]), float(l[1]), float(l[2])]
+                self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
 
 
         with open('/proc/meminfo') as f:
         with open('/proc/meminfo') as f:
             m = f.readlines()
             m = f.readlines()
@@ -276,8 +285,6 @@ class SysInfoBSD(SysInfoPOSIX):
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
             pass
             pass
 
 
-        self._platform_distro = self._platform_name + ' ' + self._platform_version
-
         try:
         try:
             s = subprocess.check_output(['ifconfig'])
             s = subprocess.check_output(['ifconfig'])
             self._net_interfaces = s.decode('utf-8')
             self._net_interfaces = s.decode('utf-8')
@@ -296,6 +303,12 @@ class SysInfoBSD(SysInfoPOSIX):
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
             self._net_connections = 'Warning: "netstat -an" command failed.\n'
             self._net_connections = 'Warning: "netstat -an" command failed.\n'
 
 
+        try:
+            s = subprocess.check_output(['netstat', '-nr'])
+            self._net_routing_table = s.decode('utf-8')
+        except (subprocess.CalledProcessError, OSError):
+            self._net_connections = 'Warning: "netstat -nr" command failed.\n'
+
 class SysInfoOpenBSD(SysInfoBSD):
 class SysInfoOpenBSD(SysInfoBSD):
     """OpenBSD implementation of the SysInfo class.
     """OpenBSD implementation of the SysInfo class.
     See the SysInfo class documentation for more information.
     See the SysInfo class documentation for more information.
@@ -303,11 +316,6 @@ class SysInfoOpenBSD(SysInfoBSD):
     def __init__(self):
     def __init__(self):
         super().__init__()
         super().__init__()
 
 
-        # Don't know how to gather these
-        self._platform_is_smp = False
-        self._mem_cached = -1
-        self._mem_buffers = -1
-
         try:
         try:
             s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
             s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
             t = s.decode('utf-8').strip()
             t = s.decode('utf-8').strip()
@@ -320,7 +328,7 @@ class SysInfoOpenBSD(SysInfoBSD):
             s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
             s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
             l = s.decode('utf-8').strip().split(' ')
             l = s.decode('utf-8').strip().split(' ')
             if len(l) >= 3:
             if len(l) >= 3:
-                self._loadavg = [float(l[0]), float(l[1]), float(l[2])]
+                self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
             pass
             pass
 
 
@@ -343,29 +351,13 @@ class SysInfoOpenBSD(SysInfoBSD):
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
             pass
             pass
 
 
-        try:
-            s = subprocess.check_output(['route', '-n', 'show'])
-            self._net_routing_table = s.decode('utf-8')
-        except (subprocess.CalledProcessError, OSError):
-            self._net_routing_table = 'Warning: "route -n show" command failed.\n'
-
-class SysInfoFreeBSD(SysInfoBSD):
-    """FreeBSD implementation of the SysInfo class.
-    See the SysInfo class documentation for more information.
+class SysInfoFreeBSDOSX(SysInfoBSD):
+    """Shared code for the FreeBSD and OS X implementations of the SysInfo
+    class. See the SysInfo class documentation for more information.
     """
     """
     def __init__(self):
     def __init__(self):
         super().__init__()
         super().__init__()
 
 
-        # Don't know how to gather these
-        self._mem_cached = -1
-        self._mem_buffers = -1
-
-        try:
-            s = subprocess.check_output(['sysctl', '-n', 'kern.smp.active'])
-            self._platform_is_smp = int(s.decode('utf-8').strip()) > 0
-        except (subprocess.CalledProcessError, OSError):
-            pass
-
         try:
         try:
             s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
             s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
             t = s.decode('utf-8').strip()
             t = s.decode('utf-8').strip()
@@ -385,7 +377,20 @@ class SysInfoFreeBSD(SysInfoBSD):
             else:
             else:
                 la = l.split(' ')
                 la = l.split(' ')
             if len(la) >= 3:
             if len(la) >= 3:
-                self._loadavg = [float(la[0]), float(la[1]), float(la[2])]
+                self._loadavg = (float(la[0]), float(la[1]), float(la[2]))
+        except (subprocess.CalledProcessError, OSError):
+            pass
+
+class SysInfoFreeBSD(SysInfoFreeBSDOSX):
+    """FreeBSD implementation of the SysInfo class.
+    See the SysInfo class documentation for more information.
+    """
+    def __init__(self):
+        super().__init__()
+
+        try:
+            s = subprocess.check_output(['sysctl', '-n', 'kern.smp.active'])
+            self._platform_is_smp = int(s.decode('utf-8').strip()) > 0
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
             pass
             pass
 
 
@@ -408,11 +413,57 @@ class SysInfoFreeBSD(SysInfoBSD):
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
             pass
             pass
 
 
+
+
+class SysInfoOSX(SysInfoFreeBSDOSX):
+    """OS X (Darwin) implementation of the SysInfo class.
+    See the SysInfo class documentation for more information.
+    """
+    def __init__(self):
+        super().__init__()
+
+        # note; this call overrides the value already set when hw.physmem
+        # was read. However, on OSX, physmem is not necessarily the correct
+        # value. But since it does not fail and does work on most BSD's, it's
+        # left in the base class and overwritten here
+        self._mem_total = None
         try:
         try:
-            s = subprocess.check_output(['netstat', '-nr'])
-            self._net_routing_table = s.decode('utf-8')
+            s = subprocess.check_output(['sysctl', '-n', 'hw.memsize'])
+            self._mem_total = int(s.decode('utf-8').strip())
         except (subprocess.CalledProcessError, OSError):
         except (subprocess.CalledProcessError, OSError):
-            self._net_connections = 'Warning: "netstat -nr" command failed.\n'
+            pass
+
+        try:
+            s = subprocess.check_output(['vm_stat'])
+            lines = s.decode('utf-8').split('\n')
+            # store all values in a dict
+            values = {}
+            page_size = None
+            page_size_re = re.compile('.*page size of ([0-9]+) bytes')
+            for line in lines:
+                page_size_m = page_size_re.match(line)
+                if page_size_m:
+                    page_size = int(page_size_m.group(1))
+                else:
+                    key, _, value = line.partition(':')
+                    values[key] = value.strip()[:-1]
+            # Only calculate memory if page size is known
+            if page_size is not None:
+                self._mem_free = int(values['Pages free']) * page_size +\
+                                 int(values['Pages speculative']) * page_size
+        except (subprocess.CalledProcessError, OSError):
+            pass
+
+        try:
+            s = subprocess.check_output(['sysctl', '-n', 'vm.swapusage'])
+            l = s.decode('utf-8').strip()
+            r = re.match('^total = (\d+\.\d+)M\s+used = (\d+\.\d+)M\s+free = (\d+\.\d+)M', l)
+            if r:
+                self._mem_swap_total = float(r.group(1).strip()) * 1024
+                self._mem_swap_free = float(r.group(3).strip()) * 1024
+        except (subprocess.CalledProcessError, OSError):
+            pass
+
 
 
 class SysInfoTestcase(SysInfo):
 class SysInfoTestcase(SysInfo):
     def __init__(self):
     def __init__(self):
@@ -429,6 +480,8 @@ def SysInfoFromFactory():
         return SysInfoOpenBSD()
         return SysInfoOpenBSD()
     elif osname == 'FreeBSD':
     elif osname == 'FreeBSD':
         return SysInfoFreeBSD()
         return SysInfoFreeBSD()
+    elif osname == 'Darwin':
+        return SysInfoOSX()
     elif osname == 'BIND10Testcase':
     elif osname == 'BIND10Testcase':
         return SysInfoTestcase()
         return SysInfoTestcase()
     else:
     else:

+ 191 - 131
src/lib/python/isc/sysinfo/tests/sysinfo_test.py

@@ -20,6 +20,14 @@ import platform
 import subprocess
 import subprocess
 import time
 import time
 
 
+# different fake 'number of processors' values used for the different
+# operating systems
+NPROCESSORS_LINUX = 42
+NPROCESSORS_OPENBSD = 43
+NPROCESSORS_FREEBSD = 44
+NPROCESSORS_OSX = 45
+
+
 def _my_testcase_platform_system():
 def _my_testcase_platform_system():
     return 'BIND10Testcase'
     return 'BIND10Testcase'
 
 
@@ -28,7 +36,7 @@ def _my_linux_platform_system():
 
 
 def _my_linux_os_sysconf(key):
 def _my_linux_os_sysconf(key):
     if key == 'SC_NPROCESSORS_CONF':
     if key == 'SC_NPROCESSORS_CONF':
-        return 42
+        return NPROCESSORS_LINUX
     assert False, 'Unhandled key'
     assert False, 'Unhandled key'
 
 
 class MyLinuxFile:
 class MyLinuxFile:
@@ -92,90 +100,166 @@ def _my_openbsd_platform_system():
 
 
 def _my_openbsd_os_sysconf(key):
 def _my_openbsd_os_sysconf(key):
     if key == 'SC_NPROCESSORS_CONF':
     if key == 'SC_NPROCESSORS_CONF':
-        return 53
+        return NPROCESSORS_OPENBSD
     assert False, 'Unhandled key'
     assert False, 'Unhandled key'
 
 
-def _my_openbsd_subprocess_check_output(command):
+def _my_openbsd_platform_uname():
+    return ('OpenBSD', 'test.example.com', '5.0', '', 'amd64')
+
+# For the BSD types, there is a hierarchy that mostly resembles the
+# class hierarchy in the sysinfo library;
+# These are output strings of commands that sysinfo calls
+#
+# The test hierarchy is used as follows:
+# Each operating system has its own _my_<OS>_subprocess_check_output
+# call. If the call is not found, it calls it's 'parent' (e.g.
+# for openbsd that is my_bsd_subprocesses_check_output).
+#
+# If that returns None, the call had no test value and the test fails
+# (and needs to be updated).
+# The child classes are checked first so that they can override
+# output from the parents, if necessary.
+#
+# Some parents have their own parent
+# (e.g. _my_freebsd_osx_subprocess_check_output), in that case,
+# if they do not recognize the command, they simply return whatever
+# their parent returns
+
+def _my_bsd_subprocess_check_output(command):
+    '''subprocess output for all bsd types'''
     assert type(command) == list, 'command argument is not a list'
     assert type(command) == list, 'command argument is not a list'
     if command == ['hostname']:
     if command == ['hostname']:
-        return b'blowfish.example.com\n'
-    elif command == ['sysctl', '-n', 'kern.boottime']:
-        return bytes(str(int(time.time() - 76632)), 'utf-8')
-    elif command == ['sysctl', '-n', 'vm.loadavg']:
-        return b'0.7 0.9 0.8\n'
+        return b'test.example.com\n'
     elif command == ['sysctl', '-n', 'hw.physmem']:
     elif command == ['sysctl', '-n', 'hw.physmem']:
         return b'543214321\n'
         return b'543214321\n'
-    elif command == ['vmstat']:
-        return b' procs    memory       page                    disks    traps          cpu\n r b w    avm     fre  flt  re  pi  po  fr  sr wd0 cd0  int   sys   cs us sy id\n 0 0 0   121212  123456   47   0   0   0   0   0   2   0    2    80   14  0  1 99\n'
-    elif command == ['swapctl', '-s', '-k']:
-        return b'total: 553507 1K-blocks allocated, 2 used, 553505 available'
     elif command == ['ifconfig']:
     elif command == ['ifconfig']:
         return b'qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n'
         return b'qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n'
-    elif command == ['route', '-n', 'show']:
-        return b'XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n'
     elif command == ['netstat', '-s']:
     elif command == ['netstat', '-s']:
         return b'osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n'
         return b'osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n'
     elif command == ['netstat', '-an']:
     elif command == ['netstat', '-an']:
         return b'Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n'
         return b'Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n'
+    elif command == ['netstat', '-nr']:
+        return b'XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n'
     else:
     else:
-        assert False, 'Unhandled command'
+        return None
+
+def _my_openbsd_subprocess_check_output(command):
+    assert type(command) == list, 'command argument is not a list'
+    if command == ['sysctl', '-n', 'kern.boottime']:
+        return bytes(str(int(time.time() - 76632)), 'utf-8')
+    elif command == ['sysctl', '-n', 'vm.loadavg']:
+        return b'0.7 0.9 0.8\n'
+    elif command == ['vmstat']:
+        return b' procs    memory       page                    disks    traps          cpu\n r b w    avm     fre  flt  re  pi  po  fr  sr wd0 cd0  int   sys   cs us sy id\n 0 0 0   121212  123456   47   0   0   0   0   0   2   0    2    80   14  0  1 99\n'
+    elif command == ['swapctl', '-s', '-k']:
+        return b'total: 553507 1K-blocks allocated, 2 used, 553505 available'
+    else:
+        bsd_output = _my_bsd_subprocess_check_output(command)
+        if bsd_output is not None:
+            return bsd_output
+        else:
+            assert False, 'Unhandled command'
 
 
 def _my_freebsd_platform_system():
 def _my_freebsd_platform_system():
     return 'FreeBSD'
     return 'FreeBSD'
 
 
 def _my_freebsd_os_sysconf(key):
 def _my_freebsd_os_sysconf(key):
     if key == 'SC_NPROCESSORS_CONF':
     if key == 'SC_NPROCESSORS_CONF':
-        return 91
+        return NPROCESSORS_FREEBSD
     assert False, 'Unhandled key'
     assert False, 'Unhandled key'
 
 
-def _my_freebsd_subprocess_check_output(command):
+def _my_freebsd_platform_uname():
+    return ('FreeBSD', 'freebsd', '8.2-RELEASE', '', 'i386')
+
+def _my_freebsd_osx_subprocess_check_output(command):
+    '''subprocess output shared for freebsd and osx'''
     assert type(command) == list, 'command argument is not a list'
     assert type(command) == list, 'command argument is not a list'
-    if command == ['hostname']:
-        return b'daemon.example.com\n'
-    elif command == ['sysctl', '-n', 'kern.smp.active']:
-        return b'1\n'
-    elif command == ['sysctl', '-n', 'kern.boottime']:
+    if command == ['sysctl', '-n', 'kern.boottime']:
         return bytes('{ sec = ' + str(int(time.time() - 76632)) + ', usec = 0 }\n', 'utf-8')
         return bytes('{ sec = ' + str(int(time.time() - 76632)) + ', usec = 0 }\n', 'utf-8')
     elif command == ['sysctl', '-n', 'vm.loadavg']:
     elif command == ['sysctl', '-n', 'vm.loadavg']:
         return b'{ 0.2 0.4 0.6 }\n'
         return b'{ 0.2 0.4 0.6 }\n'
-    elif command == ['sysctl', '-n', 'hw.physmem']:
-        return b'987654321\n'
+    else:
+        return _my_bsd_subprocess_check_output(command)
+
+def _my_freebsd_subprocess_check_output(command):
+    assert type(command) == list, 'command argument is not a list'
+    if command == ['sysctl', '-n', 'kern.smp.active']:
+        return b'1\n'
     elif command == ['vmstat', '-H']:
     elif command == ['vmstat', '-H']:
         return b' procs    memory       page                    disks    traps          cpu\n r b w    avm     fre  flt  re  pi  po  fr  sr wd0 cd0  int   sys   cs us sy id\n 0 0 0   343434  123456   47   0   0   0   0   0   2   0    2    80   14  0  1 99\n'
         return b' procs    memory       page                    disks    traps          cpu\n r b w    avm     fre  flt  re  pi  po  fr  sr wd0 cd0  int   sys   cs us sy id\n 0 0 0   343434  123456   47   0   0   0   0   0   2   0    2    80   14  0  1 99\n'
     elif command == ['swapctl', '-s', '-k']:
     elif command == ['swapctl', '-s', '-k']:
         return b'Total:         1013216    0\n'
         return b'Total:         1013216    0\n'
-    elif command == ['ifconfig']:
-        return b'qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n'
-    elif command == ['netstat', '-nr']:
-        return b'XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n'
-    elif command == ['netstat', '-s']:
-        return b'osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n'
-    elif command == ['netstat', '-an']:
-        return b'Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n'
     else:
     else:
-        assert False, 'Unhandled command'
+        freebsd_osx_output = _my_freebsd_osx_subprocess_check_output(command)
+        if freebsd_osx_output is not None:
+            return freebsd_osx_output
+        else:
+            assert False, 'Unhandled command'
+
+def _my_osx_platform_system():
+    return 'Darwin'
+
+def _my_osx_platform_uname():
+    return ('Darwin', 'test.example.com', '10.6.0', '', '')
+
+def _my_osx_os_sysconf(key):
+    if key == 'SC_NPROCESSORS_CONF':
+        return NPROCESSORS_OSX
+    assert False, 'Unhandled key'
+
+def _my_osx_subprocess_check_output(command):
+    assert type(command) == list, 'command argument is not a list'
+    if command == ['sysctl', '-n', 'hw.memsize']:
+        # Something different than physmem from bsd
+        return b'123456789\n'
+    elif command == ['vm_stat']:
+        return b'Mach Virtual Memory Statistics: (page size of 4096 bytes)\nPages free: 12345.\nPages speculative: 11111.\n'
+    elif command == ['sysctl', '-n', 'vm.swapusage']:
+        return b'total = 18432.00M  used = 17381.23M  free = 1050.77M\n'
+    else:
+        freebsd_osx_output = _my_freebsd_osx_subprocess_check_output(command)
+        if freebsd_osx_output is not None:
+            return freebsd_osx_output
+        else:
+            assert False, 'Unhandled command'
 
 
 class SysInfoTest(unittest.TestCase):
 class SysInfoTest(unittest.TestCase):
+
+    def setUp(self):
+        # Save existing implementations of library functions
+        # (they are replaced in the tests)
+        self.old_platform_system = platform.system
+        self.old_os_sysconf = os.sysconf
+        self.old_open = __builtins__.open
+        self.old_subprocess_check_output = subprocess.check_output
+
+    def tearDown(self):
+        # Restore the library functions
+        platform.system = self.old_platform_system
+        os.sysconf = self.old_os_sysconf
+        __builtins__.open = self.old_open
+        subprocess.check_output = self.old_subprocess_check_output
+
     def test_sysinfo(self):
     def test_sysinfo(self):
         """Test that the various methods on SysInfo exist and return data."""
         """Test that the various methods on SysInfo exist and return data."""
 
 
         s = SysInfo()
         s = SysInfo()
-        self.assertEqual(-1, s.get_num_processors())
+        self.assertEqual(None, s.get_num_processors())
         self.assertEqual('Unknown', s.get_endianness())
         self.assertEqual('Unknown', s.get_endianness())
         self.assertEqual('', s.get_platform_hostname())
         self.assertEqual('', s.get_platform_hostname())
         self.assertEqual('Unknown', s.get_platform_name())
         self.assertEqual('Unknown', s.get_platform_name())
         self.assertEqual('Unknown', s.get_platform_version())
         self.assertEqual('Unknown', s.get_platform_version())
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertFalse(s.get_platform_is_smp())
         self.assertFalse(s.get_platform_is_smp())
-        self.assertEqual(-1, s.get_uptime())
-        self.assertEqual([-1.0, -1.0, -1.0], s.get_loadavg())
-        self.assertEqual(-1, s.get_mem_total())
-        self.assertEqual(-1, s.get_mem_free())
-        self.assertEqual(-1, s.get_mem_cached())
-        self.assertEqual(-1, s.get_mem_buffers())
-        self.assertEqual(-1, s.get_mem_swap_total())
-        self.assertEqual(-1, s.get_mem_swap_free())
-        self.assertEqual('Unknown', s.get_platform_distro())
+        self.assertEqual(None, s.get_uptime())
+        self.assertEqual(None, s.get_loadavg())
+        self.assertEqual(None, s.get_mem_total())
+        self.assertEqual(None, s.get_mem_free())
+        self.assertEqual(None, s.get_mem_cached())
+        self.assertEqual(None, s.get_mem_buffers())
+        self.assertEqual(None, s.get_mem_swap_total())
+        self.assertEqual(None, s.get_mem_swap_free())
+        self.assertEqual(None, s.get_platform_distro())
         self.assertEqual('Unknown\n', s.get_net_interfaces())
         self.assertEqual('Unknown\n', s.get_net_interfaces())
         self.assertEqual('Unknown\n', s.get_net_routing_table())
         self.assertEqual('Unknown\n', s.get_net_routing_table())
         self.assertEqual('Unknown\n', s.get_net_stats())
         self.assertEqual('Unknown\n', s.get_net_stats())
@@ -189,7 +273,7 @@ class SysInfoTest(unittest.TestCase):
         platform.system = _my_testcase_platform_system
         platform.system = _my_testcase_platform_system
 
 
         s = SysInfoFromFactory()
         s = SysInfoFromFactory()
-        self.assertEqual(-1, s.get_num_processors())
+        self.assertEqual(None, s.get_num_processors())
         self.assertEqual('bigrastafarian', s.get_endianness())
         self.assertEqual('bigrastafarian', s.get_endianness())
         self.assertEqual('', s.get_platform_hostname())
         self.assertEqual('', s.get_platform_hostname())
         self.assertEqual('b10test', s.get_platform_name())
         self.assertEqual('b10test', s.get_platform_name())
@@ -197,14 +281,14 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertFalse(s.get_platform_is_smp())
         self.assertFalse(s.get_platform_is_smp())
         self.assertEqual(131072, s.get_uptime())
         self.assertEqual(131072, s.get_uptime())
-        self.assertEqual([-1.0, -1.0, -1.0], s.get_loadavg())
-        self.assertEqual(-1, s.get_mem_total())
-        self.assertEqual(-1, s.get_mem_free())
-        self.assertEqual(-1, s.get_mem_cached())
-        self.assertEqual(-1, s.get_mem_buffers())
-        self.assertEqual(-1, s.get_mem_swap_total())
-        self.assertEqual(-1, s.get_mem_swap_free())
-        self.assertEqual('Unknown', s.get_platform_distro())
+        self.assertEqual(None, s.get_loadavg())
+        self.assertEqual(None, s.get_mem_total())
+        self.assertEqual(None, s.get_mem_free())
+        self.assertEqual(None, s.get_mem_cached())
+        self.assertEqual(None, s.get_mem_buffers())
+        self.assertEqual(None, s.get_mem_swap_total())
+        self.assertEqual(None, s.get_mem_swap_free())
+        self.assertEqual(None, s.get_platform_distro())
         self.assertEqual('Unknown\n', s.get_net_interfaces())
         self.assertEqual('Unknown\n', s.get_net_interfaces())
         self.assertEqual('Unknown\n', s.get_net_routing_table())
         self.assertEqual('Unknown\n', s.get_net_routing_table())
         self.assertEqual('Unknown\n', s.get_net_stats())
         self.assertEqual('Unknown\n', s.get_net_stats())
@@ -217,29 +301,19 @@ class SysInfoTest(unittest.TestCase):
         tests deep into the implementation, and not just the
         tests deep into the implementation, and not just the
         interfaces."""
         interfaces."""
 
 
-        # Don't run this test on platform other than Linux as some
-        # system calls may not even be available.
-        osname = platform.system()
-        if osname != 'Linux':
-            return
-
-        # Save and replace existing implementations of library functions
+        # Replace existing implementations of library functions
         # with mock ones for testing.
         # with mock ones for testing.
-        old_platform_system = platform.system
         platform.system = _my_linux_platform_system
         platform.system = _my_linux_platform_system
-        old_os_sysconf = os.sysconf
         os.sysconf = _my_linux_os_sysconf
         os.sysconf = _my_linux_os_sysconf
-        old_open = __builtins__.open
         __builtins__.open = _my_linux_open
         __builtins__.open = _my_linux_open
-        old_subprocess_check_output = subprocess.check_output
         subprocess.check_output = _my_linux_subprocess_check_output
         subprocess.check_output = _my_linux_subprocess_check_output
 
 
         s = SysInfoFromFactory()
         s = SysInfoFromFactory()
-        self.assertEqual(42, s.get_num_processors())
+        self.assertEqual(NPROCESSORS_LINUX, s.get_num_processors())
         self.assertEqual('myhostname', s.get_platform_hostname())
         self.assertEqual('myhostname', s.get_platform_hostname())
         self.assertTrue(s.get_platform_is_smp())
         self.assertTrue(s.get_platform_is_smp())
         self.assertEqual(86401, s.get_uptime())
         self.assertEqual(86401, s.get_uptime())
-        self.assertEqual([0.1, 0.2, 0.3], s.get_loadavg())
+        self.assertEqual((0.1, 0.2, 0.3), s.get_loadavg())
         self.assertEqual(3157884928, s.get_mem_total())
         self.assertEqual(3157884928, s.get_mem_total())
         self.assertEqual(891383808, s.get_mem_free())
         self.assertEqual(891383808, s.get_mem_free())
         self.assertEqual(1335152640, s.get_mem_cached())
         self.assertEqual(1335152640, s.get_mem_cached())
@@ -256,107 +330,93 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
         self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
         self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
         self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
 
 
-        # Restore original implementations.
-        platform.system = old_platform_system
-        os.sysconf = old_os_sysconf
-        __builtins__.open = old_open
-        subprocess.check_output = old_subprocess_check_output
+    def check_bsd_values(self, s):
+        # check values shared by all bsd implementations
+        self.assertEqual('test.example.com', s.get_platform_hostname())
+        self.assertLess(abs(76632 - s.get_uptime()), 4)
+        self.assertEqual(None, s.get_mem_cached())
+        self.assertEqual(None, s.get_mem_buffers())
+        self.assertEqual(None, s.get_platform_distro())
+
+        # These test that the corresponding tools are being called (and
+        # no further processing is done on this data). Please see the
+        # implementation functions at the top of this file.
+        self.assertEqual('qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n', s.get_net_interfaces())
+        self.assertEqual('XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n', s.get_net_routing_table())
+        self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
+        self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
 
 
     def test_sysinfo_openbsd(self):
     def test_sysinfo_openbsd(self):
         """Tests the OpenBSD implementation of SysInfo. Note that this
         """Tests the OpenBSD implementation of SysInfo. Note that this
         tests deep into the implementation, and not just the
         tests deep into the implementation, and not just the
         interfaces."""
         interfaces."""
 
 
-        # Don't run this test on platform other than OpenBSD as some
-        # system calls may not even be available.
-        osname = platform.system()
-        if osname != 'OpenBSD':
-            return
-
-        # Save and replace existing implementations of library functions
+        # Replace existing implementations of library functions
         # with mock ones for testing.
         # with mock ones for testing.
-        old_platform_system = platform.system
         platform.system = _my_openbsd_platform_system
         platform.system = _my_openbsd_platform_system
-        old_os_sysconf = os.sysconf
         os.sysconf = _my_openbsd_os_sysconf
         os.sysconf = _my_openbsd_os_sysconf
-        old_subprocess_check_output = subprocess.check_output
         subprocess.check_output = _my_openbsd_subprocess_check_output
         subprocess.check_output = _my_openbsd_subprocess_check_output
+        os.uname = _my_openbsd_platform_uname
 
 
         s = SysInfoFromFactory()
         s = SysInfoFromFactory()
-        self.assertEqual(53, s.get_num_processors())
-        self.assertEqual('blowfish.example.com', s.get_platform_hostname())
-        self.assertFalse(s.get_platform_is_smp())
+        self.assertEqual(NPROCESSORS_OPENBSD, s.get_num_processors())
 
 
-        self.assertLess(abs(76632 - s.get_uptime()), 4)
-        self.assertEqual([0.7, 0.9, 0.8], s.get_loadavg())
+        self.check_bsd_values(s)
+
+        self.assertEqual((0.7, 0.9, 0.8), s.get_loadavg())
+        self.assertFalse(s.get_platform_is_smp())
         self.assertEqual(543214321, s.get_mem_total())
         self.assertEqual(543214321, s.get_mem_total())
         self.assertEqual(543214321 - (121212 * 1024), s.get_mem_free())
         self.assertEqual(543214321 - (121212 * 1024), s.get_mem_free())
-        self.assertEqual(-1, s.get_mem_cached())
-        self.assertEqual(-1, s.get_mem_buffers())
         self.assertEqual(566791168, s.get_mem_swap_total())
         self.assertEqual(566791168, s.get_mem_swap_total())
         self.assertEqual(566789120, s.get_mem_swap_free())
         self.assertEqual(566789120, s.get_mem_swap_free())
-        self.assertRegexpMatches(s.get_platform_distro(), '^OpenBSD\s+.*')
-
-        # These test that the corresponding tools are being called (and
-        # no further processing is done on this data). Please see the
-        # implementation functions at the top of this file.
-        self.assertEqual('qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n', s.get_net_interfaces())
-        self.assertEqual('XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n', s.get_net_routing_table())
-        self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
-        self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
-
-        # Restore original implementations.
-        platform.system = old_platform_system
-        os.sysconf = old_os_sysconf
-        subprocess.check_output = old_subprocess_check_output
 
 
     def test_sysinfo_freebsd(self):
     def test_sysinfo_freebsd(self):
         """Tests the FreeBSD implementation of SysInfo. Note that this
         """Tests the FreeBSD implementation of SysInfo. Note that this
         tests deep into the implementation, and not just the
         tests deep into the implementation, and not just the
         interfaces."""
         interfaces."""
 
 
-        # Don't run this test on platform other than FreeBSD as some
-        # system calls may not even be available.
-        osname = platform.system()
-        if osname != 'FreeBSD':
-            return
-
-        # Save and replace existing implementations of library functions
+        # Replace existing implementations of library functions
         # with mock ones for testing.
         # with mock ones for testing.
-        old_platform_system = platform.system
         platform.system = _my_freebsd_platform_system
         platform.system = _my_freebsd_platform_system
-        old_os_sysconf = os.sysconf
         os.sysconf = _my_freebsd_os_sysconf
         os.sysconf = _my_freebsd_os_sysconf
-        old_subprocess_check_output = subprocess.check_output
         subprocess.check_output = _my_freebsd_subprocess_check_output
         subprocess.check_output = _my_freebsd_subprocess_check_output
+        os.uname = _my_freebsd_platform_uname
 
 
         s = SysInfoFromFactory()
         s = SysInfoFromFactory()
-        self.assertEqual(91, s.get_num_processors())
-        self.assertEqual('daemon.example.com', s.get_platform_hostname())
+        self.assertEqual(NPROCESSORS_FREEBSD, s.get_num_processors())
         self.assertTrue(s.get_platform_is_smp())
         self.assertTrue(s.get_platform_is_smp())
 
 
-        self.assertLess(abs(76632 - s.get_uptime()), 4)
-        self.assertEqual([0.2, 0.4, 0.6], s.get_loadavg())
-        self.assertEqual(987654321, s.get_mem_total())
-        self.assertEqual(987654321 - (343434 * 1024), s.get_mem_free())
-        self.assertEqual(-1, s.get_mem_cached())
-        self.assertEqual(-1, s.get_mem_buffers())
+        self.check_bsd_values(s)
+
+        self.assertEqual((0.2, 0.4, 0.6), s.get_loadavg())
+        self.assertEqual(543214321, s.get_mem_total())
+        self.assertEqual(543214321 - (343434 * 1024), s.get_mem_free())
         self.assertEqual(1037533184, s.get_mem_swap_total())
         self.assertEqual(1037533184, s.get_mem_swap_total())
         self.assertEqual(1037533184, s.get_mem_swap_free())
         self.assertEqual(1037533184, s.get_mem_swap_free())
-        self.assertRegexpMatches(s.get_platform_distro(), '^FreeBSD\s+.*')
 
 
-        # These test that the corresponding tools are being called (and
-        # no further processing is done on this data). Please see the
-        # implementation functions at the top of this file.
-        self.assertEqual('qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n', s.get_net_interfaces())
-        self.assertEqual('XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n', s.get_net_routing_table())
-        self.assertEqual('osuxbrcc1g9VgaF4yf3FrtfodrfATrbSnjhqhuQSAs8=\n', s.get_net_stats())
-        self.assertEqual('Z+w0lwa02/T+5+EIio84rrst/Dtizoz/aL9Im7J7ESA=\n', s.get_net_connections())
+    def test_sysinfo_osx(self):
+        """Tests the OS X implementation of SysInfo. Note that this
+        tests deep into the implementation, and not just the
+        interfaces."""
 
 
-        # Restore original implementations.
-        platform.system = old_platform_system
-        os.sysconf = old_os_sysconf
-        subprocess.check_output = old_subprocess_check_output
+        # Replace existing implementations of library functions
+        # with mock ones for testing.
+        platform.system = _my_osx_platform_system
+        os.sysconf = _my_osx_os_sysconf
+        subprocess.check_output = _my_osx_subprocess_check_output
+        os.uname = _my_osx_platform_uname
+
+        s = SysInfoFromFactory()
+        self.assertEqual(NPROCESSORS_OSX, s.get_num_processors())
+        self.assertFalse(s.get_platform_is_smp())
+
+        self.check_bsd_values(s)
+
+        self.assertEqual((0.2, 0.4, 0.6), s.get_loadavg())
+        self.assertEqual(123456789, s.get_mem_total())
+        self.assertEqual((23456 * 4096), s.get_mem_free())
+        self.assertEqual(18874368.0, s.get_mem_swap_total())
+        self.assertEqual(1075988.48, s.get_mem_swap_free())
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
     unittest.main()
     unittest.main()

+ 2 - 2
tests/lettuce/features/resolver_basic.feature

@@ -27,10 +27,10 @@ Feature: Basic Resolver
         A query for l.root-servers.net. should have rcode REFUSED
         A query for l.root-servers.net. should have rcode REFUSED
 
 
         # Test whether acl ACCEPT works
         # Test whether acl ACCEPT works
-        When I set bind10 configuration Resolver/query_acl[0]/action to ACCEPT
+        When I set bind10 configuration Resolver/query_acl[0] to {"action": "ACCEPT", "from": "127.0.0.1"}
         # This address is currently hardcoded, so shouldn't cause outside traffic
         # This address is currently hardcoded, so shouldn't cause outside traffic
         A query for l.root-servers.net. should have rcode NOERROR
         A query for l.root-servers.net. should have rcode NOERROR
 
 
         # Check whether setting the ACL to reject again works
         # Check whether setting the ACL to reject again works
-        When I set bind10 configuration Resolver/query_acl[0]/action to REJECT
+        When I set bind10 configuration Resolver/query_acl[0] to {"action": "REJECT", "from": "127.0.0.1"}
         A query for l.root-servers.net. should have rcode REFUSED
         A query for l.root-servers.net. should have rcode REFUSED

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

@@ -24,8 +24,10 @@ libb10_perfdhcp___la_SOURCES += localized_option.h
 libb10_perfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
 libb10_perfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
 libb10_perfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
 libb10_perfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
 libb10_perfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
 libb10_perfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
+libb10_perfdhcp___la_SOURCES += stats_mgr.h
 
 
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+
 if USE_CLANGPP
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # Disable unused parameter warning caused by some of the
 # Boost headers when compiling with clang.
 # Boost headers when compiling with clang.

File diff suppressed because it is too large
+ 1137 - 0
tests/tools/perfdhcp/stats_mgr.h


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

@@ -21,6 +21,7 @@ run_unittests_SOURCES += command_options_unittest.cc
 run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt6_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += perf_pkt4_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
 run_unittests_SOURCES += localized_option_unittest.cc
+run_unittests_SOURCES += stats_mgr_unittest.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
 run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc

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

@@ -0,0 +1,450 @@
+// 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 <boost/shared_ptr.hpp>
+
+#include <exceptions/exceptions.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+#include <gtest/gtest.h>
+
+#include "../stats_mgr.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+namespace {
+
+typedef StatsMgr<dhcp::Pkt4> StatsMgr4;
+typedef StatsMgr<dhcp::Pkt6> StatsMgr6;
+
+const uint32_t common_transid = 123;
+
+class StatsMgrTest : public ::testing::Test {
+public:
+    StatsMgrTest() {
+    }
+
+    /// \brief Create DHCPv4 packet.
+    ///
+    /// Method creates DHCPv4 packet and updates its timestamp.
+    ///
+    /// \param msg_type DHCPv4 message type.
+    /// \param transid transaction id for the packet.
+    /// \return DHCPv4 packet.
+    Pkt4* createPacket4(const uint8_t msg_type,
+                        const uint32_t transid) {
+        Pkt4* pkt = new Pkt4(msg_type, transid);
+        // Packet timestamp is normally updated by interface
+        // manager on packets reception or send. Unit tests
+        // do not use interface manager so we need to do it
+        // ourselfs.
+        pkt->updateTimestamp();
+        return pkt;
+    }
+
+    /// \brief Create DHCPv6 packet.
+    ///
+    /// Method creates DHCPv6 packet and updates its timestamp.
+    ///
+    /// \param msg_type DHCPv6 message type.
+    /// \param transid transaction id.
+    /// \return DHCPv6 packet.
+    Pkt6* createPacket6(const uint8_t msg_type,
+                        const uint32_t transid) {
+        Pkt6* pkt = new Pkt6(msg_type, transid);
+        // Packet timestamp is normally updated by interface
+        // manager on packets reception or send. Unit tests
+        // do not use interface manager so we need to do it
+        // ourselfs.
+        pkt->updateTimestamp();
+        return pkt;
+    }
+
+    /// \brief Pass multiple DHCPv6 packets to Statistics Manager.
+    ///
+    /// Method simulates sending or receiving  multiple DHCPv6 packets.
+    ///
+    /// \param stats_mgr Statistics Manager instance to be used.
+    /// \param xchg_type packet exchange types.
+    /// \param packet_type DHCPv6 packet type.
+    /// \param num_packets packets to be passed to Statistics Manager.
+    /// \param receive simulated packets are received (if true)
+    /// or sent (if false)
+    void passMultiplePackets6(const boost::shared_ptr<StatsMgr6> stats_mgr,
+                              const StatsMgr6::ExchangeType xchg_type,
+                              const uint8_t packet_type,
+                              const int num_packets,
+                              const bool receive = false) {
+        for (int i = 0; i < num_packets; ++i) {
+            boost::shared_ptr<Pkt6>
+                packet(createPacket6(packet_type, i));
+
+            if (receive) {
+                ASSERT_NO_THROW(
+                    stats_mgr->passRcvdPacket(xchg_type, packet);
+                );
+            } else {
+                ASSERT_NO_THROW(
+                    stats_mgr->passSentPacket(xchg_type, packet)
+                );
+            }
+        }
+    }
+
+    /// \brief Simulate DHCPv4 DISCOVER-OFFER with delay.
+    ///
+    /// Method simulates DHCPv4 DISCOVER-OFFER exchange. The OFFER packet
+    /// creation is delayed by the specified number of seconds. This imposes
+    /// different packet timestamps and affects delay counters in Statistics
+    /// Manager.
+    ///
+    /// \param stats_mgr Statistics Manager instance.
+    /// \param delay delay in seconds between DISCOVER and OFFER packets.
+    void passDOPacketsWithDelay(const boost::shared_ptr<StatsMgr4> stats_mgr,
+                                unsigned int delay,
+                                uint32_t transid) {
+        boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
+                                                      transid));
+        ASSERT_NO_THROW(
+            stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
+        );
+
+        // There is way to differentiate timstamps of two packets other than
+        // sleep for before we create another packet. Packet is using current
+        // time to update its timestamp.
+        // Sleeping for X seconds will guarantee that delay between packets
+        // will be greater than 1 second. Note that posix time value is
+        // transformed to double value and it makes it hard to determine
+        // actual value to expect.
+        std::cout << "Sleeping for " << delay << "s to test packet delays"
+                  << std::endl;
+        sleep(delay);
+
+        boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
+                                                      transid));
+        ASSERT_NO_THROW(
+            stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
+        );
+
+        // Calculate period between packets.
+        boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
+        boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();
+
+        ASSERT_FALSE(sent_time.is_not_a_date_time());
+        ASSERT_FALSE(rcvd_time.is_not_a_date_time());
+    }
+
+};
+
+TEST_F(StatsMgrTest, Constructor) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    EXPECT_DOUBLE_EQ(
+        std::numeric_limits<double>::max(),
+        stats_mgr->getMinDelay(StatsMgr4::XCHG_DO)
+    );
+    EXPECT_DOUBLE_EQ(0, stats_mgr->getMaxDelay(StatsMgr4::XCHG_DO));
+    EXPECT_EQ(0, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
+    EXPECT_EQ(0, stats_mgr->getOrderedLookups(StatsMgr4::XCHG_DO));
+    EXPECT_EQ(0, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
+    EXPECT_EQ(0, stats_mgr->getSentPacketsNum(StatsMgr4::XCHG_DO));
+    EXPECT_EQ(0, stats_mgr->getRcvdPacketsNum(StatsMgr4::XCHG_DO));
+
+    EXPECT_THROW(stats_mgr->getAvgDelay(StatsMgr4::XCHG_DO), InvalidOperation);
+    EXPECT_THROW(stats_mgr->getStdDevDelay(StatsMgr4::XCHG_DO),
+                 InvalidOperation);
+    EXPECT_THROW(stats_mgr->getAvgUnorderedLookupSetSize(StatsMgr4::XCHG_DO),
+                 InvalidOperation);
+}
+
+TEST_F(StatsMgrTest, Exchange) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
+                                                      common_transid));
+    boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
+                                                      common_transid));
+    // This is expected to throw because XCHG_DO was not yet
+    // added to Stats Manager for tracking.
+    EXPECT_THROW(
+        stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet),
+        BadValue
+    );
+    EXPECT_THROW(
+        stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet),
+        BadValue
+    );
+
+    // Adding DISCOVER-OFFER exchanges to be tracked by Stats Manager.
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    // The following two attempts are expected to throw because
+    // invalid exchange types are passed (XCHG_RA instead of XCHG_DO)
+    EXPECT_THROW(
+        stats_mgr->passSentPacket(StatsMgr4::XCHG_RA, sent_packet),
+        BadValue
+    );
+    EXPECT_THROW(
+        stats_mgr->passRcvdPacket(StatsMgr4::XCHG_RA, rcvd_packet),
+        BadValue
+    );
+
+    // The following two attempts are expected to run fine because
+    // right exchange type is specified.
+    EXPECT_NO_THROW(
+        stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
+    );
+    EXPECT_NO_THROW(
+        stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
+    );
+}
+
+TEST_F(StatsMgrTest, MultipleExchanges) {
+    boost::shared_ptr<StatsMgr6> stats_mgr(new StatsMgr6());
+    stats_mgr->addExchangeStats(StatsMgr6::XCHG_SA);
+    stats_mgr->addExchangeStats(StatsMgr6::XCHG_RR);
+
+    // Simulate sending number of solicit packets.
+    const int solicit_packets_num = 10;
+    passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_SOLICIT,
+                         solicit_packets_num);
+
+    // Simulate sending number of request packets. It is important that
+    // number of request packets is different then number of solicit
+    // packets. We can now check if right number packets went to
+    // the right exchange type group.
+    const int request_packets_num = 5;
+    passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REQUEST,
+                         request_packets_num);
+
+    // Check if all packets are successfuly passed to packet lists.
+    EXPECT_EQ(solicit_packets_num,
+              stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_SA));
+    EXPECT_EQ(request_packets_num,
+              stats_mgr->getSentPacketsNum(StatsMgr6::XCHG_RR));
+
+    // Simulate reception of multiple packets for both SOLICIT-ADVERTISE
+    // and REQUEST-REPLY exchanges. Assume no packet drops.
+    const bool receive_packets = true;
+    passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_ADVERTISE,
+                         solicit_packets_num, receive_packets);
+
+    passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_RR, DHCPV6_REPLY,
+                         request_packets_num, receive_packets);
+
+    // Verify that all received packets are counted.
+    EXPECT_EQ(solicit_packets_num,
+              stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_SA));
+    EXPECT_EQ(request_packets_num,
+              stats_mgr->getRcvdPacketsNum(StatsMgr6::XCHG_RR));
+}
+
+TEST_F(StatsMgrTest, SendReceiveSimple) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
+                                                      common_transid));
+    boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER,
+                                                      common_transid));
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+    // The following attempt is expected to pass becase the right
+    // exchange type is used.
+    ASSERT_NO_THROW(
+        stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
+    );
+    // It is ok, to pass to received packets here. First one will
+    // be matched with sent packet. The latter one will not be
+    // matched with sent packet but orphans counter will simply
+    // increase.
+    ASSERT_NO_THROW(
+        stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
+    );
+    ASSERT_NO_THROW(
+        stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet)
+    );
+    EXPECT_EQ(1, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
+}
+
+TEST_F(StatsMgrTest, SendReceiveUnordered) {
+    const int packets_num = 10;
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+
+    // Transaction ids of 10 packets to be sent and received.
+    uint32_t transid[packets_num] =
+        { 1, 1024, 2, 1025, 3, 1026, 4, 1027, 5, 1028 };
+    for (int i = 0; i < packets_num; ++i) {
+        boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER,
+                                                          transid[i]));
+        ASSERT_NO_THROW(
+            stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
+        );
+    }
+
+    // We are simulating that received packets are coming in reverse order:
+    // 1028, 5, 1027 ....
+    for (int i = 0; i < packets_num; ++i) {
+        boost::shared_ptr<Pkt4>
+            rcvd_packet(createPacket4(DHCPDISCOVER,
+                                      transid[packets_num - 1 - i]));
+        ASSERT_NO_THROW(
+            stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
+        );
+    }
+    // All packets are expected to match (we did not drop any)
+    EXPECT_EQ(0, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
+    // Most of the time we have to do unordered lookups except for the last
+    // one. Packets are removed from the sent list every time we have a match
+    // so eventually we come up with the single packet that caching iterator
+    // is pointing to. This is counted as ordered lookup.
+    EXPECT_EQ(1, stats_mgr->getOrderedLookups(StatsMgr4::XCHG_DO));
+    EXPECT_EQ(9, stats_mgr->getUnorderedLookups(StatsMgr4::XCHG_DO));
+}
+
+TEST_F(StatsMgrTest, Orphans) {
+    const int packets_num = 6;
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+
+    // We skip every second packet to simulate drops.
+    for (int i = 0; i < packets_num; i += 2) {
+        boost::shared_ptr<Pkt4> sent_packet(createPacket4(DHCPDISCOVER, i));
+        ASSERT_NO_THROW(
+            stats_mgr->passSentPacket(StatsMgr4::XCHG_DO, sent_packet)
+        );
+    }
+    // We pass all received packets.
+    for (int i = 0; i < packets_num; ++i) {
+        boost::shared_ptr<Pkt4> rcvd_packet(createPacket4(DHCPOFFER, i));
+        ASSERT_NO_THROW(
+            stats_mgr->passRcvdPacket(StatsMgr4::XCHG_DO, rcvd_packet);
+        );
+    }
+    // The half of received packets are expected not to have matching
+    // sent packet.
+    EXPECT_EQ(packets_num / 2, stats_mgr->getOrphans(StatsMgr4::XCHG_DO));
+}
+
+TEST_F(StatsMgrTest, Delays) {
+
+    boost::shared_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+    stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+
+    // Send DISCOVER, wait 2s and receive OFFER. This will affect
+    // counters in Stats Manager.
+    const unsigned int delay1 = 2;
+    passDOPacketsWithDelay(stats_mgr, 2, common_transid);
+
+    // Initially min delay is equal to MAX_DOUBLE. After first packets
+    // are passed, it is expected to set to actual value.
+    EXPECT_LT(stats_mgr->getMinDelay(StatsMgr4::XCHG_DO),
+              std::numeric_limits<double>::max());
+    EXPECT_GT(stats_mgr->getMinDelay(StatsMgr4::XCHG_DO), 1);
+
+    // Max delay is supposed to the same value as mininimum
+    // or maximum delay.
+    EXPECT_GT(stats_mgr->getMaxDelay(StatsMgr4::XCHG_DO), 1);
+
+    // Delay sums are now the same as minimum or maximum delay.
+    EXPECT_GT(stats_mgr->getAvgDelay(StatsMgr4::XCHG_DO), 1);
+
+    // Simulate another DISCOVER-OFFER exchange with delay between
+    // sent and received packets. Delay is now shorter than earlier
+    // so standard deviation of delay will now increase.
+    const unsigned int delay2 = 1;
+    passDOPacketsWithDelay(stats_mgr, delay2, common_transid + 1);
+    // Standard deviation is expected to be non-zero.
+    EXPECT_GT(stats_mgr->getStdDevDelay(StatsMgr4::XCHG_DO), 0);
+}
+
+TEST_F(StatsMgrTest, CustomCounters) {
+    boost::scoped_ptr<StatsMgr4> stats_mgr(new StatsMgr4());
+
+    // Specify counter keys and names.
+    const std::string too_short_key("tooshort");
+    const std::string too_short_name("Too short packets");
+    const std::string too_late_key("toolate");
+    const std::string too_late_name("Packets sent too late");
+
+    // Add two custom counters.
+    stats_mgr->addCustomCounter(too_short_key, too_short_name);
+    stats_mgr->addCustomCounter(too_late_key, too_late_name);
+
+    // Increment one of the counters 10 times.
+    const uint64_t tooshort_num = 10;
+    for (uint64_t i = 0; i < tooshort_num; ++i) {
+        stats_mgr->IncrementCounter(too_short_key);
+    }
+
+    // Increment another counter by 5 times.
+    const uint64_t toolate_num = 5;
+    for (uint64_t i = 0; i < toolate_num; ++i) {
+        stats_mgr->IncrementCounter(too_late_key);
+    }
+
+    // Check counter's current value and name.
+    StatsMgr4::CustomCounterPtr tooshort_counter =
+        stats_mgr->getCounter(too_short_key);
+    EXPECT_EQ(too_short_name, tooshort_counter->getName());
+    EXPECT_EQ(tooshort_num, tooshort_counter->getValue());
+
+    // Check counter's current value and name.
+    StatsMgr4::CustomCounterPtr toolate_counter =
+        stats_mgr->getCounter(too_late_key);
+    EXPECT_EQ(too_late_name, toolate_counter->getName());
+    EXPECT_EQ(toolate_num, toolate_counter->getValue());
+
+}
+
+TEST_F(StatsMgrTest, PrintStats) {
+    std::cout << "This unit test is checking statistics printing "
+              << "capabilities. It is expected that some counters "
+              << "will be printed during this test. It may also "
+              << "cause spurious errors." << std::endl;
+    boost::shared_ptr<StatsMgr6> stats_mgr(new StatsMgr6());
+    stats_mgr->addExchangeStats(StatsMgr6::XCHG_SA);
+
+    // Simulate sending and receiving one packet. Otherwise printing
+    // functions will complain about lack of packets.
+    const int packets_num = 1;
+    passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_SOLICIT,
+                         packets_num);
+    passMultiplePackets6(stats_mgr, StatsMgr6::XCHG_SA, DHCPV6_ADVERTISE,
+                         packets_num, true);
+
+    // This function will print statistics even if packets are not
+    // archived because it relies on counters. There is at least one
+    // exchange needed to count the average delay and std deviation.
+    EXPECT_NO_THROW(stats_mgr->printStats());
+
+    // Printing timestamps is expected to fail because by default we
+    // disable packets archiving mode. Without packets we can't get
+    // timestamps.
+    EXPECT_THROW(stats_mgr->printTimestamps(), isc::InvalidOperation);
+
+    // Now, we create another statistics manager instance and enable
+    // packets archiving mode.
+    const bool archive_packets = true;
+    boost::shared_ptr<StatsMgr6> stats_mgr2(new StatsMgr6(archive_packets));
+    stats_mgr2->addExchangeStats(StatsMgr6::XCHG_SA);
+
+    // Timestamps should now get printed because packets have been preserved.
+    EXPECT_NO_THROW(stats_mgr2->printTimestamps());
+}
+
+
+}