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
 /dns++.pc
 /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
 	BIND 10 now relies on Boost offset_ptr, which caused some new
 	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
  * 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 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++
  *

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 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">
     <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 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
           as above.
         </para>
@@ -1903,37 +2140,17 @@ http://bind10.isc.org/wiki/ScalableZoneLoadDesign#a7.2UpdatingaZone
       can be used to control accessibility of the outbound zone
       transfer service.
       By default, <command>b10-xfrout</command> allows any clients to
-      perform zone transfers for any zones:
+      perform zone transfers for any zones.
     </para>
 
       <screen>&gt; <userinput>config show Xfrout/transfer_acl</userinput>
 Xfrout/transfer_acl[0]	{"action": "ACCEPT"}	any	(default)</screen>
 
     <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
       "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>
 
     <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
       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>
         The way to specify zone specific configuration (ACLs, etc) is
         likely to be changed.
@@ -2150,29 +2372,7 @@ what is XfroutClient xfr_client??
       </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>
 
       <note><simpara>
@@ -2188,21 +2388,6 @@ DDNS/zones[0]/update_acl[1]     {"action": "ACCEPT", "from": "::1", "key": "key.
       </simpara></note>
 
       <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;
         it's not possible to specify access control with higher
         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).
         The <option>Resolver/query_acl</option> configuration may
         be used to reject, drop, or allow specific IPs or networks.
-        This configuration list is first match.
+        See <xref linkend="common-acl" />.
       </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>
 
-      <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>
@@ -2499,7 +2657,7 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
 &gt; <userinput>config commit</userinput></screen></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
         are up, running, are not loopback, and have IPv4 address
         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.
 
         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>
 
@@ -2684,22 +2833,25 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
       </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
         are up, running, are not loopback, are multicast-capable, and
         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.
       </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 id="dhcp6-config">
@@ -2735,7 +2877,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
       <para>
         At this stage of development, the only way to alter server
         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:
         <screen>
 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:
 	$(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
-# TODO: permissions handled later
 
+install-data-hook:
+	-chmod 2770 $(DESTDIR)/@localstatedir@/@PACKAGE@
 
 CLEANDIRS = __pycache__
 

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

@@ -12,8 +12,8 @@
           "item_type": "map",
           "item_optional": true,
           "item_default": {
-	    "origin": "",
-	    "class": "IN",
+          "origin": "",
+          "class": "IN",
             "update_acl": []
           },
           "map_item_spec": [
@@ -33,11 +33,12 @@
               "item_name": "update_acl",
               "item_type": "list",
               "item_optional": false,
-	      "item_default": [],
+              "item_default": [],
               "list_item_spec": {
                 "item_name": "acl_element",
                 "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;
     if (getenv("B10_FROM_BUILD")) {
         specfile = string(getenv("B10_FROM_BUILD")) +
-            "/src/bin/auth/dhcp4.spec";
+            "/src/bin/dhcp4/dhcp4.spec";
     } else {
         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) {
     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;
 }

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

@@ -14,18 +14,14 @@
 
 #include <config.h>
 #include <iostream>
-#include <exceptions/exceptions.h>
 #include <log/dummylog.h>
 #include <log/logger_support.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 isc::dhcp;
 
-
-
 /// This file contains entry point (main() function) for standard DHCPv4 server
 /// component for BIND10 framework. It parses command-line arguments and
 /// instantiates ControlledDhcpv4Srv class that is responsible for establishing
@@ -44,7 +40,9 @@ usage() {
     cerr << "Usage:  b10-dhcp4 [-v]"
          << 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);
 }
 } // end of anonymous namespace
@@ -55,16 +53,26 @@ main(int argc, char* argv[]) {
     bool verbose_mode = false; // should server be verbose?
     int port_number = DHCP4_SERVER_PORT; // The default. any other values are
                                          // 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) {
         case 'v':
             verbose_mode = true;
             isc::log::denabled = true;
             break;
+        case 's':
+            stand_alone = true;
+            break;
         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
                      << "], 1-65535 allowed." << endl;
                 usage();
@@ -82,7 +90,8 @@ main(int argc, char* argv[]) {
                          isc::log::MAX_DEBUG_LEVEL, NULL);
 
     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) {
         usage();
@@ -94,11 +103,23 @@ main(int argc, char* argv[]) {
 
         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) {
         cerr << "[b10-dhcp4] Server failed: " << ex.what() << endl;
         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
 // 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);
     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);
     comment = parseAnswer(rcode, result);
     EXPECT_EQ(0, rcode); // expect success
@@ -73,7 +73,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
     ConstElementPtr x(new isc::data::IntElement(pid));
     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);
     comment = parseAnswer(rcode, result);
     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):
         pass
 
