Commit 4b8ab3bb authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[5032] mac-sources, control-socket and relay-info parsers converted

 - The parsers converted to SimpleParser
 - Unit-tests updated, cleaned up and corrected
 - Updated examples to actually use the information
 - Updated User's Guide that the empty mac-sources is not allowed
parent 12e3d281
......@@ -44,6 +44,14 @@
"lfc-interval": 3600
},
// This defines a control socket. If defined, Kea will open a UNIX socket
// and will listen for incoming commands. See section 15 of the Kea User's
// Guide for list of supported commands.
"control-socket": {
"socket-type": "unix",
"socket-name": "/tmp/kea4-ctrl-socket"
},
// Addresses will be assigned with a lifetime of 4000 seconds.
// The client is told to start renewing after 1000 seconds. If the server
// does not respond within 2000 seconds of the lease being granted, client
......@@ -83,7 +91,14 @@
},
{
"pools": [ { "pool": "192.0.4.1 - 192.0.4.254" } ],
"subnet": "192.0.4.0/24"
"subnet": "192.0.4.0/24",
// Sometimes the relay may use an IPv4 address that's not matching
// the subnet. This is discouraged, but there are valid cases when it
// makes sense. One case is when there is a shared subnet.
"relay": {
"ip-address": "192.168.1.1"
}
}
]
},
......
# This is an example configuration file for DHCPv6 server in Kea.
# It attempts to showcase some of the more advanced features.
# Topology wise, it's a basic scenario with one IPv6 subnet configured.
# It is assumed that one subnet (2001:db8:1::/64) is available directly
# over ethX interface.
#
# The following features are currently showcased here:
# 1. Configuration of MAC/hardware address sources in DHCPv6
// This is an example configuration file for DHCPv6 server in Kea.
// It attempts to showcase some of the more advanced features.
// Topology wise, it's a basic scenario with one IPv6 subnet configured.
// It is assumed that one subnet (2001:db8:1::/64) is available directly
// over ethX interface.
//
// The following features are currently showcased here:
// 1. Configuration of MAC/hardware address sources in DHCPv6
// 2. RSOO (Relay supplied options) - Some relays may insert options with the
// intention for the server to insert them into client directed messages.
// 3. Control socket. Kea can open a socket and listen for incoming
// commands.
{ "Dhcp6":
{
# Kea is told to listen on ethX network interface only.
// Kea is told to listen on ethX network interface only.
"interfaces-config": {
"interfaces": [ "ethX" ]
},
# We need to specify the the database used to store leases. As of
# September 2016, four database backends are supported: MySQL,
# PostgreSQL, Cassandra, and the in-memory database, Memfile.
# We will use memfile because it doesn't require any prior set up.
// We need to specify the the database used to store leases. As of
// September 2016, four database backends are supported: MySQL,
// PostgreSQL, Cassandra, and the in-memory database, Memfile.
// We will use memfile because it doesn't require any prior set up.
"lease-database": {
"type": "memfile",
"lfc-interval": 3600
},
# Kea 0.9.1 introduced MAC/hardware addresses support in DHCPv6. There is
# no single reliable method of getting MAC address information in DHCPv6.
# Kea supports several methods. Depending on your network set up, some
# methods may be more preferable than others, hence the configuration
# parameter. 'mac-sources' is a list of methods. Allowed parameters are:
# any, raw, duid, ipv6-link-local, client-link-addr-option, rfc6939 (which
# is an alias for client-link-addr-option), remote-id, rfc4649 (which is an
# alias for remote-id, subscriber-id, rfc4580 (which is an alias for
# subscriber-id) and docsis.
#
# Note that the order matters. Methods are attempted one by one in the order
# specified until hardware address is obtained. If you don't care which method
# is used, using 'any' is marginally faster than enumerating them all.
#
# If mac-sources are not specified, a default value of 'any' is used.
// Kea 0.9.1 introduced MAC/hardware addresses support in DHCPv6. There is
// no single reliable method of getting MAC address information in DHCPv6.
// Kea supports several methods. Depending on your network set up, some
// methods may be more preferable than others, hence the configuration
// parameter. 'mac-sources' is a list of methods. Allowed parameters are:
// any, raw, duid, ipv6-link-local, client-link-addr-option, rfc6939 (which
// is an alias for client-link-addr-option), remote-id, rfc4649 (which is an
// alias for remote-id, subscriber-id, rfc4580 (which is an alias for
// subscriber-id) and docsis.
//
// Note that the order matters. Methods are attempted one by one in the order
// specified until hardware address is obtained. If you don't care which method
// is used, using 'any' is marginally faster than enumerating them all.
//
// If mac-sources are not specified, a default value of 'any' is used.
"mac-sources": [ "client-link-addr-option", "duid", "ipv6-link-local" ],
# RFC6422 defines a mechanism called relay-supplied options option. The relay
# agent may insert certain options that the server will echo back to the
# client, if certain criteria are met. One condition is that the option must
# be RSOO-enabled (i.e. allowed to be echoed back). IANA maintains a list
# of those options here:
# http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#options-relay-supplied
# However, it is possible to allow the server to echo back additional options.
# This entry marks options 110, 120 and 130 as RSOO-enabled.
// RFC6422 defines a mechanism called relay-supplied options option. The relay
// agent may insert certain options that the server will echo back to the
// client, if certain criteria are met. One condition is that the option must
// be RSOO-enabled (i.e. allowed to be echoed back). IANA maintains a list
// of those options here:
// http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#options-relay-supplied
// However, it is possible to allow the server to echo back additional options.
// This entry marks options 110, 120 and 130 as RSOO-enabled.
"relay-supplied-options": [ "110", "120", "130" ],
# Addresses will be assigned with preferred and valid lifetimes
# being 3000 and 4000, respectively. Client is told to start
# renewing after 1000 seconds. If the server does not respond
# after 2000 seconds since the lease was granted, client is supposed
# to start REBIND procedure (emergency renewal that allows switching
# to a different server).
// This defines a control socket. If defined, Kea will open a UNIX socket
// and will listen for incoming commands. See section 15 of the Kea User's
// Guide for list of supported commands.
"control-socket": {
"socket-type": "unix",
"socket-name": "/tmp/kea6-ctrl-socket"
},
// Addresses will be assigned with preferred and valid lifetimes
// being 3000 and 4000, respectively. Client is told to start
// renewing after 1000 seconds. If the server does not respond
// after 2000 seconds since the lease was granted, client is supposed
// to start REBIND procedure (emergency renewal that allows switching
// to a different server).
"preferred-lifetime": 3000,
"valid-lifetime": 4000,
"renew-timer": 1000,
"rebind-timer": 2000,
# The following list defines subnets. Each subnet consists of at
# least subnet and pool entries.
// The following list defines subnets. Each subnet consists of at
// least subnet and pool entries.
"subnet6": [
{
"pools": [ { "pool": "2001:db8:1::/80" } ],
# This defines PD (prefix delegation) pools. In this case
# we have only one pool. That consists of /64 prefixes
# being delegated out of large /48 pool. Each delegated
# prefix will contain an excluded-prefix option.
// This defines PD (prefix delegation) pools. In this case
// we have only one pool. That consists of /64 prefixes
// being delegated out of large /48 pool. Each delegated
// prefix will contain an excluded-prefix option.
"pd-pools": [
{
"prefix": "2001:db8:abcd::",
......@@ -82,13 +95,21 @@
}
],
"subnet": "2001:db8:1::/64",
"interface": "ethX"
"interface": "ethX",
// Sometimes the relay may use an odd IPv6 address that's not matching
// the subnet. This is discouraged, but there are valid cases when it
// makes sense. One case is when the relay has only link-local address
// and another is when there is a shared subnet scenario.
"relay": {
"ip-address": "3000::1"
}
}
]
},
# The following configures logging. It assumes that messages with at least
# informational level (info, warn, error and fatal) should be logged to stdout.
// The following configures logging. It assumes that messages with at least
// informational level (info, warn, error and fatal) should be logged to stdout.
"Logging": {
"loggers": [
{
......
......@@ -3500,7 +3500,8 @@ If not specified, the default value is:
When not specified, a special value of "any" is used, which
instructs the server to attempt to use all the methods in sequence and use
value returned by the first one that succeeds.</para>
value returned by the first one that succeeds. If specified, it
has to have at least one value.</para>
<para>Supported methods are:
<itemizedlist>
......
......@@ -189,8 +189,7 @@ protected:
parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pools") == 0) {
parser = new Pools4ListParser(config_id, pools_);
} else if (config_id.compare("relay") == 0) {
parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
// relay has been converted to SimpleParser already.
// option-data has been converted to SimpleParser already.
} else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, boolean_values_);
......@@ -440,8 +439,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
parser = new D2ClientConfigParser(config_id);
} else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, globalContext()->boolean_values_);
} else if (config_id.compare("control-socket") == 0) {
parser = new ControlSocketParser(config_id);
// control-socket has been converted to SimpleParser already.
} else if (config_id.compare("expired-leases-processing") == 0) {
parser = new ExpirationConfigParser();
} else if (config_id.compare("client-classes") == 0) {
......@@ -596,6 +594,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
continue;
}
if (config_pair.first == "control-socket") {
ControlSocketParser parser;
SrvConfigPtr srv_cfg = CfgMgr::instance().getStagingCfg();
parser.parse(*srv_cfg, config_pair.second);
continue;
}
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first,
config_pair.second));
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)
......
// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -7,14 +7,14 @@
#include <gtest/gtest.h>
#include <cc/data.h>
#include <dhcp4/parser_context.h>
#include <fstream>
#include <cstdio>
#include <exceptions/exceptions.h>
#include <testutils/io_utils.h>
using namespace isc::data;
using namespace std;
namespace {
namespace isc {
namespace dhcp {
namespace test {
/// @brief compares two JSON trees
///
......@@ -128,6 +128,8 @@ TEST(ParserTest, keywordDhcp4) {
testParser(txt, Parser4Context::PARSER_DHCP4);
}
// Tests if bash (#) comments are supported. That's the only comment type that
// was supported by the old parser.
TEST(ParserTest, bashComments) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
......@@ -146,7 +148,8 @@ TEST(ParserTest, bashComments) {
testParser(txt, Parser4Context::PARSER_DHCP4, false);
}
TEST(ParserTest, cComments) {
// Tests if C++ (//) comments can start anywhere, not just in the first line.
TEST(ParserTest, cppComments) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},\n"
......@@ -161,6 +164,7 @@ TEST(ParserTest, cComments) {
testParser(txt, Parser4Context::PARSER_DHCP4, false);
}
// Tests if bash (#) comments can start anywhere, not just in the first line.
TEST(ParserTest, bashCommentsInline) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
......@@ -176,6 +180,7 @@ TEST(ParserTest, bashCommentsInline) {
testParser(txt, Parser4Context::PARSER_DHCP4, false);
}
// Tests if multi-line C style comments are handled correctly.
TEST(ParserTest, multilineComments) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
......@@ -193,89 +198,13 @@ TEST(ParserTest, multilineComments) {
testParser(txt, Parser4Context::PARSER_DHCP4, false);
}
/// @brief removes comments from a JSON file
///
/// This is rather naive implementation, but it's probably sufficient for
/// testing. It won't be able to pick any trickier cases, like # or //
/// appearing in strings, nested C++ comments etc.
///
/// @param input_file file to be stripped of comments
/// @return a new file that has comments stripped from it
std::string decommentJSONfile(const std::string& input_file) {
ifstream f(input_file);
if (!f.is_open()) {
isc_throw(isc::BadValue, "can't open input file for reading: " + input_file);
}
string outfile;
size_t last_slash = input_file.find_last_of("/");
if (last_slash != string::npos) {
outfile = input_file.substr(last_slash + 1);
} else {
outfile = input_file;
}
outfile += "-decommented";
ofstream out(outfile);
if (!out.is_open()) {
isc_throw(isc::BadValue, "can't open output file for writing: " + input_file);
}
bool in_comment = false;
string line;
while (std::getline(f, line)) {
// First, let's get rid of the # comments
size_t hash_pos = line.find("#");
if (hash_pos != string::npos) {
line = line.substr(0, hash_pos);
}
// Second, let's get rid of the // comments
size_t dblslash_pos = line.find("//");
if (dblslash_pos != string::npos) {
line = line.substr(0, dblslash_pos);
}
// Now the tricky part: c comments.
size_t begin_pos = line.find("/*");
size_t end_pos = line.find("*/");
if (in_comment && end_pos == string::npos) {
// we continue through multiline comment
line = "";
} else {
if (begin_pos != string::npos) {
in_comment = true;
if (end_pos != string::npos) {
// sigle line comment. Let's get rid of the content in between
line = line.replace(begin_pos, end_pos + 2, end_pos + 2 - begin_pos, ' ');
in_comment = false;
} else {
line = line.substr(0, begin_pos);
}
} else {
if (in_comment && end_pos != string::npos) {
line = line.replace(0, end_pos +2 , end_pos + 2, ' ');
in_comment = false;
}
}
}
// Finally, write the line to the output file.
out << line << endl;
}
f.close();
out.close();
return (outfile);
}
/// @brief Loads specified example config file
///
/// This test loads specified example file twice: first, using the legacy
/// JSON file and then second time using bison parser. Two created Element
/// trees are then compared. The input is decommented before it is passed
/// to legacy parser (as its support for comments is very limited).
/// to legacy parser (as legacy support for comments is very limited).
///
/// @param fname name of the file to be loaded
void testFile(const std::string& fname) {
......@@ -284,8 +213,7 @@ void testFile(const std::string& fname) {
string decommented = decommentJSONfile(fname);
cout << "Attempting to load file " << fname << " (" << decommented
<< ")" << endl;
cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true));
......@@ -305,6 +233,8 @@ void testFile(const std::string& fname) {
ASSERT_TRUE(test_json);
compareJSON(reference_json, test_json);
unlink(decommented);
}
// This test loads all available existing files. Each config is loaded
......@@ -329,6 +259,11 @@ TEST(ParserTest, file) {
}
}
/// @brief Tests error conditions in Dhcp4Parser
///
/// @param txt text to be parsed
/// @param parser_type type of the parser to be used in the test
/// @param msg expected content of the exception
void testError(const std::string& txt,
Parser4Context::ParserType parser_type,
const std::string& msg)
......@@ -347,7 +282,7 @@ void testError(const std::string& txt,
}
}
// Check errors
// Verify that error conditions are handled correctly.
TEST(ParserTest, errors) {
// no input
testError("", Parser4Context::PARSER_JSON,
......@@ -609,3 +544,5 @@ TEST(ParserTest, unicodeSlash) {
}
};
};
};
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -424,8 +424,7 @@ protected:
parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pools") == 0) {
parser = new Pools6ListParser(config_id, pools_);
} else if (config_id.compare("relay") == 0) {
parser = new RelayInfoParser(config_id, relay_info_, Option::V6);
// relay has been converted to SimpleParser.
} else if (config_id.compare("pd-pools") == 0) {
parser = new PdPoolListParser(config_id, pools_);
// option-data was here, but it is now converted to SimpleParser
......@@ -718,13 +717,10 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(const std::string& config_id,
parser = new HooksLibrariesParser(config_id);
} else if (config_id.compare("dhcp-ddns") == 0) {
parser = new D2ClientConfigParser(config_id);
} else if (config_id.compare("mac-sources") == 0) {
parser = new MACSourcesListConfigParser(config_id,
globalContext());
// mac-source has been converted to SimpleParser.
} else if (config_id.compare("relay-supplied-options") == 0) {
parser = new RSOOListConfigParser(config_id);
} else if (config_id.compare("control-socket") == 0) {
parser = new ControlSocketParser(config_id);
// control-socket has been converted to SimpleParser.
} else if (config_id.compare("expired-leases-processing") == 0) {
parser = new ExpirationConfigParser();
} else if (config_id.compare("client-classes") == 0) {
......@@ -870,6 +866,20 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
continue;
}
if (config_pair.first == "mac-sources") {
MACSourcesListConfigParser parser;
CfgMACSource& mac_source = CfgMgr::instance().getStagingCfg()->getMACSources();
parser.parse(mac_source, config_pair.second);
continue;
}
if (config_pair.first == "control-socket") {
ControlSocketParser parser;
SrvConfigPtr srv_config = CfgMgr::instance().getStagingCfg();
parser.parse(*srv_config, config_pair.second);
continue;
}
ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first,
config_pair.second));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED)
......
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -762,7 +762,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value should be 0 (success)
checkResult(status, 0);
}
......@@ -4327,11 +4327,9 @@ TEST_F(Dhcp6ParserTest, macSources) {
EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, mac_sources[5]);
}
/// The goal of this test is to verify that MAC sources configuration can be
/// empty.
/// Note the Dhcp6 parser requires the list to NOT be empty?!
/// The goal of this test is to verify that empty MAC sources configuration
/// is rejected. If specified, this has to have at least one value.
TEST_F(Dhcp6ParserTest, macSourcesEmpty) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
......@@ -4343,11 +4341,9 @@ TEST_F(Dhcp6ParserTest, macSourcesEmpty) {
"\"subnet6\": [ ], "
"\"valid-lifetime\": 4000 }")));
// returned value should be 0 (success)
checkResult(status, 0);
CfgMACSources mac_sources = CfgMgr::instance().getStagingCfg()->getMACSources().get();
EXPECT_EQ(0, mac_sources.size());
// returned value should be 1 (failure), because the mac-sources must not
// be empty when specified.
checkResult(status, 1);
}
/// The goal of this test is to verify that MAC sources configuration can
......
// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -7,23 +7,38 @@
#include <gtest/gtest.h>
#include <cc/data.h>
#include <dhcp6/parser_context.h>
#include <testutils/io_utils.h>
using namespace isc::data;
using namespace std;
namespace {
void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) {
namespace isc {
namespace dhcp {
namespace test {
/// @brief compares two JSON trees
///
/// If differences are discovered, gtest failure is reported (using EXPECT_EQ)
///
/// @param a first to be compared
/// @param b second to be compared
void compareJSON(ConstElementPtr a, ConstElementPtr b) {
ASSERT_TRUE(a);
ASSERT_TRUE(b);
if (print) {
// std::cout << "JSON A: -----" << endl << a->str() << std::endl;
// std::cout << "JSON B: -----" << endl << b->str() << std::endl;
// cout << "---------" << endl << endl;
}
EXPECT_EQ(a->str(), b->str());
}
/// @brief Tests if the input string can be parsed with specific parser
///
/// The input text will be passed to bison parser of specified type.
/// Then the same input text is passed to legacy JSON parser and outputs
/// from both parsers are compared. The legacy comparison can be disabled,
/// if the feature tested is not supported by the old parser (e.g.
/// new comment styles)
///
/// @param txt text to be compared
/// @param parser_type bison parser type to be instantiated
/// @param compare whether to compare the output with legacy JSON parser
void testParser(const std::string& txt, Parser6Context::ParserType parser_type) {
ElementPtr reference_json;
ConstElementPtr test_json;
......@@ -44,25 +59,6 @@ void testParser(const std::string& txt, Parser6Context::ParserType parser_type)
compareJSON(reference_json, test_json);