2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

Merge branch 'master' into trac2164

This commit is contained in:
Mukund Sivaraman
2012-08-15 16:39:04 +05:30
43 changed files with 3649 additions and 1057 deletions

1
.gitignore vendored
View File

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

View File

@@ -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.

View File

@@ -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 one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -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>
@@ -2187,21 +2387,6 @@ DDNS/zones[0]/update_acl[1] {"action": "ACCEPT", "from": "::1", "key": "key.
should have a TSIG key in its constraints.
</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
@@ -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>.
The following session is an example of extending the ACL to also
allow queries from 192.0.2.0/24:
<screen>
> <userinput>config show Resolver/query_acl</userinput>
Resolver/query_acl[0] {"action": "ACCEPT", "from": "127.0.0.1"} any (default)
Resolver/query_acl[1] {"action": "ACCEPT", "from": "::1"} any (default)
> <userinput>config add Resolver/query_acl</userinput>
> <userinput>config set Resolver/query_acl[2] {"action": "ACCEPT", "from": "192.0.2.0/24"}</userinput>
> <userinput>config add Resolver/query_acl</userinput>
> <userinput>config show Resolver/query_acl</userinput>
Resolver/query_acl[0] {"action": "ACCEPT", "from": "127.0.0.1"} any (modified)
Resolver/query_acl[1] {"action": "ACCEPT", "from": "::1"} any (modified)
Resolver/query_acl[2] {"action": "ACCEPT", "from": "192.0.2.0/24"} any (modified)
Resolver/query_acl[3] {"action": "REJECT"} any (modified)
> <userinput>config commit</userinput></screen>
Note that we didn't set the value of the last final rule
(query_acl[3]) -- in the case of resolver, rejecting all queries is
the default value of a new rule. In fact, this rule can even be
omitted completely, as the default, when a query falls off the list,
is rejection.
</para>
<para>
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:
</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:
<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>
<screen>
#<userinput>cd src/bin/dhcp6</userinput>
#<userinput>./b10-dhcp6</userinput>
</screen>
<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>
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.
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";

View File

@@ -1,4 +1,4 @@
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Messages Manual</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the messages manual for BIND 10 version 20120712. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Messages Manual"><div class="titlepage"><div><div><h1 class="title"><a name="idp98160"></a>BIND 10 Messages Manual</h1></div><div><p class="releaseinfo">This is the messages manual for BIND 10 version
<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Messages Manual</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the messages manual for BIND 10 version 20120712. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Messages Manual"><div class="titlepage"><div><div><h1 class="title"><a name="idp74800"></a>BIND 10 Messages Manual</h1></div><div><p class="releaseinfo">This is the messages manual for BIND 10 version
20120712.</p></div><div><p class="copyright">Copyright <20> 2011-2012 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
Internet Systems Consortium (ISC). It includes DNS libraries
and modular components for controlling authoritative and

View File

@@ -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__

View File

@@ -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"}
}
}
]

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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);
}
}
};
};

View File

@@ -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

View File

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

View File

@@ -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();
if (IfaceMgr::instance().countIfaces() == 0) {
cout << "Failed to detect any network interfaces. Aborting." << endl;
shutdown_ = true;
return;
}
IfaceMgr::instance().openSockets6(port);
setServerID();
/// @todo: instantiate LeaseMgr here once it is imlpemented.
} catch (const std::exception &e) {
cout << "Failed to instantiate InterfaceManager:" << e.what() << ". Aborting." << endl;
shutdown = true;
cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
shutdown_ = true;
return;
}
if (IfaceMgr::instance().countIfaces() == 0) {
cout << "Failed to detect any network interfaces. Aborting." << endl;
shutdown = true;
}
// Now try to open IPv6 sockets on detected interfaces.
IfaceMgr::instance().openSockets6(port);
/// @todo: instantiate LeaseMgr here once it is imlpemented.
setServerID();
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) {

View File

@@ -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

View File

@@ -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";
cout << "b10-dhcp6: Initiating DHCPv6 server operation." << endl;
/// @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 {
specfile = string(DHCP6_SPECFILE_LOCATION);
cout << "Skipping connection to the BIND10 msgq." << endl;
}
cout << "[b10-dhcp6] Initiating DHCPv6 operation." << endl;
Dhcpv6Srv* srv = new Dhcpv6Srv(port_number);
srv->run();
server.run();
} catch (const std::exception& ex) {
cerr << "[b10-dhcp6] Server failed: " << ex.what() << endl;

View File

@@ -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

View File

@@ -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

View File

@@ -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)
# 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("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)
self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -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

View File

@@ -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"}
}
}
],

View File

@@ -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()))
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)
f.write(' + SMP kernel: ')
if s.get_platform_is_smp():
f.write('yes')
else:
f.write('no')
f.write('\n')
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:

View File

@@ -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"}
}
}
]

View File

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

View File

@@ -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));

View File

@@ -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.
///

View File

@@ -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.
///

View File

@@ -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

View File

@@ -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")

View File

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

View File

@@ -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()

View File

@@ -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:

View File

@@ -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()

View File

@@ -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

View File

@@ -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 Load Diff

View File

@@ -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

View File

@@ -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());
}
}