-    def runDhcp4(self, params, wait=1):
+    def runCommand(self, params, wait=1):
         """
         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("      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)
 
     def test_portnumber_0(self):
         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
         self.assertTrue(returncode != 0)
@@ -145,7 +145,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
     def test_portnumber_missing(self):
         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
         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
         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):
         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
         # 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
         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__':
     unittest.main()

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

@@ -33,6 +33,7 @@ BUILT_SOURCES = spec_config.h
 pkglibexec_PROGRAMS = b10-dhcp6
 
 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
 # 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/log/libb10-log.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_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_name": "dhcp6",
+    "module_name": "Dhcp6",
     "module_description": "DHCPv6 server daemon",
     "config_data": [
       { "item_name": "interface",
@@ -9,6 +9,18 @@
         "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)
     // it may throw something if things go wrong
     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() {
@@ -72,11 +73,18 @@ Dhcpv6Srv::~Dhcpv6Srv() {
     IfaceMgr::instance().closeSockets();
 }
 
+void Dhcpv6Srv::shutdown() {
+    cout << "b10-dhcp6: DHCPv6 server shutdown." << endl;
+    shutdown_ = true;
+}
+
 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
-        Pkt6Ptr query = IfaceMgr::instance().receive6();
+        Pkt6Ptr query = IfaceMgr::instance().receive6(timeout);
         Pkt6Ptr rsp;
 
         if (query) {

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

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

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

@@ -13,47 +13,36 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #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 <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 <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 isc::util;
-
-using namespace isc;
 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 {
 
-bool verbose_mode = false;
+const char* const DHCP6_NAME = "b10-dhcp6";
 
 void
 usage() {
-    cerr << "Usage:  b10-dhcp6 [-v]"
+    cerr << "Usage: b10-dhcp6 [-v]"
          << 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);
 }
 } // end of anonymous namespace
@@ -63,16 +52,27 @@ main(int argc, char* argv[]) {
     int ch;
     int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
                                          // 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) {
         case 'v':
             verbose_mode = true;
             isc::log::denabled = true;
             break;
+        case 's':
+            stand_alone = true;
+            break;
         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
                      << "], 1-65535 allowed." << endl;
                 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) {
         usage();
@@ -92,27 +99,27 @@ main(int argc, char* argv[]) {
 
     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 {
-        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) {
         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
 
-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_srv_unittest.cc
+dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
 
 if USE_CLANGPP
 # 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/log/libb10-log.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
 

+ 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
 # 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.")
         (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):
         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
         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):
         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
         # 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 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__':
     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 >$@
 	chmod a+x $@
 
-install-data-local:
-	$(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
-# TODO: permissions handled later
-
 EXTRA_DIST += tests/normal/README
 EXTRA_DIST += tests/normal/dsset-subzone.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_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)
     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():
     try:
         opts, args = getopt.getopt(sys.argv[1:], "o:h", \
@@ -57,39 +66,39 @@ def main():
 
     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(' + 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(' + 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(' + 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\n');
@@ -97,19 +106,19 @@ def main():
     f.write('Interfaces\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('~~~~~~~~~~~~~\n\n')
-    f.write(s.get_net_routing_table())
+    write_value(f, '%s', s.get_net_routing_table)
 
     f.write('\nStatistics\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('~~~~~~~~~~~\n\n')
-    f.write(s.get_net_connections())
+    write_value(f, '%s', s.get_net_connections)
 
     try:
         if os.getuid() != 0:

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

@@ -17,7 +17,8 @@
          {
              "item_name": "acl_element",
              "item_type": "any",
-             "item_optional": true
+             "item_optional": true,
+             "item_default": {"action": "ACCEPT"}
          }
        },
        {
@@ -80,7 +81,8 @@
                    {
                        "item_name": "acl_element",
                        "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_optional": false,
         "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;
     IfaceCollection::const_iterator iface;
-
     fd_set sockets;
-    FD_ZERO(&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.
@@ -970,9 +969,108 @@ IfaceMgr::receive4(uint32_t timeout) {
     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_);
     struct sockaddr_in6 from;
     memset(&from, 0, sizeof(from));
@@ -1004,43 +1102,7 @@ Pkt6Ptr IfaceMgr::receive6() {
     m.msg_control = &control_buf_[0];
     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;
     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
     /// (e.g. remove expired leases)
     ///
+    /// @param timeout specifies timeout (in seconds)
+    ///
     /// @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.
     ///

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

@@ -139,7 +139,7 @@ public:
     /// Returns value of transaction-id field
     ///
     /// @return transaction-id
-    uint32_t getTransid() { return (transid_); };
+    uint32_t getTransid() const { return (transid_); };
 
     /// 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));
 
-    rcvPkt = ifacemgr->receive6();
+    rcvPkt = ifacemgr->receive6(10);
 
     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
        callbacks are called when 'check_command' is called on the
        ModuleCCSession"""
