BIND 10 experiments/fix-dhcp-test, updated. ba4ed797c8eaea19e9db74a402fd7c1dff4b622c [experiments/fix-dhcp-test] Merge branch 'master' into experiments/fix-dhcp-test
BIND 10 source code commits
bind10-changes at lists.isc.org
Sun Nov 11 20:19:29 UTC 2012
The branch, experiments/fix-dhcp-test has been updated
via ba4ed797c8eaea19e9db74a402fd7c1dff4b622c (commit)
via 51adf02fc69bf854a8b73b97cd481a009c5b428b (commit)
via b3526430f02aa3dc3273612524d23137b8f1fe87 (commit)
via f9e9ca679889cd4b177c72c588931d4e79300989 (commit)
via c8b32f1adba0c274dc765bb162e3b67a1fa43ca9 (commit)
via 4067c8bb2f66712c4d442fe07d39dbab84afbf27 (commit)
via e97b6bb7995463200de064519d705023f578da96 (commit)
via fed0360031161014bfdbc11d4e38adeaed697150 (commit)
via b560a5942b3b16afb01e1ddc12846030118ffb5a (commit)
via 3a32db0518e8ea18bad9f04df67ad04dcc7afe45 (commit)
via 00c64fb762435af9154d0dde6e3123de3ac482cd (commit)
via 2d1b56424ae5dba08a03f4c09038a50037c7746f (commit)
via a0bd58df7cece9187f8a962cb60eda4ada388857 (commit)
via ec63a9a5baf587354f220b5a3a306dbb36fc7fb6 (commit)
via 86ed7ae9cfb4184f5637a2e478242f0a646ba2e1 (commit)
via 88c9fb48d7c314e16a28555ba7c2c45e6fb24604 (commit)
via 0590b6d313c25728cb13a8738ba3ceeb4e6cdd2c (commit)
via 98a3c1b57bdac399863b18fc100af9264cbc0538 (commit)
via 1b828baa8eb1af112a0e2c2e39750a62316e98df (commit)
via 89886bb661e47985e05643ced62d0da4c14fdd8d (commit)
via beca6b4aefcb8aeb6f54added3bcd025c2196613 (commit)
via 08630dbbb0a9ff4ac81c0ef2b9a45030455fa0a5 (commit)
via 82ad5754e3df260373096d3fc44320bea114b629 (commit)
via 9d9c83375c4fd9d67436030bbfc6f285e271d399 (commit)
via c778e931a645b277b7e18a83f702bb08a0173ffb (commit)
via aa1643faad572dfc6c91d0fac707f4cac4d5b7a6 (commit)
via 48509346472b0e2d84a97a6bbab2e9d3199e38ce (commit)
via b4921859312474c4cd3f1c3d3f78392f85698c41 (commit)
via 560a71919bda37ad20e54efed07183acc5413a8a (commit)
via dce5a5557d44d52455e9cfbafe2ad882e6b930db (commit)
via 5946fa677b76b4b6a16173b9c9f5d835ead4e945 (commit)
via d5deb4caf172b2d8109b7bfab40b1d3e49526cdb (commit)
via 430b6e2f075a64df9e22d1a63dcf2ecade935cdc (commit)
via 5017e9a8d6993c8604722b2385c450b0b54b5cf8 (commit)
via 24862b62d9a01d277ea5c5b99666f04b1951de64 (commit)
via 31745f2d5978086e2d0cc40524efc2e5a45b832b (commit)
via ff7cec44f4ee5c59593278558269bdd41a6e5719 (commit)
via 265fb98ba1f1daab43779b8eaeb6d7f8775dd772 (commit)
via 44da058a8a9f2f7bfca5be797823a6b43bb1865e (commit)
via f25544eeedb66a4f5bb0e0c41387e2e58555daa9 (commit)
via f5c629c8fa3da5089a4260187901b2d36799a2f2 (commit)
via c1e1248555eddb771d776fe614d3078d7dfab26c (commit)
via 2c70372efd0b0ddb8059d5bbe9eb195f024d4df8 (commit)
via f94b033cbf46821f0391b5512a5fdcb70ffbc6e4 (commit)
via b966ebcc19a55c4d01825010ee1e707ba95293e8 (commit)
via 9af419deb6691876904d456faf0da731cb5c96b5 (commit)
via ccb0973459b702152f79ea23841269a379580850 (commit)
via 4d7d67838cb15ce75ceab0664e7b25e4b58d658a (commit)
via ed1a2ad7dfa4d0c87e9ab3fa446acdbc29f6cc24 (commit)
via 9f5232047b3a68299192a7885068430af6f8859b (commit)
via 282cb2ff2eb38f9ad46653c55d0d2974ca1df541 (commit)
via 7a72794ffa529568a9a962a1fa62161dd7f6b906 (commit)
via f15deef54e5c162260abfa2e9deae226f85c4220 (commit)
via b2fe684c2f5ddeceb5e45400c5990ee9106caf5a (commit)
via d0a51e7f91ec66f4b00f6011fa713e2713f24821 (commit)
via 5654a073196ca1ab1b4998d4621ef1ea9f117e61 (commit)
via fadf2a8a91581c95e080de2d4f60e9115e35416b (commit)
via 38ebe09ee1c3ed6906f6a439e5ac9088b665420e (commit)
via 50c262d64042f0350cc887b1f51000294bc74b5c (commit)
via 75622a763d3a717f00efa2cc831108a60f5d2ed8 (commit)
via cad4fb56e85555a48088a9f1e915f32057c2f8ee (commit)
via 9833efc0691e4b80f29a135bfd56fc97e6b0c847 (commit)
via 80b227af0bf08ac9e7bdf645b11e8593b717add3 (commit)
via a2090f1d5702cbc00da7b52a2c1247bd37334ebc (commit)
via f142087f73411145565baf4a3280607413162e06 (commit)
via a77ea5d4e9e7a0edf0be99d8b7e1f98a9b1d490c (commit)
via cb7e1e285e788e48afe26c3d18532299af41bd91 (commit)
via 4838e909a1458198896cd2911488b2d3d442952f (commit)
via fd94c6a10376330c5e87d9b618c422b9f9832708 (commit)
via 961921abc4ca4874d46f39f8db391601b184e6af (commit)
via ae39613c152173ff88cfbaa32f379613c81b5a7d (commit)
via 3d69190c34d28e64ddbd31439968402b8c903475 (commit)
via 3d7a6b9c7e01032d583a61f5aa10065cb31fd5e1 (commit)
via 29a6cb5fb2ceebdd45c343619e9d49fbd9d75558 (commit)
via e32617d592d204e4c5c27ff3ecab77ea4e258cb7 (commit)
via eb92bed532b40a0904f8397ba108d96f152abbbf (commit)
via 5ac6e8b56caad02994fd9352e3427e975e72f44a (commit)
via 710bac35169ec02b73a82cab2c4ce31874a8e440 (commit)
via b27d601b91d09bbaa5c20805331c7efa95eac944 (commit)
via 6b8bf7d03e221e9a1802bcddda66b2b424930042 (commit)
via 6847454e58c65b33e61fe03b16b3aa5a63df8d8d (commit)
via fd7e25adfafa0696f3c13ae5f504ceb7a110c23e (commit)
via 8a8750b7ee739de6208df6b25c9c9c7e11fb0434 (commit)
via f66ffd87e1d460956a8c0bd22700f03b7c3fd0f8 (commit)
via e8a6aee2464c295e476eff03448e39d386747140 (commit)
via d9c1638e3498459671b8257a8c3529569a3a8fd9 (commit)
via b8b87c941fa3b5973dddd0e88220dd435bec699c (commit)
via 0dd898f80907bcb0180858a3a1b2811c803012a9 (commit)
via ba71154440e841836726bbc6b9088f095d14f7d1 (commit)
via 51e26dc96b447d4f60b6aa2c428bfcfe6e6a4d04 (commit)
via f63a6cabd8ecfca7465f1856a3836bdd72ea34f3 (commit)
via 90bb56b150b1c51690dffce834bff4b91f754111 (commit)
via ae8d436d86973d85dbf512ca39167b41dcdb6a7c (commit)
via ecaa3bfbcea80a640778d8f0629e7976d87bde92 (commit)
via bd3faa6a3d1d5628509b725ecb61ef91ee3c2bb0 (commit)
via 4d2adcc95c4a49ff7efa67a1f9e188a9e3e96dca (commit)
via 53a57b7f2a75f4dded32297f3f95d5700f536036 (commit)
via 23d08556872bff7288ccd690a09010769639bff1 (commit)
via fdcffb798707a238b43f24a7dcc725acc4a4c962 (commit)
via 2e7420da8bcb358bb4c8bb6d3a8c42e3fc26d53a (commit)
via 9843e33b58ce12f13fc34fe27c7ef0e4042bd506 (commit)
via b5ca6ac342e49edb73ab75938de20c8fd3f6e8b8 (commit)
via 213e364291339b5e8367de470c02af695065d1ba (commit)
via 37d79010dbca71da368021ca2b8c252deec41a1d (commit)
via 706499470094cbc0e5084e1eac1678f0b349cde4 (commit)
via ffcecb945009df0085dd917cbacd2233d15cc9e8 (commit)
via 1f1131d4f9085063781869fba3a578eb9b3cd7bf (commit)
via b237d10894d0eebe0affdc13bf781cc7f7a15b05 (commit)
via 24e75ceec06dd94df77bec50b61a19d93962c10b (commit)
via 2c8d3ac2d8d62ef77c0f888a7c334689ebcd9b5b (commit)
via a6093a8ef88a556bb0c6094d11863e700ec8242f (commit)
via cb9e761c578cc1de9421eb5e5c1a45c3d9145239 (commit)
via 7fb91131cdfc5778e241cb247e3c2713dfe2ca3a (commit)
via 8aa5e22a0fc048058c3b45d1c2fc76065e0ac8bd (commit)
via 29ff6bebc9eef580849d063cfb58bc8e053a03e6 (commit)
from 950793925a8a21dd9449cf8471d6f75a5fa64901 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit ba4ed797c8eaea19e9db74a402fd7c1dff4b622c
Merge: 9507939 51adf02
Author: Jelte Jansen <jelte at isc.org>
Date: Sun Nov 11 21:19:21 2012 +0100
[experiments/fix-dhcp-test] Merge branch 'master' into experiments/fix-dhcp-test
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 15 +
examples/configure.ac | 9 +-
src/bin/auth/auth_messages.mes | 13 +-
src/bin/bind10/bind10_messages.mes | 20 +-
src/bin/bind10/bind10_src.py.in | 25 +-
src/bin/bindctl/bindcmd.py | 13 +-
src/bin/dhcp6/config_parser.cc | 58 +-
src/bin/dhcp6/dhcp6_messages.mes | 61 +-
src/bin/dhcp6/dhcp6_srv.cc | 348 ++++++++--
src/bin/dhcp6/dhcp6_srv.h | 77 ++-
src/bin/dhcp6/tests/config_parser_unittest.cc | 144 +++-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 707 ++++++++++++++++++--
src/bin/msgq/msgq.py.in | 9 +-
src/bin/stats/tests/b10-stats-httpd_test.py | 3 +
src/lib/dhcp/Makefile.am | 17 +-
src/lib/dhcp/addr_utilities.cc | 12 +-
src/lib/dhcp/alloc_engine.cc | 1 +
src/lib/dhcp/cfgmgr.cc | 2 +-
src/lib/dhcp/dhcp6.h | 9 +-
src/lib/dhcp/duid.cc | 19 +-
src/lib/dhcp/duid.h | 11 +-
src/lib/dhcp/iface_mgr.cc | 9 +
src/lib/dhcp/libdhcp++.cc | 102 +++
src/lib/dhcp/libdhcp++.h | 57 +-
src/lib/dhcp/{tests => }/memfile_lease_mgr.cc | 17 +-
src/lib/dhcp/{tests => }/memfile_lease_mgr.h | 0
src/lib/dhcp/option6_ia.cc | 2 +-
src/lib/dhcp/option_definition.cc | 7 +-
src/lib/dhcp/option_definition.h | 89 ++-
src/lib/dhcp/subnet.cc | 7 +
src/lib/dhcp/subnet.h | 12 +
src/lib/dhcp/tests/Makefile.am | 2 -
src/lib/dhcp/tests/alloc_engine_unittest.cc | 12 +-
src/lib/dhcp/tests/cfgmgr_unittest.cc | 24 +-
src/lib/dhcp/tests/duid_unittest.cc | 8 +
src/lib/dhcp/tests/lease_mgr_unittest.cc | 23 +-
src/lib/dhcp/tests/libdhcp++_unittest.cc | 82 +++
src/lib/dhcp/tests/option_definition_unittest.cc | 50 ++
src/lib/dhcp/tests/subnet_unittest.cc | 26 +
src/lib/dns/Makefile.am | 1 +
src/lib/dns/master_lexer.cc | 80 ++-
src/lib/dns/master_lexer.h | 138 ++++
src/lib/dns/master_lexer_inputsource.cc | 158 +++++
src/lib/dns/master_lexer_inputsource.h | 167 +++++
src/lib/dns/tests/Makefile.am | 4 +-
.../dns/tests/master_lexer_inputsource_unittest.cc | 325 +++++++++
src/lib/dns/tests/master_lexer_unittest.cc | 127 ++++
src/lib/python/isc/bind10/sockcreator.py | 40 +-
src/lib/util/interprocess_sync_file.cc | 6 +-
tests/tools/perfdhcp/command_options.cc | 5 +-
50 files changed, 2859 insertions(+), 294 deletions(-)
rename src/lib/dhcp/{tests => }/memfile_lease_mgr.cc (81%)
rename src/lib/dhcp/{tests => }/memfile_lease_mgr.h (100%)
create mode 100644 src/lib/dns/master_lexer_inputsource.cc
create mode 100644 src/lib/dns/master_lexer_inputsource.h
create mode 100644 src/lib/dns/tests/master_lexer_inputsource_unittest.cc
create mode 100644 src/lib/dns/tests/master_lexer_unittest.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 2b564e1..d69f499 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,18 @@
+501. [func] tomek
+ Added DHCPv6 allocation engine, now used in the processing of DHCPv6
+ messages.
+ (Trac #2414, git b3526430f02aa3dc3273612524d23137b8f1fe87)
+
+500. [bug] jinmei
+ Corrected the autoconf example in the examples directory so it can
+ use the configured path to Boost to check availability of the BIND 10
+ library. Previously the sample configure script could fail if
+ Boost is installed in an uncommon place. Also, it now provides a
+ helper m4 function and example usage for embedding the library
+ path to executable (using linker options like -Wl,-R) to help
+ minimize post-build hassles.
+ (Trac #2356, git 36514ddc884c02a063e166d44319467ce6fb1d8f)
+
499. [func] team
The b10-auth 'loadzone' command now uses the internal thread
introduced in 495 to (re)load a zone in the background, so that
diff --git a/examples/configure.ac b/examples/configure.ac
index ef9cce0..37515d9 100644
--- a/examples/configure.ac
+++ b/examples/configure.ac
@@ -21,7 +21,14 @@ if test "x$BIND10_RPATH" != "x"; then
LDFLAGS="$LDFLAGS $BIND10_RPATH"
fi
-# For the example host program, we require the BIND 10 DNS library
+# For the example host program, we require some socket API library
+# and the BIND 10 DNS library.
+
+# In practice, these are specific to Solaris, but wouldn't do any harm for
+# others except for the checking overhead.
+AC_SEARCH_LIBS(inet_pton, [nsl])
+AC_SEARCH_LIBS(recvfrom, [socket])
+
if test "x$BIND10_DNS_LIB" = "x"; then
AC_MSG_ERROR([unable to find BIND 10 DNS library needed to build 'host'])
fi
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index 221d122..3780499 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -47,10 +47,15 @@ available. It is issued during server startup is an indication that
the initialization is proceeding normally.
% AUTH_CONFIG_LOAD_FAIL load of configuration failed: %1
-An attempt to configure the server with information from the configuration
-database during the startup sequence has failed. (The reason for
-the failure is given in the message.) The server will continue its
-initialization although it may not be configured in the desired way.
+An attempt to configure the server with information from the
+configuration database during the startup sequence has failed. The
+server will continue its initialization although it may not be
+configured in the desired way. The reason for the failure is given in
+the message. One common reason is that the server failed to acquire a
+socket bound to a privileged port (53 for DNS). In that case the
+reason in the log message should show something like "permission
+denied", and the solution would be to restart BIND 10 as a super
+(root) user.
% AUTH_CONFIG_UPDATE_FAIL update of configuration failed: %1
At attempt to update the configuration the server with information
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index ed2a5d9..aba8da5 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -82,6 +82,21 @@ the boss process will try to force them).
A debug message. The configurator is about to perform one task of the plan it
is currently executing on the named component.
+% BIND10_CONNECTING_TO_CC_FAIL failed to connect to configuration/command channel; try -v to see output from msgq
+The boss process tried to connect to the communication channel for
+commands and configuration updates during initialization, but it
+failed. This is a fatal startup error, and process will soon
+terminate after some cleanup. There can be several reasons for the
+failure, but the most likely cause is that the msgq daemon failed to
+start, and the most likely cause of the msgq failure is that it
+doesn't have a permission to create a socket file for the
+communication. To confirm that, you can see debug messages from msgq
+by starting BIND 10 with the -v command line option. If it indicates
+permission problem for msgq, make sure the directory where the socket
+file is to be created is writable for the msgq process. Note that if
+you specify the -u option to change process users, the directory must
+be writable for that user.
+
% BIND10_INVALID_STATISTICS_DATA invalid specification of statistics data specified
An error was encountered when the boss module specified
statistics data which is invalid for the boss specification file.
@@ -94,11 +109,6 @@ and continue running as the specified user, but the user is unknown.
The boss module was not able to start every process it needed to start
during startup, and will now kill the processes that did get started.
-% BIND10_KILL_PROCESS killing process %1
-The boss module is sending a kill signal to process with the given name,
-as part of the process of killing all started processes during a failed
-startup, as described for BIND10_KILLING_ALL_PROCESSES
-
% BIND10_LOST_SOCKET_CONSUMER consumer %1 of sockets disconnected, considering all its sockets closed
A connection from one of the applications which requested a socket was
closed. This means the application has terminated, so all the sockets it was
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 45a2ccb..36ad760 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -331,11 +331,7 @@ class BoB:
each one. It then clears that list.
"""
logger.info(BIND10_KILLING_ALL_PROCESSES)
-
- for pid in self.components:
- logger.info(BIND10_KILL_PROCESS, self.components[pid].name())
- self.components[pid].kill(True)
- self.components = {}
+ self.__kill_children(True)
def _read_bind10_config(self):
"""
@@ -427,6 +423,7 @@ class BoB:
while self.cc_session is None:
# if we have been trying for "a while" give up
if (time.time() - cc_connect_start) > 5:
+ logger.error(BIND10_CONNECTING_TO_CC_FAIL)
raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
# try to connect, and if we can't wait a short while
@@ -1145,6 +1142,21 @@ def main():
options = parse_args()
+ # Announce startup. Making this is the first log message.
+ try:
+ logger.info(BIND10_STARTING, VERSION)
+ except RuntimeError as e:
+ sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
+ str(e))
+ sys.stderr.write("""\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user. Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user.
+""")
+ sys.exit(1)
+
# Check user ID.
setuid = None
setgid = None
@@ -1177,9 +1189,6 @@ def main():
logger.fatal(BIND10_INVALID_USER, options.user)
sys.exit(1)
- # Announce startup.
- logger.info(BIND10_STARTING, VERSION)
-
# Create wakeup pipe for signal handlers
wakeup_pipe = os.pipe()
signal.set_wakeup_fd(wakeup_pipe[1])
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index b4e71bf..37b74e7 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -133,7 +133,18 @@ class BindCmdInterpreter(Cmd):
return digest
def run(self):
- '''Parse commands from user and send them to cmdctl. '''
+ '''Parse commands from user and send them to cmdctl.'''
+
+ # Show helper warning about a well known issue. We only do this
+ # when stdin is attached to a terminal, because otherwise it doesn't
+ # matter and is just noisy, and could even be harmful if the output
+ # is processed by a script that expects a specific format.
+ if my_readline == sys.stdin.readline and sys.stdin.isatty():
+ sys.stdout.write("""\
+WARNING: Python readline module isn't available, so the command line editor
+ (including command history management) does not work. See BIND 10
+ guide for more details.\n\n""")
+
try:
if not self.login_to_cmdctl():
return 1
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 0fca770..e330e19 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -26,6 +26,7 @@
#include <cc/data.h>
#include <config/ccsession.h>
#include <log/logger_support.h>
+#include <dhcp/libdhcp++.h>
#include <dhcp/triplet.h>
#include <dhcp/pool.h>
#include <dhcp/subnet.h>
@@ -616,7 +617,11 @@ private:
<< " spaces");
}
+ // Get option data from the configuration database ('data' field).
+ // Option data is specified by the user as case insensitive string
+ // of hexadecimal digits for each option.
std::string option_data = getStringParam("data");
+ // Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
try {
util::encode::decodeHex(option_data, binary);
@@ -624,16 +629,49 @@ private:
isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
<< " string of hexadecimal digits: " << option_data);
}
-
- // Create the actual option.
- // @todo Currently we simply create dhcp::Option instance here but we will
- // need to use dedicated factory functions once the option definitions are
- // created for all options.
- OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
- binary));
-
- // If option is created succesfully, add it to the storage.
- options_->push_back(option);
+ // Get all existing DHCPv6 option definitions. The one that matches
+ // our option will be picked and used to create it.
+ OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
+ // Get search index #1. It allows searching for options definitions
+ // using option type value.
+ const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+ // Get all option definitions matching option code we want to create.
+ const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+ size_t num_defs = std::distance(range.first, range.second);
+ OptionPtr option;
+ // Currently we do not allow duplicated definitions and if there are
+ // any duplicates we issue internal server error.
+ if (num_defs > 1) {
+ isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
+ << " supported to initialize multiple option definitions"
+ << " for the same option code. This will be supported once"
+ << " there option spaces are implemented.");
+ } else if (num_defs == 0) {
+ // @todo We have a limited set of option definitions intiialized at the moment.
+ // In the future we want to initialize option definitions for all options.
+ // Consequently error will be issued if option definition does not exist
+ // for a particular option code. For now it is ok to create generic option
+ // if definition does not exist.
+ OptionPtr option(new Option(Option::V6, static_cast<uint16_t>(option_code),
+ binary));
+ // If option is created succesfully, add it to the storage.
+ options_->push_back(option);
+ } else {
+ // We have exactly one option definition for the particular option code.
+ // use it to create option instance.
+ const OptionDefinitionPtr& def = *(range.first);
+ // getFactory should never return NULL pointer.
+ Option::Factory* factory = def->getFactory();
+ assert(factory != NULL);
+ try {
+ OptionPtr option = factory(Option::V6, option_code, binary);
+ options_->push_back(option);
+ } catch (const isc::Exception& ex) {
+ isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
+ << " option definition (code " << option_code << "): "
+ << ex.what());
+ }
+ }
}
/// @brief Get a parameter from the strings storage.
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index ffca69a..5f9cd02 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -30,6 +30,11 @@ from the BIND 10 control system by the IPv6 DHCP server.
A debug message indicating that the IPv6 DHCP server has received an
updated configuration from the BIND 10 configuration system.
+% DHCP6_DB_BACKEND_STARTED Lease database started (backend type: %1)
+This informational message is printed every time DHCPv6 is started.
+It indicates what database backend type is being to store lease and
+other information.
+
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
@@ -42,6 +47,27 @@ interfaces and is therefore shutting down.
A debug message issued during startup, this indicates that the IPv6 DHCP
server is about to open sockets on the specified port.
+% DHCP6_LEASE_ADVERT Lease %1 advertised (client duid=%2, iaid=%3)
+This debug message indicates that the server successfully advertised
+a lease. It is up to the client to choose one server out of othe advertised
+and continue allocation with that server. This is a normal behavior and
+indicates successful operation.
+
+% DHCP6_LEASE_ALLOC lease %1 has been allocated (client duid=%2, iaid=%3)
+This debug message indicates that the server successfully granted (in
+response to client's REQUEST message) a lease. This is a normal behavior
+and incicates successful operation.
+
+% DHCP6_LEASE_ADVERT_FAIL failed to advertise a lease for client duid=%1, iaid=%2
+This message indicates that the server failed to advertise (in response to
+received SOLICIT) a lease for a given client. There may be many reasons for
+such failure. Each specific failure is logged in a separate log entry.
+
+% DHCP6_LEASE_ALLOC_FAIL failed to grant a lease for client duid=%1, iaid=%2
+This message indicates that the server failed to grant (in response to
+received REQUEST) a lease for a given client. There may be many reasons for
+such failure. Each specific failure is logged in a separate log entry.
+
% DHCP6_PACKET_PARSE_FAIL failed to parse incoming packet
The IPv6 DHCP server has received a packet that it is unable to interpret.
@@ -50,7 +76,7 @@ The IPv6 DHCP server tried to receive a packet but an error
occured during this attempt. The reason for the error is included in
the message.
-% DHCP6_PACKET_RECEIVED %1 (type %2) packet received
+% DHCP6_PACKET_RECEIVED %1 packet received
A debug message noting that the server has received the specified type
of packet. Note that a packet marked as UNKNOWN may well be a valid
DHCP packet, just a type not expected by the server (e.g. it will report
@@ -66,10 +92,15 @@ This error is output if the server failed to assemble the data to be
returned to the client into a valid packet. The reason is most likely
to be to a programming error: please raise a bug report.
-% DHCP6_QUERY_DATA received packet length %1, data length %2, data is <%3>
+% DHCP6_PROCESS_IA_NA_REQUEST server is processing IA_NA option (duid=%1, iaid=%2, hint=%3)
+This is a debug message that indicates a processing of received IA_NA
+option. It may optionally contain an address that may be used by the server
+as a hint for possible requested address.
+
+% DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3
A debug message listing the data received from the client or relay.
-% DHCP6_RESPONSE_DATA responding with packet type %1 data is <%2>
+% DHCP6_RESPONSE_DATA responding with packet type %1 data is %2
A debug message listing the data returned to the client.
% DHCP6_SERVER_FAILED server failed: %1
@@ -110,6 +141,28 @@ This is a debug message issued during the IPv6 DHCP server startup.
It lists some information about the parameters with which the server
is running.
+% DHCP6_SUBNET_SELECTED the %1 subnet was selected for client assignment
+This is a debug message informing that a given subnet was selected. It will
+be used for address and option assignment. This is one of the early steps
+in the processing of incoming client message.
+
+% DHCP6_SUBNET_SELECTION_FAILED failed to select a subnet for incoming packet, src=%1 type=%2
+This warning message is output when a packet was received from a subnet for
+which the DHCPv6 server has not been configured. The cause is most likely due
+to a misconfiguration of the server. The packet processing will continue, but
+the response will only contain generic configuration parameters and no
+addresses or prefixes.
+
+% DHCP6_NO_SUBNET_DEF_OPT failed to find subnet for address %1 when adding default options
+This warning message indicates that when attempting to add default options to a response,
+the server found that it was not configured to support the subnet from which the DHCPv6
+request was received. The packet has been ignored.
+
+% DHCP6_NO_SUBNET_REQ_OPT failed to find subnet for address %1 when adding requested options
+This warning message indicates that when attempting to add requested options to a response,
+the server found that it was not configured to support the subnet from which the DHCPv6
+request was received. The packet has been ignored.
+
% DHCP6_CONFIG_LOAD_FAIL failed to load configuration: %1
This critical error message indicates that the initial DHCPv6
configuration has failed. The server will start, but nothing will be
@@ -120,7 +173,7 @@ This is a debug message that is issued every time the server receives a
configuration. That happens start up and also when a server configuration
change is committed by the administrator.
-% DHCP6_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+% DHCP6_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
This is an informational message reporting that the configuration has
been extended to include the specified subnet.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 7c57a61..7b1f721 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -20,13 +20,27 @@
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/libdhcp++.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_ia.h>
+#include <dhcp/option6_int_array.h>
#include <dhcp/pkt6.h>
+#include <dhcp/subnet.h>
+#include <dhcp/cfgmgr.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
+#include <dhcp/duid.h>
+#include <dhcp/lease_mgr.h>
+#include <dhcp/cfgmgr.h>
+#include <dhcp/option6_iaaddr.h>
+
+// @todo: Replace this with MySQL_LeaseMgr (or a LeaseMgr factory)
+// once it is merged
+#include <dhcp/memfile_lease_mgr.h>
+
+#include <boost/foreach.hpp>
using namespace isc;
using namespace isc::asiolink;
@@ -34,50 +48,59 @@ using namespace isc::dhcp;
using namespace isc::util;
using namespace std;
-const std::string HARDCODED_LEASE = "2001:db8:1::1234:abcd";
-const uint32_t HARDCODED_T1 = 1500; // in seconds
-const uint32_t HARDCODED_T2 = 2600; // in seconds
-const uint32_t HARDCODED_PREFERRED_LIFETIME = 3600; // in seconds
-const uint32_t HARDCODED_VALID_LIFETIME = 7200; // in seconds
-const std::string HARDCODED_DNS_SERVER = "2001:db8:1::1";
+namespace isc {
+namespace dhcp {
-Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
- if (port == 0) {
- // used for testing purposes. Some tests, e.g. configuration parser,
- // require Dhcpv6Srv object, but they don't really need it to do
- // anything. This speed up and simplifies the tests.
- return;
- }
+Dhcpv6Srv::Dhcpv6Srv(uint16_t port) : alloc_engine_(), serverid_(),
+ shutdown_(false) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
- // First call to instance() will create IfaceMgr (it's a singleton)
- // it may throw something if things go wrong
+ // Initialize objects required for DHCP server operation.
try {
-
- if (IfaceMgr::instance().countIfaces() == 0) {
- LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
- shutdown_ = true;
- return;
+ // Initialize standard DHCPv6 option definitions. This function
+ // may throw bad_alloc if system goes out of memory during the
+ // creation if option definitions. It may also throw isc::Unexpected
+ // if definitions are wrong. This would mean error in implementation.
+ initStdOptionDefs();
+
+ // Port 0 is used for testing purposes. It means that the server should
+ // not open any sockets at all. Some tests, e.g. configuration parser,
+ // require Dhcpv6Srv object, but they don't really need it to do
+ // anything. This speed up and simplifies the tests.
+ if (port > 0) {
+ if (IfaceMgr::instance().countIfaces() == 0) {
+ LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
+ shutdown_ = true;
+ return;
+ }
+ IfaceMgr::instance().openSockets6(port);
}
- IfaceMgr::instance().openSockets6(port);
-
setServerID();
- /// @todo: instantiate LeaseMgr here once it is imlpemented.
-
} catch (const std::exception &e) {
LOG_ERROR(dhcp6_logger, DHCP6_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
return;
}
- shutdown_ = false;
+ // Instantiate LeaseMgr
+ // @todo: Replace this with MySQL_LeaseMgr (or a LeaseMgr factory)
+ // once it is merged
+ new isc::dhcp::test::Memfile_LeaseMgr("");
+
+ LOG_INFO(dhcp6_logger, DHCP6_DB_BACKEND_STARTED)
+ .arg(LeaseMgr::instance().getName());
+
+ // Instantiate allocation engine
+ alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
}
Dhcpv6Srv::~Dhcpv6Srv() {
IfaceMgr::instance().closeSockets();
+
+ LeaseMgr::destroy_instance();
}
void Dhcpv6Srv::shutdown() {
@@ -87,7 +110,12 @@ void Dhcpv6Srv::shutdown() {
bool Dhcpv6Srv::run() {
while (!shutdown_) {
- /// @todo: calculate actual timeout once we have lease database
+ /// @todo: calculate actual timeout to the next event (e.g. lease
+ /// expiration) once we have lease database. The idea here is that
+ /// it is possible to do everything in a single process/thread.
+ /// For now, we are just calling select for 1000 seconds. There
+ /// were some issues reported on some systems when calling select()
+ /// with too large values. Unfortunately, I don't recall the details.
int timeout = 1000;
// client's message and server's response
@@ -107,10 +135,9 @@ bool Dhcpv6Srv::run() {
continue;
}
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
- .arg(serverReceivedPacketName(query->getType()))
- .arg(query->getType());
+ .arg(serverReceivedPacketName(query->getType()));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
- .arg(query->getType())
+ .arg(static_cast<int>(query->getType()))
.arg(query->getBuffer().getLength())
.arg(query->toText());
@@ -196,7 +223,7 @@ void Dhcpv6Srv::setServerID() {
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
- // let's find suitable interface
+ // Let's find suitable interface.
for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
// All the following checks could be merged into one multi-condition
@@ -217,17 +244,17 @@ void Dhcpv6Srv::setServerID() {
continue;
}
- // let's don't use loopback
+ // Let's don't use loopback.
if (iface->flag_loopback_) {
continue;
}
- // let's skip downed interfaces. It is better to use working ones.
+ // Let's skip downed interfaces. It is better to use working ones.
if (!iface->flag_up_) {
continue;
}
- // some interfaces (like lo on Linux) report 6-bytes long
+ // Some interfaces (like lo on Linux) report 6-bytes long
// MAC adress 00:00:00:00:00:00. Let's not use such weird interfaces
// to generate DUID.
if (isRangeZero(iface->getMac(), iface->getMac() + iface->getMacLen())) {
@@ -243,37 +270,37 @@ void Dhcpv6Srv::setServerID() {
seconds -= DUID_TIME_EPOCH;
OptionBuffer srvid(8 + iface->getMacLen());
- writeUint16(DUID_LLT, &srvid[0]);
+ writeUint16(DUID::DUID_LLT, &srvid[0]);
writeUint16(HWTYPE_ETHERNET, &srvid[2]);
writeUint32(static_cast<uint32_t>(seconds), &srvid[4]);
- memcpy(&srvid[0]+8, iface->getMac(), iface->getMacLen());
+ memcpy(&srvid[0] + 8, iface->getMac(), iface->getMacLen());
serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
srvid.begin(), srvid.end()));
return;
}
- // if we reached here, there are no suitable interfaces found.
+ // If we reached here, there are no suitable interfaces found.
// Either interface detection is not supported on this platform or
// this is really weird box. Let's use DUID-EN instead.
// See Section 9.3 of RFC3315 for details.
OptionBuffer srvid(12);
- writeUint16(DUID_EN, &srvid[0]);
+ writeUint16(DUID::DUID_EN, &srvid[0]);
writeUint32(ENTERPRISE_ID_ISC, &srvid[2]);
// Length of the identifier is company specific. I hereby declare
// ISC "standard" of 6 bytes long pseudo-random numbers.
srandom(time(NULL));
- fillRandom(&srvid[6],&srvid[12]);
+ fillRandom(&srvid[6], &srvid[12]);
serverid_ = OptionPtr(new Option(Option::V6, D6O_SERVERID,
srvid.begin(), srvid.end()));
}
void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
- // add client-id
- boost::shared_ptr<Option> clientid = question->getOption(D6O_CLIENTID);
+ // Add client-id.
+ OptionPtr clientid = question->getOption(D6O_CLIENTID);
if (clientid) {
answer->addOption(clientid);
}
@@ -281,52 +308,221 @@ void Dhcpv6Srv::copyDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// TODO: Should throw if there is no client-id (except anonymous INF-REQUEST)
}
-void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
- // TODO: question is currently unused, but we need it at least to know
- // message type we are answering
-
+void Dhcpv6Srv::appendDefaultOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// add server-id
answer->addOption(getServerID());
+
+ // Get the subnet object. It holds options to be sent to the client
+ // that belongs to the particular subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+ // Warn if subnet is not supported and quit.
+ if (!subnet) {
+ LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_DEF_OPT)
+ .arg(question->getRemoteAddr().toText());
+ return;
+ }
+
+}
+
+void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+ // Get the subnet for a particular address.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+ if (!subnet) {
+ LOG_WARN(dhcp6_logger, DHCP6_NO_SUBNET_REQ_OPT)
+ .arg(question->getRemoteAddr().toText());
+ return;
+ }
+
+ // Client requests some options using ORO option. Try to
+ // get this option from client's message.
+ boost::shared_ptr<Option6IntArray<uint16_t> > option_oro =
+ boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(question->getOption(D6O_ORO));
+ // Option ORO not found. Don't do anything then.
+ if (!option_oro) {
+ return;
+ }
+ // Get the list of options that client requested.
+ const std::vector<uint16_t>& requested_opts = option_oro->getValues();
+ // Get the list of options configured for a subnet.
+ const Subnet::OptionContainer& options = subnet->getOptions();
+ const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ // Try to match requested options with those configured for a subnet.
+ // If match is found, append configured option to the answer message.
+ BOOST_FOREACH(uint16_t opt, requested_opts) {
+ const Subnet::OptionContainerTypeRange& range = idx.equal_range(opt);
+ BOOST_FOREACH(Subnet::OptionDescriptor desc, range) {
+ answer->addOption(desc.option);
+ }
+ }
}
+OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
-void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& /*question*/, Pkt6Ptr& answer) {
- // TODO: question is currently unused, but we need to extract ORO from it
- // and act on its content. Now we just send DNS-SERVERS option.
+ // @todo: Implement Option6_StatusCode and rewrite this code here
+ vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
+ data.insert(data.begin(), static_cast<uint8_t>(code % 256));
+ data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
+ OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
+ return (status);
+}
+
+Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
- // add dns-servers option
- boost::shared_ptr<Option> dnsservers(new Option6AddrLst(D6O_NAME_SERVERS,
- IOAddress(HARDCODED_DNS_SERVER)));
- answer->addOption(dnsservers);
+ return (subnet);
}
void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
- /// TODO Rewrite this once LeaseManager is implemented.
-
- // answer client's IA (this is mostly a dummy,
- // so let's answer only first IA and hope there is only one)
- boost::shared_ptr<Option> ia_opt = question->getOption(D6O_IA_NA);
- if (ia_opt) {
- // found IA
- Option* tmp = ia_opt.get();
- Option6IA* ia_req = dynamic_cast<Option6IA*>(tmp);
- if (ia_req) {
- boost::shared_ptr<Option6IA>
- ia_rsp(new Option6IA(D6O_IA_NA, ia_req->getIAID()));
- ia_rsp->setT1(HARDCODED_T1);
- ia_rsp->setT2(HARDCODED_T2);
- boost::shared_ptr<Option6IAAddr>
- addr(new Option6IAAddr(D6O_IAADDR,
- IOAddress(HARDCODED_LEASE),
- HARDCODED_PREFERRED_LIFETIME,
- HARDCODED_VALID_LIFETIME));
- ia_rsp->addOption(addr);
- answer->addOption(ia_rsp);
+
+ // We need to allocate addresses for all IA_NA options in the client's
+ // question (i.e. SOLICIT or REQUEST) message.
+
+ // We need to select a subnet the client is connected in.
+ Subnet6Ptr subnet = selectSubnet(question);
+ if (subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+ .arg(subnet->toText());
+ } else {
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SUBNET_SELECTION_FAILED);
+ }
+
+ // @todo: We should implement Option6Duid some day, but we can do without it
+ // just fine for now
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ DuidPtr duid;
+ OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
+ if (opt_duid) {
+ duid = DuidPtr(new DUID(opt_duid->getData()));
+ }
+
+ // Now that we have all information about the client, let's iterate over all
+ // received options and handle IA_NA options one by one and store our
+ // responses in answer message (ADVERTISE or REPLY).
+ //
+ // @todo: expand this to cover IA_PD and IA_TA once we implement support for
+ // prefix delegation and temporary addresses.
+ for (Option::OptionCollection::iterator opt = question->options_.begin();
+ opt != question->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = handleIA_NA(subnet, duid, question,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ answer->addOption(answer_opt);
+ }
+ break;
+ }
+ default:
+ break;
}
}
}
+OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, Pkt6Ptr question,
+ boost::shared_ptr<Option6IA> ia) {
+ // If there is no subnet selected for handling this IA_NA, the only thing to do left is
+ // to say that we are sorry, but the user won't get an address. As a convenience, we
+ // use a different status text to indicate that (compare to the same status code,
+ // but different wording below)
+ if (!subnet) {
+ // Create empty IA_NA option with IAID matching the request.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail, "Sorry, no subnet available."));
+ return (ia_rsp);
+ }
+
+ // Check if the client sent us a hint in his IA_NA. Clients may send an
+ // address in their IA_NA options as a suggestion (e.g. the last address
+ // they used before).
+ boost::shared_ptr<Option6IAAddr> hintOpt = boost::dynamic_pointer_cast<Option6IAAddr>
+ (ia->getOption(D6O_IAADDR));
+ IOAddress hint("::");
+ if (hintOpt) {
+ hint = hintOpt->getAddress();
+ }
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PROCESS_IA_NA_REQUEST)
+ .arg(duid?duid->toText():"(no-duid)").arg(ia->getIAID())
+ .arg(hintOpt?hint.toText():"(no hint)");
+
+ // "Fake" allocation is processing of SOLICIT message. We pretend to do an
+ // allocation, but we do not put the lease in the database. That is ok,
+ // because we do not guarantee that the user will get that exact lease. If
+ // the user selects this server to do actual allocation (i.e. sends REQUEST)
+ // it should include this hint. That will help us during the actual lease
+ // allocation.
+ bool fake_allocation = false;
+ if (question->getType() == DHCPV6_SOLICIT) {
+ /// @todo: Check if we support rapid commit
+ fake_allocation = true;
+ }
+
+ // Use allocation engine to pick a lease for this client. Allocation engine
+ // will try to honour the hint, but it is just a hint - some other address
+ // may be used instead. If fake_allocation is set to false, the lease will
+ // be inserted into the LeaseMgr as well.
+ Lease6Ptr lease = alloc_engine_->allocateAddress6(subnet, duid, ia->getIAID(),
+ hint, fake_allocation);
+
+ // Create IA_NA that we will put in the response.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ if (lease) {
+ // We have a lease! Let's wrap its content into IA_NA option
+ // with IAADDR suboption.
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation?
+ DHCP6_LEASE_ADVERT:DHCP6_LEASE_ALLOC)
+ .arg(lease->addr_.toText())
+ .arg(duid?duid->toText():"(no-duid)")
+ .arg(ia->getIAID());
+
+ ia_rsp->setT1(subnet->getT1());
+ ia_rsp->setT2(subnet->getT2());
+
+ boost::shared_ptr<Option6IAAddr>
+ addr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_,
+ lease->preferred_lft_,
+ lease->valid_lft_));
+ ia_rsp->addOption(addr);
+
+ // It would be possible to insert status code=0(success) as well,
+ // but this is considered waste of bandwidth as absence of status
+ // code is considered a success.
+ } else {
+ // Allocation engine did not allocate a lease. The engine logged
+ // cause of that failure. The only thing left is to insert
+ // status code to pass the sad news to the client.
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, fake_allocation?
+ DHCP6_LEASE_ADVERT_FAIL:DHCP6_LEASE_ALLOC_FAIL)
+ .arg(duid?duid->toText():"(no-duid)")
+ .arg(ia->getIAID())
+ .arg(subnet->toText());
+
+ ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+ "Sorry, no address could be allocated."));
+ }
+ return (ia_rsp);
+}
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
+
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
copyDefaultOptions(solicit, advertise);
@@ -428,3 +624,11 @@ Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
}
return (UNKNOWN);
}
+
+void
+Dhcpv6Srv::initStdOptionDefs() {
+ LibDHCP::initStdOptionDefs(Option::V6);
+}
+
+};
+};
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 1cb3236..6574226 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -15,23 +15,33 @@
#ifndef DHCPV6_SRV_H
#define DHCPV6_SRV_H
+#include <iostream>
+
#include <boost/noncopyable.hpp>
+#include <dhcp/alloc_engine.h>
#include <dhcp/dhcp6.h>
-#include <dhcp/pkt6.h>
+#include <dhcp/duid.h>
#include <dhcp/option.h>
-#include <iostream>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/pkt6.h>
+#include <dhcp/subnet.h>
namespace isc {
namespace dhcp {
/// @brief DHCPv6 server service.
///
-/// This singleton class represents DHCPv6 server. It contains all
+/// This class represents DHCPv6 server. It contains all
/// top-level methods and routines necessary for server operation.
/// In particular, it instantiates IfaceMgr, loads or generates DUID
/// that is going to be used as server-identifier, receives incoming
/// packets, processes them, manages leases assignment and generates
/// appropriate responses.
+///
+/// @note Only one instance of this class is instantated as it encompasses
+/// the whole operation of the server. Nothing, however, enforces the
+/// singleton status of the object.
class Dhcpv6Srv : public boost::noncopyable {
public:
@@ -52,7 +62,7 @@ public:
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
- /// @brief Returns server-intentifier option
+ /// @brief Returns server-intentifier option.
///
/// @return server-id option
OptionPtr getServerID() { return serverid_; }
@@ -70,7 +80,7 @@ public:
/// @brief Instructs the server to shut down.
void shutdown();
- /// @brief Return textual type of packet received by server
+ /// @brief Return textual type of packet received by server.
///
/// Returns the name of valid packet received by the server (e.g. SOLICIT).
/// If the packet is unknown - or if it is a valid DHCP packet but not one
@@ -147,7 +157,38 @@ protected:
/// @param infRequest message received from client
Pkt6Ptr processInfRequest(const Pkt6Ptr& infRequest);
- /// @brief Copies required options from client message to server answer
+ /// @brief Creates status-code option.
+ ///
+ /// @param code status code value (see RFC3315)
+ /// @param text textual explanation (will be sent in status code option)
+ /// @return status-code option
+ OptionPtr createStatusCode(uint16_t code, const std::string& text);
+
+ /// @brief Selects a subnet for a given client's packet.
+ ///
+ /// @param question client's message
+ /// @return selected subnet (or NULL if no suitable subnet was found)
+ isc::dhcp::Subnet6Ptr selectSubnet(const Pkt6Ptr& question);
+
+ /// @brief Processes IA_NA option (and assigns addresses if necessary).
+ ///
+ /// Generates response to IA_NA. This typically includes selecting (and
+ /// allocating a lease in case of REQUEST) a lease and creating
+ /// IAADDR option. In case of allocation failure, it may contain
+ /// status code option with non-zero status, denoting cause of the
+ /// allocation failure.
+ ///
+ /// @param subnet subnet the client is connected to
+ /// @param duid client's duid
+ /// @param question client's message (typically SOLICIT or REQUEST)
+ /// @param ia pointer to client's IA_NA option (client's request)
+ /// @return IA_NA option (server's response)
+ OptionPtr handleIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
+ const isc::dhcp::DuidPtr& duid,
+ isc::dhcp::Pkt6Ptr question,
+ boost::shared_ptr<Option6IA> ia);
+
+ /// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
/// to client's messages (SOLICIT, REQUEST, RENEW, REBIND, DECLINE, RELEASE).
@@ -170,8 +211,6 @@ protected:
/// @brief Appends requested options to server's answer.
///
/// Appends options requested by client to the server's answer.
- /// TODO: This method is currently a stub. It just appends DNS-SERVERS
- /// option.
///
/// @param question client's message
/// @param answer server's message (options will be added here)
@@ -199,10 +238,28 @@ protected:
/// interfaces for new DUID generation are detected.
void setServerID();
- /// server DUID (to be sent in server-identifier option)
+ /// @brief Initializes option definitions for standard options.
+ ///
+ /// Each standard option's format is described by the
+ /// dhcp::OptionDefinition object. This function creates such objects
+ /// for each standard DHCPv6 option.
+ ///
+ /// @todo list thrown exceptions.
+ /// @todo extend this function to cover all standard options. Currently
+ /// it is limited to critical options only.
+ void initStdOptionDefs();
+
+private:
+ /// @brief Allocation Engine.
+ /// Pointer to the allocation engine that we are currently using
+ /// It must be a pointer, because we will support changing engines
+ /// during normal operation (e.g. to use different allocators)
+ boost::shared_ptr<AllocEngine> alloc_engine_;
+
+ /// Server DUID (to be sent in server-identifier option)
boost::shared_ptr<isc::dhcp::Option> serverid_;
- /// indicates if shutdown is in progress. Setting it to true will
+ /// Indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
volatile bool shutdown_;
};
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 489e3c1..780b5db 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -17,14 +17,18 @@
#include <fstream>
#include <sstream>
+#include <boost/foreach.hpp>
+
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp6/config_parser.h>
#include <config/ccsession.h>
+#include <dhcp/libdhcp++.h>
#include <dhcp/subnet.h>
#include <dhcp/cfgmgr.h>
+#include <dhcp/option6_ia.h>
using namespace std;
using namespace isc;
@@ -37,16 +41,18 @@ namespace {
class Dhcp6ParserTest : public ::testing::Test {
public:
- Dhcp6ParserTest()
- :rcode_(-1) {
- // Open port 0 means to not do anything at all. We don't want to
+ Dhcp6ParserTest() :rcode_(-1), srv_(0) {
+ // srv_(0) means to not open any sockets. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
- srv_ = new Dhcpv6Srv(0);
+
+ // Create instances of option definitions and put them into storage.
+ // This is normally initialized by the server when calling run()
+ // run() function.
+ LibDHCP::initStdOptionDefs(Option::V6);
}
~Dhcp6ParserTest() {
- delete srv_;
};
/// @brief Create the simple configuration with single option.
@@ -60,6 +66,24 @@ public:
/// param value.
std::string createConfigWithOption(const std::string& param_value,
const std::string& parameter) {
+ std::map<std::string, std::string> params;
+ if (parameter == "name") {
+ params["name"] = param_value;
+ params["code"] = "80";
+ params["data"] = "AB CDEF0105";
+ } else if (parameter == "code") {
+ params["name"] = "option_foo";
+ params["code"] = param_value;
+ params["data"] = "AB CDEF0105";
+ } else if (parameter == "data") {
+ params["name"] = "option_foo";
+ params["code"] = "80";
+ params["data"] = param_value;
+ }
+ return (createConfigWithOption(params));
+ }
+
+ std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
@@ -69,21 +93,21 @@ public:
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {";
- if (parameter == "name") {
- stream <<
- " \"name\": \"" << param_value << "\","
- " \"code\": 80,"
- " \"data\": \"AB CDEF0105\"";
- } else if (parameter == "code") {
- stream <<
- " \"name\": \"option_foo\","
- " \"code\": " << param_value << ","
- " \"data\": \"AB CDEF0105\"";
- } else if (parameter == "data") {
- stream <<
- " \"name\": \"option_foo\","
- " \"code\": 80,"
- " \"data\": \"" << param_value << "\"";
+ bool first = true;
+ typedef std::pair<std::string, std::string> ParamPair;
+ BOOST_FOREACH(ParamPair param, params) {
+ if (!first) {
+ stream << ", ";
+ } else {
+ first = false;
+ }
+ if (param.first == "name") {
+ stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "code") {
+ stream << "\"code\": " << param.second << "";
+ } else if (param.first == "data") {
+ stream << "\"data\": \"" << param.second << "\"";
+ }
}
stream <<
" } ]"
@@ -107,7 +131,7 @@ public:
ConstElementPtr x;
std::string config = createConfigWithOption(param_value, parameter);
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(1, rcode_);
@@ -153,7 +177,7 @@ public:
EXPECT_TRUE(memcmp(expected_data, data, expected_data_len));
}
- Dhcpv6Srv* srv_;
+ Dhcpv6Srv srv_;
int rcode_;
ConstElementPtr comment_;
@@ -166,7 +190,7 @@ TEST_F(Dhcp6ParserTest, version) {
ConstElementPtr x;
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
Element::fromJSON("{\"version\": 0}")));
// returned value must be 0 (configuration accepted)
@@ -181,7 +205,7 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
ConstElementPtr x;
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_,
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_,
Element::fromJSON("{\"bogus\": 5}")));
// returned value must be 1 (configuration parse error)
@@ -197,7 +221,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
- EXPECT_NO_THROW(status = configureDhcp6Server(*srv_,
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
Element::fromJSON("{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
@@ -229,7 +253,7 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// check if returned status is OK
ASSERT_TRUE(status);
@@ -268,7 +292,7 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value should be 0 (configuration success)
ASSERT_TRUE(status);
@@ -301,7 +325,7 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(status = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value must be 2 (values error)
// as the pool does not belong to that subnet
@@ -329,7 +353,7 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
// returned value must be 1 (configuration parse error)
ASSERT_TRUE(x);
@@ -371,7 +395,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
@@ -446,7 +470,7 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
@@ -512,7 +536,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
@@ -630,7 +654,7 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
std::string config = createConfigWithOption("0a0b0C0D", "data");
ElementPtr json = Element::fromJSON(config);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv_, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
ASSERT_EQ(0, rcode_);
@@ -658,4 +682,58 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
}
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp6ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "OPTION_IA_NA";
+ // Option code 3 means OPTION_IA_NA.
+ params["code"] = "3";
+ params["data"] = "ABCDEF01 02030405 06070809";
+
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ const Subnet::OptionContainer& options = subnet->getOptions();
+ ASSERT_EQ(1, options.size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(D6O_IA_NA);
+ // Expect single option with the code equal to IA_NA option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option6IA> optionIA =
+ boost::dynamic_pointer_cast<Option6IA>(option);
+ // If cast is unsuccessful than option returned was of a
+ // differnt type than Option6IA. This is wrong.
+ ASSERT_TRUE(optionIA);
+ // If cast was successful we may use accessors exposed by
+ // Option6IA to validate that the content of this option
+ // has been set correctly.
+ EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
+ EXPECT_EQ(0x02030405, optionIA->getT1());
+ EXPECT_EQ(0x06070809, optionIA->getT2());
+}
+
};
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 2028706..f1cf7de 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -13,73 +13,218 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
-#include <iostream>
+
#include <fstream>
+#include <iostream>
#include <sstream>
-#include <arpa/inet.h>
#include <gtest/gtest.h>
+#include <asiolink/io_address.h>
+#include <boost/scoped_ptr.hpp>
+#include <config/ccsession.h>
+#include <dhcp/cfgmgr.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/duid.h>
+#include <dhcp/lease_mgr.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_int_array.h>
+#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
-#include <boost/scoped_ptr.hpp>
-using namespace std;
using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::util;
+using namespace std;
// namespace has to be named, because friends are defined in Dhcpv6Srv class
// Maybe it should be isc::test?
namespace {
class NakedDhcpv6Srv: public Dhcpv6Srv {
- // "naked" Interface Manager, exposes internal fields
+ // "naked" Interface Manager, exposes internal members
public:
- NakedDhcpv6Srv():Dhcpv6Srv(DHCP6_SERVER_PORT + 10000) { }
+ NakedDhcpv6Srv(uint16_t port):Dhcpv6Srv(port) { }
- boost::shared_ptr<Pkt6>
- processSolicit(boost::shared_ptr<Pkt6>& request) {
- return Dhcpv6Srv::processSolicit(request);
- }
- boost::shared_ptr<Pkt6>
- processRequest(boost::shared_ptr<Pkt6>& request) {
- return Dhcpv6Srv::processRequest(request);
- }
+ using Dhcpv6Srv::processSolicit;
+ using Dhcpv6Srv::processRequest;
+ using Dhcpv6Srv::createStatusCode;
+ using Dhcpv6Srv::selectSubnet;
};
class Dhcpv6SrvTest : public ::testing::Test {
public:
// these are empty for now, but let's keep them around
- Dhcpv6SrvTest() {
+ Dhcpv6SrvTest() : rcode_(-1) {
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
+ 2000, 3000, 4000));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ subnet_->addPool6(pool_);
+
+ CfgMgr::instance().addSubnet6(subnet_);
+ }
+
+ // Generate IA_NA option with specified parameters
+ boost::shared_ptr<Option6IA> generateIA(uint32_t iaid, uint32_t t1, uint32_t t2) {
+ boost::shared_ptr<Option6IA> ia =
+ boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, iaid));
+ ia->setT1(t1);
+ ia->setT2(t2);
+ return (ia);
+ }
+
+ // Generate client-id option
+ OptionPtr generateClientId(size_t duid_size = 32) {
+
+ OptionBuffer clnt_duid(duid_size);
+ for (int i = 0; i < duid_size; i++) {
+ clnt_duid[i] = 100 + i;
+ }
+
+ duid_ = DuidPtr(new DUID(clnt_duid));
+
+ return (OptionPtr(new Option(Option::V6, D6O_CLIENTID,
+ clnt_duid.begin(),
+ clnt_duid.begin() + duid_size)));
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper server-id.
+ void checkServerId(const Pkt6Ptr& rsp, const OptionPtr& expected_srvid) {
+ // check that server included its server-id
+ OptionPtr tmp = rsp->getOption(D6O_SERVERID);
+ EXPECT_EQ(tmp->getType(), expected_srvid->getType() );
+ ASSERT_EQ(tmp->len(), expected_srvid->len() );
+ EXPECT_TRUE(tmp->getData() == expected_srvid->getData());
+ }
+
+ // Checks if server response (ADVERTISE or REPLY) includes proper client-id.
+ void checkClientId(const Pkt6Ptr& rsp, const OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ OptionPtr tmp = rsp->getOption(D6O_CLIENTID);
+ ASSERT_TRUE(tmp);
+ EXPECT_EQ(expected_clientid->getType(), tmp->getType());
+ ASSERT_EQ(expected_clientid->len(), tmp->len());
+
+ // check that returned client-id is valid
+ EXPECT_TRUE(expected_clientid->getData() == tmp->getData());
+ }
+
+ // Checks that server response (ADVERTISE or REPLY) contains proper IA_NA option
+ // It returns IAADDR option for each chaining with checkIAAddr method.
+ boost::shared_ptr<Option6IAAddr> checkIA_NA(const Pkt6Ptr& rsp, uint32_t expected_iaid,
+ uint32_t expected_t1, uint32_t expected_t2) {
+ OptionPtr tmp = rsp->getOption(D6O_IA_NA);
+ // Can't use ASSERT_TRUE() in method that returns something
+ if (!tmp) {
+ ADD_FAILURE() << "IA_NA option not present in response";
+ return (boost::shared_ptr<Option6IAAddr>());
+ }
+
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ EXPECT_EQ(expected_iaid, ia->getIAID() );
+ EXPECT_EQ(expected_t1, ia->getT1());
+ EXPECT_EQ(expected_t2, ia->getT2());
+
+ tmp = ia->getOption(D6O_IAADDR);
+ boost::shared_ptr<Option6IAAddr> addr = boost::dynamic_pointer_cast<Option6IAAddr>(tmp);
+ return (addr);
+ }
+
+ // Check that generated IAADDR option contains expected address.
+ void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
+ const IOAddress& expected_addr,
+ uint32_t expected_preferred, uint32_t expected_valid) {
+
+ // Check that the assigned address is indeed from the configured pool.
+ // Note that when comparing addresses, we compare the textual
+ // representation. IOAddress does not support being streamed to
+ // an ostream, which means it can't be used in EXPECT_EQ.
+ EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+ EXPECT_EQ(expected_addr.toText(), addr->getAddress().toText());
+ EXPECT_EQ(addr->getPreferred(), subnet_->getPreferred());
+ EXPECT_EQ(addr->getValid(), subnet_->getValid());
+ }
+
+ // Basic checks for generated response (message type and transaction-id).
+ void checkResponse(const Pkt6Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ // Checks if the lease sent to client is present in the database
+ Lease6Ptr checkLease(const DuidPtr& duid, const OptionPtr& ia_na,
+ boost::shared_ptr<Option6IAAddr> addr) {
+ boost::shared_ptr<Option6IA> ia = boost::dynamic_pointer_cast<Option6IA>(ia_na);
+
+ Lease6Ptr lease = LeaseMgr::instance().getLease6(addr->getAddress());
+ if (!lease) {
+ cout << "Lease for " << addr->getAddress().toText()
+ << " not found in the database backend.";
+ return (Lease6Ptr());
+ }
+
+ EXPECT_EQ(addr->getAddress().toText(), lease->addr_.toText());
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_EQ(ia->getIAID(), lease->iaid_);
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
}
+
~Dhcpv6SrvTest() {
+ CfgMgr::instance().deleteSubnets6();
};
+
+ // A subnet used in most tests
+ Subnet6Ptr subnet_;
+
+ // A pool used in most tests
+ Pool6Ptr pool_;
+
+ // A DUID used in most tests (typically as client-id)
+ DuidPtr duid_;
+
+ int rcode_;
+ ConstElementPtr comment_;
};
+// Test verifies that the Dhcpv6_srv class can be instantiated. It checks a mode
+// without open sockets and with sockets opened on a high port (to not require
+// root privileges).
TEST_F(Dhcpv6SrvTest, basic) {
// srv has stubbed interface detection. It will read
// interfaces.txt instead. It will pretend to have detected
// fe80::1234 link-local address on eth0 interface. Obviously
// an attempt to bind this socket will fail.
- Dhcpv6Srv* srv = NULL;
+ boost::scoped_ptr<Dhcpv6Srv> srv;
+
ASSERT_NO_THROW( {
+ // Skip opening any sockets
+ srv.reset(new Dhcpv6Srv(0));
+ });
+ srv.reset();
+ ASSERT_NO_THROW({
// open an unpriviledged port
- srv = new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000);
+ srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
});
-
- delete srv;
}
+// Test checks that DUID is generated properly
TEST_F(Dhcpv6SrvTest, DUID) {
- // tests that DUID is generated properly
boost::scoped_ptr<Dhcpv6Srv> srv;
ASSERT_NO_THROW( {
- srv.reset(new Dhcpv6Srv(DHCP6_SERVER_PORT + 10000));
+ srv.reset(new Dhcpv6Srv(0));
});
OptionPtr srvid = srv->getServerID();
@@ -101,7 +246,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
uint16_t duid_type = data.readUint16();
cout << "Duid-type=" << duid_type << endl;
switch(duid_type) {
- case DUID_LLT: {
+ case DUID::DUID_LLT: {
// DUID must contain at least 6 bytes long MAC
// + 8 bytes of fixed header
EXPECT_GE(14, len);
@@ -125,7 +270,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
EXPECT_TRUE(mac != zeros);
break;
}
- case DUID_EN: {
+ case DUID::DUID_EN: {
// there's not much we can check. Just simple
// check if it is not all zeros
vector<uint8_t> content(len-2);
@@ -133,7 +278,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
EXPECT_FALSE(isRangeZero(content.begin(), content.end()));
break;
}
- case DUID_LL: {
+ case DUID::DUID_LL: {
// not supported yet
cout << "Test not implemented for DUID-LL." << endl;
@@ -143,86 +288,467 @@ TEST_F(Dhcpv6SrvTest, DUID) {
// and keep it despite hardware changes.
break;
}
- case DUID_UUID: // not supported yet
+ case DUID::DUID_UUID: // not supported yet
default:
ADD_FAILURE() << "Not supported duid type=" << duid_type << endl;
break;
}
}
-TEST_F(Dhcpv6SrvTest, Solicit_basic) {
+// This test checks if Option Request Option (ORO) is parsed correctly
+// and the requested options are actually assigned.
+TEST_F(Dhcpv6SrvTest, advertiseOptions) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/64\" ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"option-data\": [ {"
+ " \"name\": \"OPTION_DNS_SERVERS\","
+ " \"code\": 23,"
+ " \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
+ "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+ " },"
+ " {"
+ " \"name\": \"OPTION_FOO\","
+ " \"code\": 1000,"
+ " \"data\": \"1234\""
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv()) );
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
- // a dummy content for client-id
- OptionBuffer clntDuid(32);
- for (int i = 0; i < 32; i++) {
- clntDuid[i] = 100 + i;
- }
+ EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+
+ ASSERT_EQ(0, rcode_);
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ boost::shared_ptr<Pkt6> adv = srv->processSolicit(sol);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // We have not requested option with code 1000 so it should not
+ // be included in the response.
+ ASSERT_FALSE(adv->getOption(1000));
+ ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
+
+ // Let's now request option with code 1000.
+ // We expect that server will include this option in its reply.
+ boost::shared_ptr<Option6IntArray<uint16_t> >
+ option_oro(new Option6IntArray<uint16_t>(D6O_ORO));
+ // Create vector with two option codes.
+ std::vector<uint16_t> codes(2);
+ codes[0] = 1000;
+ codes[1] = D6O_NAME_SERVERS;
+ // Pass this code to option.
+ option_oro->setValues(codes);
+ // Append ORO to SOLICIT message.
+ sol->addOption(option_oro);
+
+ // Need to process SOLICIT again after requesting new option.
+ adv = srv->processSolicit(sol);
+ ASSERT_TRUE(adv);
+
+ OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(tmp);
+
+ boost::shared_ptr<Option6AddrLst> reply_nameservers =
+ boost::dynamic_pointer_cast<Option6AddrLst>(tmp);
+ ASSERT_TRUE(reply_nameservers);
+
+ Option6AddrLst::AddressContainer addrs = reply_nameservers->getAddresses();
+ ASSERT_EQ(2, addrs.size());
+ EXPECT_TRUE(addrs[0] == IOAddress("2001:db8:1234:FFFF::1"));
+ EXPECT_TRUE(addrs[1] == IOAddress("2001:db8:1234:FFFF::2"));
+
+ // There is a dummy option with code 1000 we requested from a server.
+ // Expect that this option is in server's response.
+ tmp = adv->getOption(1000);
+ ASSERT_TRUE(tmp);
+
+ // Check that the option contains valid data (from configuration).
+ std::vector<uint8_t> data = tmp->getData();
+ ASSERT_EQ(2, data.size());
+
+ const uint8_t foo_expected[] = {
+ 0x12, 0x34
+ };
+ EXPECT_EQ(0, memcmp(&data[0], foo_expected, 2));
+
+ // more checks to be implemented
+}
+
+
+// There are no dedicated tests for Dhcpv6Srv::handleIA_NA and Dhcpv6Srv::assignLeases
+// as they are indirectly tested in Solicit and Request tests.
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT without any hint in IA_NA.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, without any addresses)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitBasic) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv->processSolicit(sol);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
- boost::shared_ptr<Option6IA> ia =
- boost::shared_ptr<Option6IA>(new Option6IA(D6O_IA_NA, 234));
- ia->setT1(1501);
- ia->setT2(2601);
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT with IA_NA that contains a valid hint.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitHint) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // Let's create a SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ ASSERT_TRUE(subnet_->inPool(hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
sol->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv->processSolicit(sol);
- // Let's not send address in solicit yet
- // boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
- // IOAddress("2001:db8:1234:ffff::ffff"), 5001, 7001));
- // ia->addOption(addr);
- // sol->addOption(ia);
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
- // constructed very simple SOLICIT message with:
- // - client-id option (mandatory)
- // - IA option (a request for address, without any addresses)
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
- // expected returned ADVERTISE message:
- // - copy of client-id
- // - server-id
- // - IA that includes IAADDR
+ // check that we've got the address we requested
+ checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
- OptionPtr clientid = OptionPtr(new Option(Option::V6, D6O_CLIENTID,
- clntDuid.begin(),
- clntDuid.begin() + 16));
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can be handled properly, that an
+// ADVERTISE is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a SOLICIT with IA_NA that contains an invalid hint.
+//
+// constructed very simple SOLICIT message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that does not
+// belong to the configured pool, i.e. is valid as hint)
+//
+// expected returned ADVERTISE message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // Let's create a SOLICIT
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+ IOAddress hint("2001:db8:1::cafe:babe");
+ ASSERT_FALSE(subnet_->inPool(hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ sol->addOption(ia);
+ OptionPtr clientid = generateClientId();
sol->addOption(clientid);
- boost::shared_ptr<Pkt6> reply = srv->processSolicit(sol);
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply = srv->processSolicit(sol);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
+
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+}
+
+// This test checks that the server is offering different addresses to different
+// clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
+// and address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same advertise as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. ADVERTISE is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv6SrvTest, ManySolicits) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
+ Pkt6Ptr sol3 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 3456));
+
+ sol1->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol2->setRemoteAddr(IOAddress("fe80::1223"));
+ sol3->setRemoteAddr(IOAddress("fe80::3467"));
+
+ sol1->addOption(generateIA(1, 1500, 3000));
+ sol2->addOption(generateIA(2, 1500, 3000));
+ sol3->addOption(generateIA(3, 1500, 3000));
+
+ // different client-id sizes
+ OptionPtr clientid1 = generateClientId(12);
+ OptionPtr clientid2 = generateClientId(14);
+ OptionPtr clientid3 = generateClientId(16);
+
+ sol1->addOption(clientid1);
+ sol2->addOption(clientid2);
+ sol3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply1 = srv->processSolicit(sol1);
+ Pkt6Ptr reply2 = srv->processSolicit(sol2);
+ Pkt6Ptr reply3 = srv->processSolicit(sol3);
// check if we get response at all
- ASSERT_TRUE( reply != boost::shared_ptr<Pkt6>() );
+ checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
+ checkResponse(reply2, DHCPV6_ADVERTISE, 2345);
+ checkResponse(reply3, DHCPV6_ADVERTISE, 3456);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr1 = checkIA_NA(reply1, 1, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr2 = checkIA_NA(reply2, 2, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply1, srv->getServerID());
+ checkServerId(reply2, srv->getServerID());
+ checkServerId(reply3, srv->getServerID());
+ checkClientId(reply1, clientid1);
+ checkClientId(reply2, clientid2);
+ checkClientId(reply3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
+ EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
+ EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
+ cout << "Offered address to client1=" << addr1->getAddress().toText() << endl;
+ cout << "Offered address to client2=" << addr2->getAddress().toText() << endl;
+ cout << "Offered address to client3=" << addr3->getAddress().toText() << endl;
+}
+
+
+// This test verifies that incoming REQUEST can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// This test sends a REQUEST with IA_NA that contains a valid hint.
+//
+// constructed very simple REQUEST message with:
+// - client-id option (mandatory)
+// - IA option (a request for address, with an address that belongs to the
+// configured pool, i.e. is valid as hint)
+//
+// expected returned REPLY message:
+// - copy of client-id
+// - server-id
+// - IA that includes IAADDR
+TEST_F(Dhcpv6SrvTest, RequestBasic) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // Let's create a REQUEST
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(234, 1500, 3000);
+
+ // with a valid hint
+ IOAddress hint("2001:db8:1:1::dead:beef");
+ ASSERT_TRUE(subnet_->inPool(hint));
+ OptionPtr hint_opt(new Option6IAAddr(D6O_IAADDR, hint, 300, 500));
+ ia->addOption(hint_opt);
+ req->addOption(ia);
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
- EXPECT_EQ( DHCPV6_ADVERTISE, reply->getType() );
- EXPECT_EQ( 1234, reply->getTransid() );
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv->processRequest(req);
+
+ // check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
OptionPtr tmp = reply->getOption(D6O_IA_NA);
- ASSERT_TRUE( tmp );
+ ASSERT_TRUE(tmp);
- Option6IA* reply_ia = dynamic_cast<Option6IA*>(tmp.get());
- EXPECT_EQ( 234, reply_ia->getIAID() );
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
- // check that there's an address included
- EXPECT_TRUE( reply_ia->getOption(D6O_IAADDR));
+ // check that we've got the address we requested
+ checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
- // check that server included our own client-id
- tmp = reply->getOption(D6O_CLIENTID);
- ASSERT_TRUE( tmp );
- EXPECT_EQ(clientid->getType(), tmp->getType() );
- ASSERT_EQ(clientid->len(), tmp->len() );
+ // check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
- EXPECT_TRUE( clientid->getData() == tmp->getData() );
+ // check that the lease is really in the database
+ Lease6Ptr l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
+ EXPECT_TRUE(l);
+ LeaseMgr::instance().deleteLease6(addr->getAddress());
+}
- // check that server included its server-id
- tmp = reply->getOption(D6O_SERVERID);
- EXPECT_EQ(tmp->getType(), srv->getServerID()->getType() );
- ASSERT_EQ(tmp->len(), srv->getServerID()->len() );
+// This test checks that the server is offering different addresses to different
+// clients in REQUEST. Please note that ADVERTISE is not a guarantee that such
+// and address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same advertise as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. ADVERTISE is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv6SrvTest, ManyRequests) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
- EXPECT_TRUE(tmp->getData() == srv->getServerID()->getData());
+ Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+ Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
+ Pkt6Ptr req3 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 3456));
- // more checks to be implemented
+ req1->setRemoteAddr(IOAddress("fe80::abcd"));
+ req2->setRemoteAddr(IOAddress("fe80::1223"));
+ req3->setRemoteAddr(IOAddress("fe80::3467"));
+
+ req1->addOption(generateIA(1, 1500, 3000));
+ req2->addOption(generateIA(2, 1500, 3000));
+ req3->addOption(generateIA(3, 1500, 3000));
+
+ // different client-id sizes
+ OptionPtr clientid1 = generateClientId(12);
+ OptionPtr clientid2 = generateClientId(14);
+ OptionPtr clientid3 = generateClientId(16);
+
+ req1->addOption(clientid1);
+ req2->addOption(clientid2);
+ req3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr reply1 = srv->processRequest(req1);
+ Pkt6Ptr reply2 = srv->processRequest(req2);
+ Pkt6Ptr reply3 = srv->processRequest(req3);
+
+ // check if we get response at all
+ checkResponse(reply1, DHCPV6_REPLY, 1234);
+ checkResponse(reply2, DHCPV6_REPLY, 2345);
+ checkResponse(reply3, DHCPV6_REPLY, 3456);
+
+ // check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr1 = checkIA_NA(reply1, 1, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr2 = checkIA_NA(reply2, 2, subnet_->getT1(),
+ subnet_->getT2());
+ boost::shared_ptr<Option6IAAddr> addr3 = checkIA_NA(reply3, 3, subnet_->getT1(),
+ subnet_->getT2());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkIAAddr(addr1, addr1->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr2, addr2->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+ checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
+
+ // check DUIDs
+ checkServerId(reply1, srv->getServerID());
+ checkServerId(reply2, srv->getServerID());
+ checkServerId(reply3, srv->getServerID());
+ checkClientId(reply1, clientid1);
+ checkClientId(reply2, clientid2);
+ checkClientId(reply3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1->getAddress().toText(), addr2->getAddress().toText());
+ EXPECT_NE(addr2->getAddress().toText(), addr3->getAddress().toText());
+ EXPECT_NE(addr3->getAddress().toText(), addr1->getAddress().toText());
+ cout << "Assigned address to client1=" << addr1->getAddress().toText() << endl;
+ cout << "Assigned address to client2=" << addr2->getAddress().toText() << endl;
+ cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
}
+
TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
// Check all possible packet types
for (int itype = 0; itype < 256; ++itype) {
@@ -268,4 +794,41 @@ TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
}
}
+// This test verifies if the status code option is generated properly.
+TEST_F(Dhcpv6SrvTest, StatusCode) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ // a dummy content for client-id
+ uint8_t expected[] = {0x0, 0x3, 0x41, 0x42, 0x43, 0x44, 0x45};
+ OptionBuffer exp(expected, expected + sizeof(expected));
+
+ OptionPtr status = srv->createStatusCode(3, "ABCDE");
+
+ EXPECT_TRUE(status->getData() == exp);
+}
+
+// This test verifies if the selectSubnet() method works as expected.
+TEST_F(Dhcpv6SrvTest, SelectSubnet) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ // check that the packets originating from local addresses can be
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+ EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+
+ // packets originating from subnet A will select subnet A
+ pkt->setRemoteAddr(IOAddress("2001:db8:1::6789"));
+ EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+
+ // packets from a subnet that is not supported will not get
+ // a subnet
+ pkt->setRemoteAddr(IOAddress("3000::faf"));
+ EXPECT_FALSE(srv->selectSubnet(pkt));
+
+ /// @todo: expand this test once support for relays is implemented
+}
+
} // end of anonymous namespace
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index 010a1a5..bab193e 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -178,6 +178,8 @@ class MsgQ:
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
self.listen_socket.close()
+ sys.stderr.write("[b10-msgq] failed to setup listener on %s: %s\n"
+ % (self.socket_file, str(e)))
raise e
if self.poller:
@@ -543,9 +545,10 @@ if __name__ == "__main__":
msgq = MsgQ(options.msgq_socket_file, options.verbose)
- setup_result = msgq.setup()
- if setup_result:
- sys.stderr.write("[b10-msgq] Error on startup: %s\n" % setup_result)
+ try:
+ msgq.setup()
+ except Exception as e:
+ sys.stderr.write("[b10-msgq] Error on startup: %s\n" % str(e))
sys.exit(1)
try:
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index 73ed35b..627d50e 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -761,6 +761,7 @@ class TestStatsHttpd(unittest.TestCase):
self.assertEqual(ht.address_family, socket.AF_INET)
self.assertTrue(isinstance(ht.socket, socket.socket))
+ def test_httpd_anyIPv4(self):
# any address (IPv4)
server_addresses = get_availaddr(address='0.0.0.0')
self.stats_httpd = MyStatsHttpd(server_addresses)
@@ -769,6 +770,7 @@ class TestStatsHttpd(unittest.TestCase):
self.assertEqual(ht.address_family,socket.AF_INET)
self.assertTrue(isinstance(ht.socket, socket.socket))
+ def test_httpd_anyIPv6(self):
# any address (IPv6)
if self.ipv6_enabled:
server_addresses = get_availaddr(address='::')
@@ -778,6 +780,7 @@ class TestStatsHttpd(unittest.TestCase):
self.assertEqual(ht.address_family,socket.AF_INET6)
self.assertTrue(isinstance(ht.socket, socket.socket))
+ def test_httpd_failed(self):
# existent hostname
self.assertRaises(stats_httpd.HttpServerError, MyStatsHttpd,
get_availaddr(address='localhost'))
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 43a1b04..0d54a07 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -34,27 +34,30 @@ libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
libb10_dhcp___la_SOURCES += duid.cc duid.h
+libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
+libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
+libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
+libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0
+
libb10_dhcpsrv_la_SOURCES = cfgmgr.cc cfgmgr.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
+libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
+
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
libb10_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 2:0:0
EXTRA_DIST = README
-libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
-libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
-libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
-libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0
-
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
# Boost headers when compiling with clang.
diff --git a/src/lib/dhcp/addr_utilities.cc b/src/lib/dhcp/addr_utilities.cc
index db35ca6..6ac3f14 100644
--- a/src/lib/dhcp/addr_utilities.cc
+++ b/src/lib/dhcp/addr_utilities.cc
@@ -16,6 +16,7 @@
#include <exceptions/exceptions.h>
#include <dhcp/addr_utilities.h>
+using namespace isc;
using namespace isc::asiolink;
namespace {
@@ -45,14 +46,13 @@ const uint8_t bitMask6[]= { 0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff };
/// @param len prefix length
isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
- uint8_t packed[V6ADDRESS_LEN];
-
if (len > 128) {
isc_throw(isc::BadValue,
"Too large netmask. 0..128 is allowed in IPv6");
}
// First we copy the whole address as 16 bytes.
+ uint8_t packed[V6ADDRESS_LEN];
memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
// If the length is divisible by 8, it is simple. We just zero out the host
@@ -90,11 +90,11 @@ isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& pref
/// @param len netmask length (0-32)
isc::asiolink::IOAddress firstAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
- uint32_t addr = prefix;
if (len > 32) {
isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
}
+ uint32_t addr = prefix;
return (IOAddress(addr & (~bitMask4[len])));
}
@@ -107,11 +107,11 @@ isc::asiolink::IOAddress firstAddrInPrefix4(const isc::asiolink::IOAddress& pref
/// @param len netmask length (0-32)
isc::asiolink::IOAddress lastAddrInPrefix4(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
- uint32_t addr = prefix;
if (len > 32) {
isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
}
+ uint32_t addr = prefix;
return (IOAddress(addr | bitMask4[len]));
}
@@ -124,15 +124,13 @@ isc::asiolink::IOAddress lastAddrInPrefix4(const isc::asiolink::IOAddress& prefi
/// @param len netmask length (0-128)
isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
-
- uint8_t packed[V6ADDRESS_LEN];
-
if (len > 128) {
isc_throw(isc::BadValue,
"Too large netmask. 0..128 is allowed in IPv6");
}
// First we copy the whole address as 16 bytes.
+ uint8_t packed[V6ADDRESS_LEN];
memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
// if the length is divisible by 8, it is simple. We just fill the host part
diff --git a/src/lib/dhcp/alloc_engine.cc b/src/lib/dhcp/alloc_engine.cc
index 8ebefdc..650fd5f 100644
--- a/src/lib/dhcp/alloc_engine.cc
+++ b/src/lib/dhcp/alloc_engine.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <alloc_engine.h>
+#include <string.h>
#include <cstring>
diff --git a/src/lib/dhcp/cfgmgr.cc b/src/lib/dhcp/cfgmgr.cc
index d06544c..0e7c8f4 100644
--- a/src/lib/dhcp/cfgmgr.cc
+++ b/src/lib/dhcp/cfgmgr.cc
@@ -41,7 +41,7 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// configuration. Such requirement makes sense in IPv4, but not in IPv6.
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
- if (subnets6_.size() == 1) {
+ if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
return (subnets6_[0]);
}
diff --git a/src/lib/dhcp/dhcp6.h b/src/lib/dhcp/dhcp6.h
index 15c306d..22e7c8d 100644
--- a/src/lib/dhcp/dhcp6.h
+++ b/src/lib/dhcp/dhcp6.h
@@ -105,10 +105,11 @@ extern const int dhcpv6_type_name_max;
/* DUID type definitions (RFC3315 section 9).
*/
-#define DUID_LLT 1
-#define DUID_EN 2
-#define DUID_LL 3
-#define DUID_UUID 4
+// see isc::dhcp::DUID::DUIDType enum in dhcp/duid.h
+// #define DUID_LLT 1
+// #define DUID_EN 2
+// #define DUID_LL 3
+// #define DUID_UUID 4
// Define hardware types
// Taken from http://www.iana.org/assignments/arp-parameters/
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index db7ba25..01dcfe1 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -12,11 +12,13 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <vector>
#include <exceptions/exceptions.h>
#include <stdint.h>
#include <util/io_utilities.h>
#include <dhcp/duid.h>
+#include <vector>
+#include <sstream>
+#include <iomanip>
namespace isc {
namespace dhcp {
@@ -53,6 +55,21 @@ DUID::DUIDType DUID::getType() const {
}
}
+std::string DUID::toText() const {
+ std::stringstream tmp;
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = duid_.begin();
+ it != duid_.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
bool DUID::operator == (const DUID& other) const {
return (this->duid_ == other.duid_);
}
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 5f8ad58..be575dd 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -61,11 +61,14 @@ class DUID {
/// @brief returns DUID type
DUIDType getType() const;
- // compares two DUIDs
- bool operator == (const DUID& other) const;
+ /// returns textual prepresentation (e.g. 00:01:02:03:ff)
+ std::string toText() const;
- // compares two DUIDs
- bool operator != (const DUID& other) const;
+ /// compares two DUIDs
+ bool operator==(const DUID& other) const;
+
+ /// compares two DUIDs
+ bool operator!=(const DUID& other) const;
protected:
/// the actual content of the DUID
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index ecaa652..1c2a11c 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -247,6 +247,15 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
continue;
}
+ // Bind link-local addresses only. Otherwise we bind several sockets
+ // on interfaces that have several global addresses. For examples
+ // with interface with 2 global addresses, we would bind 3 sockets
+ // (one for link-local and two for global). That would result in
+ // getting each message 3 times.
+ if (!addr->getAddress().to_v6().is_link_local()){
+ continue;
+ }
+
sock = openSocket(iface->getName(), *addr, port);
if (sock < 0) {
isc_throw(SocketConfigError, "failed to open unicast socket");
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 12e1bbd..99a3b91 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -23,6 +23,8 @@
#include <dhcp/option.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option6_int_array.h>
using namespace std;
using namespace isc::dhcp;
@@ -34,6 +36,23 @@ std::map<unsigned short, Option::Factory*> LibDHCP::v4factories_;
// static array with factories for options
std::map<unsigned short, Option::Factory*> LibDHCP::v6factories_;
+// Static container with DHCPv4 option definitions.
+OptionDefContainer LibDHCP::v4option_defs_;
+
+// Static container with DHCPv6 option definitions.
+OptionDefContainer LibDHCP::v6option_defs_;
+
+const OptionDefContainer&
+LibDHCP::getOptionDefs(Option::Universe u) {
+ switch (u) {
+ case Option::V4:
+ return (v4option_defs_);
+ case Option::V6:
+ return (v6option_defs_);
+ default:
+ isc_throw(isc::BadValue, "invalid universe " << u << " specified");
+ }
+}
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
@@ -88,6 +107,11 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
buf.begin() + offset,
buf.begin() + offset + opt_len));
break;
+ case D6O_ORO:
+ opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ break;
default:
opt = OptionPtr(new Option(Option::V6,
opt_type,
@@ -201,3 +225,81 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
return;
}
+
+void
+LibDHCP::initStdOptionDefs(Option::Universe u) {
+ switch (u) {
+ case Option::V4:
+ initStdOptionDefs4();
+ break;
+ case Option::V6:
+ initStdOptionDefs6();
+ break;
+ default:
+ isc_throw(isc::BadValue, "invalid universe " << u << " specified");
+ }
+}
+
+void
+LibDHCP::initStdOptionDefs4() {
+ isc_throw(isc::NotImplemented, "initStdOptionDefs4 is not implemented");
+}
+
+void
+LibDHCP::initStdOptionDefs6() {
+ v6option_defs_.clear();
+
+ struct OptionParams {
+ std::string name;
+ uint16_t code;
+ OptionDefinition::DataType type;
+ bool array;
+ };
+ OptionParams params[] = {
+ { "CLIENTID", D6O_CLIENTID, OptionDefinition::BINARY_TYPE, false },
+ { "SERVERID", D6O_SERVERID, OptionDefinition::BINARY_TYPE, false },
+ { "IA_NA", D6O_IA_NA, OptionDefinition::RECORD_TYPE, false },
+ { "IAADDR", D6O_IAADDR, OptionDefinition::RECORD_TYPE, false },
+ { "ORO", D6O_ORO, OptionDefinition::UINT16_TYPE, true },
+ { "ELAPSED_TIME", D6O_ELAPSED_TIME, OptionDefinition::UINT16_TYPE, false },
+ { "STATUS_CODE", D6O_STATUS_CODE, OptionDefinition::RECORD_TYPE, false },
+ { "RAPID_COMMIT", D6O_RAPID_COMMIT, OptionDefinition::EMPTY_TYPE, false },
+ { "DNS_SERVERS", D6O_NAME_SERVERS, OptionDefinition::IPV6_ADDRESS_TYPE, true }
+ };
+ const int params_size = sizeof(params) / sizeof(params[0]);
+
+ for (int i = 0; i < params_size; ++i) {
+ OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].type,
+ params[i].array));
+ switch(params[i].code) {
+ case D6O_IA_NA:
+ for (int j = 0; j < 3; ++j) {
+ definition->addRecordField(OptionDefinition::UINT32_TYPE);
+ }
+ break;
+ case D6O_IAADDR:
+ definition->addRecordField(OptionDefinition::IPV6_ADDRESS_TYPE);
+ definition->addRecordField(OptionDefinition::UINT32_TYPE);
+ definition->addRecordField(OptionDefinition::UINT32_TYPE);
+ break;
+ case D6O_STATUS_CODE:
+ definition->addRecordField(OptionDefinition::UINT16_TYPE);
+ definition->addRecordField(OptionDefinition::STRING_TYPE);
+ break;
+ default:
+ // The default case is intentionally left empty
+ // as it does not need any processing.
+ ;
+ }
+ try {
+ definition->validate();
+ } catch (const Exception& ex) {
+ isc_throw(isc::Unexpected, "internal server error: invalid definition of standard"
+ << " DHCPv6 option (with code " << params[i].code << "): "
+ << ex.what());
+ }
+ v6option_defs_.push_back(definition);
+ }
+}
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index ae90701..c057eea 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-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
@@ -15,9 +15,10 @@
#ifndef LIBDHCP_H_
#define LIBDHCP_H_
-#include <iostream>
-#include <util/buffer.h>
+#include <dhcp/option_definition.h>
#include <dhcp/pkt6.h>
+#include <util/buffer.h>
+#include <iostream>
namespace isc {
namespace dhcp {
@@ -29,6 +30,12 @@ public:
/// Map of factory functions.
typedef std::map<unsigned short, Option::Factory*> FactoryMap;
+ /// @brief Return collection of option definitions.
+ ///
+ /// @param u universe of the options (V4 or V6).
+ /// @return collection of option definitions.
+ static const OptionDefContainer& getOptionDefs(Option::Universe u);
+
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
@@ -102,12 +109,54 @@ public:
static void OptionFactoryRegister(Option::Universe u,
uint16_t type,
Option::Factory * factory);
-protected:
+
+ /// Initialize standard DHCP options (V4 or V6).
+ ///
+ /// The method creates option definitions for all options
+ /// (DHCPv4 or DHCPv6 depending on universe specified).
+ /// Currently DHCPv4 option definitions initialization is not
+ /// implemented thus this function will throw isc::NotImplemented
+ /// if V4 universe is specified.
+ ///
+ /// @param u universe
+ /// @throw isc::Unexpected if internal error occured during option
+ /// definitions creation.
+ /// @throw std::bad_alloc if system went out of memory.
+ /// @throw isc::NotImplemented when V4 universe specified.
+ static void initStdOptionDefs(Option::Universe u);
+
+private:
+
+ /// Initialize standard DHCPv4 option definitions.
+ ///
+ /// The method creates option definitions for all DHCPv4 options.
+ /// Currently this function is not implemented.
+ ///
+ /// @todo implemend this function.
+ ///
+ /// @throw isc::NotImplemeneted
+ static void initStdOptionDefs4();
+
+ /// Initialize standard DHCPv6 option definitions.
+ ///
+ /// The method creates option definitions for all DHCPv6 options.
+ ///
+ /// @throw isc::Unexpected if internal error occured during option
+ /// definitions creation.
+ /// @throw std::bad_alloc if system went out of memory.
+ static void initStdOptionDefs6();
+
/// pointers to factories that produce DHCPv6 options
static FactoryMap v4factories_;
/// pointers to factories that produce DHCPv6 options
static FactoryMap v6factories_;
+
+ /// Container with DHCPv4 option definitions.
+ static OptionDefContainer v4option_defs_;
+
+ /// Container with DHCPv6 option definitions.
+ static OptionDefContainer v6option_defs_;
};
}
diff --git a/src/lib/dhcp/memfile_lease_mgr.cc b/src/lib/dhcp/memfile_lease_mgr.cc
new file mode 100644
index 0000000..63023c4
--- /dev/null
+++ b/src/lib/dhcp/memfile_lease_mgr.cc
@@ -0,0 +1,124 @@
+// 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 <iostream>
+#include "memfile_lease_mgr.h"
+
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
+ : LeaseMgr(dbconfig) {
+ std::cout << "Warning: Using memfile database backend. It is usable for" << std::endl;
+ std::cout << "Warning: limited testing only. File support not implemented yet." << std::endl;
+ std::cout << "Warning: Leases will be lost after restart." << std::endl;
+}
+
+Memfile_LeaseMgr::~Memfile_LeaseMgr() {
+}
+
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
+ return (false);
+}
+
+bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+ if (getLease6(lease->addr_)) {
+ // there is a lease with specified address already
+ return (false);
+ }
+ storage6_.insert(lease);
+ return (true);
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
+ return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
+ return (Lease4Collection());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
+ SubnetID) const {
+ return (Lease4Ptr());
+}
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
+ SubnetID) const {
+ return (Lease4Ptr());
+}
+
+
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
+ SubnetID) const {
+ return (Lease4Ptr());
+}
+
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
+ return (Lease4Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+ Lease6Storage::iterator l = storage6_.find(addr);
+ if (l == storage6_.end()) {
+ return (Lease6Ptr());
+ } else {
+ return (*l);
+ }
+}
+
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+ return (Lease6Collection());
+}
+
+Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
+ SubnetID subnet_id) const {
+ /// @todo: Slow, naive implementation. Write it using additional indexes
+ for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
+ if ( (*((*l)->duid_) == duid) &&
+ ( (*l)->iaid_ == iaid) &&
+ ( (*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
+ return (Lease6Ptr());
+}
+
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+}
+
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+
+}
+
+bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
+ return (false);
+}
+
+bool Memfile_LeaseMgr::deleteLease6(const isc::asiolink::IOAddress& addr) {
+ Lease6Storage::iterator l = storage6_.find(addr);
+ if (l == storage6_.end()) {
+ // no such lease
+ return (false);
+ } else {
+ storage6_.erase(l);
+ return (true);
+ }
+}
+
+std::string Memfile_LeaseMgr::getDescription() const {
+ return (std::string("This is a dummy memfile backend implementation.\n"
+ "It does not offer any useful lease management and its only\n"
+ "purpose is to test abstract lease manager API."));
+}
diff --git a/src/lib/dhcp/memfile_lease_mgr.h b/src/lib/dhcp/memfile_lease_mgr.h
new file mode 100644
index 0000000..c5a41e6
--- /dev/null
+++ b/src/lib/dhcp/memfile_lease_mgr.h
@@ -0,0 +1,228 @@
+// 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 MEMFILE_LEASE_MGR_H
+#define MEMFILE_LEASE_MGR_H
+
+#include <dhcp/lease_mgr.h>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/indexed_by.hpp>
+#include <boost/multi_index/ordered_index.hpp>
+#include <boost/multi_index/member.hpp>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// This is a concrete implementation of a Lease database.
+//
+// It is for testing purposes only. It is NOT a production code.
+//
+// It does not do anything useful now, and is used for abstract LeaseMgr
+// class testing. It may later evolve into more useful backend if the
+// need arises. We can reuse code from memfile benchmark. See code in
+// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
+class Memfile_LeaseMgr : public LeaseMgr {
+public:
+
+ /// @brief The sole lease manager constructor
+ ///
+ /// dbconfig is a generic way of passing parameters. Parameters
+ /// are passed in the "name=value" format, separated by spaces.
+ /// Values may be enclosed in double quotes, if needed.
+ ///
+ /// @param dbconfig database configuration
+ Memfile_LeaseMgr(const std::string& dbconfig);
+
+ /// @brief Destructor (closes file)
+ virtual ~Memfile_LeaseMgr();
+
+ /// @brief Adds an IPv4 lease.
+ ///
+ /// @todo Not implemented yet
+ /// @param lease lease to be added
+ virtual bool addLease(const Lease4Ptr& lease);
+
+ /// @brief Adds an IPv6 lease.
+ ///
+ /// @param lease lease to be added
+ virtual bool addLease(const Lease6Ptr& lease);
+
+ /// @brief Returns existing IPv4 lease for specified IPv4 address.
+ ///
+ /// @todo Not implemented yet
+ /// @param addr address of the searched lease
+ ///
+ /// @return a collection of leases
+ virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
+
+ /// @brief Returns existing IPv4 lease for specific address and subnet
+ ///
+ /// @todo Not implemented yet
+ /// @param addr address of the searched lease
+ /// @param subnet_id ID of the subnet the lease must belong to
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
+ SubnetID subnet_id) const;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address.
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// Although in the usual case there will be only one lease, for mobile
+ /// clients or clients with multiple static/fixed/reserved leases there
+ /// can be more than one. Thus return type is a container, not a single
+ /// pointer.
+ ///
+ /// @param hwaddr hardware address of the client
+ ///
+ /// @return lease collection
+ virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+
+ /// @brief Returns existing IPv4 leases for specified hardware address
+ /// and a subnet
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @param hwaddr hardware address of the client
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param clientid client identifier
+ virtual Lease4Collection getLease4(const ClientId& clientid) const;
+
+ /// @brief Returns existing IPv4 lease for specified client-id
+ ///
+ /// There can be at most one lease for a given HW address in a single
+ /// pool, so this method with either return a single lease or NULL.
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param clientid client identifier
+ /// @param subnet_id identifier of the subnet that lease must belong to
+ ///
+ /// @return a pointer to the lease (or NULL if a lease is not found)
+ virtual Lease4Ptr getLease4(const ClientId& clientid,
+ SubnetID subnet_id) const;
+
+ /// @brief Returns existing IPv6 lease for a given IPv6 address.
+ ///
+ /// @param addr address of the searched lease
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ ///
+ /// @return collection of IPv6 leases
+ Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
+
+ /// @brief Returns existing IPv6 lease for a given DUID+IA combination
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param duid client DUID
+ /// @param iaid IA identifier
+ /// @param subnet_id identifier of the subnet the lease must belong to
+ ///
+ /// @return smart pointer to the lease (or NULL if a lease is not found)
+ Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// If no such lease is present, an exception will be thrown.
+ void updateLease4(const Lease4Ptr& lease4);
+
+ /// @brief Updates IPv4 lease.
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param lease4 The lease to be updated.
+ ///
+ /// If no such lease is present, an exception will be thrown.
+ void updateLease6(const Lease6Ptr& lease6);
+
+ /// @brief Deletes a lease.
+ ///
+ /// @todo Not implemented yet
+ ///
+ /// @param addr IPv4 address of the lease to be deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists
+ bool deleteLease4(uint32_t addr);
+
+ /// @brief Deletes a lease.
+ ///
+ /// @param addr IPv4 address of the lease to be deleted.
+ ///
+ /// @return true if deletion was successful, false if no such lease exists
+ bool deleteLease6(const isc::asiolink::IOAddress& addr);
+
+ /// @brief Returns backend name.
+ ///
+ /// Each backend have specific name, e.g. "mysql" or "sqlite".
+ std::string getName() const { return ("memfile"); }
+
+ /// @brief Returns description of the backend.
+ ///
+ /// This description may be multiline text that describes the backend.
+ std::string getDescription() const;
+
+ /// @brief Returns backend version.
+ std::string getVersion() const { return ("test-version"); }
+
+ using LeaseMgr::getParameter;
+
+protected:
+
+ typedef boost::multi_index_container< // this is a multi-index container...
+ Lease6Ptr, // it will hold shared_ptr to leases6
+ boost::multi_index::indexed_by< // and will be sorted by
+ // IPv6 address that are unique. That particular key is a member
+ // of the Lease6 structure, is of type IOAddress and can be accessed
+ // by doing &Lease6::addr_
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_>
+ >
+ >
+ > Lease6Storage; // Let the whole contraption be called Lease6Storage.
+
+ Lease6Storage storage6_;
+};
+
+}; // end of isc::dhcp::test namespace
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // MEMFILE_LEASE_MGR_H
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
index 65be711..beba75f 100644
--- a/src/lib/dhcp/option6_ia.cc
+++ b/src/lib/dhcp/option6_ia.cc
@@ -29,7 +29,7 @@ namespace isc {
namespace dhcp {
Option6IA::Option6IA(uint16_t type, uint32_t iaid)
- :Option(Option::V6, type), iaid_(iaid) {
+ :Option(Option::V6, type), iaid_(iaid), t1_(0), t2_(0) {
}
Option6IA::Option6IA(uint16_t type, OptionBufferConstIter begin, OptionBufferConstIter end)
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index f562316..87b3216 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -29,6 +29,7 @@ namespace dhcp {
OptionDefinition::DataTypeUtil::DataTypeUtil() {
data_types_["empty"] = EMPTY_TYPE;
+ data_types_["binary"] = BINARY_TYPE;
data_types_["boolean"] = BOOLEAN_TYPE;
data_types_["int8"] = INT8_TYPE;
data_types_["int16"] = INT16_TYPE;
@@ -97,11 +98,15 @@ OptionDefinition::addRecordField(const DataType data_type) {
Option::Factory*
OptionDefinition::getFactory() const {
+ validate();
+
// @todo This function must be extended to return more factory
// functions that create instances of more specialized options.
// This requires us to first implement those more specialized
// options that will be derived from Option class.
- if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
+ if (type_ == BINARY_TYPE) {
+ return (factoryGeneric);
+ } else if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
return (factoryAddrList6);
} else if (type_ == IPV4_ADDRESS_TYPE && array_type_) {
return (factoryAddrList4);
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index c274ce9..166765f 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -16,13 +16,42 @@
#define OPTION_DEFINITION_H_
#include <dhcp/option_data_types.h>
-#include <dhcp/option6_int.h>
-#include <dhcp/option6_int_array.h>
#include <dhcp/option.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/multi_index_container.hpp>
+#include <boost/multi_index/hashed_index.hpp>
+#include <boost/multi_index/sequenced_index.hpp>
+#include <boost/multi_index/mem_fun.hpp>
namespace isc {
namespace dhcp {
+/// @brief Forward declaration to OptionDefinition.
+class OptionDefinition;
+
+/// @brief Pointer to option definition object.
+typedef boost::shared_ptr<OptionDefinition> OptionDefinitionPtr;
+
+/// @brief Forward declaration to Option6Int.
+///
+/// This forward declaration is needed to access Option6Int class
+/// without having to include option6_int.h header. This is because
+/// this header includes libdhcp++.h and this causes circular
+/// inclusion between libdhcp++.h, option_definition.h and
+/// option6_int.h.
+template<typename T>
+class Option6Int;
+
+/// @brief Forward declaration to Option6IntArray.
+///
+/// This forward declaration is needed to access Option6IntArray class
+/// without having to include option6_int_array.h header. This is because
+/// this header includes libdhcp++.h and this causes circular
+/// inclusion between libdhcp++.h, option_definition.h and
+/// option6_int_array.h.
+template<typename T>
+class Option6IntArray;
+
/// @brief Base class representing a DHCP option definition.
///
/// This is a base class representing a DHCP option definition, which describes
@@ -52,7 +81,7 @@ namespace dhcp {
///
/// Should the option comprise data fields of different types, the "record"
/// option type is used. In such cases the data field types within the record
-/// are specified using \ref OptionDefinition::addRecordField.
+/// are specified using \ref OptioDefinition::addRecordField.
///
/// When the OptionDefinition object has been sucessfully created, it can be
/// queried to return the appropriate option factory function for the specified
@@ -84,6 +113,7 @@ public:
/// Data types of DHCP option fields.
enum DataType {
EMPTY_TYPE,
+ BINARY_TYPE,
BOOLEAN_TYPE,
INT8_TYPE,
INT16_TYPE,
@@ -202,7 +232,9 @@ public:
/// @brief Return factory function for the given definition.
///
- /// @return pointer to factory function.
+ /// @throw isc::OutOfRange if \ref validate returns it.
+ /// @throw isc::BadValue if \ref validate returns it.
+ /// @return pointer to a factory function.
Option::Factory* getFactory() const;
/// @brief Return option name.
@@ -377,6 +409,55 @@ private:
};
+/// @brief Multi index container for DHCP option definitions.
+///
+/// This container allows to search for DHCP option definition
+/// using two indexes:
+/// - sequenced: used to access elements in the order they have
+/// been added to the container
+/// - option code: used to search defintions of options
+/// with a specified option code (aka option type).
+/// Note that this container can hold multiple options with the
+/// same code. For this reason, the latter index can be used to
+/// obtain a range of options for a particular option code.
+///
+/// @todo: need an index to search options using option space name
+/// once option spaces are implemented.
+typedef boost::multi_index_container<
+ // Container comprises elements of OptionDefinition type.
+ OptionDefinitionPtr,
+ // Here we start enumerating various indexes.
+ boost::multi_index::indexed_by<
+ // Sequenced index allows accessing elements in the same way
+ // as elements in std::list. Sequenced is an index #0.
+ boost::multi_index::sequenced<>,
+ // Start definition of index #1.
+ boost::multi_index::hashed_non_unique<
+ // Use option type as the index key. The type is held
+ // in OptionDefinition object so we have to call
+ // OptionDefinition::getCode to retrieve this key
+ // for each element. The option code is non-unique so
+ // multiple elements with the same option code can
+ // be returned by this index.
+ boost::multi_index::const_mem_fun<
+ OptionDefinition,
+ uint16_t,
+ &OptionDefinition::getCode
+ >
+ >
+ >
+> OptionDefContainer;
+
+/// Type of the index #1 - option type.
+typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
+/// Pair of iterators to represent the range of options definitions
+/// having the same option type value. The first element in this pair
+/// represents the begining of the range, the second element
+/// represents the end.
+typedef std::pair<OptionDefContainerTypeIndex::const_iterator,
+ OptionDefContainerTypeIndex::const_iterator> OptionDefContainerTypeRange;
+
+
} // namespace isc::dhcp
} // namespace isc
diff --git a/src/lib/dhcp/subnet.cc b/src/lib/dhcp/subnet.cc
index f6ce1b1..28f2391 100644
--- a/src/lib/dhcp/subnet.cc
+++ b/src/lib/dhcp/subnet.cc
@@ -15,6 +15,7 @@
#include <dhcp/addr_utilities.h>
#include <asiolink/io_address.h>
#include <dhcp/subnet.h>
+#include <sstream>
using namespace isc::asiolink;
@@ -52,6 +53,12 @@ Subnet::delOptions() {
options_.clear();
}
+std::string Subnet::toText() const {
+ std::stringstream tmp;
+ tmp << prefix_.toText() << "/" << static_cast<unsigned int>(prefix_len_);
+ return (tmp.str());
+}
+
Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t1,
const Triplet<uint32_t>& t2,
diff --git a/src/lib/dhcp/subnet.h b/src/lib/dhcp/subnet.h
index a28c7cd..00b3375 100644
--- a/src/lib/dhcp/subnet.h
+++ b/src/lib/dhcp/subnet.h
@@ -284,6 +284,18 @@ public:
/// @return unique ID for that subnet
SubnetID getID() const { return (id_); }
+ /// @brief returns subnet parameters (prefix and prefix length)
+ ///
+ /// @return (prefix, prefix length) pair
+ std::pair<isc::asiolink::IOAddress, uint8_t> get() const {
+ return (std::make_pair(prefix_, prefix_len_));
+ }
+
+ /// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
+ ///
+ /// @return textual representation
+ virtual std::string toText() const;
+
protected:
/// @brief protected constructor
//
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index fcbdec8..57055a7 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -48,7 +48,6 @@ libdhcpsrv_unittests_SOURCES = run_unittests.cc
libdhcpsrv_unittests_SOURCES += cfgmgr_unittest.cc triplet_unittest.cc
libdhcpsrv_unittests_SOURCES += pool_unittest.cc subnet_unittest.cc
libdhcpsrv_unittests_SOURCES += addr_utilities_unittest.cc
-libdhcpsrv_unittests_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
libdhcpsrv_unittests_SOURCES += lease_mgr_unittest.cc
libdhcpsrv_unittests_SOURCES += alloc_engine_unittest.cc
@@ -60,7 +59,6 @@ libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptio
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcpsrv.la
-libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libdhcpsrv_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
diff --git a/src/lib/dhcp/tests/alloc_engine_unittest.cc b/src/lib/dhcp/tests/alloc_engine_unittest.cc
index 945c8e9..91e08d8 100644
--- a/src/lib/dhcp/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcp/tests/alloc_engine_unittest.cc
@@ -18,7 +18,7 @@
#include <dhcp/duid.h>
#include <dhcp/alloc_engine.h>
#include <dhcp/cfgmgr.h>
-#include "memfile_lease_mgr.h"
+#include <dhcp/memfile_lease_mgr.h>
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
@@ -130,7 +130,7 @@ detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
// This test checks if the simple allocation can succeed
TEST_F(AllocEngineTest, simpleAlloc) {
- boost::scoped_ptr<AllocEngine>(engine);
+ boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -153,7 +153,7 @@ TEST_F(AllocEngineTest, simpleAlloc) {
// This test checks if the fake allocation (for SOLICIT) can succeed
TEST_F(AllocEngineTest, fakeAlloc) {
- boost::scoped_ptr<AllocEngine>(engine);
+ boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -174,7 +174,7 @@ TEST_F(AllocEngineTest, fakeAlloc) {
// This test checks if the allocation with a hint that is valid (in range,
// in pool and free) can succeed
TEST_F(AllocEngineTest, allocWithValidHint) {
- boost::scoped_ptr<AllocEngine>(engine);
+ boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -202,7 +202,7 @@ TEST_F(AllocEngineTest, allocWithValidHint) {
// This test checks if the allocation with a hint that is in range,
// in pool, but is currently used) can succeed
TEST_F(AllocEngineTest, allocWithUsedHint) {
- boost::scoped_ptr<AllocEngine>(engine);
+ boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -241,7 +241,7 @@ TEST_F(AllocEngineTest, allocWithUsedHint) {
// This test checks if the allocation with a hint that is out the blue
// can succeed. The invalid hint should be ignored completely.
TEST_F(AllocEngineTest, allocBogusHint) {
- boost::scoped_ptr<AllocEngine>(engine);
+ boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
diff --git a/src/lib/dhcp/tests/cfgmgr_unittest.cc b/src/lib/dhcp/tests/cfgmgr_unittest.cc
index a1ad865..5cc6345 100644
--- a/src/lib/dhcp/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcp/tests/cfgmgr_unittest.cc
@@ -32,13 +32,22 @@ using boost::scoped_ptr;
namespace {
+class CfgMgrTest : public ::testing::Test {
+public:
+ CfgMgrTest() {
+ }
+
+ ~CfgMgrTest() {
+ CfgMgr::instance().deleteSubnets6();
+ }
+};
+
+
// This test verifies if the configuration manager is able to hold and return
// valid leases
-TEST(CfgMgrTest, subnet4) {
+TEST_F(CfgMgrTest, subnet4) {
CfgMgr& cfg_mgr = CfgMgr::instance();
- ASSERT_TRUE(&cfg_mgr != 0);
-
Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3));
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
@@ -66,10 +75,9 @@ TEST(CfgMgrTest, subnet4) {
// This test verifies if the configuration manager is able to hold and return
// valid leases
-TEST(CfgMgrTest, DISABLED_subnet6) {
- CfgMgr& cfg_mgr = CfgMgr::instance();
- ASSERT_TRUE(&cfg_mgr != 0);
+TEST_F(CfgMgrTest, subnet6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
Subnet6Ptr subnet1(new Subnet6(IOAddress("2000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
@@ -83,6 +91,10 @@ TEST(CfgMgrTest, DISABLED_subnet6) {
// Now we have only one subnet, any request will be served from it
EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("2000::1")));
+ // If we have only a single subnet and the request came from a local
+ // address, let's use that subnet
+ EXPECT_EQ(subnet1, cfg_mgr.getSubnet6(IOAddress("fe80::dead:beef")));
+
cfg_mgr.addSubnet6(subnet2);
cfg_mgr.addSubnet6(subnet3);
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index aaf6d91..435d2d2 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -166,4 +166,12 @@ TEST(ClientIdTest, operators) {
EXPECT_TRUE(*id1 != *id3);
}
+// Test checks if the toText() returns valid texual representation
+TEST(ClientIdTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ DUID duid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/lease_mgr_unittest.cc b/src/lib/dhcp/tests/lease_mgr_unittest.cc
index e46bd09..66521ba 100644
--- a/src/lib/dhcp/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/lease_mgr_unittest.cc
@@ -19,7 +19,7 @@
#include <asiolink/io_address.h>
#include <dhcp/lease_mgr.h>
#include <dhcp/duid.h>
-#include "memfile_lease_mgr.h"
+#include <dhcp/memfile_lease_mgr.h>
using namespace std;
using namespace isc;
@@ -99,6 +99,27 @@ TEST_F(LeaseMgrTest, addGetDelete) {
EXPECT_EQ(x->t1_, 50);
EXPECT_EQ(x->t2_, 80);
+ // Test getLease6(duid, iaid, subnet_id) - positive case
+ Lease6Ptr y = leaseMgr->getLease6(*duid, iaid, subnet_id);
+ ASSERT_TRUE(y);
+ EXPECT_TRUE(*y->duid_ == *duid);
+ EXPECT_EQ(y->iaid_, iaid);
+ EXPECT_EQ(y->addr_.toText(), addr.toText());
+
+ // Test getLease6(duid, iaid, subnet_id) - wrong iaid
+ uint32_t invalid_iaid = 9; // no such iaid
+ y = leaseMgr->getLease6(*duid, invalid_iaid, subnet_id);
+ EXPECT_FALSE(y);
+
+ uint32_t invalid_subnet_id = 999;
+ y = leaseMgr->getLease6(*duid, iaid, invalid_subnet_id);
+ EXPECT_FALSE(y);
+
+ // truncated duid
+ DuidPtr invalid_duid(new DUID(llt, sizeof(llt) - 1));
+ y = leaseMgr->getLease6(*invalid_duid, iaid, subnet_id);
+ EXPECT_FALSE(y);
+
// should return false - there's no such address
EXPECT_FALSE(leaseMgr->deleteLease6(IOAddress("2001:db8:1::789")));
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index d17eb65..0c9dc89 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -21,6 +21,11 @@
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/option6_ia.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option6_int.h>
+#include <dhcp/option6_int_array.h>
+#include <dhcp/option6_addrlst.h>
#include "config.h"
using namespace std;
@@ -46,6 +51,57 @@ public:
Option* option = new Option(u, type, buf);
return OptionPtr(option);
}
+
+ /// @brief Test option option definition.
+ ///
+ /// This function tests if option definition for standard
+ /// option has been initialized correctly.
+ ///
+ /// @param code option code.
+ /// @param bug buffer to be used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ static void testInitOptionDefs6(const uint16_t code,
+ const OptionBuffer& buf,
+ const std::type_info& expected_type) {
+ // Initialize stdandard options definitions. They are held
+ // in the static container throughout the program.
+ LibDHCP::initStdOptionDefs(Option::V6);
+ // Get all option definitions, we will use them to extract
+ // the definition for a particular option code.
+ OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
+ // Get the container index #1. This one allows for searching
+ // option definitions using option code.
+ const OptionDefContainerTypeIndex& idx = options.get<1>();
+ // Get 'all' option definitions for a particular option code.
+ // For standard options we expect that the range returned
+ // will contain single option as their codes are unique.
+ OptionDefContainerTypeRange range = idx.equal_range(code);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // If we have single option definition returned, the
+ // first iterator holds it.
+ OptionDefinitionPtr def = *(range.first);
+ // It should not happen that option definition is NULL but
+ // let's make sure (test should take things like that into
+ // account).
+ ASSERT_TRUE(def);
+ // Check that option definition is valid.
+ ASSERT_NO_THROW(def->validate());
+ // Get the factory function for the particular option
+ // definition. We will use this factory function to
+ // create option instance.
+ Option::Factory* factory = NULL;
+ ASSERT_NO_THROW(factory = def->getFactory());
+ OptionPtr option;
+ // Create the option.
+ ASSERT_NO_THROW(option = factory(Option::V6, code, buf));
+ // Make sure it is not NULL.
+ ASSERT_TRUE(option);
+ // And the actual object type is the one that we expect.
+ // Note that for many options there are dedicated classes
+ // derived from Option class to represent them.
+ EXPECT_TRUE(typeid(*option) == expected_type);
+ }
};
static const uint8_t packed[] = {
@@ -311,4 +367,30 @@ TEST(LibDhcpTest, unpackOptions4) {
EXPECT_TRUE(x == options.end()); // option 2 not found
}
+// Test that definitions of standard options have been initialized
+// correctly.
+// @todo Only limited number of option definitions are now created
+// This test have to be extended once all option definitions are
+// created.
+TEST(LibDhcpTest, initStdOptionDefs) {
+ LibDhcpTest::testInitOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
+ typeid(Option));
+ LibDhcpTest::testInitOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
+ typeid(Option));
+ LibDhcpTest::testInitOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
+ typeid(Option6IA));
+ LibDhcpTest::testInitOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
+ typeid(Option6IAAddr));
+ LibDhcpTest::testInitOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
+ typeid(Option6IntArray<uint16_t>));
+ LibDhcpTest::testInitOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
+ typeid(Option6Int<uint16_t>));
+ LibDhcpTest::testInitOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
+ typeid(Option));
+ LibDhcpTest::testInitOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
+ typeid(Option));
+ LibDhcpTest::testInitOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
+ typeid(Option6AddrLst));
+}
+
}
diff --git a/src/lib/dhcp/tests/memfile_lease_mgr.cc b/src/lib/dhcp/tests/memfile_lease_mgr.cc
deleted file mode 100644
index 195fd8b..0000000
--- a/src/lib/dhcp/tests/memfile_lease_mgr.cc
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "memfile_lease_mgr.h"
-
-using namespace isc::dhcp;
-using namespace isc::dhcp::test;
-
-Memfile_LeaseMgr::Memfile_LeaseMgr(const std::string& dbconfig)
- : LeaseMgr(dbconfig) {
-}
-
-Memfile_LeaseMgr::~Memfile_LeaseMgr() {
-}
-
-bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
- return (false);
-}
-
-bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
- if (getLease6(lease->addr_)) {
- // there is a lease with specified address already
- return (false);
- }
- storage6_.insert(lease);
- return (true);
-}
-
-Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress) const {
- return (Lease4Ptr());
-}
-
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
- return (Lease4Collection());
-}
-
-Lease4Ptr Memfile_LeaseMgr::getLease4(isc::asiolink::IOAddress ,
- SubnetID) const {
- return (Lease4Ptr());
-}
-
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
- SubnetID) const {
- return (Lease4Ptr());
-}
-
-
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
- SubnetID) const {
- return (Lease4Ptr());
-}
-
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
- return (Lease4Collection());
-}
-
-Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
- Lease6Storage::iterator l = storage6_.find(addr);
- if (l == storage6_.end()) {
- return (Lease6Ptr());
- } else {
- return (*l);
- }
-}
-
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
- return (Lease6Collection());
-}
-
-Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID&, uint32_t,
- SubnetID) const {
-
- return (Lease6Ptr());
-}
-
-void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
-}
-
-void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
-
-}
-
-bool Memfile_LeaseMgr::deleteLease4(uint32_t ) {
- return (false);
-}
-
-bool Memfile_LeaseMgr::deleteLease6(const isc::asiolink::IOAddress& addr) {
- Lease6Storage::iterator l = storage6_.find(addr);
- if (l == storage6_.end()) {
- // no such lease
- return (false);
- } else {
- storage6_.erase(l);
- return (true);
- }
-}
-
-std::string Memfile_LeaseMgr::getDescription() const {
- return (std::string("This is a dummy memfile backend implementation.\n"
- "It does not offer any useful lease management and its only\n"
- "purpose is to test abstract lease manager API."));
-}
diff --git a/src/lib/dhcp/tests/memfile_lease_mgr.h b/src/lib/dhcp/tests/memfile_lease_mgr.h
deleted file mode 100644
index c5a41e6..0000000
--- a/src/lib/dhcp/tests/memfile_lease_mgr.h
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef MEMFILE_LEASE_MGR_H
-#define MEMFILE_LEASE_MGR_H
-
-#include <dhcp/lease_mgr.h>
-#include <boost/multi_index_container.hpp>
-#include <boost/multi_index/indexed_by.hpp>
-#include <boost/multi_index/ordered_index.hpp>
-#include <boost/multi_index/member.hpp>
-
-namespace isc {
-namespace dhcp {
-namespace test {
-
-// This is a concrete implementation of a Lease database.
-//
-// It is for testing purposes only. It is NOT a production code.
-//
-// It does not do anything useful now, and is used for abstract LeaseMgr
-// class testing. It may later evolve into more useful backend if the
-// need arises. We can reuse code from memfile benchmark. See code in
-// tests/tools/dhcp-ubench/memfile_bench.{cc|h}
-class Memfile_LeaseMgr : public LeaseMgr {
-public:
-
- /// @brief The sole lease manager constructor
- ///
- /// dbconfig is a generic way of passing parameters. Parameters
- /// are passed in the "name=value" format, separated by spaces.
- /// Values may be enclosed in double quotes, if needed.
- ///
- /// @param dbconfig database configuration
- Memfile_LeaseMgr(const std::string& dbconfig);
-
- /// @brief Destructor (closes file)
- virtual ~Memfile_LeaseMgr();
-
- /// @brief Adds an IPv4 lease.
- ///
- /// @todo Not implemented yet
- /// @param lease lease to be added
- virtual bool addLease(const Lease4Ptr& lease);
-
- /// @brief Adds an IPv6 lease.
- ///
- /// @param lease lease to be added
- virtual bool addLease(const Lease6Ptr& lease);
-
- /// @brief Returns existing IPv4 lease for specified IPv4 address.
- ///
- /// @todo Not implemented yet
- /// @param addr address of the searched lease
- ///
- /// @return a collection of leases
- virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr) const;
-
- /// @brief Returns existing IPv4 lease for specific address and subnet
- ///
- /// @todo Not implemented yet
- /// @param addr address of the searched lease
- /// @param subnet_id ID of the subnet the lease must belong to
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(isc::asiolink::IOAddress addr,
- SubnetID subnet_id) const;
-
- /// @brief Returns existing IPv4 leases for specified hardware address.
- ///
- /// @todo Not implemented yet
- ///
- /// Although in the usual case there will be only one lease, for mobile
- /// clients or clients with multiple static/fixed/reserved leases there
- /// can be more than one. Thus return type is a container, not a single
- /// pointer.
- ///
- /// @param hwaddr hardware address of the client
- ///
- /// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
-
- /// @brief Returns existing IPv4 leases for specified hardware address
- /// and a subnet
- ///
- /// @todo Not implemented yet
- ///
- /// There can be at most one lease for a given HW address in a single
- /// pool, so this method with either return a single lease or NULL.
- ///
- /// @param hwaddr hardware address of the client
- /// @param subnet_id identifier of the subnet that lease must belong to
- ///
- /// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
- SubnetID subnet_id) const;
-
- /// @brief Returns existing IPv4 lease for specified client-id
- ///
- /// @todo Not implemented yet
- ///
- /// @param clientid client identifier
- virtual Lease4Collection getLease4(const ClientId& clientid) const;
-
- /// @brief Returns existing IPv4 lease for specified client-id
- ///
- /// There can be at most one lease for a given HW address in a single
- /// pool, so this method with either return a single lease or NULL.
- ///
- /// @todo Not implemented yet
- ///
- /// @param clientid client identifier
- /// @param subnet_id identifier of the subnet that lease must belong to
- ///
- /// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const ClientId& clientid,
- SubnetID subnet_id) const;
-
- /// @brief Returns existing IPv6 lease for a given IPv6 address.
- ///
- /// @param addr address of the searched lease
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
-
- /// @brief Returns existing IPv6 lease for a given DUID+IA combination
- ///
- /// @todo Not implemented yet
- ///
- /// @param duid client DUID
- /// @param iaid IA identifier
- ///
- /// @return collection of IPv6 leases
- Lease6Collection getLease6(const DUID& duid, uint32_t iaid) const;
-
- /// @brief Returns existing IPv6 lease for a given DUID+IA combination
- ///
- /// @todo Not implemented yet
- ///
- /// @param duid client DUID
- /// @param iaid IA identifier
- /// @param subnet_id identifier of the subnet the lease must belong to
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- Lease6Ptr getLease6(const DUID& duid, uint32_t iaid, SubnetID subnet_id) const;
-
- /// @brief Updates IPv4 lease.
- ///
- /// @todo Not implemented yet
- ///
- /// @param lease4 The lease to be updated.
- ///
- /// If no such lease is present, an exception will be thrown.
- void updateLease4(const Lease4Ptr& lease4);
-
- /// @brief Updates IPv4 lease.
- ///
- /// @todo Not implemented yet
- ///
- /// @param lease4 The lease to be updated.
- ///
- /// If no such lease is present, an exception will be thrown.
- void updateLease6(const Lease6Ptr& lease6);
-
- /// @brief Deletes a lease.
- ///
- /// @todo Not implemented yet
- ///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @return true if deletion was successful, false if no such lease exists
- bool deleteLease4(uint32_t addr);
-
- /// @brief Deletes a lease.
- ///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @return true if deletion was successful, false if no such lease exists
- bool deleteLease6(const isc::asiolink::IOAddress& addr);
-
- /// @brief Returns backend name.
- ///
- /// Each backend have specific name, e.g. "mysql" or "sqlite".
- std::string getName() const { return ("memfile"); }
-
- /// @brief Returns description of the backend.
- ///
- /// This description may be multiline text that describes the backend.
- std::string getDescription() const;
-
- /// @brief Returns backend version.
- std::string getVersion() const { return ("test-version"); }
-
- using LeaseMgr::getParameter;
-
-protected:
-
- typedef boost::multi_index_container< // this is a multi-index container...
- Lease6Ptr, // it will hold shared_ptr to leases6
- boost::multi_index::indexed_by< // and will be sorted by
- // IPv6 address that are unique. That particular key is a member
- // of the Lease6 structure, is of type IOAddress and can be accessed
- // by doing &Lease6::addr_
- boost::multi_index::ordered_unique<
- boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_>
- >
- >
- > Lease6Storage; // Let the whole contraption be called Lease6Storage.
-
- Lease6Storage storage6_;
-};
-
-}; // end of isc::dhcp::test namespace
-}; // end of isc::dhcp namespace
-}; // end of isc namespace
-
-#endif // MEMFILE_LEASE_MGR_H
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index 4b83517..7e92680 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -281,6 +281,56 @@ TEST_F(OptionDefinitionTest, factoryEmpty) {
EXPECT_THROW(factory(Option::V6, D6O_RAPID_COMMIT,OptionBuffer(2)),isc::BadValue);
}
+TEST_F(OptionDefinitionTest, factoryBinary) {
+ // Binary option is the one that is represented by the generic
+ // Option class. In fact all options can be represented by this
+ // class but for some of them it is just natural. The SERVERID
+ // option consists of the option code, length and binary data so
+ // this one was picked for this test.
+ OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, "binary");
+ Option::Factory* factory(NULL);
+ EXPECT_NO_THROW(factory = opt_def.getFactory());
+ ASSERT_TRUE(factory != NULL);
+
+ // Prepare some dummy data (serverid): 0, 1, 2 etc.
+ OptionBuffer buf(14);
+ for (int i = 0; i < 14; ++i) {
+ buf[i] = i;
+ }
+ // Create option instance with the factory function.
+ // If the OptionDefinition code works properly than
+ // object of the type Option should be returned.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = factory(Option::V6, D6O_SERVERID, buf);
+ );
+ // Expect base option type returned.
+ ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+ // Sanity check on universe, length and size. These are
+ // the basic parameters identifying any option.
+ EXPECT_EQ(Option::V6, option_v6->getUniverse());
+ EXPECT_EQ(4, option_v6->getHeaderLen());
+ ASSERT_EQ(buf.size(), option_v6->getData().size());
+
+ // Get the server id data from the option and compare
+ // against reference buffer. They are expected to match.
+ EXPECT_TRUE(std::equal(option_v6->getData().begin(),
+ option_v6->getData().end(),
+ buf.begin()));
+
+ // Repeat the same test scenario for DHCPv4 option.
+ OptionPtr option_v4;
+ ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, buf));
+ // Expect 'empty' DHCPv4 option.
+ EXPECT_EQ(Option::V4, option_v4->getUniverse());
+ EXPECT_EQ(2, option_v4->getHeaderLen());
+ ASSERT_EQ(buf.size(), option_v4->getData().size());
+
+ EXPECT_TRUE(std::equal(option_v6->getData().begin(),
+ option_v6->getData().end(),
+ buf.begin()));
+}
+
TEST_F(OptionDefinitionTest, factoryIA6) {
// This option consists of IAID, T1 and T2 fields (each 4 bytes long).
const int option6_ia_len = 12;
diff --git a/src/lib/dhcp/tests/subnet_unittest.cc b/src/lib/dhcp/tests/subnet_unittest.cc
index be25bc1..0d9c546 100644
--- a/src/lib/dhcp/tests/subnet_unittest.cc
+++ b/src/lib/dhcp/tests/subnet_unittest.cc
@@ -158,6 +158,19 @@ TEST(Subnet4Test, inRangeinPool) {
EXPECT_FALSE(subnet->inPool(IOAddress("192.3.0.0")));
}
+// This test checks if the toText() method returns text representation
+TEST(Subnet4Test, toText) {
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ EXPECT_EQ("192.0.2.0/24", subnet->toText());
+}
+
+// This test checks if the get() method returns proper parameters
+TEST(Subnet4Test, get) {
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 28, 1, 2, 3));
+ EXPECT_EQ("192.0.2.0", subnet->get().first.toText());
+ EXPECT_EQ(28, subnet->get().second);
+}
+
// Tests for Subnet6
TEST(Subnet6Test, constructor) {
@@ -418,4 +431,17 @@ TEST(Subnet6Test, inRangeinPool) {
EXPECT_FALSE(subnet->inPool(IOAddress("2001:db8::21")));
}
+// This test checks if the toText() method returns text representation
+TEST(Subnet6Test, toText) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+ EXPECT_EQ("2001:db8::/32", subnet.toText());
+}
+
+// This test checks if the get() method returns proper parameters
+TEST(Subnet6Test, get) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+ EXPECT_EQ("2001:db8::", subnet.get().first.toText());
+ EXPECT_EQ(32, subnet.get().second);
+}
+
};
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 5cf0732..e81ef76 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -93,6 +93,7 @@ libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
libb10_dns___la_SOURCES =
libb10_dns___la_SOURCES += edns.h edns.cc
libb10_dns___la_SOURCES += exceptions.h exceptions.cc
+libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
libb10_dns___la_SOURCES += labelsequence.h labelsequence.cc
libb10_dns___la_SOURCES += masterload.h masterload.cc
libb10_dns___la_SOURCES += master_lexer.h master_lexer.cc
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index 2a5c886..c9c5528 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -12,10 +12,86 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <exceptions/exceptions.h>
+
#include <dns/master_lexer.h>
+#include <dns/master_lexer_inputsource.h>
+
+#include <boost/shared_ptr.hpp>
#include <cassert>
#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+
+namespace {
+typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
+}
+using namespace master_lexer_internal;
+
+struct MasterLexer::MasterLexerImpl {
+ MasterLexerImpl() : token_(Token::NOT_STARTED) {}
+
+ std::vector<InputSourcePtr> sources_;
+ Token token_;
+};
+
+MasterLexer::MasterLexer() : impl_(new MasterLexerImpl) {
+}
+
+MasterLexer::~MasterLexer() {
+ delete impl_;
+}
+
+bool
+MasterLexer::pushSource(const char* filename, std::string* error) {
+ if (filename == NULL) {
+ isc_throw(InvalidParameter,
+ "NULL filename for MasterLexer::pushSource");
+ }
+ try {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(filename)));
+ } catch (const InputSource::OpenError& ex) {
+ if (error != NULL) {
+ *error = ex.what();
+ }
+ return (false);
+ }
+
+ return (true);
+}
+
+void
+MasterLexer::pushSource(std::istream& input) {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+}
+
+void
+MasterLexer::popSource() {
+ if (impl_->sources_.empty()) {
+ isc_throw(InvalidOperation,
+ "MasterLexer::popSource on an empty source");
+ }
+ impl_->sources_.pop_back();
+}
+
+std::string
+MasterLexer::getSourceName() const {
+ if (impl_->sources_.empty()) {
+ return (std::string());
+ }
+ return (impl_->sources_.back()->getName());
+}
+
+size_t
+MasterLexer::getSourceLine() const {
+ if (impl_->sources_.empty()) {
+ return (0);
+ }
+ return (impl_->sources_.back()->getCurrentLine());
+}
namespace {
const char* const error_text[] = {
@@ -27,9 +103,6 @@ const char* const error_text[] = {
const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
}
-namespace isc {
-namespace dns {
-
std::string
MasterLexer::Token::getErrorText() const {
if (type_ != ERROR) {
@@ -42,6 +115,5 @@ MasterLexer::Token::getErrorText() const {
return (error_text[val_.error_code_]);
}
-
} // end of namespace dns
} // end of namespace isc
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index bd86a04..da6bb5d 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -17,6 +17,7 @@
#include <exceptions/exceptions.h>
+#include <istream>
#include <string>
#include <stdint.h>
@@ -24,9 +25,146 @@
namespace isc {
namespace dns {
+/// \brief Tokenizer for parsing DNS master files.
+///
+/// The \c MasterLexer class provides tokenize interfaces for parsing DNS
+/// master files. It understands some special rules of master files as
+/// defined in RFC 1035, such as comments, character escaping, and multi-line
+/// data, and provides the user application with the actual data in a
+/// more convenient form such as a std::string object.
+///
+/// In order to support the $INCLUDE notation, this class is designed to be
+/// able to operate on multiple files or input streams in the nested way.
+/// The \c pushSource() and \c popSource() methods correspond to the push
+/// and pop operations.
+///
+/// While this class is public, it is less likely to be used by normal
+/// applications; it's mainly expected to be used within this library,
+/// specifically by the \c MasterLoader class and \c Rdata implementation
+/// classes.
+///
+/// \note The error handling policy of this class is slightly different from
+/// that of other classes of this library. We generally throw an exception
+/// for an invalid input, whether it's more likely to be a program error or
+/// a "user error", which means an invalid input that comes from outside of
+/// the library. But, this class returns an error code for some certain
+/// types of user errors instead of throwing an exception. Such cases include
+/// a syntax error identified by the lexer or a misspelled file name that
+/// causes a system error at the time of open. This is based on the assumption
+/// that the main user of this class is a parser of master files, where
+/// we want to give an option to ignore some non fatal errors and continue
+/// the parsing. This will be useful if it just performs overall error
+/// checks on a master file. When the (immediate) caller needs to do explicit
+/// error handling, exceptions are not that a useful tool for error reporting
+/// because we cannot separate the normal and error cases anyway, which would
+/// be one major advantage when we use exceptions. And, exceptions are
+/// generally more expensive, either when it happens or just by being able
+/// to handle with \c try and \c catch (depending on the underlying
+/// implementation of the exception handling). For these reasons, some of
+/// this class does not throw for an error that would be reported as an
+/// exception in other classes.
class MasterLexer {
public:
class Token; // we define it separately for better readability
+
+ /// \brief The constructor.
+ ///
+ /// \throw std::bad_alloc Internal resource allocation fails (rare case).
+ MasterLexer();
+
+ /// \brief The destructor.
+ ///
+ /// It internally closes any remaining input sources.
+ ~MasterLexer();
+
+ /// \brief Open a file and make it the current input source of MasterLexer.
+ ///
+ /// The opened file can be explicitly closed by the \c popSource() method;
+ /// if \c popSource() is not called within the lifetime of the
+ /// \c MasterLexer, it will be closed in the destructor.
+ ///
+ /// In the case possible system errors in opening the file (most likely
+ /// because of specifying a non-existent or unreadable file), it returns
+ /// false, and if the optional \c error parameter is non NULL, it will be
+ /// set to a description of the error (any existing content of the string
+ /// will be discarded). If opening the file succeeds, the given
+ /// \c error parameter will be intact.
+ ///
+ /// Note that this method has two styles of error reporting: one by
+ /// returning \c false (and setting \c error optionally) and the other
+ /// by throwing an exception. See the note for the class description
+ /// about the distinction.
+ ///
+ /// \throw InvalidParameter filename is NULL
+ /// \param filename A non NULL string specifying a master file
+ /// \param error If non null, a placeholder to set error description in
+ /// case of failure.
+ ///
+ /// \return true if pushing the file succeeds; false otherwise.
+ bool pushSource(const char* filename, std::string* error = NULL);
+
+ /// \brief Make the given stream the current input source of MasterLexer.
+ ///
+ /// The caller still holds the ownership of the passed stream; it's the
+ /// caller's responsibility to keep it valid as long as it's used in
+ /// \c MasterLexer or to release any resource for the stream after that.
+ /// The caller can explicitly tell \c MasterLexer to stop using the
+ /// stream by calling the \c popSource() method.
+ ///
+ /// \param input An input stream object that produces textual
+ /// representation of DNS RRs.
+ void pushSource(std::istream& input);
+
+ /// \brief Stop using the most recently opened input source (file or
+ /// stream).
+ ///
+ /// If it's a file, the previously opened file will be closed internally.
+ /// If it's a stream, \c MasterLexer will simply stop using
+ /// the stream; the caller can assume it will be never used in
+ /// \c MasterLexer thereafter.
+ ///
+ /// This method must not be called when there is no source pushed for
+ /// \c MasterLexer. This method is otherwise exception free.
+ ///
+ /// \throw isc::InvalidOperation Called with no pushed source.
+ void popSource();
+
+ /// \brief Return the name of the current input source name.
+ ///
+ /// If it's a file, it will be the C string given at the corresponding
+ /// \c pushSource() call, that is, its filename. If it's a stream, it will
+ /// be formatted as \c "stream-%p" where \c %p is hex representation
+ /// of the address of the stream object.
+ ///
+ /// If there is no opened source at the time of the call, this method
+ /// returns an empty string.
+ ///
+ /// \throw std::bad_alloc Resource allocation failed for string
+ /// construction (rare case)
+ ///
+ /// \return A string representation of the current source (see the
+ /// description)
+ std::string getSourceName() const;
+
+ /// \brief Return the input source line number.
+ ///
+ /// If there is an opened source, the return value will be a non-0
+ /// integer indicating the line number of the current source where
+ /// the \c MasterLexer is currently working. The expected usage of
+ /// this value is to print a helpful error message when parsing fails
+ /// by specifically identifying the position of the error.
+ ///
+ /// If there is no opened source at the time of the call, this method
+ /// returns 0.
+ ///
+ /// \throw None
+ ///
+ /// \return The current line number of the source (see the description)
+ size_t getSourceLine() const;
+
+private:
+ struct MasterLexerImpl;
+ MasterLexerImpl* impl_;
};
/// \brief Tokens for \c MasterLexer
diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
new file mode 100644
index 0000000..effe163
--- /dev/null
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -0,0 +1,158 @@
+// 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 <dns/master_lexer_inputsource.h>
+
+#include <cerrno>
+#include <cstring>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+
+namespace { // unnamed namespace
+
+std::string
+createStreamName(const std::istream& input_stream) {
+ std::stringstream ss;
+ ss << "stream-" << &input_stream;
+ return (ss.str());
+}
+
+} // end of unnamed namespace
+
+// Explicit definition of class static constant. The value is given in the
+// declaration so it's not needed here.
+const int InputSource::END_OF_STREAM;
+
+InputSource::InputSource(std::istream& input_stream) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ name_(createStreamName(input_stream)),
+ input_(input_stream)
+{}
+
+InputSource::InputSource(const char* filename) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ name_(filename),
+ input_(file_stream_)
+{
+ errno = 0;
+ file_stream_.open(filename);
+ if (file_stream_.fail()) {
+ std::string error_txt("Error opening the input source file: ");
+ error_txt += filename;
+ if (errno != 0) {
+ error_txt += "; possible cause: ";
+ error_txt += std::strerror(errno);
+ }
+ isc_throw(OpenError, error_txt);
+ }
+}
+
+InputSource::~InputSource()
+{
+ if (file_stream_.is_open()) {
+ file_stream_.close();
+ }
+}
+
+int
+InputSource::getChar() {
+ if (buffer_pos_ == buffer_.size()) {
+ // We may have reached EOF at the last call to
+ // getChar(). at_eof_ will be set then. We then simply return
+ // early.
+ if (at_eof_) {
+ return (END_OF_STREAM);
+ }
+ // We are not yet at EOF. Read from the stream.
+ const int c = input_.get();
+ // Have we reached EOF now? If so, set at_eof_ and return early,
+ // but don't modify buffer_pos_ (which should still be equal to
+ // the size of buffer_).
+ if (input_.eof()) {
+ at_eof_ = true;
+ return (END_OF_STREAM);
+ }
+ // This has to come after the .eof() check as some
+ // implementations seem to check the eofbit also in .fail().
+ if (input_.fail()) {
+ isc_throw(ReadError,
+ "Error reading from the input stream: " << getName());
+ }
+ buffer_.push_back(c);
+ }
+
+ const int c = buffer_[buffer_pos_];
+ ++buffer_pos_;
+ if (c == '\n') {
+ ++line_;
+ }
+
+ return (c);
+}
+
+void
+InputSource::ungetChar() {
+ if (at_eof_) {
+ at_eof_ = false;
+ } else if (buffer_pos_ == 0) {
+ isc_throw(UngetBeforeBeginning,
+ "Cannot skip before the start of buffer");
+ } else {
+ --buffer_pos_;
+ if (buffer_[buffer_pos_] == '\n') {
+ --line_;
+ }
+ }
+}
+
+void
+InputSource::ungetAll() {
+ buffer_pos_ = 0;
+ line_ = saved_line_;
+ at_eof_ = false;
+}
+
+void
+InputSource::saveLine() {
+ saved_line_ = line_;
+}
+
+void
+InputSource::compact() {
+ if (buffer_pos_ == buffer_.size()) {
+ buffer_.clear();
+ } else {
+ buffer_.erase(buffer_.begin(), buffer_.begin() + buffer_pos_);
+ }
+
+ buffer_pos_ = 0;
+}
+
+void
+InputSource::mark() {
+ saveLine();
+ compact();
+}
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h
new file mode 100644
index 0000000..8feffa2
--- /dev/null
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -0,0 +1,167 @@
+// 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 DNS_INPUTSOURCE_H
+#define DNS_INPUTSOURCE_H 1
+
+#include <exceptions/exceptions.h>
+
+#include <boost/noncopyable.hpp>
+
+#include <iostream>
+#include <fstream>
+#include <string>
+#include <vector>
+
+namespace isc {
+namespace dns {
+namespace master_lexer_internal {
+
+/// \brief An input source that is used internally by MasterLexer.
+///
+/// This is a helper internal class for MasterLexer, and represents
+/// state of a single source of the entire zone data to be
+/// parsed. Normally this means the master zone file, but MasterLexer
+/// can have multiple InputSources if $INCLUDE is used. The source can
+/// also be generic input stream (std::istream).
+///
+/// This class is not meant for public use. We also enforce that
+/// instances are non-copyable.
+class InputSource : boost::noncopyable {
+public:
+ /// \brief Returned by getChar() when end of stream is reached.
+ ///
+ /// \note C++ allows a static const class member of an integral type to
+ /// be used without explicit definition as long as its address isn't
+ /// required. But, since this is a public member variable and we cannot
+ /// assume how it's used, we give a definition in the implementation.
+ static const int END_OF_STREAM = -1;
+
+ /// \brief Exception thrown when ungetChar() is made to go before
+ /// the start of buffer.
+ struct UngetBeforeBeginning : public OutOfRange {
+ UngetBeforeBeginning(const char* file, size_t line, const char* what) :
+ OutOfRange(file, line, what)
+ {}
+ };
+
+ /// \brief Exception thrown when we fail to read from the input
+ /// stream or file.
+ struct ReadError : public Unexpected {
+ ReadError(const char* file, size_t line, const char* what) :
+ Unexpected(file, line, what)
+ {}
+ };
+
+ /// \brief Exception thrown when we fail to open the input file.
+ struct OpenError : public Unexpected {
+ OpenError(const char* file, size_t line, const char* what) :
+ Unexpected(file, line, what)
+ {}
+ };
+
+ /// \brief Constructor which takes an input stream. The stream is
+ /// read-from, but it is not closed.
+ explicit InputSource(std::istream& input_stream);
+
+ /// \brief Constructor which takes a filename to read from. The
+ /// associated file stream is managed internally.
+ ///
+ /// \throws OpenError when opening the input file fails.
+ explicit InputSource(const char* filename);
+
+ /// \brief Destructor
+ ~InputSource();
+
+ /// \brief Returns a name for the InputSource. Typically this is the
+ /// filename, but if the InputSource was constructed for an
+ /// \c std::istream, it returns a name in the format "stream-%p".
+ const std::string& getName() const {
+ return (name_);
+ }
+
+ /// \brief Returns if the input source is at end of file.
+ bool atEOF() const {
+ return (at_eof_);
+ }
+
+ /// \brief Returns the current line number being read.
+ size_t getCurrentLine() const {
+ return (line_);
+ }
+
+ /// \brief Saves the current line being read. Later, when
+ /// \c ungetAll() is called, it skips back to the last-saved line.
+ ///
+ /// TODO: Please make this method private if it is unused after the
+ /// MasterLexer implementation is complete (and only \c mark() is
+ /// used instead).
+ void saveLine();
+
+ /// Removes buffered content before the current location in the
+ /// \c InputSource. It's not possible to \c ungetChar() after this,
+ /// unless we read more data using \c getChar().
+ ///
+ /// TODO: Please make this method private if it is unused after the
+ /// MasterLexer implementation is complete (and only \c mark() is
+ /// used instead).
+ void compact();
+
+ /// Calls \c saveLine() and \c compact() in sequence.
+ void mark();
+
+ /// \brief Returns a single character from the input source. If end
+ /// of file is reached, \c END_OF_STREAM is returned.
+ ///
+ /// \throws ReadError when reading from the input stream or file
+ /// fails.
+ int getChar();
+
+ /// \brief Skips backward a single character in the input
+ /// source. The last-read character is unget.
+ ///
+ /// \throws UngetBeforeBeginning if we go backwards past the start
+ /// of reading, or backwards past the last time compact() was
+ /// called.
+ void ungetChar();
+
+ /// Forgets what was read, and skips back to the position where
+ /// \c compact() was last called. If \c compact() was not called, it
+ /// skips back to where reading started. If \c saveLine() was called
+ /// previously, it sets the current line number to the line number
+ /// saved.
+ void ungetAll();
+
+private:
+ bool at_eof_;
+ size_t line_;
+ size_t saved_line_;
+
+ std::vector<char> buffer_;
+ size_t buffer_pos_;
+
+ const std::string name_;
+ std::ifstream file_stream_;
+ std::istream& input_;
+};
+
+} // namespace master_lexer_internal
+} // namespace dns
+} // namespace isc
+
+#endif // DNS_INPUTSOURCE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 6df0e62..d5adc21 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -4,7 +4,7 @@ AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/util -I$(top_builddir)/src/lib/util
-AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_DATA_SRCDIR=\"$(abs_top_srcdir)/src/lib/dns/tests/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/dns/tests/testdata\"
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -22,9 +22,11 @@ if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = unittest_util.h unittest_util.cc
run_unittests_SOURCES += edns_unittest.cc
+run_unittests_SOURCES += master_lexer_inputsource_unittest.cc
run_unittests_SOURCES += labelsequence_unittest.cc
run_unittests_SOURCES += messagerenderer_unittest.cc
run_unittests_SOURCES += master_lexer_token_unittest.cc
+run_unittests_SOURCES += master_lexer_unittest.cc
run_unittests_SOURCES += name_unittest.cc
run_unittests_SOURCES += nsec3hash_unittest.cc
run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
new file mode 100644
index 0000000..db0cbec
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -0,0 +1,325 @@
+// 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 <dns/master_lexer_inputsource.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include <string.h>
+
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::master_lexer_internal;
+
+namespace {
+
+class InputSourceTest : public ::testing::Test {
+protected:
+ InputSourceTest() :
+ str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+ str_length_(strlen(str_)),
+ iss_(str_),
+ source_(iss_)
+ {}
+
+ const char* str_;
+ const size_t str_length_;
+ stringstream iss_;
+ InputSource source_;
+};
+
+// Test the default return values set during InputSource construction.
+TEST_F(InputSourceTest, defaults) {
+ EXPECT_EQ(1, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+}
+
+// getName() on file and stream sources
+TEST_F(InputSourceTest, getName) {
+ EXPECT_EQ(0, source_.getName().find("stream-"));
+
+ // Use some file; doesn't really matter what.
+ InputSource source2(TEST_DATA_SRCDIR "/masterload.txt");
+ EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", source2.getName());
+}
+
+TEST_F(InputSourceTest, nonExistentFile) {
+ EXPECT_THROW({
+ InputSource source(TEST_DATA_SRCDIR "/does-not-exist");
+ }, InputSource::OpenError);
+}
+
+// getChar() should return characters from the input stream in
+// sequence. ungetChar() should skip backwards.
+void
+checkGetAndUngetChar(InputSource& source,
+ const char* str, const size_t str_length)
+{
+ for (size_t i = 0; i < str_length; ++i) {
+ EXPECT_EQ(str[i], source.getChar());
+ EXPECT_FALSE(source.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source.atEOF());
+
+ // Now, let's go backwards. This should cause the EOF to be set to
+ // false.
+ source.ungetChar();
+
+ // Now, EOF should be false.
+ EXPECT_FALSE(source.atEOF());
+
+ // This should cause EOF to be set again.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source.atEOF());
+
+ // Now, let's go backwards in a loop. Start by skipping the EOF.
+ source.ungetChar();
+
+ for (size_t i = 0; i < str_length; ++i) {
+ const size_t index = str_length - 1 - i;
+ // Skip one character.
+ source.ungetChar();
+ EXPECT_EQ(str[index], source.getChar());
+ // Skip the character we received again.
+ source.ungetChar();
+ }
+
+ // Skipping past the start of buffer should throw.
+ EXPECT_THROW(source.ungetChar(), InputSource::UngetBeforeBeginning);
+}
+
+TEST_F(InputSourceTest, stream) {
+ checkGetAndUngetChar(source_, str_, str_length_);
+}
+
+TEST_F(InputSourceTest, file) {
+ std::ifstream fs(TEST_DATA_SRCDIR "/masterload.txt");
+ const std::string str((std::istreambuf_iterator<char>(fs)),
+ std::istreambuf_iterator<char>());
+ fs.close();
+
+ InputSource source(TEST_DATA_SRCDIR "/masterload.txt");
+ checkGetAndUngetChar(source, str.c_str(), str.size());
+}
+
+// ungetAll() should skip back to the place where the InputSource
+// started at construction, or the last saved start of line.
+TEST_F(InputSourceTest, ungetAll) {
+ while (!source_.atEOF()) {
+ source_.getChar();
+ }
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ source_.ungetAll();
+
+ // Now we are back to where we started.
+ EXPECT_EQ(1, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+}
+
+TEST_F(InputSourceTest, compact) {
+ // Compact at the start
+ source_.compact();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ for (size_t i = 0; i < str_length_; ++i) {
+ EXPECT_EQ(str_[i], source_.getChar());
+ EXPECT_FALSE(source_.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source_.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Compact again
+ source_.compact();
+
+ // We are still at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Skip the EOF.
+ source_.ungetChar();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+ EXPECT_TRUE(source_.atEOF());
+}
+
+TEST_F(InputSourceTest, markDuring) {
+ // First, skip to line 2.
+ while (!source_.atEOF() &&
+ (source_.getCurrentLine() != 2)) {
+ source_.getChar();
+ }
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(2, source_.getCurrentLine());
+
+ // Now, unget a couple of characters. This should cause the
+ // buffer_pos_ to be not equal to the size of the buffer.
+ source_.ungetChar();
+ source_.ungetChar();
+
+ // Now "mark" the source, meaning that we save line number and also
+ // compact the internal buffer at this stage.
+ source_.mark();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ for (size_t i = 13; i < str_length_; ++i) {
+ EXPECT_EQ(str_[i], source_.getChar());
+ EXPECT_FALSE(source_.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source_.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source_.atEOF());
+
+ // Now, ungetAll() and check where it goes back.
+ source_.ungetAll();
+
+ // Ungetting here must throw.
+ EXPECT_THROW(source_.ungetChar(), InputSource::UngetBeforeBeginning);
+
+ for (size_t i = 13; i < str_length_; ++i) {
+ EXPECT_EQ(str_[i], source_.getChar());
+ EXPECT_FALSE(source_.atEOF());
+ }
+
+ // At this point, we still have not reached EOF.
+ EXPECT_FALSE(source_.atEOF());
+
+ // This should cause EOF to be set.
+ EXPECT_EQ(InputSource::END_OF_STREAM, source_.getChar());
+
+ // Now, EOF should be set.
+ EXPECT_TRUE(source_.atEOF());
+}
+
+// Test line counters.
+TEST_F(InputSourceTest, lines) {
+ size_t line = 1;
+ while (!source_.atEOF()) {
+ if (source_.getChar() == '\n') {
+ ++line;
+ }
+ EXPECT_EQ(line, source_.getCurrentLine());
+ }
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Go backwards 2 characters, skipping the last EOF and '\n'.
+ source_.ungetChar();
+ source_.ungetChar();
+
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(3, source_.getCurrentLine());
+
+ source_.ungetAll();
+
+ // Now we are back to where we started.
+ EXPECT_EQ(1, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+
+ // Now check that line numbers are decremented properly (as much as
+ // possible using the available API).
+ while (!source_.atEOF()) {
+ source_.getChar();
+ }
+ line = source_.getCurrentLine();
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, line);
+
+ EXPECT_THROW({
+ while (true) {
+ source_.ungetChar();
+ EXPECT_TRUE(((line == source_.getCurrentLine()) ||
+ ((line - 1) == source_.getCurrentLine())));
+ line = source_.getCurrentLine();
+ }
+ }, InputSource::UngetBeforeBeginning);
+
+ // Now we are back to where we started.
+ EXPECT_EQ(1, source_.getCurrentLine());
+}
+
+// ungetAll() after saveLine() should skip back to the last-saved place.
+TEST_F(InputSourceTest, saveLine) {
+ // First, skip to line 2.
+ while (!source_.atEOF() &&
+ (source_.getCurrentLine() != 2)) {
+ source_.getChar();
+ }
+ EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(2, source_.getCurrentLine());
+
+ // Now, save the line.
+ source_.saveLine();
+
+ // Now, go to EOF
+ while (!source_.atEOF()) {
+ source_.getChar();
+ }
+
+ // Now, we are at EOF.
+ EXPECT_TRUE(source_.atEOF());
+ EXPECT_EQ(4, source_.getCurrentLine());
+
+ // Now, ungetAll() and check where it goes back.
+ source_.ungetAll();
+
+ // Now we are back to where we last-saved.
+ EXPECT_EQ(2, source_.getCurrentLine());
+ EXPECT_FALSE(source_.atEOF());
+}
+
+} // end namespace
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
new file mode 100644
index 0000000..93fead7
--- /dev/null
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -0,0 +1,127 @@
+// 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 <exceptions/exceptions.h>
+
+#include <dns/master_lexer.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
+using namespace isc::dns;
+using std::string;
+using std::stringstream;
+using boost::lexical_cast;
+
+namespace {
+
+class MasterLexerTest : public ::testing::Test {
+protected:
+ MasterLexerTest() :
+ expected_stream_name("stream-" + lexical_cast<string>(&ss))
+ {}
+
+ MasterLexer lexer;
+ stringstream ss;
+ const string expected_stream_name;
+};
+
+// Commonly used check case where the input sources stack is empty.
+void
+checkEmptySource(const MasterLexer& lexer) {
+ EXPECT_TRUE(lexer.getSourceName().empty());
+ EXPECT_EQ(0, lexer.getSourceLine());
+}
+
+TEST_F(MasterLexerTest, preOpen) {
+ // Initially sources stack is empty.
+ checkEmptySource(lexer);
+}
+
+TEST_F(MasterLexerTest, pushStream) {
+ lexer.pushSource(ss);
+ EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+
+ // From the point of view of this test, we only have to check (though
+ // indirectly) getSourceLine calls InputSource::getCurrentLine. It should
+ // return 1 initially.
+ EXPECT_EQ(1, lexer.getSourceLine());
+
+ // By popping it the stack will be empty again.
+ lexer.popSource();
+ checkEmptySource(lexer);
+}
+
+TEST_F(MasterLexerTest, pushFile) {
+ // We use zone file (-like) data, but in this test that actually doesn't
+ // matter.
+ EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt"));
+ EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+ EXPECT_EQ(1, lexer.getSourceLine());
+
+ lexer.popSource();
+ checkEmptySource(lexer);
+
+ // If we give a non NULL string pointer, its content will be intact
+ // if pushSource succeeds.
+ std::string error_txt = "dummy";
+ EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt",
+ &error_txt));
+ EXPECT_EQ("dummy", error_txt);
+}
+
+TEST_F(MasterLexerTest, pushBadFileName) {
+ EXPECT_THROW(lexer.pushSource(NULL), isc::InvalidParameter);
+}
+
+TEST_F(MasterLexerTest, pushFileFail) {
+ // The file to be pushed doesn't exist. pushSource() fails and
+ // some non empty error string should be set.
+ std::string error_txt;
+ EXPECT_TRUE(error_txt.empty());
+ EXPECT_FALSE(lexer.pushSource("no-such-file", &error_txt));
+ EXPECT_FALSE(error_txt.empty());
+
+ // It's safe to pass NULL error_txt (either explicitly or implicitly as
+ // the default)
+ EXPECT_FALSE(lexer.pushSource("no-such-file", NULL));
+ EXPECT_FALSE(lexer.pushSource("no-such-file"));
+}
+
+TEST_F(MasterLexerTest, nestedPush) {
+ lexer.pushSource(ss);
+ EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+
+ // We can push another source without popping the previous one.
+ lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
+ EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+
+ // popSource() works on the "topmost" (last-pushed) source
+ lexer.popSource();
+ EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+
+ lexer.popSource();
+ EXPECT_TRUE(lexer.getSourceName().empty());
+}
+
+TEST_F(MasterLexerTest, invalidPop) {
+ // popSource() cannot be called if the sources stack is empty.
+ EXPECT_THROW(lexer.popSource(), isc::InvalidOperation);
+}
+
+}
diff --git a/src/lib/python/isc/bind10/sockcreator.py b/src/lib/python/isc/bind10/sockcreator.py
index 55142ee..593d1a6 100644
--- a/src/lib/python/isc/bind10/sockcreator.py
+++ b/src/lib/python/isc/bind10/sockcreator.py
@@ -16,6 +16,7 @@
import socket
import struct
import os
+import errno
import copy
import subprocess
import copy
@@ -36,16 +37,16 @@ class CreatorError(Exception):
passed to the __init__ function.
"""
- def __init__(self, message, fatal, errno=None):
+ def __init__(self, message, fatal, error_num=None):
"""
Creates the exception. The message argument is the usual string.
The fatal one tells if the error is fatal (eg. the creator crashed)
- and errno is the errno value returned from socket creator, if
+ and error_num is the errno value returned from socket creator, if
applicable.
"""
Exception.__init__(self, message)
self.fatal = fatal
- self.errno = errno
+ self.errno = error_num
class Parser:
"""
@@ -94,6 +95,13 @@ class Parser:
self.__socket = None
raise CreatorError(str(se), True)
+ def __addrport_str(self, address, port):
+ '''Convert a pair of IP address and port to common form for logging.'''
+ if address.family == socket.AF_INET:
+ return str(address) + ':' + str(port)
+ else:
+ return '[' + str(address) + ']:' + str(port)
+
def get_socket(self, address, port, socktype):
"""
Asks the socket creator process to create a socket. Pass an address
@@ -136,9 +144,9 @@ class Parser:
elif answer == b'E':
# There was an error, read the error as well
error = self.__socket.recv(1)
- errno = struct.unpack('i',
- self.__read_all(len(struct.pack('i',
- 0))))
+ rcv_errno = struct.unpack('i',
+ self.__read_all(len(struct.pack('i',
+ 0))))
if error == b'S':
cause = 'socket'
elif error == b'B':
@@ -147,10 +155,22 @@ class Parser:
self.__socket = None
logger.fatal(BIND10_SOCKCREATOR_BAD_CAUSE, error)
raise CreatorError('Unknown error cause' + str(answer), True)
- logger.error(BIND10_SOCKET_ERROR, cause, errno[0],
- os.strerror(errno[0]))
- raise CreatorError('Error creating socket on ' + cause, False,
- errno[0])
+ logger.error(BIND10_SOCKET_ERROR, cause, rcv_errno[0],
+ os.strerror(rcv_errno[0]))
+
+ # Provide as detailed information as possible on the error,
+ # as error related to socket creation is a common operation
+ # trouble. In particular, we are intentionally very verbose
+ # if it fails due to "permission denied" so the administrator
+ # can easily identify what is wrong and how to fix it.
+ addrport = self.__addrport_str(address, port)
+ error_text = 'Error creating socket on ' + cause + \
+ ' to be bound to ' + addrport + ': ' + \
+ os.strerror(rcv_errno[0])
+ if rcv_errno[0] == errno.EACCES:
+ error_text += ' - probably need to restart BIND 10 ' + \
+ 'as a super user'
+ raise CreatorError(error_text, False, rcv_errno[0])
else:
self.__socket = None
logger.fatal(BIND10_SOCKCREATOR_BAD_RESPONSE, answer)
diff --git a/src/lib/util/interprocess_sync_file.cc b/src/lib/util/interprocess_sync_file.cc
index d045449..25af55c 100644
--- a/src/lib/util/interprocess_sync_file.cc
+++ b/src/lib/util/interprocess_sync_file.cc
@@ -15,6 +15,8 @@
#include "interprocess_sync_file.h"
#include <string>
+#include <cerrno>
+#include <cstring>
#include <stdlib.h>
#include <string.h>
@@ -68,8 +70,8 @@ InterprocessSyncFile::do_lock(int cmd, short l_type) {
if (fd_ == -1) {
isc_throw(InterprocessSyncFileError,
- "Unable to use interprocess sync lockfile: " +
- lockfile_path);
+ "Unable to use interprocess sync lockfile ("
+ << std::strerror(errno) << "): " << lockfile_path);
}
}
diff --git a/tests/tools/perfdhcp/command_options.cc b/tests/tools/perfdhcp/command_options.cc
index 9aa6abf..9de07ca 100644
--- a/tests/tools/perfdhcp/command_options.cc
+++ b/tests/tools/perfdhcp/command_options.cc
@@ -23,6 +23,7 @@
#include <exceptions/exceptions.h>
#include <dhcp/iface_mgr.h>
+#include <dhcp/duid.h>
#include "command_options.h"
using namespace std;
@@ -567,8 +568,8 @@ CommandOptions::generateDuidTemplate() {
const uint8_t duid_template_len = 14;
duid_template_.resize(duid_template_len);
// The first four octets consist of DUID LLT and hardware type.
- duid_template_[0] = DUID_LLT >> 8;
- duid_template_[1] = DUID_LLT & 0xff;
+ duid_template_[0] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) >> 8);
+ duid_template_[1] = static_cast<uint8_t>(static_cast<uint16_t>(isc::dhcp::DUID::DUID_LLT) & 0xff);
duid_template_[2] = HWTYPE_ETHERNET >> 8;
duid_template_[3] = HWTYPE_ETHERNET & 0xff;
More information about the bind10-changes
mailing list