17.2. Logging Message Format
Each message written by BIND 10 to the configured logging
destinations comprises a number of components that identify
the origin of the message and, if the message indicates
diff --git a/doc/guide/bind10-guide.txt b/doc/guide/bind10-guide.txt
index 02cb343d37..abec8534ec 100644
--- a/doc/guide/bind10-guide.txt
+++ b/doc/guide/bind10-guide.txt
@@ -4,7 +4,7 @@ Administrator Reference for BIND 10
This is the reference guide for BIND 10 version 20120712.
- Copyright (c) 2010-2012 Internet Systems Consortium, Inc.
+ Copyright © 2010-2012 Internet Systems Consortium, Inc.
Abstract
@@ -81,14 +81,9 @@ Administrator Reference for BIND 10
8.2. Data Source Backends
- 8.2.1. In-memory Data Source
+ 8.2.1. Data source types
- 8.2.2. In-memory Data Source with SQLite3
- Backend
-
- 8.2.3. Reloading an In-memory Data Source
-
- 8.2.4. Disabling In-memory Data Sources
+ 8.2.2. Examples
8.3. Loading Master Zones Files
@@ -170,12 +165,12 @@ Preface
1. Acknowledgements
-1. Acknowledgements
+1. Acknowledgements
ISC would like to acknowledge generous support for BIND 10 development of
DHCPv4 and DHCPv6 components provided by Comcast.
-Chapter 1. Introduction
+Chapter 1. Introduction
Table of Contents
@@ -197,7 +192,7 @@ Chapter 1. Introduction
This guide covers the experimental prototype of BIND 10 version 20120712.
-1.1. Supported Platforms
+1.1. Supported Platforms
BIND 10 builds have been tested on (in no particular order) Debian
GNU/Linux 6 and unstable, Ubuntu 9.10, NetBSD 5, Solaris 10 and 11,
@@ -206,13 +201,13 @@ Chapter 1. Introduction
planned for BIND 10 to build, install and run on Windows and standard
Unix-type platforms.
-1.2. Required Software at Run-time
+1.2. Required Software at Run-time
Running BIND 10 uses various extra software which may not be provided in
some operating systems' default installations nor standard packages
collections. You may need to install this required software separately.
- (For the build requirements, also see Section 2.3, "Building
- Requirements".)
+ (For the build requirements, also see Section 2.3, “Building
+ Requirementsâ€.)
BIND 10 requires at least Python 3.1 (http://www.python.org/). It also
works with Python 3.2.
@@ -232,7 +227,7 @@ Chapter 1. Introduction
included with Python). Python modules need to be built for the
corresponding Python 3.
-1.3. Starting and Stopping the Server
+1.3. Starting and Stopping the Server
BIND 10 is modular. Part of this modularity is accomplished using multiple
cooperating processes which, together, provide the server functionality.
@@ -245,50 +240,49 @@ Chapter 1. Introduction
processes as needed. The processes started by the bind10 command have
names starting with "b10-", including:
- o b10-auth -- Authoritative DNS server. This process serves DNS
- requests.
- o b10-cfgmgr -- Configuration manager. This process maintains all of the
+ o b10-auth — Authoritative DNS server. This process serves DNS requests.
+ o b10-cfgmgr — Configuration manager. This process maintains all of the
configuration for BIND 10.
- o b10-cmdctl -- Command and control service. This process allows
- external control of the BIND 10 system.
- o b10-ddns -- Dynamic DNS update service. This process is used to handle
+ o b10-cmdctl — Command and control service. This process allows external
+ control of the BIND 10 system.
+ o b10-ddns — Dynamic DNS update service. This process is used to handle
incoming DNS update requests to allow granted clients to update zones
for which BIND 10 is serving as a primary server.
- o b10-msgq -- Message bus daemon. This process coordinates communication
+ o b10-msgq — Message bus daemon. This process coordinates communication
between all of the other BIND 10 processes.
- o b10-resolver -- Recursive name server. This process handles incoming
+ o b10-resolver — Recursive name server. This process handles incoming
DNS queries and provides answers from its cache or by recursively
doing remote lookups.
- o b10-sockcreator -- Socket creator daemon. This process creates sockets
+ o b10-sockcreator — Socket creator daemon. This process creates sockets
used by network-listening BIND 10 processes.
- o b10-stats -- Statistics collection daemon. This process collects and
+ o b10-stats — Statistics collection daemon. This process collects and
reports statistics data.
- o b10-stats-httpd -- HTTP server for statistics reporting. This process
+ o b10-stats-httpd — HTTP server for statistics reporting. This process
reports statistics data in XML format over HTTP.
- o b10-xfrin -- Incoming zone transfer service. This process is used to
+ o b10-xfrin — Incoming zone transfer service. This process is used to
transfer a new copy of a zone into BIND 10, when acting as a secondary
server.
- o b10-xfrout -- Outgoing zone transfer service. This process is used to
+ o b10-xfrout — Outgoing zone transfer service. This process is used to
handle transfer requests to send a local zone to a remote secondary
server.
- o b10-zonemgr -- Secondary zone manager. This process keeps track of
+ o b10-zonemgr — Secondary zone manager. This process keeps track of
timers and other necessary information for BIND 10 to act as a slave
server.
These are ran by bind10 and do not need to be manually started
independently.
-1.4. Managing BIND 10
+1.4. Managing BIND 10
Once BIND 10 is running, a few commands are used to interact directly with
the system:
- o bindctl -- Interactive administration interface. This is a low-level
+ o bindctl — Interactive administration interface. This is a low-level
command-line tool which allows a developer or an experienced
administrator to control BIND 10.
- o b10-loadzone -- Zone file loader. This tool will load standard
+ o b10-loadzone — Zone file loader. This tool will load standard
masterfile-format zone files into BIND 10.
- o b10-cmdctl-usermgr -- User access control. This tool allows an
+ o b10-cmdctl-usermgr — User access control. This tool allows an
administrator to authorize additional users to manage BIND 10.
The tools and modules are covered in full detail in this guide. In
@@ -298,7 +292,7 @@ Chapter 1. Introduction
Python for the message bus, configuration backend, and, of course, DNS.
These include detailed developer documentation and code examples.
-Chapter 2. Installation
+Chapter 2. Installation
Table of Contents
@@ -322,7 +316,7 @@ Chapter 2. Installation
2.5.5. Install
-2.1. Packages
+2.1. Packages
Some operating systems or softare package vendors may provide
ready-to-use, pre-built software packages for the BIND 10 suite.
@@ -332,28 +326,28 @@ Chapter 2. Installation
FreeBSD ports, NetBSD pkgsrc, and Debian testing package collections
provide all the prerequisite packages.
-2.2. Install Hierarchy
+2.2. Install Hierarchy
The following is the standard, common layout of the complete BIND 10
installation:
- o bin/ -- general tools and diagnostic clients.
- o etc/bind10-devel/ -- configuration files.
- o lib/ -- libraries and python modules.
- o libexec/bind10-devel/ -- executables that a user wouldn't normally run
+ o bin/ — general tools and diagnostic clients.
+ o etc/bind10-devel/ — configuration files.
+ o lib/ — libraries and python modules.
+ o libexec/bind10-devel/ — executables that a user wouldn't normally run
directly and are not run independently. These are the BIND 10 modules
which are daemons started by the bind10 tool.
- o sbin/ -- commands used by the system administrator.
- o share/bind10-devel/ -- configuration specifications.
- o share/doc/bind10-devel/ -- this guide and other supplementary
+ o sbin/ — commands used by the system administrator.
+ o share/bind10-devel/ — configuration specifications.
+ o share/doc/bind10-devel/ — this guide and other supplementary
documentation.
- o share/man/ -- manual pages (online documentation).
- o var/bind10-devel/ -- data source and configuration databases.
+ o share/man/ — manual pages (online documentation).
+ o var/bind10-devel/ — data source and configuration databases.
-2.3. Building Requirements
+2.3. Building Requirements
- In addition to the run-time requirements (listed in Section 1.2, "Required
- Software at Run-time"), building BIND 10 from source code requires various
+ In addition to the run-time requirements (listed in Section 1.2, “Required
+ Software at Run-timeâ€), building BIND 10 from source code requires various
development include headers and program development tools.
Note
@@ -378,7 +372,7 @@ Chapter 2. Installation
http://bind10.isc.org/wiki/SystemSpecificNotes for system-specific
installation tips.
-2.4. Quick start
+2.4. Quick start
Note
@@ -389,48 +383,48 @@ Chapter 2. Installation
To quickly get started with BIND 10, follow these steps.
- 1. Install required run-time and build dependencies.
- 2. Download the BIND 10 source tar file from
+  1. Install required run-time and build dependencies.
+  2. Download the BIND 10 source tar file from
ftp://ftp.isc.org/isc/bind10/.
- 3. Extract the tar file:
+  3. Extract the tar file:
$ gzcat bind10-VERSION.tar.gz | tar -xvf -
- 4. Go into the source and run configure:
+  4. Go into the source and run configure:
$ cd bind10-VERSION
$ ./configure
- 5. Build it:
+  5. Build it:
$ make
- 6. Install it (to default /usr/local):
+  6. Install it (to default /usr/local):
$ make install
- 7. Start the server:
+  7. Start the server:
$ /usr/local/sbin/bind10
- 8. Test it; for example:
+  8. Test it; for example:
$ dig @127.0.0.1 -c CH -t TXT authors.bind
- 9. Load desired zone file(s), for example:
+  9. Load desired zone file(s), for example:
$ b10-loadzone your.zone.example.org
- 10. Test the new zone.
+ 10. Test the new zone.
-2.5. Installation from source
+2.5. Installation from source
BIND 10 is open source software written in C++ and Python. It is freely
available in source code form from ISC as a downloadable tar file or via
BIND 10's Git code revision control service. (It may also be available in
pre-compiled ready-to-use packages from operating system vendors.)
- 2.5.1. Download Tar File
+ 2.5.1. Download Tar File
Downloading a release tar file is the recommended method to obtain the
source code.
@@ -439,7 +433,7 @@ Chapter 2. Installation
ftp://ftp.isc.org/isc/bind10/. Periodic development snapshots may also be
available.
- 2.5.2. Retrieve from Git
+ 2.5.2. Retrieve from Git
Downloading this "bleeding edge" code is recommended only for developers
or advanced users. Using development code in a production environment is
@@ -454,7 +448,7 @@ Chapter 2. Installation
The latest development code (and temporary experiments and un-reviewed
code) is available via the BIND 10 code revision control system. This is
powered by Git and all the BIND 10 development is public. The leading
- development is done in the "master" branch.
+ development is done in the “master†branch.
The code can be checked out from git://git.bind10.isc.org/bind10; for
example:
@@ -467,7 +461,7 @@ Chapter 2. Installation
the --install switch. This will run autoconf, aclocal, libtoolize,
autoheader, automake, and related commands.
- 2.5.3. Configure before the build
+ 2.5.3. Configure before the build
BIND 10 uses the GNU Build System to discover build environment details.
To generate the makefiles using the defaults, simply run:
@@ -502,14 +496,14 @@ Chapter 2. Installation
If the configure fails, it may be due to missing or old dependencies.
- 2.5.4. Build
+ 2.5.4. Build
After the configure step is complete, to build the executables from the
C++ code and prepare the Python scripts, run:
$ make
- 2.5.5. Install
+ 2.5.5. Install
To install the BIND 10 executables, support files, and documentation, run:
@@ -519,7 +513,7 @@ Chapter 2. Installation
The install step may require superuser privileges.
-Chapter 3. Starting BIND10 with bind10
+Chapter 3. Starting BIND10 with bind10
Table of Contents
@@ -547,7 +541,7 @@ Chapter 3. Starting BIND10 with bind10
b10-cmdctl for administration tools to communicate with the system, and
b10-stats for statistics collection.
-3.1. Starting BIND 10
+3.1. Starting BIND 10
To start the BIND 10 service, simply run bind10. Run it with the --verbose
switch to get additional debugging or diagnostic output.
@@ -556,9 +550,9 @@ Chapter 3. Starting BIND10 with bind10
If the setproctitle Python module is detected at start up, the process
names for the Python-based daemons will be renamed to better identify them
- instead of just "python". This is not needed on some operating systems.
+ instead of just “pythonâ€. This is not needed on some operating systems.
-3.2. Configuration to start processes
+3.2. Configuration to start processes
The processes to be used can be configured for bind10 to start, with the
exception of the required b10-sockcreator, b10-msgq and b10-cfgmgr
@@ -574,7 +568,7 @@ Chapter 3. Starting BIND10 with bind10
> config set Boss/components/b10-resolver/priority 10
> config commit
- Now, what it means. We add an entry called "b10-resolver". It is both a
+ Now, what it means. We add an entry called “b10-resolverâ€. It is both a
name used to reference this component in the configuration and the name of
the process to start. Then we set some parameters on how to start it.
@@ -583,7 +577,7 @@ Chapter 3. Starting BIND10 with bind10
a usual way. This is the list of components that need to be started in a
special way, with the value of special used for them:
- Table 3.1. Special startup components
+ Table 3.1. Special startup components
+----------------------------------------------------------------------+
| Component | Special | Description |
@@ -596,11 +590,11 @@ Chapter 3. Starting BIND10 with bind10
+----------------------------------------------------------------------+
The kind specifies how a failure of the component should be handled. If it
- is set to "dispensable" (the default unless you set something else), it
- will get started again if it fails. If it is set to "needed" and it fails
+ is set to “dispensable†(the default unless you set something else), it
+ will get started again if it fails. If it is set to “needed†and it fails
at startup, the whole bind10 shuts down and exits with an error exit code.
But if it fails some time later, it is just started again. If you set it
- to "core", you indicate that the system is not usable without the
+ to “coreâ€, you indicate that the system is not usable without the
component and if such component fails, the system shuts down no matter
when the failure happened. This is the behaviour of the core components
(the ones you can't turn off), but you can declare any other components as
@@ -616,7 +610,7 @@ Chapter 3. Starting BIND10 with bind10
address. It is the address used by the component on the b10-msgq message
bus. The special components already know their address, but the usual ones
don't. The address is by convention the thing after b10-, with the first
- letter capitalized (eg. b10-stats would have "Stats" as its address).
+ letter capitalized (eg. b10-stats would have “Stats†as its address).
The last one is process. It is the name of the process to be started. It
defaults to the name of the component if not set, but you can use this to
@@ -652,11 +646,11 @@ Chapter 3. Starting BIND10 with bind10
a situation, so it would probably not do what you want. Such support is
yet to be implemented.
-Chapter 4. Command channel
+Chapter 4. Command channel
The BIND 10 components use the b10-msgq message routing daemon to
communicate with other BIND 10 components. The b10-msgq implements what is
- called the "Command Channel". Processes intercommunicate by sending
+ called the “Command Channelâ€. Processes intercommunicate by sending
messages on the command channel. Example messages include shutdown, get
configurations, and set configurations. This Command Channel is not used
for DNS message passing. It is used only to control and monitor the BIND
@@ -667,7 +661,7 @@ Chapter 4. Command channel
/usr/local/var/bind10-devel/msg_socket for this interprocess
communication.
-Chapter 5. Configuration manager
+Chapter 5. Configuration manager
The configuration manager, b10-cfgmgr, handles all BIND 10 system
configuration. It provides persistent storage for configuration, and
@@ -679,7 +673,7 @@ Chapter 5. Configuration manager
The administrator doesn't connect to it directly, but uses a user
interface to communicate with the configuration manager via b10-cmdctl's
- REST-ful interface. b10-cmdctl is covered in Chapter 6, Remote control
+ REST-ful interface. b10-cmdctl is covered in Chapter 6, Remote control
daemon.
Note
@@ -701,10 +695,10 @@ Chapter 5. Configuration manager
The configuration manager does not have any command line arguments.
Normally it is not started manually, but is automatically started using
- the bind10 master process (as covered in Chapter 3, Starting BIND10 with
+ the bind10 master process (as covered in Chapter 3, Starting BIND10 with
bind10).
-Chapter 6. Remote control daemon
+Chapter 6. Remote control daemon
Table of Contents
@@ -717,7 +711,7 @@ Chapter 6. Remote control daemon
When b10-cmdctl starts, it firsts asks b10-cfgmgr about what modules are
running and what their configuration is (over the b10-msgq channel). Then
- it will start listening on HTTPS for clients -- the user interface -- such
+ it will start listening on HTTPS for clients — the user interface — such
as bindctl.
b10-cmdctl directly sends commands (received from the user interface) to
@@ -744,7 +738,7 @@ Chapter 6. Remote control daemon
/usr/local/etc/bind10-devel/cmdctl-accounts.csv. This comma-delimited file
lists the accounts with a user name, hashed password, and salt. (A sample
file is at /usr/local/share/bind10-devel/cmdctl-accounts.csv. It contains
- the user named "root" with the password "bind10".)
+ the user named “root†with the password “bind10â€.)
The administrator may create a user account with the b10-cmdctl-usermgr
tool.
@@ -755,7 +749,7 @@ Chapter 6. Remote control daemon
connection is stateless and times out in 1200 seconds by default. This can
be redefined by using the --idle-timeout command line argument.
-6.1. Configuration specification for b10-cmdctl
+6.1. Configuration specification for b10-cmdctl
The configuration items for b10-cmdctl are: accounts_file which defines
the path to the user accounts database (the default is
@@ -765,7 +759,7 @@ Chapter 6. Remote control daemon
defines the path to the PEM private key file (the default is
/usr/local/etc/bind10-devel/cmdctl-keyfile.pem).
-Chapter 7. Control and configure user interface
+Chapter 7. Control and configure user interface
Note
@@ -785,7 +779,7 @@ Chapter 7. Control and configure user interface
b10-cfgmgr which then stores the details and relays (over a b10-msgq
command channel) the configuration on to the specified module.
-Chapter 8. Authoritative Server
+Chapter 8. Authoritative Server
Table of Contents
@@ -793,13 +787,9 @@ Chapter 8. Authoritative Server
8.2. Data Source Backends
- 8.2.1. In-memory Data Source
+ 8.2.1. Data source types
- 8.2.2. In-memory Data Source with SQLite3 Backend
-
- 8.2.3. Reloading an In-memory Data Source
-
- 8.2.4. Disabling In-memory Data Sources
+ 8.2.2. Examples
8.3. Loading Master Zones Files
@@ -807,10 +797,10 @@ Chapter 8. Authoritative Server
IPv6, and SQLite3 and in-memory zone data backends. Normally it is started
by the bind10 master process.
-8.1. Server Configurations
+8.1. Server Configurations
b10-auth is configured via the b10-cfgmgr configuration manager. The
- module name is "Auth". The configuration data items are:
+ module name is “Authâ€. The configuration data items are:
database_file
This is an optional string to define the path to find the SQLite3
@@ -819,10 +809,10 @@ Chapter 8. Authoritative Server
datasources
datasources configures data sources. The list items include: type
- to define the required data source type (such as "memory"); class
- to optionally select the class (it defaults to "IN"); and zones to
- define the file path name, the filetype ("sqlite3" to load from a
- SQLite3 database file or "text" to load from a master text file),
+ to define the required data source type (such as “memoryâ€); class
+ to optionally select the class (it defaults to “INâ€); and zones to
+ define the file path name, the filetype (“sqlite3†to load from a
+ SQLite3 database file or “text†to load from a master text file),
and the origin (default domain). By default, this is empty.
Note
@@ -852,7 +842,7 @@ Chapter 8. Authoritative Server
There are plans to solve the problem such that the server handles
it by itself. But until it is actually implemented, it is
- recommended to alter the configuration -- remove the wildcard
+ recommended to alter the configuration — remove the wildcard
addresses and list all addresses explicitly. Then the server will
answer on the same interface the request came on, preserving the
correct address.
@@ -867,9 +857,9 @@ Chapter 8. Authoritative Server
loadzone
loadzone tells b10-auth to load or reload a zone file. The
arguments include: class which optionally defines the class (it
- defaults to "IN"); origin is the domain name of the zone; and
+ defaults to “INâ€); origin is the domain name of the zone; and
datasrc optionally defines the type of datasource (it defaults to
- "memory").
+ “memoryâ€).
Note
@@ -885,79 +875,123 @@ Chapter 8. Authoritative Server
argument to select the process ID to stop. (Note that the BIND 10
boss process may restart this service if configured.)
-8.2. Data Source Backends
+8.2. Data Source Backends
+
+ Bind 10 has the concept of data sources. A data source is a place where
+ authoritative zone data reside and where they can be served from. This can
+ be a master file, a database or something completely different.
+
+ Once a query arrives, b10-auth goes through a configured list of data
+ sources and finds the one containing a best matching zone. From the
+ equally good ones, the first one is taken. This data source is then used
+ to answer the query.
Note
- For the development prototype release, b10-auth supports a SQLite3 data
- source backend and in-memory data source backend. Upcoming versions will
+ In the development prototype release, b10-auth can serve data from a
+ SQLite3 data source backend and from master files. Upcoming versions will
be able to use multiple different data sources, such as MySQL and Berkeley
DB.
- By default, the SQLite3 backend uses the data file located at
- /usr/local/var/bind10-devel/zone.sqlite3. (The full path is what was
- defined at build configure time for --localstatedir. The default is
- /usr/local/var/.) This data file location may be changed by defining the
- "database_file" configuration.
+ The configuration is located in data_sources/classes. Each item there
+ represents one RR class and a list used to answer queries for that class.
+ The default contains two classes. The CH class contains a static data
+ source — one that serves things like “AUTHORS.BIND.â€. The IN class
+ contains single SQLite3 data source with database file located at
+ /usr/local/var/bind10-devel/zone.sqlite3.
- 8.2.1. In-memory Data Source
+ Each data source has several options. The first one is type, which
+ specifies the type of data source to use. Valid types include the ones
+ listed below, but bind10 uses dynamically loaded modules for them, so
+ there may be more in your case. This option is mandatory.
- The following commands to bindctl provide an example of configuring an
- in-memory data source containing the "example.com" zone with the zone file
- named "example.com.zone":
+ Another option is params. This option is type specific; it holds different
+ data depending on the type above. Also, depending on the type, it could be
+ possible to omit it.
- > config add Auth/datasources
- > config set Auth/datasources[0]/type "memory"
- > config add Auth/datasources[0]/zones
- > config set Auth/datasources[0]/zones[0]/origin "example.com"
- > config set Auth/datasources[0]/zones[0]/file "example.com.zone"
+ There are two options related to the so-called cache. If you enable cache,
+ zone data from the data source are loaded into memory. Then, when
+ answering a query, b10-auth looks into the memory only instead of the data
+ source, which speeds answering up. The first option is cache-enable, a
+ boolean value turning the cache on and off (off is the default). The
+ second one, cache-zones, is a list of zone origins to load into in-memory.
+ Remember that zones in the data source not listed here will not be loaded
+ and will not be available at all.
+
+ 8.2.1. Data source types
+
+ As mentioned, the type used by default is “sqlite3â€. It has single
+ configuration option inside params — database_file, which contains the
+ path to the sqlite3 file containing the data.
+
+ Another type is called “MasterFilesâ€. This one is slightly special. The
+ data are stored in RFC1034 master files. Because answering directly from
+ them would be impractical, this type mandates the cache to be enabled.
+ Also, the list of zones (cache-zones) should be omitted. The params is a
+ dictionary mapping from zone origins to the files they reside in.
+
+ 8.2.2. Examples
+
+ As this is one of the more complex configurations of Bind10, we show some
+ examples. They all assume they start with default configuration.
+
+ First, let's disable the static data source (“VERSION.BIND†and friends).
+ As it is the only data source in the CH class, we can remove the whole
+ class.
+
+ > config remove data_sources/classes CH
> config commit
- The authoritative server will begin serving it immediately after the zone
- data is loaded from the master text file.
+ Another one, let's say our default data source contains zones
+ “example.org.†and “example.net.â€. We want them to be served from memory
+ to make the answering faster.
- 8.2.2. In-memory Data Source with SQLite3 Backend
-
- The following commands to bindctl provide an example of configuring an
- in-memory data source containing the "example.org" zone with a SQLite3
- backend file named "example.org.sqlite3":
-
- > config add Auth/datasources
- > config set Auth/datasources[1]/type "memory"
- > config add Auth/datasources[1]/zones
- > config set Auth/datasources[1]/zones[0]/origin "example.org"
- > config set Auth/datasources[1]/zones[0]/file "example.org.sqlite3"
- > config set Auth/datasources[1]/zones[0]/filetype "sqlite3"
+ > config set data_sources/classes/IN[0]/cache-enable true
+ > config add data_sources/classes/IN[0]/cache-zones example.org.
+ > config add data_sources/classes/IN[0]/cache-zones example.net.
> config commit
- The authoritative server will begin serving it immediately after the zone
- data is loaded from the database file.
+ Now every time the zone in the data source is changed by the operator,
+ Bind10 needs to be told to reload it, by
- 8.2.3. Reloading an In-memory Data Source
+ > Auth loadzone example.org
- Use the Auth loadzone command in bindctl to reload a changed master file
- into memory; for example:
+ You don't need to do this when the zone is modified by XfrIn, it does so
+ automatically.
- > Auth loadzone origin="example.com"
+ Now, the last example is when there are master files we want to serve in
+ addition to whatever is inside the sqlite3 database.
- 8.2.4. Disabling In-memory Data Sources
-
- By default, the memory data source is disabled; it must be configured
- explicitly. To disable all the in-memory zones, specify a null list for
- Auth/datasources:
-
- > config set Auth/datasources/ []
+ > config add data_sources/classes/IN
+ > config set data_sources/classes/IN[1]/type MasterFiles
+ > config set data_sources/classes/IN[1]/cache-enable true
+ > config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }
> config commit
- The following example stops serving a specific zone:
+ 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.
- > config remove Auth/datasources[0]/zones[0]
- > config commit
+ > config set data_sources/classes/IN[1]/params {}
+ > config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org
+ > config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com
+ > config remove data_sources/classes/IN[1]/params another.example.org
- (Replace the list number(s) in datasources[0] and/or zones[0] for the
- relevant zone as needed.)
-8.3. Loading Master Zones Files
+ bindctl. To reload a zone, you the same command as above.
+
+ Note
+
+ There's also Auth/database_file configuration variable, pointing to a
+ sqlite3 database file. This is no longer used by b10-auth, but it is left
+ in place for now, since other modules use it. Once b10-xfrin, b10-xfrout
+ and b10-ddns are ported to the new configuration, this will disappear. But
+ for now, make sure that if you use any of these modules, the new and old
+ configuration correspond. The defaults are consistent, so unless you
+ tweaked either the new or the old configuration, you're good.
+
+8.3. Loading Master Zones Files
RFC 1035 style DNS master zone files may imported into a BIND 10 SQLite3
data source by using the b10-loadzone utility.
@@ -988,7 +1022,7 @@ Chapter 8. Authoritative Server
If you reload a zone already existing in the database, all records from
that prior zone disappear and a whole new set appears.
-Chapter 9. Incoming Zone Transfers
+Chapter 9. Incoming Zone Transfers
Table of Contents
@@ -1012,7 +1046,7 @@ Chapter 9. Incoming Zone Transfers
implementation limitations of the current development release, however, it
only tries AXFR by default, and care should be taken to enable IXFR.
-9.1. Configuration for Incoming Zone Transfers
+9.1. Configuration for Incoming Zone Transfers
In practice, you need to specify a list of secondary zones to enable
incoming zone transfers for these zones (you can still trigger a zone
@@ -1029,7 +1063,7 @@ Chapter 9. Incoming Zone Transfers
(We assume there has been no zone configuration before).
-9.2. Enabling IXFR
+9.2. Enabling IXFR
As noted above, b10-xfrin uses AXFR for zone transfers by default. To
enable IXFR for zone transfers for a particular zone, set the use_ixfr
@@ -1052,7 +1086,7 @@ Chapter 9. Incoming Zone Transfers
be implemented in a near future version, at which point we will enable
IXFR by default.
-9.3. Secondary Manager
+9.3. Secondary Manager
The b10-zonemgr process is started by bind10. It keeps track of SOA
refresh, retry, and expire timers and other details for BIND 10 to perform
@@ -1077,14 +1111,14 @@ Chapter 9. Incoming Zone Transfers
for it), b10-zonemgr will automatically tell b10-xfrin to transfer the
zone in.
-9.4. Trigger an Incoming Zone Transfer Manually
+9.4. Trigger an Incoming Zone Transfer Manually
To manually trigger a zone transfer to retrieve a remote zone, you may use
the bindctl utility. For example, at the bindctl prompt run:
> Xfrin retransfer zone_name="foo.example.org" master=192.0.2.99
-9.5. Incoming Transfers with In-memory Datasource
+9.5. Incoming Transfers with In-memory Datasource
In the case of an incoming zone transfer, the received zone is first
stored in the corresponding BIND 10 datasource. In case the secondary zone
@@ -1094,9 +1128,9 @@ Chapter 9. Incoming Zone Transfers
The administrator doesn't have to do anything for b10-auth to serve the
new version of the zone, except for the configuration such as the one
- described in Section 8.2.2, "In-memory Data Source with SQLite3 Backend".
+ described in Section 8.2, “Data Source Backendsâ€.
-Chapter 10. Outbound Zone Transfers
+Chapter 10. Outbound Zone Transfers
The b10-xfrout process is started by bind10. When the b10-auth
authoritative DNS server receives an AXFR or IXFR request, b10-auth
@@ -1145,7 +1179,7 @@ Chapter 10. Outbound Zone Transfers
The way to specify zone specific configuration (ACLs, etc) is likely to be
changed.
-Chapter 11. Dynamic DNS Update
+Chapter 11. Dynamic DNS Update
Table of Contents
@@ -1166,10 +1200,9 @@ Chapter 11. Dynamic DNS Update
successful update, REFUSED if rejected due to ACL check, etc). If the zone
has been changed as a result, it will internally notify b10-xfrout so that
other secondary servers will be notified via the DNS NOTIFY protocol. In
- addition, if b10-auth serves the updated zone from its in-memory cache (as
- described in Section 8.2.2, "In-memory Data Source with SQLite3 Backend"),
- b10-ddns will also notify b10-auth so that b10-auth will re-cache the
- updated zone content.
+ addition, if b10-auth serves the updated zone (as described in
+ Section 8.2, “Data Source Backendsâ€), b10-ddns will also notify b10-auth
+ so that b10-auth will re-cache the updated zone content if necessary.
The b10-ddns component supports requests over both UDP and TCP, and both
IPv6 and IPv4; for TCP requests, however, it terminates the TCP connection
@@ -1182,7 +1215,7 @@ Chapter 11. Dynamic DNS Update
As of this writing b10-ddns does not support update forwarding for
secondary zones. If it receives an update request for a secondary zone, it
- will immediately return a "not implemented" response.
+ will immediately return a “not implemented†response.
Note
@@ -1190,7 +1223,7 @@ Chapter 11. Dynamic DNS Update
supported. But currently it's considered a lower priority task and there
is no specific plan of implementing this feature.
-11.1. Enabling Dynamic Update
+11.1. Enabling Dynamic Update
First off, it must be made sure that a few components on which b10-ddns
depends are configured to run, which are b10-auth and b10-zonemgr. In
@@ -1239,7 +1272,7 @@ Chapter 11. Dynamic DNS Update
b10-ddns would start and work without specifying it. But for it to
shutdown gracefully this parameter should also be specified.
-11.2. Access Control
+11.2. Access Control
By default, b10-ddns rejects any update requests from any clients by
returning a REFUSED response. To allow updates to take effect, an access
@@ -1253,7 +1286,7 @@ Chapter 11. Dynamic DNS Update
The zone's origin name
class
- The RR class of the zone (normally "IN", and in that case can be
+ The RR class of the zone (normally “INâ€, and in that case can be
omitted in configuration)
update_acl
@@ -1264,7 +1297,7 @@ Chapter 11. Dynamic DNS Update
In general, an update ACL rule that allows an update request should be
configured with a TSIG key. This is an example update ACL that allows
- updates to the zone named "example.org" (of default RR class "IN") from
+ updates to the zone named “example.org†(of default RR class “INâ€) from
clients that send requests signed with a TSIG whose key name is
"key.example.org" (and refuses all others):
@@ -1273,7 +1306,7 @@ Chapter 11. Dynamic DNS Update
> config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "key": "key.example.org"}
> config commit
- The TSIG key must be configured system wide (see Chapter 10, Outbound Zone
+ The TSIG key must be configured system wide (see Chapter 10, Outbound Zone
Transfers.)
Multiple rules can be specified in the ACL, and an ACL rule can consist of
@@ -1309,7 +1342,7 @@ Chapter 11. Dynamic DNS Update
which is rejecting any requests in the case of b10-ddns.
Other actions than "ACCEPT", namely "REJECT" and "DROP", can be used, too.
- See Chapter 12, Recursive Name Server about their effects.
+ See Chapter 12, Recursive Name Server about their effects.
Currently update ACL can only control updates per zone basis; it's not
possible to specify access control with higher granularity such as for
@@ -1329,7 +1362,7 @@ Chapter 11. Dynamic DNS Update
clients. There have been other troubles that could have been avoided if
the ACL could be checked before the prerequisite check.
-11.3. Miscellaneous Operational Issues
+11.3. Miscellaneous Operational Issues
Unlike BIND 9, BIND 10 currently does not support automatic re-signing of
DNSSEC-signed zone when it's updated via DDNS. It could be possible to
@@ -1338,20 +1371,20 @@ Chapter 11. Dynamic DNS Update
operation. In general, it's not advisable to allow DDNS for a signed zone
at this moment.
- Also unlike BIND 9, it's currently not possible to "freeze" a zone
+ Also unlike BIND 9, it's currently not possible to “freeze†a zone
temporarily in order to suspend DDNS while you manually update the zone.
If you need to make manual updates to a dynamic zone, you'll need to
temporarily reject any updates to the zone via the update ACLs.
Dynamic updates are only applicable to primary zones. In order to avoid
updating secondary zones via DDNS requests, b10-ddns refers to the
- "secondary_zones" configuration of b10-zonemgr. Zones listed in
- "secondary_zones" will never be updated via DDNS regardless of the update
+ “secondary_zones†configuration of b10-zonemgr. Zones listed in
+ “secondary_zones†will never be updated via DDNS regardless of the update
ACL configuration; b10-ddns will return a NOTAUTH (server not
authoritative for the zone) response. If you have a "conceptual" secondary
zone whose content is a copy of some external source but is not updated
via the standard zone transfers and therefore not listed in
- "secondary_zones", be careful not to allow DDNS for the zone; it would be
+ “secondary_zonesâ€, be careful not to allow DDNS for the zone; it would be
quite likely to lead to inconsistent state between different servers.
Normally this should not be a problem because the default update ACL
rejects any update requests, but you may want to take an extra care about
@@ -1362,7 +1395,7 @@ Chapter 11. Dynamic DNS Update
can be retrieved in the form of outbound IXFR. This is done automatically;
it does not require specific configuration to make this possible.
-Chapter 12. Recursive Name Server
+Chapter 12. Recursive Name Server
Table of Contents
@@ -1393,26 +1426,26 @@ Chapter 12. Recursive Name Server
> config set Resolver/listen_on[2]/port 53
> config commit
- (Replace the "2" as needed; run "config show Resolver/listen_on" if
+ (Replace the “2†as needed; run “config show Resolver/listen_on†if
needed.)
-12.1. Access Control
+12.1. Access Control
By default, the b10-resolver daemon only accepts DNS queries from the
localhost (127.0.0.1 and ::1). The Resolver/query_acl configuration may be
used to reject, drop, or allow specific IPs or networks. This
configuration list is first match.
- The configuration's action item may be set to "ACCEPT" to allow the
- incoming query, "REJECT" to respond with a DNS REFUSED return code, or
- "DROP" to ignore the query without any response (such as a blackhole). For
+ The configuration's action item may be set to “ACCEPT†to allow the
+ incoming query, “REJECT†to respond with a DNS REFUSED return code, or
+ “DROP†to ignore the query without any response (such as a blackhole). For
more information, see the respective debugging messages:
RESOLVER_QUERY_ACCEPTED, RESOLVER_QUERY_REJECTED, and
RESOLVER_QUERY_DROPPED.
The required configuration's from item is set to an IPv4 or IPv6 address,
addresses with an network mask, or to the special lowercase keywords
- "any6" (for any IPv6 address) or "any4" (for any IPv4 address).
+ “any6†(for any IPv6 address) or “any4†(for any IPv4 address).
For example to allow the 192.168.1.0/24 network to use your recursive name
server, at the bindctl prompt run:
@@ -1422,14 +1455,14 @@ Chapter 12. Recursive Name Server
> config set Resolver/query_acl[2]/from "192.168.1.0/24"
> config commit
- (Replace the "2" as needed; run "config show Resolver/query_acl" if
+ (Replace the “2†as needed; run “config show Resolver/query_acl†if
needed.)
Note
This prototype access control configuration syntax may be changed.
-12.2. Forwarding
+12.2. Forwarding
To enable forwarding, the upstream address and port must be configured to
forward queries to, such as:
@@ -1445,7 +1478,7 @@ Chapter 12. Recursive Name Server
> config set Resolver/forward_addresses []
> config commit
-Chapter 13. DHCPv4 Server
+Chapter 13. DHCPv4 Server
Table of Contents
@@ -1465,7 +1498,7 @@ Chapter 13. DHCPv4 Server
clients. Even though principles of both DHCPv4 and DHCPv6 are somewhat
similar, these are two radically different protocols. BIND10 offers server
implementations for both DHCPv4 and DHCPv6. This chapter is about DHCP for
- IPv4. For a description of the DHCPv6 server, see Chapter 14, DHCPv6
+ IPv4. For a description of the DHCPv6 server, see Chapter 14, DHCPv6
Server.
The DHCPv4 server component is currently under intense development. You
@@ -1473,7 +1506,7 @@ Chapter 13. DHCPv4 Server
developers mailing list.
The DHCPv4 and DHCPv6 components in BIND10 architecture are internally
- code named "Kea".
+ code named “Keaâ€.
Note
@@ -1481,17 +1514,17 @@ Chapter 13. DHCPv4 Server
servers. That means that while they are capable of performing DHCP
configuration, they are not fully functional yet. In particular, neither
has functional lease databases. This means that they will assign the same,
- fixed, hardcoded addresses to any client that will ask. See Section 13.4,
- "DHCPv4 Server Limitations" and Section 14.4, "DHCPv6 Server Limitations"
+ fixed, hardcoded addresses to any client that will ask. See Section 13.4,
+ “DHCPv4 Server Limitations†and Section 14.4, “DHCPv6 Server Limitationsâ€
for detailed description.
-13.1. DHCPv4 Server Usage
+13.1. DHCPv4 Server Usage
BIND10 provides the DHCPv4 server component since December 2011. It is a
skeleton server and can be described as an early prototype that is not
fully functional yet. It is mature enough to conduct first tests in lab
- environment, but it has significant limitations. See Section 13.4, "DHCPv4
- Server Limitations" for details.
+ environment, but it has significant limitations. See Section 13.4, “DHCPv4
+ Server Limitations†for details.
b10-dhcp4 is a BIND10 component and is being run under BIND10 framework.
To add a DHCPv4 process to the set of running BIND10 services, you can use
@@ -1510,21 +1543,15 @@ Chapter 13. DHCPv4 Server
> config remove Boss/components b10-dhcp4
> config commit
- At start, 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. The server will then listen
- to incoming traffic. Currently supported client messages are DISCOVER and
- REQUEST. The server 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.
+ 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. The server will then
+ listen to incoming traffic. Currently supported client messages are
+ DISCOVER and REQUEST. The server 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.
- Note
-
- Integration with bind10 is planned. Ultimately, b10-dhcp4 will not be
- started directly, but rather via bind10. Please be aware of this planned
- change.
-
-13.2. DHCPv4 Server Configuration
+13.2. DHCPv4 Server Configuration
The DHCPv4 server does not have a lease database implemented yet nor any
support for configuration, so every time the same set of configuration
@@ -1545,55 +1572,55 @@ Chapter 13. DHCPv4 Server
Lease database and configuration support is planned for 2012.
-13.3. Supported standards
+13.3. Supported standards
The following standards and draft standards are currently supported:
- o RFC2131: Supported messages are DISCOVER, OFFER, REQUEST, and ACK.
- o RFC2132: Supported options are: PAD (0), END(255), Message Type(53),
+ o RFC2131: Supported messages are DISCOVER, OFFER, REQUEST, and ACK.
+ o RFC2132: Supported options are: PAD (0), END(255), Message Type(53),
DHCP Server Identifier (54), Domain Name (15), DNS Servers (6), IP
Address Lease Time (51), Subnet mask (1), and Routers (3).
-13.4. DHCPv4 Server Limitations
+13.4. DHCPv4 Server Limitations
These are the current limitations of the DHCPv4 server software. Most of
them are reflections of the early stage of development and should be
- treated as "not implemented yet", rather than actual limitations.
+ treated as “not implemented yetâ€, rather than actual limitations.
- o During initial IPv4 node configuration, the server is expected to send
+ o During initial IPv4 node configuration, the server is expected to send
packets to a node that does not have IPv4 address assigned yet. The
server requires certain tricks (or hacks) to transmit such packets.
This is not implemented yet, therefore DHCPv4 server supports relayed
traffic only (that is, normal point to point communication).
- o b10-dhcp4 provides a single, fixed, hardcoded lease to any client that
+ o b10-dhcp4 provides a single, fixed, hardcoded lease to any client that
asks. There is no lease manager implemented. If two clients request
addresses, they will both get the same fixed address.
- o b10-dhcp4 does not support any configuration mechanisms yet. The whole
+ o b10-dhcp4 does not support any configuration mechanisms yet. The whole
configuration is currently hardcoded. The only way to tweak
- configuration is to directly modify source code. See see Section 13.2,
- "DHCPv4 Server Configuration" for details.
- o Upon start, the server will open sockets on all interfaces that are
+ configuration is to directly modify source code. See see Section 13.2,
+ “DHCPv4 Server Configuration†for details.
+ o Upon start, the server will open sockets on all interfaces that are
not loopback, are up and running and have IPv4 address.
- o PRL (Parameter Request List, a list of options requested by a client)
+ o PRL (Parameter Request List, a list of options requested by a client)
is currently ignored and server assigns DNS SERVER and DOMAIN NAME
options.
- o b10-dhcp4 does not support BOOTP. That is a design choice. This
+ o b10-dhcp4 does not support BOOTP. That is a design choice. This
limitation is permanent. If you have legacy nodes that can't use DHCP
and require BOOTP support, please use latest version of ISC DHCP
http://www.isc.org/software/dhcp.
- o Interface detection is currently working on Linux only. See
- Section 15.1, "Interface detection" for details.
- o b10-dhcp4 does not verify that assigned address is unused. According
+ o Interface detection is currently working on Linux only. See
+ Section 15.1, “Interface detection†for details.
+ o b10-dhcp4 does not verify that assigned address is unused. According
to RFC2131, the allocating server should verify that address is no
used by sending ICMP echo request.
- o Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM),
+ o Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM),
duplication report (DECLINE) and release (RELEASE) are not supported
yet.
- o DNS Update is not supported yet.
- o -v (verbose) command line option is currently the default, and cannot
+ o DNS Update is not supported yet.
+ o -v (verbose) command line option is currently the default, and cannot
be disabled.
-Chapter 14. DHCPv6 Server
+Chapter 14. DHCPv6 Server
Table of Contents
@@ -1608,14 +1635,14 @@ Chapter 14. DHCPv6 Server
Dynamic Host Configuration Protocol for IPv6 (DHCPv6) is specified in
RFC3315. BIND10 provides DHCPv6 server implementation that is described in
this chapter. For a description of the DHCPv4 server implementation, see
- Chapter 13, DHCPv4 Server.
+ Chapter 13, DHCPv4 Server.
The DHCPv6 server component is currently under intense development. You
may want to check out BIND10 DHCP (Kea) wiki and recent posts on BIND10
developers mailing list.
The DHCPv4 and DHCPv6 components in BIND10 architecture are internally
- code named "Kea".
+ code named “Keaâ€.
Note
@@ -1623,43 +1650,45 @@ Chapter 14. DHCPv6 Server
servers. That means that while they are capable of performing DHCP
configuration, they are not fully functional yet. In particular, neither
has functional lease databases. This means that they will assign the same,
- fixed, hardcoded addresses to any client that will ask. See Section 13.4,
- "DHCPv4 Server Limitations" and Section 14.4, "DHCPv6 Server Limitations"
+ fixed, hardcoded addresses to any client that will ask. See Section 13.4,
+ “DHCPv4 Server Limitations†and Section 14.4, “DHCPv6 Server Limitationsâ€
for detailed description.
-14.1. DHCPv6 Server Usage
+14.1. DHCPv6 Server Usage
BIND10 provides the DHCPv6 server component since September 2011. It is a
skeleton server and can be described as an early prototype that is not
fully functional yet. It is mature enough to conduct first tests in lab
- environment, but it has significant limitations. See Section 14.4, "DHCPv6
- Server Limitations" for details.
+ environment, but it has significant limitations. See Section 14.4, “DHCPv6
+ Server Limitations†for details.
- The DHCPv6 server is implemented as b10-dhcp6 daemon. As it is not
- configurable yet, it is fully autonomous, that is it does not interact
- with b10-cfgmgr. To start DHCPv6 server, simply input:
+ b10-dhcp6 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 bindctl:
- #cd src/bin/dhcp6
- #./b10-dhcp6
+ > config add Boss/components b10-dhcp6
+ > config set Boss/components/b10-dhcp6/kind dispensable
+ > config commit
- Depending on your installation, b10-dhcp6 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 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. The server will then
- listen to incoming traffic. Currently supported client messages are
- SOLICIT and REQUEST. The server will respond to them with ADVERTISE and
- REPLY, respectively. Since the DHCPv6 server opens privileged ports, it
- requires root access. Make sure you run this daemon as root.
+ To shutdown running b10-dhcp6, please use the following command:
- Note
+ > Dhcp6 shutdown
- Integration with bind10 is planned. Ultimately, b10-dhcp6 will not be
- started directly, but rather via bind10. Please be aware of this planned
- change.
+ or
-14.2. DHCPv6 Server Configuration
+ > config remove Boss/components b10-dhcp6
+ > config commit
+
+ 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.
+ The server will then listen to incoming traffic. Currently supported
+ client messages are SOLICIT and REQUEST. The server will respond to them
+ with ADVERTISE and REPLY, respectively. Since the DHCPv6 server opens
+ privileged ports, it requires root access. Make sure you run this daemon
+ as root.
+
+14.2. DHCPv6 Server Configuration
The DHCPv6 server does not have lease database implemented yet or any
support for configuration, so every time the same set of configuration
@@ -1667,7 +1696,7 @@ Chapter 14. DHCPv6 Server
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 parameters and
+ src/bin/dhcp6/dhcp6_srv.cc file, modify the following parameters and
recompile:
const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
@@ -1679,50 +1708,50 @@ Chapter 14. DHCPv6 Server
Lease database and configuration support is planned for 2012.
-14.3. Supported DHCPv6 Standards
+14.3. Supported DHCPv6 Standards
The following standards and draft standards are currently supported:
- o RFC3315: Supported messages are SOLICIT, ADVERTISE, REQUEST, and
+ o RFC3315: Supported messages are SOLICIT, ADVERTISE, REQUEST, and
REPLY. Supported options are SERVER_ID, CLIENT_ID, IA_NA, and
IAADDRESS.
- o RFC3646: Supported option is DNS_SERVERS.
+ o RFC3646: Supported option is DNS_SERVERS.
-14.4. DHCPv6 Server Limitations
+14.4. DHCPv6 Server Limitations
These are the current limitations of the DHCPv6 server software. Most of
them are reflections of the early stage of development and should be
- treated as "not implemented yet", rather than actual limitations.
+ treated as “not implemented yetâ€, rather than actual limitations.
- o Relayed traffic is not supported.
- o b10-dhcp6 provides a single, fixed, hardcoded lease to any client that
+ o Relayed traffic is not supported.
+ o b10-dhcp6 provides a single, fixed, hardcoded lease to any client that
asks. There is no lease manager implemented. If two clients request
addresses, they will both get the same fixed address.
- o b10-dhcp6 does not support any configuration mechanisms yet. The whole
+ o b10-dhcp6 does not support any configuration mechanisms yet. The whole
configuration is currently hardcoded. The only way to tweak
- configuration is to directly modify source code. See see Section 14.2,
- "DHCPv6 Server Configuration" for details.
- o Upon start, the server will open sockets on all interfaces that are
+ configuration is to directly modify source code. See see Section 14.2,
+ “DHCPv6 Server Configuration†for details.
+ o Upon start, the server will open sockets on all interfaces that are
not loopback, are up, running and are multicast capable and have IPv6
address. Support for multiple interfaces is not coded in reception
routines yet, so if you are running this code on a machine that has
many interfaces and b10-dhcp6 happens to listen on wrong interface,
the easiest way to work around this problem is to turn down other
interfaces. This limitation will be fixed shortly.
- o ORO (Option Request Option, a list of options requested by a client)
+ o ORO (Option Request Option, a list of options requested by a client)
is currently ignored and server assigns DNS SERVER option.
- o Temporary addresses are not supported yet.
- o Prefix delegation is not supported yet.
- o Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM),
+ o Temporary addresses are not supported yet.
+ o Prefix delegation is not supported yet.
+ o Address renewal (RENEW), rebinding (REBIND), confirmation (CONFIRM),
duplication report (DECLINE) and release (RELEASE) are not supported
yet.
- o DNS Update is not supported yet.
- o Interface detection is currently working on Linux only. See
- Section 15.1, "Interface detection" for details.
- o -v (verbose) command line option is currently the default, and cannot
+ o DNS Update is not supported yet.
+ o Interface detection is currently working on Linux only. See
+ Section 15.1, “Interface detection†for details.
+ o -v (verbose) command line option is currently the default, and cannot
be disabled.
-Chapter 15. libdhcp++ library
+Chapter 15. libdhcp++ library
Table of Contents
@@ -1740,7 +1769,7 @@ Chapter 15. libdhcp++ library
is designed to be portable, universal library useful for any kind of
DHCP-related software.
-15.1. Interface detection
+15.1. Interface detection
Both DHCPv4 and DHCPv6 components share network interface detection
routines. Interface detection is currently only supported on Linux
@@ -1751,11 +1780,11 @@ Chapter 15. libdhcp++ library
lo0) can be easily predicted. Please contact BIND10 development team if
you are interested in running DHCP components on systems other than Linux.
-15.2. DHCPv4/DHCPv6 packet handling
+15.2. DHCPv4/DHCPv6 packet handling
TODO: Describe packet handling here, with pointers to wiki
-Chapter 16. Statistics
+Chapter 16. Statistics
The b10-stats process is started by bind10. It periodically collects
statistics data from various modules and aggregates it.
@@ -1787,7 +1816,7 @@ Chapter 16. Statistics
}
-Chapter 17. Logging
+Chapter 17. Logging
Table of Contents
@@ -1801,13 +1830,13 @@ Chapter 17. Logging
17.2. Logging Message Format
-17.1. Logging configuration
+17.1. Logging configuration
The logging system in BIND 10 is configured through the Logging module.
All BIND 10 modules will look at the configuration in Logging to see what
should be logged and to where.
- 17.1.1. Loggers
+ 17.1.1. Loggers
Within BIND 10, a message is logged through a component called a "logger".
Different parts of BIND 10 log messages through different loggers, and
@@ -1820,78 +1849,78 @@ Chapter 17. Logging
(the component that is generating the messages), the severity (what to
log), and the output_options (where to log).
- 17.1.1.1. name (string)
+ 17.1.1.1. name (string)
Each logger in the system has a name, the name being that of the component
using it to log messages. For instance, if you want to configure logging
- for the resolver module, you add an entry for a logger named "Resolver".
+ for the resolver module, you add an entry for a logger named “Resolverâ€.
This configuration will then be used by the loggers in the Resolver
module, and all the libraries used by it.
If you want to specify logging for one specific library within the module,
you set the name to module.library. For example, the logger used by the
- nameserver address store component has the full name of "Resolver.nsas".
+ nameserver address store component has the full name of “Resolver.nsasâ€.
If there is no entry in Logging for a particular library, it will use the
configuration given for the module.
To illustrate this, suppose you want the cache library to log messages of
severity DEBUG, and the rest of the resolver code to log messages of
severity INFO. To achieve this you specify two loggers, one with the name
- "Resolver" and severity INFO, and one with the name "Resolver.cache" with
+ “Resolver†and severity INFO, and one with the name “Resolver.cache†with
severity DEBUG. As there are no entries for other libraries (e.g. the
- nsas), they will use the configuration for the module ("Resolver"), so
+ nsas), they will use the configuration for the module (“Resolverâ€), so
giving the desired behavior.
- One special case is that of a module name of "*" (asterisks), which is
+ One special case is that of a module name of “*†(asterisks), which is
interpreted as any module. You can set global logging options by using
this, including setting the logging configuration for a library that is
- used by multiple modules (e.g. "*.config" specifies the configuration
+ used by multiple modules (e.g. “*.config†specifies the configuration
library code in whatever module is using it).
If there are multiple logger specifications in the configuration that
might match a particular logger, the specification with the more specific
logger name takes precedence. For example, if there are entries for for
- both "*" and "Resolver", the resolver module -- and all libraries it uses
- -- will log messages according to the configuration in the second entry
- ("Resolver"). All other modules will use the configuration of the first
- entry ("*"). If there was also a configuration entry for "Resolver.cache",
+ both “*†and “Resolverâ€, the resolver module — and all libraries it uses —
+ will log messages according to the configuration in the second entry
+ (“Resolverâ€). All other modules will use the configuration of the first
+ entry (“*â€). If there was also a configuration entry for “Resolver.cacheâ€,
the cache library within the resolver would use that in preference to the
- entry for "Resolver".
+ entry for “Resolverâ€.
One final note about the naming. When specifying the module name within a
logger, use the name of the module as specified in bindctl, e.g.
- "Resolver" for the resolver module, "Xfrout" for the xfrout module, etc.
+ “Resolver†for the resolver module, “Xfrout†for the xfrout module, etc.
When the message is logged, the message will include the name of the
logger generating the message, but with the module name replaced by the
name of the process implementing the module (so for example, a message
- generated by the "Auth.cache" logger will appear in the output with a
- logger name of "b10-auth.cache").
+ generated by the “Auth.cache†logger will appear in the output with a
+ logger name of “b10-auth.cacheâ€).
- 17.1.1.2. severity (string)
+ 17.1.1.2. severity (string)
This specifies the category of messages logged. Each message is logged
with an associated severity which may be one of the following (in
descending order of severity):
- o FATAL
- o ERROR
- o WARN
- o INFO
- o DEBUG
+ o FATAL
+ o ERROR
+ o WARN
+ o INFO
+ o DEBUG
When the severity of a logger is set to one of these values, it will only
log messages of that severity, and the severities above it. The severity
may also be set to NONE, in which case all messages from that logger are
inhibited.
- 17.1.1.3. output_options (list)
+ 17.1.1.3. output_options (list)
Each logger can have zero or more output_options. These specify where log
messages are sent to. These are explained in detail below.
The other options for a logger are:
- 17.1.1.4. debuglevel (integer)
+ 17.1.1.4. debuglevel (integer)
When a logger's severity is set to DEBUG, this value specifies what debug
messages should be printed. It ranges from 0 (least verbose) to 99 (most
@@ -1899,80 +1928,80 @@ Chapter 17. Logging
If severity for the logger is not DEBUG, this value is ignored.
- 17.1.1.5. additive (true or false)
+ 17.1.1.5. additive (true or false)
If this is true, the output_options from the parent will be used. For
- example, if there are two loggers configured; "Resolver" and
- "Resolver.cache", and additive is true in the second, it will write the
- log messages not only to the destinations specified for "Resolver.cache",
+ example, if there are two loggers configured; “Resolver†and
+ “Resolver.cacheâ€, and additive is true in the second, it will write the
+ log messages not only to the destinations specified for “Resolver.cacheâ€,
but also to the destinations as specified in the output_options in the
- logger named "Resolver".
+ logger named “Resolverâ€.
- 17.1.2. Output Options
+ 17.1.2. Output Options
The main settings for an output option are the destination and a value
called output, the meaning of which depends on the destination that is
set.
- 17.1.2.1. destination (string)
+ 17.1.2.1. destination (string)
The destination is the type of output. It can be one of:
- o console
- o file
- o syslog
+ o console
+ o file
+ o syslog
- 17.1.2.2. output (string)
+ 17.1.2.2. output (string)
Depending on what is set as the output destination, this value is
interpreted as follows:
- destination is "console"
+ destination is “consoleâ€
- The value of output must be one of "stdout" (messages printed to
- standard output) or "stderr" (messages printed to standard error).
+ The value of output must be one of “stdout†(messages printed to
+ standard output) or “stderr†(messages printed to standard error).
- Note: if output is set to "stderr" and a lot of messages are
+ Note: if output is set to “stderr†and a lot of messages are
produced in a short time (e.g. if the logging level is set to
DEBUG), you may occasionally see some messages jumbled up
together. This is due to a combination of the way that messages
are written to the screen and the unbuffered nature of the
standard error stream. If this occurs, it is recommended that
- output be set to "stdout".
+ output be set to “stdoutâ€.
- destination is "file"
+ destination is “fileâ€
The value of output is interpreted as a file name; log messages
will be appended to this file.
- destination is "syslog"
+ destination is “syslogâ€
The value of output is interpreted as the syslog facility (e.g.
local0) that should be used for log messages.
The other options for output_options are:
- 17.1.2.2.1. flush (true of false)
+ 17.1.2.2.1. flush (true of false)
Flush buffers after each log message. Doing this will reduce performance
but will ensure that if the program terminates abnormally, all messages up
to the point of termination are output.
- 17.1.2.2.2. maxsize (integer)
+ 17.1.2.2.2. maxsize (integer)
Only relevant when destination is file, this is maximum file size of
output files in bytes. When the maximum size is reached, the file is
renamed and a new file opened. (For example, a ".1" is appended to the
- name -- if a ".1" file exists, it is renamed ".2", etc.)
+ name — if a ".1" file exists, it is renamed ".2", etc.)
If this is 0, no maximum file size is used.
- 17.1.2.2.3. maxver (integer)
+ 17.1.2.2.3. maxver (integer)
Maximum number of old log files to keep around when rolling the output
- file. Only relevant when destination is "file".
+ file. Only relevant when destination is “fileâ€.
- 17.1.3. Example session
+ 17.1.3. Example session
In this example we want to set the global logging to write to the file
/var/log/my_bind10.log, at severity WARN. We want the authoritative server
@@ -2071,9 +2100,9 @@ Chapter 17. Logging
> config remove Logging/loggers[1]
> config commit
- And every module will now be using the values from the logger named "*".
+ And every module will now be using the values from the logger named “*â€.
-17.2. Logging Message Format
+17.2. Logging Message Format
Each message written by BIND 10 to the configured logging destinations
comprises a number of components that identify the origin of the message
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index c5df326db8..46da1bc8ce 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1302,6 +1302,232 @@ TODO
+
+ Common configuration elements
+
+
+ Some things are configured in the same or similar manner across
+ many modules. So we show them here in one place.
+
+
+
+ ACLs
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ 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.
+
+
+
+ This is encoded in form of JSON. Semi-formal description could look
+ something like this. It is described in more details below.
+
+ 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 := "<ip range>"
+KEY_MATCH := "key": [KEY, KEY, KEY, ...] | KEY
+KEY := "<key name>"
+NOT_MATCH := "NOT": RULE_RAW
+OR_MATCH := "ANY": [ RULE_RAW, RULE_RAW, ... ]
+AND_MATCH := "ALL": [ RULE_RAW, RULE_RAW, ... ]
+
+
+
+
+ Matching properties
+
+
+ The first thing you can check against is the source address
+ of request. The name is from 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").
+
+
+
+ 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.
+
+
+
+ More properties to match are planned — the destination
+ address, ports, matches against the packet content.
+
+
+
+
+ More complicated matches
+
+
+ From time to time, you need to express something more complex
+ than just a single address or key.
+
+
+
+ You can specify a list of values instead of single value. Then
+ the property needs to match at least one of the values listed
+ — so you can say "from": ["192.0.2.0/24",
+ "2001:db8::/32"]
to match any address in the ranges
+ set aside for documentation. The keys or any future properties
+ will work in a similar way.
+
+
+
+
+ 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
+ Trac #2191 .
+ Until then, the value must be a single string.
+
+
+
+
+ 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 —
+ each subexpression is a similar dictionary, just not containing
+ the "action" element. The "NOT" contains single subexpression.
+ Their function should be obvious — "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.
+
+
+
+
+ Examples
+
+
+ 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
+ config show_json called on the entry containing
+ the ACL.
+
+
+
+ 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 — making it match any query.
+
+ [
+ {
+ "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"
+ }
+]
+
+
+
+ Now we show two ways to accept only the queries from private ranges.
+ This is the same as rejecting anything that is outside.
+
+ [
+ {
+ "from": [
+ "10.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16",
+ "fc00::/7"
+ ],
+ "action": "ACCEPT"
+ },
+ {
+ "action": "REJECT"
+ }
+]
+
+ [
+ {
+ "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"
+ }
+]
+
+
+
+
+ Interaction with bindctl
+
+
+ Currently, bindctl 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.
+
+
+
+ This limitation is planned to be solved soon at least partially.
+
+
+
+ You'd do something like this to create the second example.
+ Note that the whole JSON must be on a single line.
+
+ > config add somewhere/acl
+> 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" }
+> config add somewhere/acl
+> config set somewhere/acl[1] { "action": "REJECT" }
+> config commit
+
+
+
+
+
Authoritative Server
@@ -1611,8 +1837,19 @@ can use various data source backends.
> config set data_sources/classes/IN[1]/params { "example.org": "/path/to/example.org", "example.com": "/path/to/example.com" }
> config commit
- 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.
+
+
+> config set data_sources/classes/IN[1]/params {}
+> config add data_sources/classes/IN[1]/params another.example.org /path/to/another.example.org
+> config add data_sources/classes/IN[1]/params another.example.com /path/to/another.example.com
+> config remove data_sources/classes/IN[1]/params another.example.org
+
+
bindctl . To reload a zone, you the same command
as above.
@@ -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, b10-xfrout allows any clients to
- perform zone transfers for any zones:
+ perform zone transfers for any zones.
> config show Xfrout/transfer_acl
Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
-
- 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:
-
-
- > config set Xfrout/transfer_acl[0] {"action": "REJECT"}
-> config add Xfrout/zone_config
-> config set Xfrout/zone_config[0]/origin "example.com"
-> config set Xfrout/zone_config[0]/transfer_acl [{"action": "ACCEPT", "from": "192.0.2.1"},
- {"action": "ACCEPT", "from": "2001:db8::1"}]
-> config commit
-
-
- In the above example the lines
- for transfer_acl were divided for
- readability. In the actual input it must be in a single line.
-
-
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.
> config set tsig_keys/keys ["key.example:<base64-key>"]
@@ -1944,6 +2161,11 @@ Xfrout/transfer_acl[0] {"action": "ACCEPT"} any (default)
will use the system wide keyring to check
TSIGs in the incoming messages and to sign responses.
+
+ For further details on ACL configuration, see
+ .
+
+
The way to specify zone specific configuration (ACLs, etc) is
likely to be changed.
@@ -2150,29 +2372,7 @@ what is XfroutClient xfr_client??
- 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.
-
-> config add DDNS/zones[0]/update_acl {"action": "ACCEPT", "from": "::1", "key": "key.example"}
-> config show DDNS/zones[0]/update_acl
-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)
-> config commit
-
- (Note the "add" in the first line. Before this sequence, we
- have had only entry in zones[0]/update_acl .
- The add 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 .
@@ -2187,21 +2387,6 @@ DDNS/zones[0]/update_acl[1] {"action": "ACCEPT", "from": "::1", "key": "key.
should have a TSIG key in its constraints.
-
- 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
- b10-ddns .
-
-
-
-
- Other actions than "ACCEPT", namely "REJECT" and "DROP", can be
- used, too.
- See about their effects.
-
-
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 Resolver/query_acl configuration may
be used to reject, drop, or allow specific IPs or networks.
- This configuration list is first match.
+ See .
- The configuration's action item may be
- set to ACCEPT
to allow the incoming query,
- REJECT
to respond with a DNS REFUSED return
- code, or DROP
to ignore the query without
- any response (such as a blackhole). For more information,
- see the respective debugging messages: RESOLVER_QUERY_ACCEPTED ,
- RESOLVER_QUERY_REJECTED ,
- and RESOLVER_QUERY_DROPPED .
+ The following session is an example of extending the ACL to also
+ allow queries from 192.0.2.0/24:
+
+> config show Resolver/query_acl
+Resolver/query_acl[0] {"action": "ACCEPT", "from": "127.0.0.1"} any (default)
+Resolver/query_acl[1] {"action": "ACCEPT", "from": "::1"} any (default)
+> config add Resolver/query_acl
+> config set Resolver/query_acl[2] {"action": "ACCEPT", "from": "192.0.2.0/24"}
+> config add Resolver/query_acl
+> config show Resolver/query_acl
+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)
+> config commit
+ 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.
-
- The required configuration's from item is set
- to an IPv4 or IPv6 address, addresses with an network mask, or to
- the special lowercase keywords any6
(for
- any IPv6 address) or any4
(for any IPv4
- address).
-
-
-
-
-
- For example to allow the 192.168.1.0/24
- network to use your recursive name server, at the
- bindctl prompt run:
-
-
-
-> config add Resolver/query_acl
-> config set Resolver/query_acl[2 ]/action "ACCEPT"
-> config set Resolver/query_acl[2 ]/from "192.168.1.0/24 "
-> config commit
-
-
- (Replace the 2
- as needed; run config show
- Resolver/query_acl
if needed.)
-
-
- This prototype access control configuration
- syntax may be changed.
-
@@ -2499,7 +2657,7 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
> config commit
- 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.
-
-
-
- Integration with bind10 is
- planned. Ultimately, b10-dhcp4 will not
- be started directly, but rather via
- bind10 . Please be aware of this planned
- change.
-
-
+ access. Make sure you run this daemon as root.
+
@@ -2684,22 +2833,25 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
- The DHCPv6 server is implemented as b10-dhcp6
- daemon. As it is not configurable yet, it is fully autonomous,
- that is it does not interact with b10-cfgmgr .
- To start DHCPv6 server, simply input:
+ b10-dhcp6 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 bindctl :
+ > config add Boss/components b10-dhcp6
+> config set Boss/components/b10-dhcp6/kind dispensable
+> config commit
+
-
-#cd src/bin/dhcp6
-#./b10-dhcp6
-
+
+ To shutdown running b10-dhcp6 , please use the
+ following command:
+ > Dhcp6 shutdown
+ or
+ > config remove Boss/components b10-dhcp6
+> config commit
+
- Depending on your installation, b10-dhcp6
- 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
+
+ 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";
access. Make sure you run this daemon as root.
-
-
- Integration with bind10 is
- planned. Ultimately, b10-dhcp6 will not
- be started directly, but rather via
- bind10 . Please be aware of this planned
- change.
-
-
-
@@ -2735,7 +2877,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
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:
const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
diff --git a/doc/guide/bind10-messages.html b/doc/guide/bind10-messages.html
index 37c742b7e5..688027245b 100644
--- a/doc/guide/bind10-messages.html
+++ b/doc/guide/bind10-messages.html
@@ -1,4 +1,4 @@
-BIND 10 Messages Manual This is the messages manual for BIND 10 version
+
BIND 10 Messages Manual This is the messages manual for BIND 10 version
20120712.
Copyright © 2011-2012 Internet Systems Consortium, Inc.
Abstract
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
diff --git a/src/bin/cfgmgr/Makefile.am b/src/bin/cfgmgr/Makefile.am
index aee78cf995..4a6fc0d30a 100644
--- a/src/bin/cfgmgr/Makefile.am
+++ b/src/bin/cfgmgr/Makefile.am
@@ -26,8 +26,9 @@ b10-cfgmgr: b10-cfgmgr.py
install-data-local:
$(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
-# TODO: permissions handled later
+install-data-hook:
+ -chmod 2770 $(DESTDIR)/@localstatedir@/@PACKAGE@
CLEANDIRS = __pycache__
diff --git a/src/bin/ddns/ddns.spec b/src/bin/ddns/ddns.spec
index 70611e690f..3061cdc18d 100644
--- a/src/bin/ddns/ddns.spec
+++ b/src/bin/ddns/ddns.spec
@@ -12,8 +12,8 @@
"item_type": "map",
"item_optional": true,
"item_default": {
- "origin": "",
- "class": "IN",
+ "origin": "",
+ "class": "IN",
"update_acl": []
},
"map_item_spec": [
@@ -33,11 +33,12 @@
"item_name": "update_acl",
"item_type": "list",
"item_optional": false,
- "item_default": [],
+ "item_default": [],
"list_item_spec": {
"item_name": "acl_element",
"item_type": "any",
- "item_optional": true
+ "item_optional": true,
+ "item_default": {"action": "REJECT"}
}
}
]
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index c6ba9ff80a..724ddf2de5 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -86,7 +86,7 @@ void ControlledDhcpv4Srv::establishSession() {
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
- "/src/bin/auth/dhcp4.spec";
+ "/src/bin/dhcp4/dhcp4.spec";
} else {
specfile = string(DHCP4_SPECFILE_LOCATION);
}
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 4fb0a771a4..a86b010792 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -37,16 +37,21 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";
Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
cout << "Initialization: opening sockets on port " << port << endl;
- // first call to instance() will create IfaceMgr (it's a singleton)
- // it may throw something if things go wrong
- IfaceMgr::instance();
+ try {
+ // first call to instance() will create IfaceMgr (it's a singleton)
+ // it may throw something if things go wrong
+ IfaceMgr::instance();
- /// @todo: instantiate LeaseMgr here once it is imlpemented.
- IfaceMgr::instance().printIfaces();
+ /// @todo: instantiate LeaseMgr here once it is imlpemented.
- IfaceMgr::instance().openSockets4(port);
+ IfaceMgr::instance().openSockets4(port);
- setServerID();
+ setServerID();
+ } catch (const std::exception &e) {
+ cerr << "Error during DHCPv4 server startup: " << e.what() << endl;
+ shutdown_ = true;
+ return;
+ }
shutdown_ = false;
}
diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc
index 9cc9c5bc1b..1087cc7fb6 100644
--- a/src/bin/dhcp4/main.cc
+++ b/src/bin/dhcp4/main.cc
@@ -14,18 +14,14 @@
#include
#include
-#include
#include
#include
#include
-#include
-#include
+#include
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(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;
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index 080425665e..5af0cc9857 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -64,7 +64,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
ConstElementPtr comment = parseAnswer(rcode, result);
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
- // case 1: send shutdown command without any parameters
+ // case 2: send shutdown command without any parameters
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
@@ -73,7 +73,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
ConstElementPtr x(new isc::data::IntElement(pid));
params->set("pid", x);
- // case 2: send shutdown command with 1 parameter: pid
+ // case 3: send shutdown command with 1 parameter: pid
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
diff --git a/src/bin/dhcp4/tests/dhcp4_test.py b/src/bin/dhcp4/tests/dhcp4_test.py
index 18d23fff8d..935bba645a 100644
--- a/src/bin/dhcp4/tests/dhcp4_test.py
+++ b/src/bin/dhcp4/tests/dhcp4_test.py
@@ -34,7 +34,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
def tearDown(self):
pass
- def runDhcp4(self, params, wait=1):
+ def runCommand(self, params, wait=1):
"""
This method runs dhcp4 and returns a touple: (returncode, stdout, stderr)
"""
@@ -127,14 +127,14 @@ class TestDhcpv4Daemon(unittest.TestCase):
print("Note: Purpose of some of the tests is to check if DHCPv4 server can be started,")
print(" not that is can bind sockets correctly. Please ignore binding errors.")
- (returncode, output, error) = self.runDhcp4(["../b10-dhcp4", "-v"])
+ (returncode, output, error) = self.runCommand(["../b10-dhcp4", "-v"])
self.assertEqual( str(output).count("[b10-dhcp4] Initiating DHCPv4 server operation."), 1)
def test_portnumber_0(self):
print("Check that specifying port number 0 is not allowed.")
- (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '0'])
+ (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p', '0'])
# When invalid port number is specified, return code must not be success
self.assertTrue(returncode != 0)
@@ -145,7 +145,7 @@ class TestDhcpv4Daemon(unittest.TestCase):
def test_portnumber_missing(self):
print("Check that -p option requires a parameter.")
- (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p'])
+ (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p'])
# When invalid port number is specified, return code must not be success
self.assertTrue(returncode != 0)
@@ -153,10 +153,32 @@ class TestDhcpv4Daemon(unittest.TestCase):
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(error).count("option requires an argument"), 1)
+ def test_portnumber_invalid1(self):
+ print("Check that -p option is check against bogus port number (999999).")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p','999999'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+ def test_portnumber_invalid2(self):
+ print("Check that -p option is check against bogus port number (123garbage).")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-p','123garbage'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
def test_portnumber_nonroot(self):
print("Check that specifying unprivileged port number will work.")
- (returncode, output, error) = self.runDhcp4(['../b10-dhcp4', '-p', '10057'])
+ (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
# When invalid port number is specified, return code must not be success
# TODO: Temporarily commented out as socket binding on systems that do not have
@@ -166,5 +188,18 @@ class TestDhcpv4Daemon(unittest.TestCase):
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(output).count("opening sockets on port 10057"), 1)
+ def test_skip_msgq(self):
+ print("Check that connection to BIND10 msgq can be disabled.")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp4', '-s', '-p', '10057'])
+
+ # When invalid port number is specified, return code must not be success
+ # TODO: Temporarily commented out as socket binding on systems that do not have
+ # interface detection implemented currently fails.
+ # self.assertTrue(returncode == 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(output).count("Skipping connection to the BIND10 msgq."), 1)
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index e68ce2e412..7fcd3fe727 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -33,6 +33,7 @@ BUILT_SOURCES = spec_config.h
pkglibexec_PROGRAMS = b10-dhcp6
b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
+b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
@@ -44,6 +45,8 @@ b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
b10_dhcp6dir = $(pkgdatadir)
b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
new file mode 100644
index 0000000000..461d5f1d64
--- /dev/null
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -0,0 +1,159 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+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);
+ }
+}
+
+
+};
+};
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
new file mode 100644
index 0000000000..91fc80acb2
--- /dev/null
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -0,0 +1,123 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef CTRL_DHCPV6_SRV_H
+#define CTRL_DHCPV6_SRV_H
+
+#include
+#include
+#include
+#include
+#include
+
+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
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index 05c3529865..2a82a2d3a0 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -1,6 +1,6 @@
{
"module_spec": {
- "module_name": "dhcp6",
+ "module_name": "Dhcp6",
"module_description": "DHCPv6 server daemon",
"config_data": [
{ "item_name": "interface",
@@ -9,6 +9,18 @@
"item_default": "eth0"
}
],
- "commands": []
+ "commands": [
+ {
+ "command_name": "shutdown",
+ "command_description": "Shuts down DHCPv6 server.",
+ "command_args": [
+ {
+ "item_name": "pid",
+ "item_type": "integer",
+ "item_optional": true
+ }
+ ]
+ }
+ ]
}
}
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 293e6004ae..9b43f5374f 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -45,25 +45,26 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
// first call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
try {
- IfaceMgr::instance();
+
+ 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) {
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index b6ae6376f2..9d1bf19497 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -67,6 +67,8 @@ public:
/// critical error.
bool run();
+ /// @brief Instructs the server to shut down.
+ void shutdown();
protected:
/// @brief Processes incoming SOLICIT and returns response.
///
@@ -184,7 +186,7 @@ protected:
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
- volatile bool shutdown;
+ volatile bool shutdown_;
};
}; // namespace isc::dhcp
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index 62c0d20807..aebee90699 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.cc
@@ -13,47 +13,36 @@
// PERFORMANCE OF THIS SOFTWARE.
#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
#include
-
-#include
-#if 0
-// TODO cc is not used yet. It should be eventually
-#include
-#include
-#endif
-
-#include
#include
-
-#include
-#include "dhcp6/dhcp6_srv.h"
+#include
+#include
+#include
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(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;
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index 9b67804951..66dc252830 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -42,9 +42,10 @@ if HAVE_GTEST
TESTS += dhcp6_unittests
-dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc
+dhcp6_unittests_SOURCES = ../dhcp6_srv.h ../dhcp6_srv.cc ../ctrl_dhcp6_srv.cc
dhcp6_unittests_SOURCES += dhcp6_unittests.cc
dhcp6_unittests_SOURCES += dhcp6_srv_unittest.cc
+dhcp6_unittests_SOURCES += ctrl_dhcp6_srv_unittest.cc
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
@@ -59,6 +60,8 @@ dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
endif
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
new file mode 100644
index 0000000000..1e88f83bba
--- /dev/null
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -0,0 +1,85 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+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
diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py
index cf04f60be7..399c370742 100644
--- a/src/bin/dhcp6/tests/dhcp6_test.py
+++ b/src/bin/dhcp6/tests/dhcp6_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2011,2012 Internet Systems Consortium.
+# copyright (C) 2011,2012 Internet Systems Consortium.
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -131,7 +131,7 @@ class TestDhcpv6Daemon(unittest.TestCase):
print(" not that is can bind sockets correctly. Please ignore binding errors.")
(returncode, output, error) = self.runCommand(["../b10-dhcp6", "-v"])
- self.assertEqual( str(output).count("[b10-dhcp6] Initiating DHCPv6 operation."), 1)
+ self.assertEqual( str(output).count("b10-dhcp6: Initiating DHCPv6 server operation."), 1)
def test_portnumber_0(self):
print("Check that specifying port number 0 is not allowed.")
@@ -155,18 +155,52 @@ class TestDhcpv6Daemon(unittest.TestCase):
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(error).count("option requires an argument"), 1)
+ def test_portnumber_invalid1(self):
+ print("Check that -p option is check against bogus port number (999999).")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p','999999'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
+ def test_portnumber_invalid2(self):
+ print("Check that -p option is check against bogus port number (123garbage).")
+
+ (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p','123garbage'])
+
+ # When invalid port number is specified, return code must not be success
+ self.assertTrue(returncode != 0)
+
+ # Check that there is an error message about invalid port number printed on stderr
+ self.assertEqual( str(error).count("Failed to parse port number"), 1)
+
def test_portnumber_nonroot(self):
print("Check that specifying unprivileged port number will work.")
- (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-p', '10057'])
+ (returncode, output, error) = self.runCommand(['../b10-dhcp6', '-s', '-p', '10547'])
# When invalid port number is specified, return code must not be success
# TODO: Temporarily commented out as socket binding on systems that do not have
# interface detection implemented currently fails.
# self.assertTrue(returncode == 0)
- # 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()
diff --git a/src/bin/loadzone/Makefile.am b/src/bin/loadzone/Makefile.am
index a235d68bd0..74386ae891 100644
--- a/src/bin/loadzone/Makefile.am
+++ b/src/bin/loadzone/Makefile.am
@@ -20,10 +20,6 @@ b10-loadzone: b10-loadzone.py
-e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" b10-loadzone.py >$@
chmod a+x $@
-install-data-local:
- $(mkinstalldirs) $(DESTDIR)/@localstatedir@/@PACKAGE@
-# TODO: permissions handled later
-
EXTRA_DIST += tests/normal/README
EXTRA_DIST += tests/normal/dsset-subzone.example.com
EXTRA_DIST += tests/normal/example.com
diff --git a/src/bin/resolver/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
index d6bb22675b..138f4e38bb 100644
--- a/src/bin/resolver/resolver.spec.pre.in
+++ b/src/bin/resolver/resolver.spec.pre.in
@@ -116,37 +116,23 @@
},
{
"item_name": "query_acl",
- "item_type": "list",
- "item_optional": false,
- "item_default": [
- {
- "action": "ACCEPT",
- "from": "127.0.0.1"
- },
- {
- "action": "ACCEPT",
- "from": "::1"
- }
- ],
- "list_item_spec": {
- "item_name": "rule",
- "item_type": "map",
- "item_optional": false,
- "item_default": {},
- "map_item_spec": [
- {
- "item_name": "action",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- },
- {
- "item_name": "from",
- "item_type": "string",
- "item_optional": false,
- "item_default": ""
- }
- ]
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [
+ {
+ "action": "ACCEPT",
+ "from": "127.0.0.1"
+ },
+ {
+ "action": "ACCEPT",
+ "from": "::1"
+ }
+ ],
+ "list_item_spec": {
+ "item_name": "rule",
+ "item_type": "any",
+ "item_optional": false,
+ "item_default": {"action": "REJECT"}
}
}
],
diff --git a/src/bin/sysinfo/sysinfo.py.in b/src/bin/sysinfo/sysinfo.py.in
index 1e77acb27e..24cf309a31 100755
--- a/src/bin/sysinfo/sysinfo.py.in
+++ b/src/bin/sysinfo/sysinfo.py.in
@@ -32,6 +32,15 @@ def usage():
file=sys.stderr)
exit(1)
+def write_value(out, fmt, call):
+ '''Helper function for standard value writing.
+ Writes the result from the call in the given format to out.
+ Does not write anything if the result of the call is None.
+ '''
+ value = call()
+ if value is not None:
+ out.write(fmt % value)
+
def main():
try:
opts, args = getopt.getopt(sys.argv[1:], "o:h", \
@@ -57,39 +66,39 @@ def main():
s = SysInfoFromFactory()
- f.write('BIND 10 ShowTech tool\n')
- f.write('=====================\n')
+ f.write('ISC Sysinfo tool\n')
+ f.write('================\n')
f.write('\nCPU\n');
- f.write(' + Number of processors: %d\n' % (s.get_num_processors()))
- f.write(' + Endianness: %s\n' % (s.get_endianness()))
+ write_value(f, ' + Number of processors: %d\n', s.get_num_processors)
+ write_value(f, ' + Endianness: %s\n', s.get_endianness)
f.write('\nPlatform\n');
- f.write(' + Operating system: %s\n' % (s.get_platform_name()))
- f.write(' + Distribution: %s\n' % (s.get_platform_distro()))
- f.write(' + Kernel version: %s\n' % (s.get_platform_version()))
+ 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:
diff --git a/src/bin/xfrout/xfrout.spec.pre.in b/src/bin/xfrout/xfrout.spec.pre.in
index dfcc6d98f4..6b113b0bf2 100644
--- a/src/bin/xfrout/xfrout.spec.pre.in
+++ b/src/bin/xfrout/xfrout.spec.pre.in
@@ -17,7 +17,8 @@
{
"item_name": "acl_element",
"item_type": "any",
- "item_optional": true
+ "item_optional": true,
+ "item_default": {"action": "ACCEPT"}
}
},
{
@@ -80,7 +81,8 @@
{
"item_name": "acl_element",
"item_type": "any",
- "item_optional": true
+ "item_optional": true,
+ "item_default": {"action": "ACCEPT"}
}
}
]
diff --git a/src/lib/config/tests/testdata/spec40.spec b/src/lib/config/tests/testdata/spec40.spec
index f778fc09a2..6fbec10f3f 100644
--- a/src/lib/config/tests/testdata/spec40.spec
+++ b/src/lib/config/tests/testdata/spec40.spec
@@ -6,6 +6,15 @@
"item_type": "any",
"item_optional": false,
"item_default": "asdf"
+ },
+ { "item_name": "item2",
+ "item_type": "any",
+ "item_optional": true
+ },
+ { "item_name": "item3",
+ "item_type": "any",
+ "item_optional": true,
+ "item_default": null
}
]
}
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index ae975d0820..4a24260595 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -805,13 +805,12 @@ IfaceMgr::receive4(uint32_t timeout) {
const SocketInfo* candidate = 0;
IfaceCollection::const_iterator iface;
-
fd_set sockets;
- FD_ZERO(&sockets);
int maxfd = 0;
-
stringstream names;
+ FD_ZERO(&sockets);
+
/// @todo: marginal performance optimization. We could create the set once
/// and then use its copy for select(). Please note that select() modifies
/// provided set to indicated which sockets have something to read.
@@ -970,9 +969,108 @@ IfaceMgr::receive4(uint32_t timeout) {
return (pkt);
}
-Pkt6Ptr IfaceMgr::receive6() {
- uint8_t buf[RCVBUFSIZE];
+Pkt6Ptr IfaceMgr::receive6(uint32_t timeout) {
+ const SocketInfo* candidate = 0;
+ fd_set sockets;
+ int maxfd = 0;
+ stringstream names;
+
+ FD_ZERO(&sockets);
+
+ /// @todo: marginal performance optimization. We could create the set once
+ /// and then use its copy for select(). Please note that select() modifies
+ /// provided set to indicated which sockets have something to read.
+ IfaceCollection::const_iterator iface;
+ for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
+
+ for (SocketCollection::const_iterator s = iface->sockets_.begin();
+ s != iface->sockets_.end(); ++s) {
+
+ // Only deal with IPv4 addresses.
+ if (s->addr_.getFamily() == AF_INET6) {
+ names << s->sockfd_ << "(" << iface->getName() << ") ";
+
+ // Add this socket to listening set
+ FD_SET(s->sockfd_, &sockets);
+ if (maxfd < s->sockfd_) {
+ maxfd = s->sockfd_;
+ }
+ }
+ }
+ }
+
+ // if there is session socket registered...
+ if (session_socket_ != INVALID_SOCKET) {
+ // at it to the set as well
+ FD_SET(session_socket_, &sockets);
+ if (maxfd < session_socket_)
+ maxfd = session_socket_;
+ names << session_socket_ << "(session)";
+ }
+
+ cout << "Trying to receive data on sockets:" << names.str()
+ << ".Timeout is " << timeout << " seconds." << endl;
+
+ /// @todo: implement sub-second precision one day
+ struct timeval select_timeout;
+ select_timeout.tv_sec = timeout;
+ select_timeout.tv_usec = 0;
+
+ int result = select(maxfd + 1, &sockets, NULL, NULL, &select_timeout);
+
+ if (result == 0) {
+ // nothing received and timeout has been reached
+ return (Pkt6Ptr()); // NULL
+ } else if (result < 0) {
+ cout << "Socket read error: " << strerror(errno) << endl;
+
+ /// @todo: perhaps throw here?
+ return (Pkt6Ptr()); // NULL
+ }
+
+ // Let's find out which socket has the data
+ if ((session_socket_ != INVALID_SOCKET) && (FD_ISSET(session_socket_, &sockets))) {
+ // something received over session socket
+ cout << "BIND10 command or config available over session socket." << endl;
+
+ if (session_callback_) {
+ // in theory we could call io_service.run_one() here, instead of
+ // implementing callback mechanism, but that would introduce
+ // asiolink dependency to libdhcp++ and that is something we want
+ // to avoid (see CPE market and out long term plans for minimalistic
+ // implementations.
+ session_callback_();
+ }
+
+ return (Pkt6Ptr()); // NULL
+ }
+
+ // Let's find out which interface/socket has the data
+ for (iface = ifaces_.begin(); iface != ifaces_.end(); ++iface) {
+ for (SocketCollection::const_iterator s = iface->sockets_.begin();
+ s != iface->sockets_.end(); ++s) {
+ if (FD_ISSET(s->sockfd_, &sockets)) {
+ candidate = &(*s);
+ break;
+ }
+ }
+ if (candidate) {
+ break;
+ }
+ }
+
+ if (!candidate) {
+ cout << "Received data over unknown socket." << endl;
+ return (Pkt6Ptr()); // NULL
+ }
+
+ cout << "Trying to receive over UDP6 socket " << candidate->sockfd_ << " bound to "
+ << candidate->addr_.toText() << "/port=" << candidate->port_ << " on "
+ << iface->getFullName() << endl;
+
+ // Now we have a socket, let's get some data from it!
+ uint8_t buf[RCVBUFSIZE];
memset(&control_buf_[0], 0, control_buf_len_);
struct sockaddr_in6 from;
memset(&from, 0, sizeof(from));
@@ -1004,43 +1102,7 @@ Pkt6Ptr IfaceMgr::receive6() {
m.msg_control = &control_buf_[0];
m.msg_controllen = control_buf_len_;
- /// TODO: Need to move to select() and pool over
- /// all available sockets. For now, we just take the
- /// first interface and use first socket from it.
- IfaceCollection::const_iterator iface = ifaces_.begin();
- const SocketInfo* candidate = 0;
- while (iface != ifaces_.end()) {
- for (SocketCollection::const_iterator s = iface->sockets_.begin();
- s != iface->sockets_.end(); ++s) {
- if (s->addr_.getFamily() != AF_INET6) {
- continue;
- }
- if (s->addr_.getAddress().to_v6().is_multicast()) {
- candidate = &(*s);
- break;
- }
- if (!candidate) {
- candidate = &(*s); // it's not multicast, but it's better than nothing
- }
- }
- if (candidate) {
- break;
- }
- ++iface;
- }
- if (iface == ifaces_.end()) {
- isc_throw(Unexpected, "No suitable IPv6 interfaces detected. Can't receive anything.");
- }
-
- if (!candidate) {
- isc_throw(Unexpected, "Interface " << iface->getFullName()
- << " does not have any sockets open.");
- }
-
- cout << "Trying to receive over UDP6 socket " << candidate->sockfd_ << " bound to "
- << candidate->addr_.toText() << "/port=" << candidate->port_ << " on "
- << iface->getFullName() << endl;
- int result = recvmsg(candidate->sockfd_, &m, 0);
+ result = recvmsg(candidate->sockfd_, &m, 0);
struct in6_addr to_addr;
memset(&to_addr, 0, sizeof(to_addr));
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h
index a40f95c69d..7de6a4440a 100644
--- a/src/lib/dhcp/iface_mgr.h
+++ b/src/lib/dhcp/iface_mgr.h
@@ -345,8 +345,10 @@ public:
/// to not wait infinitely, but rather do something useful
/// (e.g. remove expired leases)
///
+ /// @param timeout specifies timeout (in seconds)
+ ///
/// @return Pkt6 object representing received packet (or NULL)
- Pkt6Ptr receive6();
+ Pkt6Ptr receive6(uint32_t timeout);
/// @brief Tries to receive IPv4 packet over open IPv4 sockets.
///
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 2612f27046..b3a35679ef 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -139,7 +139,7 @@ public:
/// Returns value of transaction-id field
///
/// @return transaction-id
- uint32_t getTransid() { return (transid_); };
+ uint32_t getTransid() const { return (transid_); };
/// Adds an option to this packet.
///
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index df818d8cbe..5562551a0e 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -387,7 +387,7 @@ TEST_F(IfaceMgrTest, sendReceive6) {
EXPECT_EQ(true, ifacemgr->send(sendPkt));
- rcvPkt = ifacemgr->receive6();
+ rcvPkt = ifacemgr->receive6(10);
ASSERT_TRUE(rcvPkt); // received our own packet
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 703d1968eb..a95316d6bd 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -144,7 +144,7 @@ class ModuleCCSession(ConfigData):
module, and one to update the configuration run-time. These
callbacks are called when 'check_command' is called on the
ModuleCCSession"""
-
+
def __init__(self, spec_file_name, config_handler, command_handler,
cc_session=None, handle_logging_config=True,
socket_file = None):
@@ -178,9 +178,9 @@ class ModuleCCSession(ConfigData):
"""
module_spec = isc.config.module_spec_from_file(spec_file_name)
ConfigData.__init__(self, module_spec)
-
+
self._module_name = module_spec.get_module_name()
-
+
self.set_config_handler(config_handler)
self.set_command_handler(command_handler)
@@ -248,7 +248,7 @@ class ModuleCCSession(ConfigData):
returns nothing.
It calls check_command_without_recvmsg()
to parse the received message.
-
+
If nonblock is True, it just checks if there's a command
and does nothing if there isn't. If nonblock is False, it
waits until it arrives. It temporarily sets timeout to infinity,
@@ -265,7 +265,7 @@ class ModuleCCSession(ConfigData):
"""Parse the given message to see if there is a command or a
configuration update. Calls the corresponding handler
functions if present. Responds on the channel if the
- handler returns a message."""
+ handler returns a message."""
# should we default to an answer? success-by-default? unhandled error?
if msg is not None and not 'result' in msg:
answer = None
@@ -314,7 +314,7 @@ class ModuleCCSession(ConfigData):
answer = create_answer(1, str(exc))
if answer:
self._session.group_reply(env, answer)
-
+
def set_config_handler(self, config_handler):
"""Set the config handler for this module. The handler is a
function that takes the full configuration and handles it.
@@ -521,7 +521,7 @@ class UIModuleCCSession(MultiConfigData):
if not cur_list:
cur_list = []
- if value is None:
+ if value is None and "list_item_spec" in module_spec:
if "item_default" in module_spec["list_item_spec"]:
value = module_spec["list_item_spec"]["item_default"]
@@ -572,8 +572,14 @@ class UIModuleCCSession(MultiConfigData):
if module_spec is None:
raise isc.cc.data.DataNotFoundError("Unknown item " + str(identifier))
+ # for type any, we determine the 'type' by what value is set
+ # (which would be either list or dict)
+ cur_value, _ = self.get_value(identifier)
+ type_any = module_spec['item_type'] == 'any'
+
# the specified element must be a list or a named_set
- if 'list_item_spec' in module_spec:
+ if 'list_item_spec' in module_spec or\
+ (type_any and type(cur_value) == list):
value = None
# in lists, we might get the value with spaces, making it
# the third argument. In that case we interpret both as
@@ -583,11 +589,12 @@ class UIModuleCCSession(MultiConfigData):
value_str += set_value_str
value = isc.cc.data.parse_value_str(value_str)
self._add_value_to_list(identifier, value, module_spec)
- elif 'named_set_item_spec' in module_spec:
+ elif 'named_set_item_spec' in module_spec or\
+ (type_any and type(cur_value) == dict):
item_name = None
item_value = None
if value_str is not None:
- item_name = isc.cc.data.parse_value_str(value_str)
+ item_name = value_str
if set_value_str is not None:
item_value = isc.cc.data.parse_value_str(set_value_str)
else:
@@ -643,12 +650,23 @@ class UIModuleCCSession(MultiConfigData):
if value_str is not None:
value = isc.cc.data.parse_value_str(value_str)
- if 'list_item_spec' in module_spec:
- if value is not None:
+ # for type any, we determine the 'type' by what value is set
+ # (which would be either list or dict)
+ cur_value, _ = self.get_value(identifier)
+ type_any = module_spec['item_type'] == 'any'
+
+ # there's two forms of 'remove from list'; the remove-value-from-list
+ # form, and the 'remove-by-index' form. We can recognize the second
+ # case by value is None
+ if 'list_item_spec' in module_spec or\
+ (type_any and type(cur_value) == list) or\
+ value is None:
+ if not type_any and value is not None:
isc.config.config_data.check_type(module_spec['list_item_spec'], value)
self._remove_value_from_list(identifier, value)
- elif 'named_set_item_spec' in module_spec:
- self._remove_value_from_named_set(identifier, value)
+ elif 'named_set_item_spec' in module_spec or\
+ (type_any and type(cur_value) == dict):
+ self._remove_value_from_named_set(identifier, value_str)
else:
raise isc.cc.data.DataNotFoundError(str(identifier) + " is not a list or a named_set")
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index 174e98c911..413d052b0b 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -204,6 +204,9 @@ def find_spec_part(element, identifier, strict_identifier = True):
# always want the 'full' spec of the item
for id_part in id_parts[:-1]:
cur_el = _find_spec_part_single(cur_el, id_part)
+ # As soon as we find 'any', return that
+ if cur_el["item_type"] == "any":
+ return cur_el
if strict_identifier and spec_part_is_list(cur_el) and\
not isc.cc.data.identifier_has_list_index(id_part):
raise isc.cc.data.DataNotFoundError(id_part +
@@ -553,7 +556,6 @@ class MultiConfigData:
if 'item_default' in spec:
# one special case, named_set
if spec['item_type'] == 'named_set':
- print("is " + id_part + " in named set?")
return spec['item_default']
else:
return spec['item_default']
@@ -582,6 +584,14 @@ class MultiConfigData:
value = self.get_default_value(identifier)
if value is not None:
return value, self.DEFAULT
+ else:
+ # get_default_value returns None for both
+ # the cases where there is no default, and where
+ # it is set to null, so we need to catch the latter
+ spec_part = self.find_spec_part(identifier)
+ if spec_part and 'item_default' in spec_part and\
+ spec_part['item_default'] is None:
+ return None, self.DEFAULT
return None, self.NONE
def _append_value_item(self, result, spec_part, identifier, all, first = False):
@@ -742,6 +752,8 @@ class MultiConfigData:
# list
cur_list = cur_value
for list_index in list_indices:
+ if type(cur_list) != list:
+ raise isc.cc.data.DataTypeError(id + " is not a list")
if list_index >= len(cur_list):
raise isc.cc.data.DataNotFoundError("No item " +
str(list_index) + " in " + id_part)
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index d1060bf008..0101d50fb1 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -33,7 +33,7 @@ class TestHelperFunctions(unittest.TestCase):
self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [] })
self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 'not_an_rcode' ] })
self.assertRaises(ModuleCCSessionError, parse_answer, { 'result': [ 1, 2 ] })
-
+
rcode, val = parse_answer({ 'result': [ 0 ] })
self.assertEqual(0, rcode)
self.assertEqual(None, val)
@@ -107,7 +107,7 @@ class TestModuleCCSession(unittest.TestCase):
def spec_file(self, file):
return self.data_path + os.sep + file
-
+
def create_session(self, spec_file_name, config_handler = None,
command_handler = None, cc_session = None):
return ModuleCCSession(self.spec_file(spec_file_name),
@@ -335,7 +335,7 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 1)
self.assertEqual({'result': [1, 'No config_data specification']},
fake_session.get_message('Spec1', None))
-
+
def test_check_command3(self):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -348,7 +348,7 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 1)
self.assertEqual({'result': [0]},
fake_session.get_message('Spec2', None))
-
+
def test_check_command4(self):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -361,7 +361,7 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 1)
self.assertEqual({'result': [1, 'aaa should be an integer']},
fake_session.get_message('Spec2', None))
-
+
def test_check_command5(self):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -374,7 +374,7 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 1)
self.assertEqual({'result': [1, 'aaa should be an integer']},
fake_session.get_message('Spec2', None))
-
+
def test_check_command6(self):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec2.spec", None, None, fake_session)
@@ -460,7 +460,7 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 1)
self.assertEqual({'result': [1, 'No config_data specification']},
fake_session.get_message('Spec1', None))
-
+
def test_check_command_without_recvmsg2(self):
"copied from test_check_command3"
fake_session = FakeModuleCCSession()
@@ -474,7 +474,7 @@ class TestModuleCCSession(unittest.TestCase):
self.assertEqual(len(fake_session.message_queue), 1)
self.assertEqual({'result': [0]},
fake_session.get_message('Spec2', None))
-
+
def test_check_command_without_recvmsg3(self):
"copied from test_check_command7"
fake_session = FakeModuleCCSession()
@@ -487,7 +487,7 @@ class TestModuleCCSession(unittest.TestCase):
mccs.check_command_without_recvmsg(cmd, env)
self.assertEqual({'result': [0]},
fake_session.get_message('Spec2', None))
-
+
def test_check_command_block_timeout(self):
"""Check it works if session has timeout and it sets it back."""
def cmd_check(mccs, session):
@@ -893,22 +893,22 @@ class fakeUIConn():
def set_get_answer(self, name, answer):
self.get_answers[name] = answer
-
+
def set_post_answer(self, name, answer):
self.post_answers[name] = answer
-
+
def send_GET(self, name, arg = None):
if name in self.get_answers:
return self.get_answers[name]
else:
return {}
-
+
def send_POST(self, name, arg = None):
if name in self.post_answers:
return self.post_answers[name]
else:
return fakeAnswer()
-
+
class TestUIModuleCCSession(unittest.TestCase):
def setUp(self):
@@ -919,9 +919,9 @@ class TestUIModuleCCSession(unittest.TestCase):
def spec_file(self, file):
return self.data_path + os.sep + file
-
- def create_uccs2(self, fake_conn):
- module_spec = isc.config.module_spec_from_file(self.spec_file("spec2.spec"))
+
+ def create_uccs(self, fake_conn, specfile="spec2.spec"):
+ module_spec = isc.config.module_spec_from_file(self.spec_file(specfile))
fake_conn.set_get_answer('/module_spec', { module_spec.get_module_name(): module_spec.get_full_spec()})
fake_conn.set_get_answer('/config_data', { 'version': BIND10_CONFIG_DATA_VERSION })
return UIModuleCCSession(fake_conn)
@@ -989,7 +989,7 @@ class TestUIModuleCCSession(unittest.TestCase):
def test_add_remove_value(self):
fake_conn = fakeUIConn()
- uccs = self.create_uccs2(fake_conn)
+ uccs = self.create_uccs(fake_conn)
self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, 1, "a")
self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "no_such_item", "a")
@@ -1020,6 +1020,88 @@ class TestUIModuleCCSession(unittest.TestCase):
self.assertRaises(isc.cc.data.DataTypeError,
uccs.remove_value, "Spec2/item5", None)
+ # Check that the difference between no default and default = null
+ # is recognized
+ def test_default_null(self):
+ fake_conn = fakeUIConn()
+ uccs = self.create_uccs(fake_conn, "spec40.spec")
+ (value, status) = uccs.get_value("/Spec40/item2")
+ self.assertIsNone(value)
+ self.assertEqual(uccs.NONE, status)
+ (value, status) = uccs.get_value("/Spec40/item3")
+ self.assertIsNone(value)
+ self.assertEqual(uccs.DEFAULT, status)
+
+ # Test adding and removing values for type = any
+ def test_add_remove_value_any(self):
+ fake_conn = fakeUIConn()
+ uccs = self.create_uccs(fake_conn, "spec40.spec")
+
+ # Test item set of basic types
+ items = [ 1234, "foo", True, False ]
+ items_as_str = [ '1234', 'foo', 'true', 'false' ]
+
+ def test_fails():
+ self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo")
+ self.assertRaises(isc.cc.data.DataNotFoundError, uccs.add_value, "Spec40/item1", "foo", "bar")
+ self.assertRaises(isc.cc.data.DataNotFoundError, uccs.remove_value, "Spec40/item1", "foo")
+ self.assertRaises(isc.cc.data.DataTypeError, uccs.remove_value, "Spec40/item1[0]", None)
+
+ # A few helper functions to perform a number of tests
+ # (to repeat the same test for nested data)
+ def check_list(identifier):
+ for item in items_as_str:
+ uccs.add_value(identifier, item)
+ self.assertEqual((items, 1), uccs.get_value(identifier))
+
+ # Removing from list should work in both ways
+ uccs.remove_value(identifier, "foo")
+ uccs.remove_value(identifier + "[1]", None)
+ self.assertEqual(([1234, False], 1), uccs.get_value(identifier))
+
+ # As should item indexing
+ self.assertEqual((1234, 1), uccs.get_value(identifier + "[0]"))
+ self.assertEqual((False, 1), uccs.get_value(identifier + "[1]"))
+
+ def check_named_set(identifier):
+ for item in items_as_str:
+ # use string version as key as well
+ uccs.add_value(identifier, item, item)
+
+ self.assertEqual((1234, 1), uccs.get_value(identifier + "/1234"))
+ self.assertEqual((True, 1), uccs.get_value(identifier + "/true"))
+
+ for item in items_as_str:
+ # use string version as key as well
+ uccs.remove_value(identifier, item)
+
+
+ # should fail when set to value of primitive type
+ for item in items:
+ uccs.set_value("Spec40/item1", item)
+ test_fails()
+
+ # When set to list, add and remove should work, and its elements
+ # should be considered of type 'any' themselves.
+ uccs.set_value("Spec40/item1", [])
+ check_list("Spec40/item1")
+
+ # When set to dict, it should have the behaviour of a named set
+ uccs.set_value("Spec40/item1", {})
+ check_named_set("Spec40/item1")
+
+ # And, or course, we may need nesting.
+ uccs.set_value("Spec40/item1", { "foo": {}, "bar": [] })
+ check_named_set("Spec40/item1/foo")
+ check_list("Spec40/item1/bar")
+ uccs.set_value("Spec40/item1", [ {}, [] ] )
+ check_named_set("Spec40/item1[0]")
+ check_list("Spec40/item1[1]")
+ uccs.set_value("Spec40/item1", [[[[[[]]]]]] )
+ check_list("Spec40/item1[0][0][0][0][0]")
+ uccs.set_value("Spec40/item1", { 'a': { 'a': { 'a': {} } } } )
+ check_named_set("Spec40/item1/a/a/a")
+
def test_add_dup_value(self):
fake_conn = fakeUIConn()
uccs = self.create_uccs_listtest(fake_conn)
@@ -1101,7 +1183,7 @@ class TestUIModuleCCSession(unittest.TestCase):
def test_commit(self):
fake_conn = fakeUIConn()
- uccs = self.create_uccs2(fake_conn)
+ uccs = self.create_uccs(fake_conn)
uccs.commit()
uccs._local_changes = {'Spec2': {'item5': [ 'a' ]}}
uccs.commit()
diff --git a/src/lib/python/isc/sysinfo/sysinfo.py b/src/lib/python/isc/sysinfo/sysinfo.py
index 0663a2e021..97b9e59253 100644
--- a/src/lib/python/isc/sysinfo/sysinfo.py
+++ b/src/lib/python/isc/sysinfo/sysinfo.py
@@ -25,27 +25,31 @@ import time
class SysInfo:
def __init__(self):
- self._num_processors = -1
+ self._num_processors = None
self._endianness = 'Unknown'
self._hostname = ''
self._platform_name = 'Unknown'
self._platform_version = 'Unknown'
self._platform_machine = 'Unknown'
- self._platform_is_smp = False
- self._uptime = -1
- self._loadavg = [-1.0, -1.0, -1.0]
- self._mem_total = -1
- self._mem_free = -1
- self._mem_cached = -1
- self._mem_buffers = -1
- self._mem_swap_total = -1
- self._mem_swap_free = -1
- self._platform_distro = 'Unknown'
+ self._platform_is_smp = None
+ self._uptime = None
+ self._loadavg = None
+ self._mem_total = None
+ self._mem_free = None
+ self._mem_swap_total = None
+ self._mem_swap_free = None
self._net_interfaces = 'Unknown\n'
self._net_routing_table = 'Unknown\n'
self._net_stats = 'Unknown\n'
self._net_connections = 'Unknown\n'
+ # The following are Linux speicific, and should eventually be removed
+ # from this level; for now we simply default to None (so they won't
+ # be printed)
+ self._platform_distro = None
+ self._mem_cached = None
+ self._mem_buffers = None
+
def get_num_processors(self):
"""Returns the number of processors. This is the number of
hyperthreads when hyper-threading is enabled.
@@ -77,7 +81,12 @@ class SysInfo:
return self._platform_is_smp
def get_platform_distro(self):
- """Returns the name of the OS distribution in use."""
+ """Returns the name of the OS distribution in use.
+
+ Note: the concept of 'distribution' is Linux specific. This shouldn't
+ be at this level.
+
+ """
return self._platform_distro
def get_uptime(self):
@@ -164,7 +173,7 @@ class SysInfoLinux(SysInfoPOSIX):
with open('/proc/loadavg') as f:
l = f.read().strip().split(' ')
if len(l) >= 3:
- self._loadavg = [float(l[0]), float(l[1]), float(l[2])]
+ self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
with open('/proc/meminfo') as f:
m = f.readlines()
@@ -276,8 +285,6 @@ class SysInfoBSD(SysInfoPOSIX):
except (subprocess.CalledProcessError, OSError):
pass
- self._platform_distro = self._platform_name + ' ' + self._platform_version
-
try:
s = subprocess.check_output(['ifconfig'])
self._net_interfaces = s.decode('utf-8')
@@ -296,6 +303,12 @@ class SysInfoBSD(SysInfoPOSIX):
except (subprocess.CalledProcessError, OSError):
self._net_connections = 'Warning: "netstat -an" command failed.\n'
+ try:
+ s = subprocess.check_output(['netstat', '-nr'])
+ self._net_routing_table = s.decode('utf-8')
+ except (subprocess.CalledProcessError, OSError):
+ self._net_connections = 'Warning: "netstat -nr" command failed.\n'
+
class SysInfoOpenBSD(SysInfoBSD):
"""OpenBSD implementation of the SysInfo class.
See the SysInfo class documentation for more information.
@@ -303,11 +316,6 @@ class SysInfoOpenBSD(SysInfoBSD):
def __init__(self):
super().__init__()
- # Don't know how to gather these
- self._platform_is_smp = False
- self._mem_cached = -1
- self._mem_buffers = -1
-
try:
s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
t = s.decode('utf-8').strip()
@@ -320,7 +328,7 @@ class SysInfoOpenBSD(SysInfoBSD):
s = subprocess.check_output(['sysctl', '-n', 'vm.loadavg'])
l = s.decode('utf-8').strip().split(' ')
if len(l) >= 3:
- self._loadavg = [float(l[0]), float(l[1]), float(l[2])]
+ self._loadavg = (float(l[0]), float(l[1]), float(l[2]))
except (subprocess.CalledProcessError, OSError):
pass
@@ -343,29 +351,13 @@ class SysInfoOpenBSD(SysInfoBSD):
except (subprocess.CalledProcessError, OSError):
pass
- try:
- s = subprocess.check_output(['route', '-n', 'show'])
- self._net_routing_table = s.decode('utf-8')
- except (subprocess.CalledProcessError, OSError):
- self._net_routing_table = 'Warning: "route -n show" command failed.\n'
-
-class SysInfoFreeBSD(SysInfoBSD):
- """FreeBSD implementation of the SysInfo class.
- See the SysInfo class documentation for more information.
+class SysInfoFreeBSDOSX(SysInfoBSD):
+ """Shared code for the FreeBSD and OS X implementations of the SysInfo
+ class. See the SysInfo class documentation for more information.
"""
def __init__(self):
super().__init__()
- # Don't know how to gather these
- self._mem_cached = -1
- self._mem_buffers = -1
-
- try:
- s = subprocess.check_output(['sysctl', '-n', 'kern.smp.active'])
- self._platform_is_smp = int(s.decode('utf-8').strip()) > 0
- except (subprocess.CalledProcessError, OSError):
- pass
-
try:
s = subprocess.check_output(['sysctl', '-n', 'kern.boottime'])
t = s.decode('utf-8').strip()
@@ -385,7 +377,20 @@ class SysInfoFreeBSD(SysInfoBSD):
else:
la = l.split(' ')
if len(la) >= 3:
- self._loadavg = [float(la[0]), float(la[1]), float(la[2])]
+ self._loadavg = (float(la[0]), float(la[1]), float(la[2]))
+ except (subprocess.CalledProcessError, OSError):
+ pass
+
+class SysInfoFreeBSD(SysInfoFreeBSDOSX):
+ """FreeBSD implementation of the SysInfo class.
+ See the SysInfo class documentation for more information.
+ """
+ def __init__(self):
+ super().__init__()
+
+ try:
+ s = subprocess.check_output(['sysctl', '-n', 'kern.smp.active'])
+ self._platform_is_smp = int(s.decode('utf-8').strip()) > 0
except (subprocess.CalledProcessError, OSError):
pass
@@ -408,11 +413,57 @@ class SysInfoFreeBSD(SysInfoBSD):
except (subprocess.CalledProcessError, OSError):
pass
+
+
+class SysInfoOSX(SysInfoFreeBSDOSX):
+ """OS X (Darwin) implementation of the SysInfo class.
+ See the SysInfo class documentation for more information.
+ """
+ def __init__(self):
+ super().__init__()
+
+ # note; this call overrides the value already set when hw.physmem
+ # was read. However, on OSX, physmem is not necessarily the correct
+ # value. But since it does not fail and does work on most BSD's, it's
+ # left in the base class and overwritten here
+ self._mem_total = None
try:
- s = subprocess.check_output(['netstat', '-nr'])
- self._net_routing_table = s.decode('utf-8')
+ s = subprocess.check_output(['sysctl', '-n', 'hw.memsize'])
+ self._mem_total = int(s.decode('utf-8').strip())
except (subprocess.CalledProcessError, OSError):
- self._net_connections = 'Warning: "netstat -nr" command failed.\n'
+ pass
+
+ try:
+ s = subprocess.check_output(['vm_stat'])
+ lines = s.decode('utf-8').split('\n')
+ # store all values in a dict
+ values = {}
+ page_size = None
+ page_size_re = re.compile('.*page size of ([0-9]+) bytes')
+ for line in lines:
+ page_size_m = page_size_re.match(line)
+ if page_size_m:
+ page_size = int(page_size_m.group(1))
+ else:
+ key, _, value = line.partition(':')
+ values[key] = value.strip()[:-1]
+ # Only calculate memory if page size is known
+ if page_size is not None:
+ self._mem_free = int(values['Pages free']) * page_size +\
+ int(values['Pages speculative']) * page_size
+ except (subprocess.CalledProcessError, OSError):
+ pass
+
+ try:
+ s = subprocess.check_output(['sysctl', '-n', 'vm.swapusage'])
+ l = s.decode('utf-8').strip()
+ r = re.match('^total = (\d+\.\d+)M\s+used = (\d+\.\d+)M\s+free = (\d+\.\d+)M', l)
+ if r:
+ self._mem_swap_total = float(r.group(1).strip()) * 1024
+ self._mem_swap_free = float(r.group(3).strip()) * 1024
+ except (subprocess.CalledProcessError, OSError):
+ pass
+
class SysInfoTestcase(SysInfo):
def __init__(self):
@@ -429,6 +480,8 @@ def SysInfoFromFactory():
return SysInfoOpenBSD()
elif osname == 'FreeBSD':
return SysInfoFreeBSD()
+ elif osname == 'Darwin':
+ return SysInfoOSX()
elif osname == 'BIND10Testcase':
return SysInfoTestcase()
else:
diff --git a/src/lib/python/isc/sysinfo/tests/sysinfo_test.py b/src/lib/python/isc/sysinfo/tests/sysinfo_test.py
index 4d712cdf46..0add0367e2 100644
--- a/src/lib/python/isc/sysinfo/tests/sysinfo_test.py
+++ b/src/lib/python/isc/sysinfo/tests/sysinfo_test.py
@@ -20,6 +20,14 @@ import platform
import subprocess
import time
+# different fake 'number of processors' values used for the different
+# operating systems
+NPROCESSORS_LINUX = 42
+NPROCESSORS_OPENBSD = 43
+NPROCESSORS_FREEBSD = 44
+NPROCESSORS_OSX = 45
+
+
def _my_testcase_platform_system():
return 'BIND10Testcase'
@@ -28,7 +36,7 @@ def _my_linux_platform_system():
def _my_linux_os_sysconf(key):
if key == 'SC_NPROCESSORS_CONF':
- return 42
+ return NPROCESSORS_LINUX
assert False, 'Unhandled key'
class MyLinuxFile:
@@ -92,90 +100,166 @@ def _my_openbsd_platform_system():
def _my_openbsd_os_sysconf(key):
if key == 'SC_NPROCESSORS_CONF':
- return 53
+ return NPROCESSORS_OPENBSD
assert False, 'Unhandled key'
-def _my_openbsd_subprocess_check_output(command):
+def _my_openbsd_platform_uname():
+ return ('OpenBSD', 'test.example.com', '5.0', '', 'amd64')
+
+# For the BSD types, there is a hierarchy that mostly resembles the
+# class hierarchy in the sysinfo library;
+# These are output strings of commands that sysinfo calls
+#
+# The test hierarchy is used as follows:
+# Each operating system has its own _my__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()
diff --git a/tests/lettuce/features/resolver_basic.feature b/tests/lettuce/features/resolver_basic.feature
index 409210186e..47fc12304c 100644
--- a/tests/lettuce/features/resolver_basic.feature
+++ b/tests/lettuce/features/resolver_basic.feature
@@ -27,10 +27,10 @@ Feature: Basic Resolver
A query for l.root-servers.net. should have rcode REFUSED
# Test whether acl ACCEPT works
- When I set bind10 configuration Resolver/query_acl[0]/action to ACCEPT
+ When I set bind10 configuration Resolver/query_acl[0] to {"action": "ACCEPT", "from": "127.0.0.1"}
# This address is currently hardcoded, so shouldn't cause outside traffic
A query for l.root-servers.net. should have rcode NOERROR
# Check whether setting the ACL to reject again works
- When I set bind10 configuration Resolver/query_acl[0]/action to REJECT
+ When I set bind10 configuration Resolver/query_acl[0] to {"action": "REJECT", "from": "127.0.0.1"}
A query for l.root-servers.net. should have rcode REFUSED
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
index d46f2d14b0..7c8064ea14 100644
--- a/tests/tools/perfdhcp/Makefile.am
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -24,8 +24,10 @@ libb10_perfdhcp___la_SOURCES += localized_option.h
libb10_perfdhcp___la_SOURCES += perf_pkt6.cc perf_pkt6.h
libb10_perfdhcp___la_SOURCES += perf_pkt4.cc perf_pkt4.h
libb10_perfdhcp___la_SOURCES += pkt_transform.cc pkt_transform.h
+libb10_perfdhcp___la_SOURCES += stats_mgr.h
libb10_perfdhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
# Boost headers when compiling with clang.
diff --git a/tests/tools/perfdhcp/stats_mgr.h b/tests/tools/perfdhcp/stats_mgr.h
new file mode 100644
index 0000000000..245c69e3cf
--- /dev/null
+++ b/tests/tools/perfdhcp/stats_mgr.h
@@ -0,0 +1,1137 @@
+// 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 __STATS_MGR_H
+#define __STATS_MGR_H
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+namespace isc {
+namespace perfdhcp {
+
+/// \brief Statistics Manager
+///
+/// This class template is a storage for various performance statistics
+/// collected during performance tests execution with perfdhcp tool.
+///
+/// Statistics Manager holds lists of sent and received packets and
+/// groups them into exchanges. For example: DHCPDISCOVER message and
+/// corresponding DHCPOFFER messages belong to one exchange, DHCPREQUEST
+/// and corresponding DHCPACK message belong to another exchange etc.
+/// In order to update statistics for a particular exchange type, client
+/// class passes sent and received packets. Internally, Statistics Manager
+/// tries to match transaction id of received packet with sent packet
+/// stored on the list of sent packets. When packets are matched the
+/// round trip time can be calculated.
+///
+/// \tparam T class representing DHCPv4 or DHCPv6 packet.
+template
+class StatsMgr : public boost::noncopyable {
+public:
+
+ /// \brief Custom Counter
+ ///
+ /// This class represents custom statistics counters. Client class
+ /// may create unlimited number of counters. Such counters are
+ /// being stored in map in Statistics Manager and access using
+ /// unique string key.
+ class CustomCounter {
+ public:
+ /// \brief Constructor.
+ ///
+ /// This constructor sets counter name. This name is used in
+ /// log file to report value of each counter.
+ ///
+ /// \param name name of the counter used in log file.
+ CustomCounter(const std::string& name) :
+ counter_(0),
+ name_(name) { };
+
+ /// \brief Increment operator.
+ const CustomCounter& operator++() {
+ ++counter_;
+ return(*this);
+ }
+
+ /// \brief Increment operator.
+ const CustomCounter& operator++(int) {
+ CustomCounter& this_counter(*this);
+ operator++();
+ return(this_counter);
+ }
+
+ /// \brief Return counter value.
+ ///
+ /// Method returns counter value.
+ ///
+ /// \return counter value.
+ uint64_t getValue() const { return(counter_); }
+
+ /// \brief Return counter name.
+ ///
+ /// Method returns counter name.
+ ///
+ /// \return counter name.
+ const std::string& getName() const { return(name_); }
+ private:
+ /// \brief Default constructor.
+ ///
+ /// Default constrcutor is private because we don't want client
+ /// class to call it because we want client class to specify
+ /// counter's name.
+ CustomCounter() { };
+
+ uint64_t counter_; ///< Counter's value.
+ std::string name_; ///< Counter's name.
+ };
+
+ typedef typename boost::shared_ptr CustomCounterPtr;
+
+ /// DHCP packet exchange types.
+ enum ExchangeType {
+ XCHG_DO, ///< DHCPv4 DISCOVER-OFFER
+ XCHG_RA, ///< DHCPv4 REQUEST-ACK
+ XCHG_SA, ///< DHCPv6 SOLICIT-ADVERTISE
+ XCHG_RR ///< DHCPv6 REQUEST-REPLY
+ };
+
+ /// \brief Exchange Statistics.
+ ///
+ /// This class collects statistics for exchanges. Parent class
+ /// may define number of different packet exchanges like:
+ /// DHCPv4 DISCOVER-OFFER, DHCPv6 SOLICIT-ADVERTISE etc. Performance
+ /// statistics will be collected for each of those separately in
+ /// corresponding instance of ExchangeStats.
+ class ExchangeStats {
+ public:
+
+ /// \brief Hash transaction id of the packet.
+ ///
+ /// Function hashes transaction id of the packet. Hashing is
+ /// non-unique. Many packets may have the same hash value and thus
+ /// they belong to the same packet buckets. Packet buckets are
+ /// used for unordered packets search with multi index container.
+ ///
+ /// \param packet packet which transaction id is to be hashed.
+ /// \throw isc::BadValue if packet is null.
+ /// \return transaction id hash.
+ static uint32_t hashTransid(const boost::shared_ptr& packet) {
+ if (!packet) {
+ isc_throw(BadValue, "Packet is null");
+ }
+ return(packet->getTransid() & 1023);
+ }
+
+ /// \brief List of packets (sent or received).
+ ///
+ /// List of packets based on multi index container allows efficient
+ /// search of packets based on their sequence (order in which they
+ /// were inserted) as well as based on their hashed transaction id.
+ /// The first index (sequenced) provides the way to use container
+ /// as a regular list (including iterators, removal of elements from
+ /// the middle of the collection etc.). This index is meant to be used
+ /// more frequently than the latter one and it is based on the
+ /// assumption that responses from the DHCP server are received in
+ /// order. In this case, when next packet is received it can be
+ /// matched with next packet on the list of sent packets. This
+ /// prevents intensive searches on the list of sent packets every
+ /// time new packet arrives. In many cases however packets can be
+ /// dropped by the server or may be sent out of order and we still
+ /// want to have ability to search packets using transaction id.
+ /// The second index can be used for this purpose. This index is
+ /// hashing transaction ids using custom function \ref hashTransid.
+ /// Note that other possibility would be to simply specify index
+ /// that uses transaction id directly (instead of hashing with
+ /// \ref hashTransid). In this case however we have chosen to use
+ /// hashing function because it shortens the index size to just
+ /// 1023 values maximum. Search operation on this index generally
+ /// returns the range of packets that have the same transaction id
+ /// hash assigned but most often these ranges will be short so further
+ /// search within a range to find a packet with pacrticular transaction
+ /// id will not be intensive.
+ ///
+ /// Example 1: Add elements to the list
+ /// \code
+ /// PktList packets_collection();
+ /// boost::shared_ptr pkt1(new Pkt4(...));
+ /// boost::shared_ptr pkt2(new Pkt4(...));
+ /// // Add new packet to the container, it will be available through
+ /// // both indexes
+ /// packets_collection.push_back(pkt1);
+ /// // Here is another way to add packet to the container. The result
+ /// // is exactly the same as previously.
+ /// packets_collection.template get<0>().push_back(pkt2);
+ /// \endcode
+ ///
+ /// Example 2: Access elements through sequencial index
+ /// \code
+ /// PktList packets_collection();
+ /// ... # Add elements to the container
+ /// for (PktListIterator it = packets_collection.begin();
+ /// it != packets_collection.end();
+ /// ++it) {
+ /// boost::shared_ptr pkt = *it;
+ /// # Do something with packet;
+ /// }
+ /// \endcode
+ ///
+ /// Example 3: Access elements through hashed index
+ /// \code
+ /// // Get the instance of the second search index.
+ /// PktListTransidHashIndex& idx = sent_packets_.template get<1>();
+ /// // Get the range (bucket) of packets sharing the same transaction
+ /// // id hash.
+ /// std::pair p =
+ /// idx.equal_range(hashTransid(rcvd_packet));
+ /// // Iterate through the returned bucket.
+ /// for (PktListTransidHashIterator it = p.first; it != p.second;
+ /// ++it) {
+ /// boost::shared_ptr pkt = *it;
+ /// ... # Do something with the packet (e.g. check transaction id)
+ /// }
+ /// \endcode
+ typedef boost::multi_index_container<
+ boost::shared_ptr,
+ boost::multi_index::indexed_by<
+ boost::multi_index::sequenced<>,
+ boost::multi_index::hashed_non_unique<
+ boost::multi_index::global_fun<
+ const boost::shared_ptr&,
+ uint32_t,
+ &ExchangeStats::hashTransid
+ >
+ >
+ >
+ > PktList;
+
+ /// Packet list iterator for sequencial access to elements.
+ typedef typename PktList::const_iterator PktListIterator;
+ /// Packet list index to search packets using transaction id hash.
+ typedef typename PktList::template nth_index<1>::type
+ PktListTransidHashIndex;
+ /// Packet list iterator to access packets using transaction id hash.
+ typedef typename PktListTransidHashIndex::const_iterator
+ PktListTransidHashIterator;
+
+ /// \brief Constructor
+ ///
+ /// \param xchg_type exchange type
+ /// \param archive_enabled if true packets archive mode is enabled.
+ /// In this mode all packets are stored throughout the test execution.
+ ExchangeStats(const ExchangeType xchg_type, const bool archive_enabled)
+ : xchg_type_(xchg_type),
+ min_delay_(std::numeric_limits::max()),
+ max_delay_(0.),
+ sum_delay_(0.),
+ orphans_(0),
+ sum_delay_squared_(0.),
+ ordered_lookups_(0),
+ unordered_lookup_size_sum_(0),
+ unordered_lookups_(0),
+ sent_packets_num_(0),
+ rcvd_packets_num_(0),
+ sent_packets_(),
+ rcvd_packets_(),
+ archived_packets_(),
+ archive_enabled_(archive_enabled) {
+ next_sent_ = sent_packets_.begin();
+ }
+
+ /// \brief Add new packet to list of sent packets.
+ ///
+ /// Method adds new packet to list of sent packets.
+ ///
+ /// \param packet packet object to be added.
+ /// \throw isc::BadValue if packet is null.
+ void appendSent(const boost::shared_ptr& packet) {
+ if (!packet) {
+ isc_throw(BadValue, "Packet is null");
+ }
+ ++sent_packets_num_;
+ sent_packets_.template get<0>().push_back(packet);
+ }
+
+ /// \brief Add new packet to list of received packets.
+ ///
+ /// Method adds new packet to list of received packets.
+ ///
+ /// \param packet packet object to be added.
+ /// \throw isc::BadValue if packet is null.
+ void appendRcvd(const boost::shared_ptr& packet) {
+ if (!packet) {
+ isc_throw(BadValue, "Packet is null");
+ }
+ rcvd_packets_.push_back(packet);
+ }
+
+ /// \brief Update delay counters.
+ ///
+ /// Method updates delay counters based on timestamps of
+ /// sent and received packets.
+ ///
+ /// \param sent_packet sent packet
+ /// \param rcvd_packet received packet
+ /// \throw isc::BadValue if sent or received packet is null.
+ /// \throw isc::Unexpected if failed to calculate timestamps
+ void updateDelays(const boost::shared_ptr& sent_packet,
+ const boost::shared_ptr& rcvd_packet) {
+ if (!sent_packet) {
+ isc_throw(BadValue, "Sent packet is null");
+ }
+ if (!rcvd_packet) {
+ isc_throw(BadValue, "Received packet is null");
+ }
+
+ boost::posix_time::ptime sent_time = sent_packet->getTimestamp();
+ boost::posix_time::ptime rcvd_time = rcvd_packet->getTimestamp();
+
+ if (sent_time.is_not_a_date_time() ||
+ rcvd_time.is_not_a_date_time()) {
+ isc_throw(Unexpected,
+ "Timestamp must be set for sent and "
+ "received packet to measure RTT");
+ }
+ boost::posix_time::time_period period(sent_time, rcvd_time);
+ // We don't bother calculating deltas in nanoseconds. It is much
+ // more convenient to use seconds instead because we are going to
+ // sum them up.
+ double delta =
+ static_cast(period.length().total_nanoseconds()) / 1e9;
+
+ if (delta < 0) {
+ isc_throw(Unexpected, "Sent packet's timestamp must not be "
+ "greater than received packet's timestamp");
+ }
+
+ // Record the minimum delay between sent and received packets.
+ if (delta < min_delay_) {
+ min_delay_ = delta;
+ }
+ // Record the maximum delay between sent and received packets.
+ if (delta > max_delay_) {
+ max_delay_ = delta;
+ }
+ // Update delay sum and square sum. That will be used to calculate
+ // mean delays.
+ sum_delay_ += delta;
+ sum_delay_squared_ += delta * delta;
+ }
+
+ /// \brief Match received packet with the corresponding sent packet.
+ ///
+ /// Method finds packet with specified transaction id on the list
+ /// of sent packets. It is used to match received packet with
+ /// corresponding sent packet.
+ /// Since packets from the server most often come in the same order
+ /// as they were sent by client, this method will first check if
+ /// next sent packet matches. If it doesn't, function will search
+ /// the packet using indexing by transaction id. This reduces
+ /// packet search time significantly.
+ ///
+ /// \param rcvd_packet received packet to be matched with sent packet.
+ /// \throw isc::BadValue if received packet is null.
+ /// \return packet having specified transaction or NULL if packet
+ /// not found
+ boost::shared_ptr matchPackets(const boost::shared_ptr& rcvd_packet) {
+ if (!rcvd_packet) {
+ isc_throw(BadValue, "Received packet is null");
+ }
+
+ if (sent_packets_.size() == 0) {
+ // List of sent packets is empty so there is no sense
+ // to continue looking fo the packet. It also means
+ // that the received packet we got has no corresponding
+ // sent packet so orphans counter has to be updated.
+ ++orphans_;
+ return(boost::shared_ptr());
+ } else if (next_sent_ == sent_packets_.end()) {
+ // Even if there are still many unmatched packets on the
+ // list we might hit the end of it because of unordered
+ // lookups. The next logical step is to reset iterator.
+ next_sent_ = sent_packets_.begin();
+ }
+
+ // With this variable we will be signalling success or failure
+ // to find the packet.
+ bool packet_found = false;
+ // Most likely responses are sent from the server in the same
+ // order as client's requests to the server. We are caching
+ // next sent packet and first try to match it with the next
+ // incoming packet. We are successful if there is no
+ // packet drop or out of order packets sent. This is actually
+ // the fastest way to look for packets.
+ if ((*next_sent_)->getTransid() == rcvd_packet->getTransid()) {
+ ++ordered_lookups_;
+ packet_found = true;
+ } else {
+ // If we are here, it means that we were unable to match the
+ // next incoming packet with next sent packet so we need to
+ // take a little more expensive approach to look packets using
+ // alternative index (transaction id & 1023).
+ PktListTransidHashIndex& idx = sent_packets_.template get<1>();
+ // Packets are grouped using trasaction id masked with value
+ // of 1023. For instance, packets with transaction id equal to
+ // 1, 1024 ... will belong to the same group (a.k.a. bucket).
+ // When using alternative index we don't find the packet but
+ // bucket of packets and we need to iterate through the bucket
+ // to find the one that has desired transaction id.
+ std::pair p =
+ idx.equal_range(hashTransid(rcvd_packet));
+ // We want to keep statistics of unordered lookups to make
+ // sure that there is a right balance between number of
+ // unordered lookups and ordered lookups. If number of unordered
+ // lookups is high it may mean that many packets are lost or
+ // sent out of order.
+ ++unordered_lookups_;
+ // We also want to keep the mean value of the bucket. The lower
+ // bucket size the better. If bucket sizes appear to big we
+ // might want to increase number of buckets.
+ unordered_lookup_size_sum_ += std::distance(p.first, p.second);
+ for (PktListTransidHashIterator it = p.first; it != p.second;
+ ++it) {
+ if ((*it)->getTransid() == rcvd_packet->getTransid()) {
+ packet_found = true;
+ next_sent_ =
+ sent_packets_.template project<0>(it);
+ break;
+ }
+ }
+ }
+
+ if (!packet_found) {
+ // If we are here, it means that both ordered lookup and
+ // unordered lookup failed. Searched packet is not on the list.
+ ++orphans_;
+ return(boost::shared_ptr());
+ }
+
+ // Packet is matched so we count it. We don't count unmatched packets
+ // as they are counted as orphans with a separate counter.
+ ++rcvd_packets_num_;
+ boost::shared_ptr sent_packet(*next_sent_);
+ // If packet was found, we assume it will be never searched
+ // again. We want to delete this packet from the list to
+ // improve performance of future searches.
+ next_sent_ = eraseSent(next_sent_);
+ return(sent_packet);
+ }
+
+ /// \brief Return minumum delay between sent and received packet.
+ ///
+ /// Method returns minimum delay between sent and received packet.
+ ///
+ /// \return minimum delay between packets.
+ double getMinDelay() const { return(min_delay_); }
+
+ /// \brief Return maxmimum delay between sent and received packet.
+ ///
+ /// Method returns maximum delay between sent and received packet.
+ ///
+ /// \return maximum delay between packets.
+ double getMaxDelay() const { return(max_delay_); }
+
+ /// \brief Return avarage packet delay.
+ ///
+ /// Method returns average packet delay. If no packets have been
+ /// received for this exchange avg delay can't be calculated and
+ /// thus method throws exception.
+ ///
+ /// \throw isc::InvalidOperation if no packets for this exchange
+ /// have been received yet.
+ /// \return average packet delay.
+ double getAvgDelay() const {
+ if (rcvd_packets_num_ == 0) {
+ isc_throw(InvalidOperation, "no packets received");
+ }
+ return(sum_delay_ / rcvd_packets_num_);
+ }
+
+ /// \brief Return standard deviation of packet delay.
+ ///
+ /// Method returns standard deviation of packet delay. If no
+ /// packets have been received for this exchange, the standard
+ /// deviation can't be calculated and thus method throws
+ /// exception.
+ ///
+ /// \throw isc::InvalidOperation if number of received packets
+ /// for the exchange is equal to zero.
+ /// \return standard deviation of packet delay.
+ double getStdDevDelay() const {
+ if (rcvd_packets_num_ == 0) {
+ isc_throw(InvalidOperation, "no packets received");
+ }
+ return(sqrt(sum_delay_squared_ / rcvd_packets_num_ -
+ getAvgDelay() * getAvgDelay()));
+ }
+
+ /// \brief Return number of orphant packets.
+ ///
+ /// Method returns number of received packets that had no matching
+ /// sent packet. It is possible that such packet was late or not
+ /// for us.
+ ///
+ /// \return number of orphant received packets.
+ uint64_t getOrphans() const { return(orphans_); }
+
+ /// \brief Return average unordered lookup set size.
+ ///
+ /// Method returns average unordered lookup set size.
+ /// This value changes every time \ref ExchangeStats::matchPackets
+ /// function performs unordered packet lookup.
+ ///
+ /// \throw isc::InvalidOperation if there have been no unordered
+ /// lookups yet.
+ /// \return average unordered lookup set size.
+ double getAvgUnorderedLookupSetSize() const {
+ if (unordered_lookups_ == 0) {
+ isc_throw(InvalidOperation, "no unordered lookups");
+ }
+ return(static_cast(unordered_lookup_size_sum_) /
+ static_cast(unordered_lookups_));
+ }
+
+ /// \brief Return number of unordered sent packets lookups
+ ///
+ /// Method returns number of unordered sent packet lookups.
+ /// Unordered lookup is used when received packet was sent
+ /// out of order by server - transaction id of received
+ /// packet does not match transaction id of next sent packet.
+ ///
+ /// \return number of unordered lookups.
+ uint64_t getUnorderedLookups() const { return(unordered_lookups_); }
+
+ /// \brief Return number of ordered sent packets lookups
+ ///
+ /// Method returns number of ordered sent packet lookups.
+ /// Ordered lookup is used when packets are received in the
+ /// same order as they were sent to the server.
+ /// If packets are skipped or received out of order, lookup
+ /// function will use unordered lookup (with hash table).
+ ///
+ /// \return number of ordered lookups.
+ uint64_t getOrderedLookups() const { return(ordered_lookups_); }
+
+ /// \brief Return total number of sent packets
+ ///
+ /// Method returns total number of sent packets.
+ ///
+ /// \return number of sent packets.
+ uint64_t getSentPacketsNum() const { return(sent_packets_num_); }
+
+ /// \brief Return total number of received packets
+ ///
+ /// Method returns total number of received packets.
+ ///
+ /// \return number of received packets.
+ uint64_t getRcvdPacketsNum() const { return(rcvd_packets_num_); }
+
+ /// \brief Print main statistics for packet exchange.
+ ///
+ /// Method prints main statistics for particular exchange.
+ /// Statistics includes: number of sent and received packets,
+ /// number of dropped packets and number of orphans.
+ void printMainStats() const {
+ using namespace std;
+ uint64_t drops = getRcvdPacketsNum() - getSentPacketsNum();
+ cout << "sent packets: " << getSentPacketsNum() << endl
+ << "received packets: " << getRcvdPacketsNum() << endl
+ << "drops: " << drops << endl
+ << "orphans: " << getOrphans() << endl;
+ }
+
+ /// \brief Print round trip time packets statistics.
+ ///
+ /// Method prints round trip time packets statistics. Statistics
+ /// includes minimum packet delay, maximum packet delay, average
+ /// packet delay and standard deviation of delays. Packet delay
+ /// is a duration between sending a packet to server and receiving
+ /// response from server.
+ void printRTTStats() const {
+ using namespace std;
+ try {
+ cout << fixed << setprecision(3)
+ << "min delay: " << getMinDelay() * 1e3 << " ms" << endl
+ << "avg delay: " << getAvgDelay() * 1e3 << " ms" << endl
+ << "max delay: " << getMaxDelay() * 1e3 << " ms" << endl
+ << "std deviation: " << getStdDevDelay() * 1e3 << " ms"
+ << endl;
+ } catch (const Exception& e) {
+ cout << "Unavailable! No packets received." << endl;
+ }
+ }
+
+ //// \brief Print timestamps for sent and received packets.
+ ///
+ /// Method prints timestamps for all sent and received packets for
+ /// packet exchange. In order to run this method the packets
+ /// archiving mode has to be enabled during object constructions.
+ /// Otherwise sent packets are not stored during tests execution
+ /// and this method has no ability to get and print their timestamps.
+ ///
+ /// \throw isc::InvalidOperation if found packet with no timestamp or
+ /// if packets archive mode is disabled.
+ void printTimestamps() {
+ // If archive mode is disabled there is no sense to proceed
+ // because we don't have packets and their timestamps.
+ if (!archive_enabled_) {
+ isc_throw(isc::InvalidOperation,
+ "packets archive mode is disabled");
+ }
+ if (rcvd_packets_num_ == 0) {
+ std::cout << "Unavailable! No packets received." << std::endl;
+ }
+ // We will be using boost::posix_time extensivelly here
+ using namespace boost::posix_time;
+
+ // Iterate through all received packets.
+ for (PktListIterator it = rcvd_packets_.begin();
+ it != rcvd_packets_.end();
+ ++it) {
+ boost::shared_ptr rcvd_packet = *it;
+ PktListTransidHashIndex& idx =
+ archived_packets_.template get<1>();
+ std::pair p =
+ idx.equal_range(hashTransid(rcvd_packet));
+ for (PktListTransidHashIterator it_archived = p.first;
+ it_archived != p.second;
+ ++it) {
+ if ((*it_archived)->getTransid() ==
+ rcvd_packet->getTransid()) {
+ boost::shared_ptr sent_packet = *it_archived;
+ // Get sent and received packet times.
+ ptime sent_time = sent_packet->getTimestamp();
+ ptime rcvd_time = rcvd_packet->getTimestamp();
+ // All sent and received packets should have timestamps
+ // set but if there is a bug somewhere and packet does
+ // not have timestamp we want to catch this here.
+ if (sent_time.is_not_a_date_time() ||
+ rcvd_time.is_not_a_date_time()) {
+ isc_throw(InvalidOperation,
+ "packet time is not set");
+ }
+ // Calculate durations of packets from beginning of epoch.
+ ptime epoch_time(min_date_time);
+ time_period sent_period(epoch_time, sent_time);
+ time_period rcvd_period(epoch_time, rcvd_time);
+ // Print timestamps for sent and received packet.
+ std::cout << "sent / received: "
+ << to_iso_string(sent_period.length())
+ << " / "
+ << to_iso_string(rcvd_period.length())
+ << std::endl;
+ break;
+ }
+ }
+ }
+ }
+
+ private:
+
+ /// \brief Private default constructor.
+ ///
+ /// Default constructor is private because we want the client
+ /// class to specify exchange type explicitely.
+ ExchangeStats();
+
+ /// \brief Erase packet from the list of sent packets.
+ ///
+ /// Method erases packet from the list of sent packets.
+ ///
+ /// \param it iterator pointing to packet to be erased.
+ /// \return iterator pointing to packet following erased
+ /// packet or sent_packets_.end() if packet not found.
+ PktListIterator eraseSent(const PktListIterator it) {
+ if (archive_enabled_) {
+ // We don't want to keep list of all sent packets
+ // because it will affect packet lookup performance.
+ // If packet is matched with received packet we
+ // move it to list of archived packets. List of
+ // archived packets may be used for diagnostics
+ // when test is completed.
+ archived_packets_.push_back(*it);
+ }
+ // get<0>() template returns sequencial index to
+ // container.
+ return(sent_packets_.template get<0>().erase(it));
+ }
+
+ ExchangeType xchg_type_; ///< Packet exchange type.
+ PktList sent_packets_; ///< List of sent packets.
+
+ /// Iterator pointing to the packet on sent list which will most
+ /// likely match next received packet. This is based on the
+ /// assumption that server responds in order to incoming packets.
+ PktListIterator next_sent_;
+
+ PktList rcvd_packets_; ///< List of received packets.
+
+ /// List of archived packets. All sent packets that have
+ /// been matched with received packet are moved to this
+ /// list for diagnostics purposes.
+ PktList archived_packets_;
+
+ /// Indicates all packets have to be preserved after matching.
+ /// By default this is disabled which means that when received
+ /// packet is matched with sent packet both are deleted. This
+ /// is important when test is executed for extended period of
+ /// time and high memory usage might be the issue.
+ /// When timestamps listing is specified from the command line
+ /// (using diagnostics selector), all packets have to be preserved
+ /// so as the printing method may read their timestamps and
+ /// print it to user. In such usage model it will be rare to
+ /// run test for extended period of time so it should be fine
+ /// to keep all packets archived throughout the test.
+ bool archive_enabled_;
+
+ double min_delay_; ///< Minimum delay between sent
+ ///< and received packets.
+ double max_delay_; ///< Maximum delay between sent
+ ///< and received packets.
+ double sum_delay_; ///< Sum of delays between sent
+ ///< and received packets.
+ double sum_delay_squared_; ///< Squared sum of delays between
+ ///< sent and recived packets.
+
+ uint64_t orphans_; ///< Number of orphant received packets.
+
+ /// Sum of unordered lookup sets. Needed to calculate mean size of
+ /// lookup set. It is desired that number of unordered lookups is
+ /// minimal for performance reasons. Tracking number of lookups and
+ /// mean size of the lookup set should give idea of packets serach
+ /// complexity.
+ uint64_t unordered_lookup_size_sum_;
+
+ uint64_t unordered_lookups_; ///< Number of unordered sent packets
+ ///< lookups.
+ uint64_t ordered_lookups_; ///< Number of ordered sent packets
+ ///< lookups.
+
+ uint64_t sent_packets_num_; ///< Total number of sent packets.
+ uint64_t rcvd_packets_num_; ///< Total number of received packets.
+ };
+
+ /// Pointer to ExchangeStats.
+ typedef boost::shared_ptr ExchangeStatsPtr;
+ /// Map containing all specified exchange types.
+ typedef typename std::map ExchangesMap;
+ /// Iterator poiting to \ref ExchangesMap
+ typedef typename ExchangesMap::const_iterator ExchangesMapIterator;
+ /// Map containing custom counters.
+ typedef typename std::map CustomCountersMap;
+ /// Iterator for \ref CustomCountersMap.
+ typedef typename CustomCountersMap::const_iterator CustomCountersMapIterator;
+
+ /// \brief Constructor.
+ ///
+ /// This constructor by default disables packets archiving mode.
+ /// In this mode all packets from the list of sent packets are
+ /// moved to list of archived packets once they have been matched
+ /// with received packets. This is required if it has been selected
+ /// from the command line to print timestamps for all packets after
+ /// the test. If this is not selected archiving should be disabled
+ /// for performance reasons and to avoid waste of memory for storing
+ /// large list of archived packets.
+ ///
+ /// \param archive_enabled true indicates that packets
+ /// archive mode is enabled.
+ StatsMgr(const bool archive_enabled = false) :
+ exchanges_(),
+ custom_counters_(),
+ archive_enabled_(archive_enabled) {
+ }
+
+ /// \brief Specify new exchange type.
+ ///
+ /// This method creates new \ref ExchangeStats object that will
+ /// collect statistics data from packets exchange of the specified
+ /// type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if exchange of specified type exists.
+ void addExchangeStats(const ExchangeType xchg_type) {
+ if (exchanges_.find(xchg_type) != exchanges_.end()) {
+ isc_throw(BadValue, "Exchange of specified type already added.");
+ }
+ exchanges_[xchg_type] =
+ ExchangeStatsPtr(new ExchangeStats(xchg_type, archive_enabled_));
+ }
+
+ /// \brief Add named custom uint64 counter.
+ ///
+ /// Method creates new named counter and stores in counter's map under
+ /// key specified here as short_name.
+ ///
+ /// \param short_name key to use to access counter in the map.
+ /// \param long_name name of the counter presented in the log file.
+ void addCustomCounter(const std::string& short_name,
+ const std::string& long_name) {
+ if (custom_counters_.find(short_name) != custom_counters_.end()) {
+ isc_throw(BadValue,
+ "Custom counter " << short_name << " already added.");
+ }
+ custom_counters_[short_name] =
+ CustomCounterPtr(new CustomCounter(long_name));
+ }
+
+ /// \brief Return specified counter.
+ ///
+ /// Method returns specified counter.
+ ///
+ /// \param counter_key key poiting to the counter in the counters map.
+ /// The short counter name has to be used to access counter.
+ /// \return pointer to specified counter object.
+ CustomCounterPtr getCounter(const std::string& counter_key) {
+ CustomCountersMapIterator it = custom_counters_.find(counter_key);
+ if (it == custom_counters_.end()) {
+ isc_throw(BadValue,
+ "Custom counter " << counter_key << "does not exist");
+ }
+ return(it->second);
+ }
+
+ /// \brief Increment specified counter.
+ ///
+ /// Increement counter value by one.
+ ///
+ /// \param counter_key key poitinh to the counter in the counters map.
+ /// \return pointer to specified counter after incrementation.
+ const CustomCounter& IncrementCounter(const std::string& counter_key) {
+ CustomCounterPtr counter = getCounter(counter_key);
+ return(++(*counter));
+ }
+
+ /// \brief Adds new packet to the sent packets list.
+ ///
+ /// Method adds new packet to the sent packets list.
+ /// Packets are added to the list sequentially and
+ /// most often read sequentially.
+ ///
+ /// \param xchg_type exchange type.
+ /// \param packet packet to be added to the list
+ /// \throw isc::BadValue if invalid exchange type specified or
+ /// packet is null.
+ void passSentPacket(const ExchangeType xchg_type,
+ const boost::shared_ptr& packet) {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ xchg_stats->appendSent(packet);
+ }
+
+ /// \brief Add new received packet and match with sent packet.
+ ///
+ /// Method adds new packet to the list of received packets. It
+ /// also searches for corresponding packet on the list of sent
+ /// packets. When packets are matched the statistics counters
+ /// are updated accordingly for the particular exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \param packet received packet
+ /// \throw isc::BadValue if invalid exchange type specified
+ /// or packet is null.
+ /// \throw isc::Unexpected if corresponding packet was not
+ /// found on the list of sent packets.
+ void passRcvdPacket(const ExchangeType xchg_type,
+ const boost::shared_ptr& packet) {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ boost::shared_ptr sent_packet
+ = xchg_stats->matchPackets(packet);
+
+ if (sent_packet) {
+ xchg_stats->updateDelays(sent_packet, packet);
+ if (archive_enabled_) {
+ xchg_stats->appendRcvd(packet);
+ }
+ }
+ }
+
+ /// \brief Return minumum delay between sent and received packet.
+ ///
+ /// Method returns minimum delay between sent and received packet
+ /// for specified exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return minimum delay between packets.
+ double getMinDelay(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getMinDelay());
+ }
+
+ /// \brief Return maxmimum delay between sent and received packet.
+ ///
+ /// Method returns maximum delay between sent and received packet
+ /// for specified exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return maximum delay between packets.
+ double getMaxDelay(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getMaxDelay());
+ }
+
+ /// \brief Return avarage packet delay.
+ ///
+ /// Method returns average packet delay for specified
+ /// exchange type.
+ ///
+ /// \return average packet delay.
+ double getAvgDelay(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getAvgDelay());
+ }
+
+ /// \brief Return standard deviation of packet delay.
+ ///
+ /// Method returns standard deviation of packet delay
+ /// for specified exchange type.
+ ///
+ /// \return standard deviation of packet delay.
+ double getStdDevDelay(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getStdDevDelay());
+ }
+
+ /// \brief Return number of orphant packets.
+ ///
+ /// Method returns number of orphant packets for specified
+ /// exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return number of orphant packets so far.
+ uint64_t getOrphans(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getOrphans());
+ }
+
+ /// \brief Return average unordered lookup set size.
+ ///
+ /// Method returns average unordered lookup set size.
+ /// This value changes every time \ref ExchangeStats::matchPackets
+ /// function performs unordered packet lookup.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return average unordered lookup set size.
+ double getAvgUnorderedLookupSetSize(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getAvgUnorderedLookupSetSize());
+ }
+
+ /// \brief Return number of unordered sent packets lookups
+ ///
+ /// Method returns number of unordered sent packet lookups.
+ /// Unordered lookup is used when received packet was sent
+ /// out of order by server - transaction id of received
+ /// packet does not match transaction id of next sent packet.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return number of unordered lookups.
+ uint64_t getUnorderedLookups(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getUnorderedLookups());
+ }
+
+ /// \brief Return number of ordered sent packets lookups
+ ///
+ /// Method returns number of ordered sent packet lookups.
+ /// Ordered lookup is used when packets are received in the
+ /// same order as they were sent to the server.
+ /// If packets are skipped or received out of order, lookup
+ /// function will use unordered lookup (with hash table).
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return number of ordered lookups.
+ uint64_t getOrderedLookups(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getOrderedLookups());
+ }
+
+ /// \brief Return total number of sent packets
+ ///
+ /// Method returns total number of sent packets for specified
+ /// exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return number of sent packets.
+ uint64_t getSentPacketsNum(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getSentPacketsNum());
+ }
+
+ /// \brief Return total number of received packets
+ ///
+ /// Method returns total number of received packets for specified
+ /// exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return number of received packets.
+ uint64_t getRcvdPacketsNum(const ExchangeType xchg_type) const {
+ ExchangeStatsPtr xchg_stats = getExchangeStats(xchg_type);
+ return(xchg_stats->getRcvdPacketsNum());
+ }
+
+ /// \brief Return name of the exchange.
+ ///
+ /// Method returns name of the specified exchange type.
+ /// This function is mainly for logging purposes.
+ ///
+ /// \param xchg_type exchange type.
+ /// \return string representing name of the exchange.
+ std::string exchangeToString(ExchangeType xchg_type) const {
+ switch(xchg_type) {
+ case XCHG_DO:
+ return("DISCOVER-OFFER");
+ case XCHG_RA:
+ return("REQUEST-ACK");
+ case XCHG_SA:
+ return("SOLICIT-ADVERTISE");
+ case XCHG_RR:
+ return("REQUEST-REPLY");
+ default:
+ return("Unknown exchange type");
+ }
+ }
+
+ /// \brief Print statistics counters for all exchange types.
+ ///
+ /// Method prints statistics for all exchange types.
+ /// Statistics includes:
+ /// - number of sent and received packets
+ /// - number of dropped packets and number of orphans
+ /// - minimum packets delay,
+ /// - average packets delay,
+ /// - maximum packets delay,
+ /// - standard deviation of packets delay.
+ ///
+ /// \throw isc::InvalidOperation if no exchange type added to
+ /// track statistics.
+ void printStats() const {
+ if (exchanges_.size() == 0) {
+ isc_throw(isc::InvalidOperation,
+ "no exchange type added for tracking");
+ }
+ for (ExchangesMapIterator it = exchanges_.begin();
+ it != exchanges_.end();
+ ++it) {
+ ExchangeStatsPtr xchg_stats = it->second;
+ std::cout << "***Statistics for: " << exchangeToString(it->first)
+ << "***" << std::endl;
+ xchg_stats->printMainStats();
+ std::cout << std::endl;
+ xchg_stats->printRTTStats();
+ std::cout << std::endl;
+ }
+ }
+
+ /// \brief Print timestamps of all packets.
+ ///
+ /// Method prints timestamps of all sent and received
+ /// packets for all defined exchange types.
+ ///
+ /// \throw isc::InvalidOperation if one of the packets has
+ /// no timestamp value set or if packets archive mode is
+ /// disabled.
+ ///
+ /// \throw isc::InvalidOperation if no exchange type added to
+ /// track statistics or packets archive mode is disabled.
+ void printTimestamps() const {
+ if (exchanges_.size() == 0) {
+ isc_throw(isc::InvalidOperation,
+ "no exchange type added for tracking");
+ }
+ for (ExchangesMapIterator it = exchanges_.begin();
+ it != exchanges_.end();
+ ++it) {
+ ExchangeStatsPtr xchg_stats = it->second;
+ std::cout << "***Timestamps for packets: "
+ << exchangeToString(it->first)
+ << "***" << std::endl;
+ xchg_stats->printTimestamps();
+ std::cout << std::endl;
+ }
+ }
+
+ /// \brief Print names and values of custom counters.
+ ///
+ /// Method prints names and values of custom counters. Custom counters
+ /// are defined by client class for tracking different statistics.
+ ///
+ /// \throw isc::InvalidOperation if no custom counters added for tracking.
+ void printCustomCounters() const {
+ if (custom_counters_.size() == 0) {
+ isc_throw(isc::InvalidOperation, "no custom counters specified");
+ }
+ for (CustomCountersMapIterator it = custom_counters_.begin();
+ it != custom_counters_.end();
+ ++it) {
+ CustomCounterPtr counter = it->second;
+ std::cout << counter->getName() << ": " << counter->getValue()
+ << std::endl;
+ }
+ }
+
+private:
+
+ /// \brief Return exchange stats object for given exchange type
+ ///
+ /// Method returns exchange stats object for given exchange type.
+ ///
+ /// \param xchg_type exchange type.
+ /// \throw isc::BadValue if invalid exchange type specified.
+ /// \return exchange stats object.
+ ExchangeStatsPtr getExchangeStats(const ExchangeType xchg_type) const {
+ ExchangesMapIterator it = exchanges_.find(xchg_type);
+ if (it == exchanges_.end()) {
+ isc_throw(BadValue, "Packets exchange not specified");
+ }
+ ExchangeStatsPtr xchg_stats = it->second;
+ return(xchg_stats);
+ }
+
+ ExchangesMap exchanges_; ///< Map of exchange types.
+ CustomCountersMap custom_counters_; ///< Map with custom counters.
+
+ /// Indicates that packets from list of sent packets should be
+ /// archived (moved to list of archived packets) once they are
+ /// matched with received packets. This is required when it has
+ /// been selected from the command line to print packets'
+ /// timestamps after test. This may affect performance and
+ /// consume large amount of memory when the test is running
+ /// for extended period of time and many packets have to be
+ /// archived.
+ bool archive_enabled_;
+};
+
+} // namespace perfdhcp
+} // namespace isc
+
+#endif // __STATS_MGR_H
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
index ec1b4032c6..16e18425ac 100644
--- a/tests/tools/perfdhcp/tests/Makefile.am
+++ b/tests/tools/perfdhcp/tests/Makefile.am
@@ -21,6 +21,7 @@ run_unittests_SOURCES += command_options_unittest.cc
run_unittests_SOURCES += perf_pkt6_unittest.cc
run_unittests_SOURCES += perf_pkt4_unittest.cc
run_unittests_SOURCES += localized_option_unittest.cc
+run_unittests_SOURCES += stats_mgr_unittest.cc
run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/command_options.cc
run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/pkt_transform.cc
run_unittests_SOURCES += $(top_builddir)/tests/tools/perfdhcp/perf_pkt6.cc
diff --git a/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc b/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
new file mode 100644
index 0000000000..2233847bb1
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/stats_mgr_unittest.cc
@@ -0,0 +1,450 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "../stats_mgr.h"
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::perfdhcp;
+
+namespace {
+
+typedef StatsMgr StatsMgr4;
+typedef StatsMgr 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 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
+ 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 stats_mgr,
+ unsigned int delay,
+ uint32_t transid) {
+ boost::shared_ptr 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 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 stats_mgr(new StatsMgr4());
+ stats_mgr->addExchangeStats(StatsMgr4::XCHG_DO);
+ EXPECT_DOUBLE_EQ(
+ std::numeric_limits::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 stats_mgr(new StatsMgr4());
+ boost::shared_ptr sent_packet(createPacket4(DHCPDISCOVER,
+ common_transid));
+ boost::shared_ptr 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 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 stats_mgr(new StatsMgr4());
+ boost::shared_ptr sent_packet(createPacket4(DHCPDISCOVER,
+ common_transid));
+ boost::shared_ptr 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 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 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
+ 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 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 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 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 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::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 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 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 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());
+}
+
+
+}