-       
+
     def __init__(self, spec_file_name, config_handler, command_handler,
                  cc_session=None, handle_logging_config=True,
                  socket_file = None):
@@ -178,9 +178,9 @@ class ModuleCCSession(ConfigData):
         """
         module_spec = isc.config.module_spec_from_file(spec_file_name)
         ConfigData.__init__(self, module_spec)
-        
+
         self._module_name = module_spec.get_module_name()
-        
+
         self.set_config_handler(config_handler)
         self.set_command_handler(command_handler)
 
@@ -248,7 +248,7 @@ class ModuleCCSession(ConfigData):
            returns nothing.
            It calls check_command_without_recvmsg()
            to parse the received message.
-           
+
            If nonblock is True, it just checks if there's a command
            and does nothing if there isn't. If nonblock is False, it
            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
            configuration update. Calls the corresponding handler
            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?
         if msg is not None and not 'result' in msg:
             answer = None
@@ -314,7 +314,7 @@ class ModuleCCSession(ConfigData):
                 answer = create_answer(1, str(exc))
             if answer:
                 self._session.group_reply(env, answer)
-    
+
     def set_config_handler(self, config_handler):
         """Set the config handler for this module. The handler is a
            function that takes the full configuration and handles it.
@@ -521,7 +521,7 @@ class UIModuleCCSession(MultiConfigData):
         if not 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"]:
                 value = module_spec["list_item_spec"]["item_default"]
 
@@ -572,8 +572,14 @@ class UIModuleCCSession(MultiConfigData):
         if module_spec is None:
             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
-        if 'list_item_spec' in module_spec:
+        if 'list_item_spec' in module_spec or\
+           (type_any and type(cur_value) == list):
             value = None
             # in lists, we might get the value with spaces, making it
             # the third argument. In that case we interpret both as
@@ -583,11 +589,12 @@ class UIModuleCCSession(MultiConfigData):
                     value_str += set_value_str
                 value = isc.cc.data.parse_value_str(value_str)
             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_value = 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:
                 item_value = isc.cc.data.parse_value_str(set_value_str)
             else:
@@ -643,12 +650,23 @@ class UIModuleCCSession(MultiConfigData):
         if value_str is not None:
             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)
             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:
             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
     for id_part in id_parts[:-1]:
         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\
            not isc.cc.data.identifier_has_list_index(id_part):
             raise isc.cc.data.DataNotFoundError(id_part +
@@ -553,7 +556,6 @@ class MultiConfigData:
             if 'item_default' in spec:
                 # one special case, named_set
                 if spec['item_type'] == 'named_set':
-                    print("is " + id_part + " in named set?")
                     return spec['item_default']
                 else:
                     return spec['item_default']
@@ -582,6 +584,14 @@ class MultiConfigData:
             value = self.get_default_value(identifier)
             if value is not None:
                 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
 
     def _append_value_item(self, result, spec_part, identifier, all, first = False):
@@ -742,6 +752,8 @@ class MultiConfigData:
                 # list
                 cur_list = cur_value
                 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):
                         raise isc.cc.data.DataNotFoundError("No item " +
                                   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': [ 'not_an_rcode' ] })
         self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 1, 2 ] })
-        
+
         rcode, val = parse_answer({ 'result': [ 0 ] })
         self.assertEqual(0, rcode)
         self.assertEqual(None, val)
@@ -107,7 +107,7 @@ class TestModuleCCSession(unittest.TestCase):
 
     def spec_file(self, file):
         return self.data_path + os.sep + file
-        
+
     def create_session(self, spec_file_name, config_handler = None,
                        command_handler = None, cc_session = None):
         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({'result': [1, 'No config_data specification']},
                          fake_session.get_message('Spec1', None))
-        
+
     def test_check_command3(self):
         fake_session = FakeModuleCCSession()
         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({'result': [0]},
                          fake_session.get_message('Spec2', None))
-        
+
     def test_check_command4(self):
         fake_session = FakeModuleCCSession()
         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({'result': [1, 'aaa should be an integer']},
                          fake_session.get_message('Spec2', None))
-        
+
     def test_check_command5(self):
         fake_session = FakeModuleCCSession()
         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({'result': [1, 'aaa should be an integer']},
                          fake_session.get_message('Spec2', None))
-        
+
     def test_check_command6(self):
         fake_session = FakeModuleCCSession()
         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({'result': [1, 'No config_data specification']},
                          fake_session.get_message('Spec1', None))
- 
+
     def test_check_command_without_recvmsg2(self):
         "copied from test_check_command3"
         fake_session = FakeModuleCCSession()
@@ -474,7 +474,7 @@ class TestModuleCCSession(unittest.TestCase):
         self.assertEqual(len(fake_session.message_queue), 1)
         self.assertEqual({'result': [0]},
                           fake_session.get_message('Spec2', None))
- 
+
     def test_check_command_without_recvmsg3(self):
         "copied from test_check_command7"
         fake_session = FakeModuleCCSession()
@@ -487,7 +487,7 @@ class TestModuleCCSession(unittest.TestCase):
         mccs.check_command_without_recvmsg(cmd, env)
         self.assertEqual({'result': [0]},
                          fake_session.get_message('Spec2', None))
- 
+
     def test_check_command_block_timeout(self):
         """Check it works if session has timeout and it sets it back."""
         def cmd_check(mccs, session):
@@ -893,22 +893,22 @@ class fakeUIConn():
 
     def set_get_answer(self, name, answer):
         self.get_answers[name] = answer
-    
+
     def set_post_answer(self, name, answer):
         self.post_answers[name] = answer
-    
+
     def send_GET(self, name, arg = None):
         if name in self.get_answers:
             return self.get_answers[name]
         else:
             return {}
-    
+
     def send_POST(self, name, arg = None):
         if name in self.post_answers:
             return self.post_answers[name]
         else:
             return fakeAnswer()
-    
+
 
 class TestUIModuleCCSession(unittest.TestCase):
     def setUp(self):
@@ -919,9 +919,9 @@ class TestUIModuleCCSession(unittest.TestCase):
 
     def spec_file(self, 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('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
         return UIModuleCCSession(fake_conn)
@@ -989,7 +989,7 @@ class TestUIModuleCCSession(unittest.TestCase):
 
     def test_add_remove_value(self):
         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, "no_such_item", "a")
@@ -1020,6 +1020,88 @@ class TestUIModuleCCSession(unittest.TestCase):
         self.assertRaises(isc.cc.data.DataTypeError,
                           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):
         fake_conn = fakeUIConn()
         uccs = self.create_uccs_listtest(fake_conn)
@@ -1101,7 +1183,7 @@ class TestUIModuleCCSession(unittest.TestCase):
 
     def test_commit(self):
         fake_conn = fakeUIConn()
-        uccs = self.create_uccs2(fake_conn)
+        uccs = self.create_uccs(fake_conn)
         uccs.commit()
         uccs._local_changes = {'Spec2': {'item5': [ 'a' ]}}
         uccs.commit()

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

@@ -25,27 +25,31 @@ import time
 
 class SysInfo:
     def __init__(self):
-        self._num_processors = -1
+        self._num_processors = None
         self._endianness = 'Unknown'
         self._hostname = ''
         self._platform_name = 'Unknown'
         self._platform_version = '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_routing_table = 'Unknown\n'
         self._net_stats = '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):
         """Returns the number of processors. This is the number of
         hyperthreads when hyper-threading is enabled.
@@ -77,7 +81,12 @@ class SysInfo:
         return self._platform_is_smp
 
     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
 
     def get_uptime(self):
@@ -164,7 +173,7 @@ class SysInfoLinux(SysInfoPOSIX):
         with open('/proc/loadavg') as f:
             l = f.read().strip().split(' ')
             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:
             m = f.readlines()
@@ -276,8 +285,6 @@ class SysInfoBSD(SysInfoPOSIX):
         except (subprocess.CalledProcessError, OSError):
             pass
 
-        self._platform_distro = self._platform_name + ' ' + self._platform_version
-
         try:
             s = subprocess.check_output(['ifconfig'])
             self._net_interfaces = s.decode('utf-8')
@@ -296,6 +303,12 @@ class SysInfoBSD(SysInfoPOSIX):
         except (subprocess.CalledProcessError, OSError):
             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):
     """OpenBSD implementation of the SysInfo class.
     See the SysInfo class documentation for more information.
@@ -303,11 +316,6 @@ class SysInfoOpenBSD(SysInfoBSD):
     def __init__(self):
         super().__init__()
 
-        # Don't know how to gather these
-        self._platform_is_smp = False
-        self._mem_cached = -1
-        self._mem_buffers = -1
-
         try:
             s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
             t = s.decode('utf-8').strip()
@@ -320,7 +328,7 @@ class SysInfoOpenBSD(SysInfoBSD):
             s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
             l = s.decode('utf-8').strip().split(' ')
             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):
             pass
 
@@ -343,29 +351,13 @@ class SysInfoOpenBSD(SysInfoBSD):
         except (subprocess.CalledProcessError, OSError):
             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):
         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:
             s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
             t = s.decode('utf-8').strip()
@@ -385,7 +377,20 @@ class SysInfoFreeBSD(SysInfoBSD):
             else:
                 la = l.split(' ')
             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):
             pass
 
@@ -408,11 +413,57 @@ class SysInfoFreeBSD(SysInfoBSD):
         except (subprocess.CalledProcessError, OSError):
             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:
-            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):
-            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):
     def __init__(self):
@@ -429,6 +480,8 @@ def SysInfoFromFactory():
         return SysInfoOpenBSD()
     elif osname == 'FreeBSD':
         return SysInfoFreeBSD()
+    elif osname == 'Darwin':
+        return SysInfoOSX()
     elif osname == 'BIND10Testcase':
         return SysInfoTestcase()
     else:

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

@@ -20,6 +20,14 @@ import platform
 import subprocess
 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():
     return 'BIND10Testcase'
 
@@ -28,7 +36,7 @@ def _my_linux_platform_system():
 
 def _my_linux_os_sysconf(key):
     if key == 'SC_NPROCESSORS_CONF':
-        return 42
+        return NPROCESSORS_LINUX
     assert False, 'Unhandled key'
 
 class MyLinuxFile:
@@ -92,90 +100,166 @@ def _my_openbsd_platform_system():
 
 def _my_openbsd_os_sysconf(key):
     if key == 'SC_NPROCESSORS_CONF':
-        return 53
+        return NPROCESSORS_OPENBSD
     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'
     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']:
         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']:
         return b'qB2osV6vUOjqm3P/+tQ4d92xoYz8/U8P9v3KWRpNwlI=\n'
-    elif command == ['route', '-n', 'show']:
-        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'
+    elif command == ['netstat', '-nr']:
+        return b'XfizswwNA9NkXz6K36ZExpjV08Y5IXkHI8jjDSV+5Nc=\n'
     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():
     return 'FreeBSD'
 
 def _my_freebsd_os_sysconf(key):
     if key == 'SC_NPROCESSORS_CONF':
-        return 91
+        return NPROCESSORS_FREEBSD
     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'
-    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')
     elif command == ['sysctl', '-n', 'vm.loadavg']:
         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']:
         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']:
         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:
-        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):
+
+    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):
         """Test that the various methods on SysInfo exist and return data."""
 
         s = SysInfo()
-        self.assertEqual(-1, s.get_num_processors())
+        self.assertEqual(None, s.get_num_processors())
         self.assertEqual('Unknown', s.get_endianness())
         self.assertEqual('', s.get_platform_hostname())
         self.assertEqual('Unknown', s.get_platform_name())
         self.assertEqual('Unknown', s.get_platform_version())
         self.assertEqual('Unknown', s.get_platform_machine())
         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_routing_table())
         self.assertEqual('Unknown\n', s.get_net_stats())
@@ -189,7 +273,7 @@ class SysInfoTest(unittest.TestCase):
         platform.system = _my_testcase_platform_system
 
         s = SysInfoFromFactory()
-        self.assertEqual(-1, s.get_num_processors())
+        self.assertEqual(None, s.get_num_processors())
         self.assertEqual('bigrastafarian', s.get_endianness())
         self.assertEqual('', s.get_platform_hostname())
         self.assertEqual('b10test', s.get_platform_name())
@@ -197,14 +281,14 @@ class SysInfoTest(unittest.TestCase):
         self.assertEqual('Unknown', s.get_platform_machine())
         self.assertFalse(s.get_platform_is_smp())
         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_routing_table())
         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
         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.
-        old_platform_system = platform.system
         platform.system = _my_linux_platform_system
-        old_os_sysconf = os.sysconf
         os.sysconf = _my_linux_os_sysconf
-        old_open = __builtins__.open
         __builtins__.open = _my_linux_open
-        old_subprocess_check_output = subprocess.check_output
         subprocess.check_output = _my_linux_subprocess_check_output
 
         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.assertTrue(s.get_platform_is_smp())
         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(891383808, s.get_mem_free())
         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('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):
         """Tests the OpenBSD implementation of SysInfo. Note that this
         tests deep into the implementation, and not just the
         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.
-        old_platform_system = platform.system
         platform.system = _my_openbsd_platform_system
-        old_os_sysconf = os.sysconf
         os.sysconf = _my_openbsd_os_sysconf
-        old_subprocess_check_output = subprocess.check_output
         subprocess.check_output = _my_openbsd_subprocess_check_output
+        os.uname = _my_openbsd_platform_uname
 
         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 - (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(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):
         """Tests the FreeBSD implementation of SysInfo. Note that this
         tests deep into the implementation, and not just the
         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.
-        old_platform_system = platform.system
         platform.system = _my_freebsd_platform_system
-        old_os_sysconf = os.sysconf
         os.sysconf = _my_freebsd_os_sysconf
-        old_subprocess_check_output = subprocess.check_output
         subprocess.check_output = _my_freebsd_subprocess_check_output
+        os.uname = _my_freebsd_platform_uname
 
         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.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_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__":
     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
 
         # 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
         A query for l.root-servers.net. should have rcode NOERROR
 
         # 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

+ 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_pkt4.cc perf_pkt4.h
 libb10_perfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
+libb10_perfdhcp___la_SOURCES += stats_mgr.h
 
 libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+
 if USE_CLANGPP
 # Disable unused parameter warning caused by some of the
 # 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_pkt4_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/pkt_transform.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());
+}
+
+
+}