2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-31 22:15:23 +00:00

[master] Merge branch 'trac5032' (mac-sources, control-socket, relay parsers)

# Conflicts:
#	src/lib/testutils/io_utils.cc
#	src/lib/testutils/io_utils.h
This commit is contained in:
Tomek Mrugalski
2017-01-05 15:42:39 +01:00
16 changed files with 594 additions and 437 deletions

View File

@@ -44,6 +44,14 @@
"lfc-interval": 3600 "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. // Addresses will be assigned with a lifetime of 4000 seconds.
// The client is told to start renewing after 1000 seconds. If the server // 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 // 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" } ], "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 does not match
// 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"
}
} }
] ]
}, },

View File

@@ -1,77 +1,90 @@
# This is an example configuration file for DHCPv6 server in Kea. // This is an example configuration file for DHCPv6 server in Kea.
# It attempts to showcase some of the more advanced features. // It attempts to showcase some of the more advanced features.
# Topology wise, it's a basic scenario with one IPv6 subnet configured. // 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 // It is assumed that one subnet (2001:db8:1::/64) is available directly
# over ethX interface. // over ethX interface.
# //
# The following features are currently showcased here: // The following features are currently showcased here:
# 1. Configuration of MAC/hardware address sources in DHCPv6 // 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": { "Dhcp6":
{ {
# Kea is told to listen on ethX network interface only. // Kea is told to listen on ethX network interface only.
"interfaces-config": { "interfaces-config": {
"interfaces": [ "ethX" ] "interfaces": [ "ethX" ]
}, },
# We need to specify the the database used to store leases. As of // We need to specify the the database used to store leases. As of
# September 2016, four database backends are supported: MySQL, // September 2016, four database backends are supported: MySQL,
# PostgreSQL, Cassandra, and the in-memory database, Memfile. // PostgreSQL, Cassandra, and the in-memory database, Memfile.
# We will use memfile because it doesn't require any prior set up. // We will use memfile because it doesn't require any prior set up.
"lease-database": { "lease-database": {
"type": "memfile", "type": "memfile",
"lfc-interval": 3600 "lfc-interval": 3600
}, },
# Kea 0.9.1 introduced MAC/hardware addresses support in DHCPv6. There is // Kea 0.9.1 introduced MAC/hardware addresses support in DHCPv6. There is
# no single reliable method of getting MAC address information in DHCPv6. // no single reliable method of getting MAC address information in DHCPv6.
# Kea supports several methods. Depending on your network set up, some // Kea supports several methods. Depending on your network set up, some
# methods may be more preferable than others, hence the configuration // methods may be more preferable than others, hence the configuration
# parameter. 'mac-sources' is a list of methods. Allowed parameters are: // parameter. 'mac-sources' is a list of methods. Allowed parameters are:
# any, raw, duid, ipv6-link-local, client-link-addr-option, rfc6939 (which // 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 // 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 // alias for remote-id, subscriber-id, rfc4580 (which is an alias for
# subscriber-id) and docsis. // subscriber-id) and docsis.
# //
# Note that the order matters. Methods are attempted one by one in the order // 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 // specified until hardware address is obtained. If you don't care which method
# is used, using 'any' is marginally faster than enumerating them all. // is used, using 'any' is marginally faster than enumerating them all.
# //
# If mac-sources are not specified, a default value of 'any' is used. // If mac-sources are not specified, a default value of 'any' is used.
"mac-sources": [ "client-link-addr-option", "duid", "ipv6-link-local" ], "mac-sources": [ "client-link-addr-option", "duid", "ipv6-link-local" ],
# RFC6422 defines a mechanism called relay-supplied options option. The relay // RFC6422 defines a mechanism called relay-supplied options option. The relay
# agent may insert certain options that the server will echo back to the // 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 // 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 // be RSOO-enabled (i.e. allowed to be echoed back). IANA maintains a list
# of those options here: // of those options here:
# http://www.iana.org/assignments/dhcpv6-parameters/dhcpv6-parameters.xhtml#options-relay-supplied // 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. // However, it is possible to allow the server to echo back additional options.
# This entry marks options 110, 120 and 130 as RSOO-enabled. // This entry marks options 110, 120 and 130 as RSOO-enabled.
"relay-supplied-options": [ "110", "120", "130" ], "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 // This defines a control socket. If defined, Kea will open a UNIX socket
# renewing after 1000 seconds. If the server does not respond // and will listen for incoming commands. See section 15 of the Kea User's
# after 2000 seconds since the lease was granted, client is supposed // Guide for list of supported commands.
# to start REBIND procedure (emergency renewal that allows switching "control-socket": {
# to a different server). "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, "preferred-lifetime": 3000,
"valid-lifetime": 4000, "valid-lifetime": 4000,
"renew-timer": 1000, "renew-timer": 1000,
"rebind-timer": 2000, "rebind-timer": 2000,
# The following list defines subnets. Each subnet consists of at // The following list defines subnets. Each subnet consists of at
# least subnet and pool entries. // least subnet and pool entries.
"subnet6": [ "subnet6": [
{ {
"pools": [ { "pool": "2001:db8:1::/80" } ], "pools": [ { "pool": "2001:db8:1::/80" } ],
# This defines PD (prefix delegation) pools. In this case // This defines PD (prefix delegation) pools. In this case
# we have only one pool. That consists of /64 prefixes // we have only one pool. That consists of /64 prefixes
# being delegated out of large /48 pool. Each delegated // being delegated out of large /48 pool. Each delegated
# prefix will contain an excluded-prefix option. // prefix will contain an excluded-prefix option.
"pd-pools": [ "pd-pools": [
{ {
"prefix": "2001:db8:abcd::", "prefix": "2001:db8:abcd::",
@@ -82,13 +95,21 @@
} }
], ],
"subnet": "2001:db8:1::/64", "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 // The following configures logging. It assumes that messages with at least
# informational level (info, warn, error and fatal) should be logged to stdout. // informational level (info, warn, error and fatal) should be logged to stdout.
"Logging": { "Logging": {
"loggers": [ "loggers": [
{ {

View File

@@ -3203,6 +3203,9 @@ src/lib/dhcpsrv/cfg_host_operations.cc -->
</screen> </screen>
</para> </para>
<para>If "relay" is specified, the "ip-address" parameter within
it is mandatory.</para>
</section> </section>
<section id="dhcp4-srv-example-client-class-relay"> <section id="dhcp4-srv-example-client-class-relay">

View File

@@ -3410,6 +3410,9 @@ If not specified, the default value is:
</screen> </screen>
</para> </para>
<para>If "relay" is specified, the "ip-address" parameter within
it is mandatory.</para>
</section> </section>
<section id="dhcp6-client-class-relay"> <section id="dhcp6-client-class-relay">
@@ -3500,7 +3503,8 @@ If not specified, the default value is:
When not specified, a special value of "any" is used, which 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 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: <para>Supported methods are:
<itemizedlist> <itemizedlist>

View File

@@ -189,8 +189,7 @@ protected:
parser = new StringParser(config_id, string_values_); parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pools") == 0) { } else if (config_id.compare("pools") == 0) {
parser = new Pools4ListParser(config_id, pools_); parser = new Pools4ListParser(config_id, pools_);
} else if (config_id.compare("relay") == 0) { // relay has been converted to SimpleParser already.
parser = new RelayInfoParser(config_id, relay_info_, Option::V4);
// option-data has been converted to SimpleParser already. // option-data has been converted to SimpleParser already.
} else if (config_id.compare("match-client-id") == 0) { } else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, boolean_values_); parser = new BooleanParser(config_id, boolean_values_);
@@ -440,8 +439,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id,
parser = new D2ClientConfigParser(config_id); parser = new D2ClientConfigParser(config_id);
} else if (config_id.compare("match-client-id") == 0) { } else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, globalContext()->boolean_values_); parser = new BooleanParser(config_id, globalContext()->boolean_values_);
} else if (config_id.compare("control-socket") == 0) { // control-socket has been converted to SimpleParser already.
parser = new ControlSocketParser(config_id);
} else if (config_id.compare("expired-leases-processing") == 0) { } else if (config_id.compare("expired-leases-processing") == 0) {
parser = new ExpirationConfigParser(); parser = new ExpirationConfigParser();
} else if (config_id.compare("client-classes") == 0) { } else if (config_id.compare("client-classes") == 0) {
@@ -637,6 +635,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
continue; 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, ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first,
config_pair.second)); config_pair.second));
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED) LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_PARSER_CREATED)

View File

@@ -1,4 +1,4 @@
// 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 // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,14 +7,14 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cc/data.h> #include <cc/data.h>
#include <dhcp4/parser_context.h> #include <dhcp4/parser_context.h>
#include <fstream> #include <testutils/io_utils.h>
#include <cstdio>
#include <exceptions/exceptions.h>
using namespace isc::data; using namespace isc::data;
using namespace std; using namespace std;
namespace { namespace isc {
namespace dhcp {
namespace test {
/// @brief compares two JSON trees /// @brief compares two JSON trees
/// ///
@@ -128,6 +128,8 @@ TEST(ParserTest, keywordDhcp4) {
testParser(txt, Parser4Context::PARSER_DHCP4); 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) { TEST(ParserTest, bashComments) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {" string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
@@ -146,7 +148,8 @@ TEST(ParserTest, bashComments) {
testParser(txt, Parser4Context::PARSER_DHCP4, false); 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\": {" string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
"},\n" "},\n"
@@ -161,6 +164,7 @@ TEST(ParserTest, cComments) {
testParser(txt, Parser4Context::PARSER_DHCP4, false); testParser(txt, Parser4Context::PARSER_DHCP4, false);
} }
// Tests if bash (#) comments can start anywhere, not just in the first line.
TEST(ParserTest, bashCommentsInline) { TEST(ParserTest, bashCommentsInline) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {" string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
@@ -176,6 +180,7 @@ TEST(ParserTest, bashCommentsInline) {
testParser(txt, Parser4Context::PARSER_DHCP4, false); testParser(txt, Parser4Context::PARSER_DHCP4, false);
} }
// Tests if multi-line C style comments are handled correctly.
TEST(ParserTest, multilineComments) { TEST(ParserTest, multilineComments) {
string txt= "{ \"Dhcp4\": { \"interfaces-config\": {" string txt= "{ \"Dhcp4\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
@@ -193,89 +198,13 @@ TEST(ParserTest, multilineComments) {
testParser(txt, Parser4Context::PARSER_DHCP4, false); 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 /// @brief Loads specified example config file
/// ///
/// This test loads specified example file twice: first, using the legacy /// This test loads specified example file twice: first, using the legacy
/// JSON file and then second time using bison parser. Two created Element /// JSON file and then second time using bison parser. Two created Element
/// trees are then compared. The input is decommented before it is passed /// 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 /// @param fname name of the file to be loaded
void testFile(const std::string& fname) { void testFile(const std::string& fname) {
@@ -284,8 +213,7 @@ void testFile(const std::string& fname) {
string decommented = decommentJSONfile(fname); string decommented = decommentJSONfile(fname);
cout << "Attempting to load file " << fname << " (" << decommented cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
<< ")" << endl;
EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true)); EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true));
@@ -329,6 +257,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, void testError(const std::string& txt,
Parser4Context::ParserType parser_type, Parser4Context::ParserType parser_type,
const std::string& msg) const std::string& msg)
@@ -347,7 +280,7 @@ void testError(const std::string& txt,
} }
} }
// Check errors // Verify that error conditions are handled correctly.
TEST(ParserTest, errors) { TEST(ParserTest, errors) {
// no input // no input
testError("", Parser4Context::PARSER_JSON, testError("", Parser4Context::PARSER_JSON,
@@ -591,7 +524,7 @@ TEST(ParserTest, unicodeEscapes) {
} }
} }
// This test checks that all representations of a slash is recognized properly. // This test checks that all representations of a slash are recognized properly.
TEST(ParserTest, unicodeSlash) { TEST(ParserTest, unicodeSlash) {
// check the 4 possible encodings of solidus '/' // check the 4 possible encodings of solidus '/'
ConstElementPtr result; ConstElementPtr result;
@@ -609,3 +542,5 @@ TEST(ParserTest, unicodeSlash) {
} }
}; };
};
};

View File

@@ -424,8 +424,7 @@ protected:
parser = new StringParser(config_id, string_values_); parser = new StringParser(config_id, string_values_);
} else if (config_id.compare("pools") == 0) { } else if (config_id.compare("pools") == 0) {
parser = new Pools6ListParser(config_id, pools_); parser = new Pools6ListParser(config_id, pools_);
} else if (config_id.compare("relay") == 0) { // relay has been converted to SimpleParser.
parser = new RelayInfoParser(config_id, relay_info_, Option::V6);
} else if (config_id.compare("pd-pools") == 0) { } else if (config_id.compare("pd-pools") == 0) {
parser = new PdPoolListParser(config_id, pools_); parser = new PdPoolListParser(config_id, pools_);
// option-data was here, but it is now converted to SimpleParser // 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); parser = new HooksLibrariesParser(config_id);
} else if (config_id.compare("dhcp-ddns") == 0) { } else if (config_id.compare("dhcp-ddns") == 0) {
parser = new D2ClientConfigParser(config_id); parser = new D2ClientConfigParser(config_id);
} else if (config_id.compare("mac-sources") == 0) { // mac-source has been converted to SimpleParser.
parser = new MACSourcesListConfigParser(config_id,
globalContext());
} else if (config_id.compare("relay-supplied-options") == 0) { } else if (config_id.compare("relay-supplied-options") == 0) {
parser = new RSOOListConfigParser(config_id); parser = new RSOOListConfigParser(config_id);
} else if (config_id.compare("control-socket") == 0) { // control-socket has been converted to SimpleParser.
parser = new ControlSocketParser(config_id);
} else if (config_id.compare("expired-leases-processing") == 0) { } else if (config_id.compare("expired-leases-processing") == 0) {
parser = new ExpirationConfigParser(); parser = new ExpirationConfigParser();
} else if (config_id.compare("client-classes") == 0) { } else if (config_id.compare("client-classes") == 0) {
@@ -911,6 +907,20 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
continue; 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, ParserPtr parser(createGlobal6DhcpConfigParser(config_pair.first,
config_pair.second)); config_pair.second));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED) LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PARSER_CREATED)

View File

@@ -1,4 +1,4 @@
// 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 // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -251,6 +251,9 @@ public:
ASSERT_TRUE(status); ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status); comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(expected_code, rcode_); EXPECT_EQ(expected_code, rcode_);
if (expected_code != rcode_) {
cout << "The comment returned was: [" << comment_->stringValue() << "]" << endl;
}
} }
/// @brief Returns an interface configuration used by the most of the /// @brief Returns an interface configuration used by the most of the
@@ -762,7 +765,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status; ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value should be 0 (success) // returned value should be 0 (success)
checkResult(status, 0); checkResult(status, 0);
} }
@@ -4292,16 +4295,15 @@ TEST_F(Dhcp6ParserTest, reservationBogus) {
} }
/// The goal of this test is to verify that configuration can include /// The goal of this test is to verify that configuration can include
/// MAC/Hardware sources. This test also checks if the aliases are /// MAC/Hardware sources. This case uses RFC numbers to reference methods.
/// handled properly (rfc6939 = client-addr-relay, rfc4649 = remote-id, /// Also checks if the aliases are handled properly (rfc6939 = client-addr-relay,
/// rfc4580 = subscriber-id). /// rfc4649 = remote-id, rfc4580 = subscriber-id).
TEST_F(Dhcp6ParserTest, macSources) { TEST_F(Dhcp6ParserTest, macSources1) {
ConstElementPtr json; ConstElementPtr json;
ASSERT_NO_THROW(json = ASSERT_NO_THROW(json =
parseDHCP6("{ " + genIfaceConfig() + "," parseDHCP6("{ " + genIfaceConfig() + ","
"\"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\"," "\"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\" ],"
"\"client-link-addr-option\", \"remote-id\", \"subscriber-id\"],"
"\"preferred-lifetime\": 3000," "\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, " "\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, " "\"renew-timer\": 1000, "
@@ -4310,28 +4312,49 @@ TEST_F(Dhcp6ParserTest, macSources) {
ConstElementPtr status; ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json)); EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value should be 0 (success)
checkResult(status, 0); checkResult(status, 0);
CfgMACSources mac_sources = CfgMgr::instance().getStagingCfg()->getMACSources().get(); CfgMACSources sources = CfgMgr::instance().getStagingCfg()->getMACSources().get();
ASSERT_EQ(6, mac_sources.size());
// Let's check the aliases. They should be recognized to their base methods.
EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, mac_sources[0]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac_sources[1]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, mac_sources[2]);
// Let's check if the actual methods are recognized properly. ASSERT_EQ(3, sources.size());
EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, mac_sources[3]); // Let's check the aliases. They should be recognized to their base methods.
EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, mac_sources[4]); EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, sources[0]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, mac_sources[5]); EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, sources[1]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, sources[2]);
} }
/// The goal of this test is to verify that MAC sources configuration can be /// The goal of this test is to verify that configuration can include
/// empty. /// MAC/Hardware sources. This uses specific method names.
/// Note the Dhcp6 parser requires the list to NOT be empty?! TEST_F(Dhcp6ParserTest, macSources2) {
TEST_F(Dhcp6ParserTest, macSourcesEmpty) {
ConstElementPtr json;
ASSERT_NO_THROW(json =
parseDHCP6("{ " + genIfaceConfig() + ","
"\"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", "
" \"subscriber-id\"],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet6\": [ ], "
"\"valid-lifetime\": 4000 }"));
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
checkResult(status, 0);
CfgMACSources sources = CfgMgr::instance().getStagingCfg()->getMACSources().get();
ASSERT_EQ(3, sources.size());
// Let's check the aliases. They should be recognized to their base methods.
EXPECT_EQ(HWAddr::HWADDR_SOURCE_CLIENT_ADDR_RELAY_OPTION, sources[0]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_REMOTE_ID, sources[1]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_SUBSCRIBER_ID, sources[2]);
}
/// 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; ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
@@ -4343,11 +4366,9 @@ TEST_F(Dhcp6ParserTest, macSourcesEmpty) {
"\"subnet6\": [ ], " "\"subnet6\": [ ], "
"\"valid-lifetime\": 4000 }"))); "\"valid-lifetime\": 4000 }")));
// returned value should be 0 (success) // returned value should be 1 (failure), because the mac-sources must not
checkResult(status, 0); // be empty when specified.
checkResult(status, 1);
CfgMACSources mac_sources = CfgMgr::instance().getStagingCfg()->getMACSources().get();
EXPECT_EQ(0, mac_sources.size());
} }
/// The goal of this test is to verify that MAC sources configuration can /// The goal of this test is to verify that MAC sources configuration can

View File

@@ -1,4 +1,4 @@
// 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 // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,28 +7,42 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cc/data.h> #include <cc/data.h>
#include <dhcp6/parser_context.h> #include <dhcp6/parser_context.h>
#include <testutils/io_utils.h>
using namespace isc::data; using namespace isc::data;
using namespace std; using namespace std;
namespace { namespace isc {
namespace dhcp {
namespace test {
void compareJSON(ConstElementPtr a, ConstElementPtr b, bool print = true) { /// @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(a);
ASSERT_TRUE(b); 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()); EXPECT_EQ(a->str(), b->str());
} }
void testParser(const std::string& txt, Parser6Context::ParserType parser_type) { /// @brief Tests if the input string can be parsed with specific parser
ElementPtr reference_json; ///
/// 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,
bool compare = true) {
ConstElementPtr test_json; ConstElementPtr test_json;
ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
ASSERT_NO_THROW({ ASSERT_NO_THROW({
try { try {
Parser6Context ctx; Parser6Context ctx;
@@ -40,29 +54,16 @@ void testParser(const std::string& txt, Parser6Context::ParserType parser_type)
}); });
if (!compare) {
return;
}
// Now compare if both representations are the same. // Now compare if both representations are the same.
ElementPtr reference_json;
ASSERT_NO_THROW(reference_json = Element::fromJSON(txt, true));
compareJSON(reference_json, test_json); compareJSON(reference_json, test_json);
} }
void testParser2(const std::string& txt, Parser6Context::ParserType parser_type) {
ConstElementPtr test_json;
ASSERT_NO_THROW({
try {
Parser6Context ctx;
test_json = ctx.parseString(txt, parser_type);
} catch (const std::exception &e) {
cout << "EXCEPTION: " << e.what() << endl;
throw;
}
});
/// @todo: Implement actual validation here. since the original
/// Element::fromJSON does not support several comment types, we don't
/// have anything to compare with.
/// std::cout << "Original text:" << txt << endl;
/// std::cout << "Parsed text :" << test_json->str() << endl;
}
TEST(ParserTest, mapInMap) { TEST(ParserTest, mapInMap) {
string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }"; string txt = "{ \"xyzzy\": { \"foo\": 123, \"baz\": 456 } }";
testParser(txt, Parser6Context::PARSER_JSON); testParser(txt, Parser6Context::PARSER_JSON);
@@ -125,9 +126,11 @@ TEST(ParserTest, keywordDhcp6) {
" \"subnet\": \"2001:db8:1::/48\", " " \"subnet\": \"2001:db8:1::/48\", "
" \"interface\": \"test\" } ],\n" " \"interface\": \"test\" } ],\n"
"\"valid-lifetime\": 4000 } }"; "\"valid-lifetime\": 4000 } }";
testParser2(txt, Parser6Context::PARSER_DHCP6); testParser(txt, Parser6Context::PARSER_DHCP6);
} }
// Tests if bash (#) comments are supported. That's the only comment type that
// was supported by the old parser.
TEST(ParserTest, bashComments) { TEST(ParserTest, bashComments) {
string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
@@ -144,10 +147,11 @@ TEST(ParserTest, bashComments) {
" \"interface\": \"eth0\"" " \"interface\": \"eth0\""
" } ]," " } ],"
"\"valid-lifetime\": 4000 } }"; "\"valid-lifetime\": 4000 } }";
testParser2(txt, Parser6Context::PARSER_DHCP6); testParser(txt, Parser6Context::PARSER_DHCP6);
} }
TEST(ParserTest, cComments) { // Tests if C++ (//) comments can start anywhere, not just in the first line.
TEST(ParserTest, cppComments) {
string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
"},\n" "},\n"
@@ -160,9 +164,10 @@ TEST(ParserTest, cComments) {
" \"interface\": \"eth0\"" " \"interface\": \"eth0\""
" } ]," " } ],"
"\"valid-lifetime\": 4000 } }"; "\"valid-lifetime\": 4000 } }";
testParser2(txt, Parser6Context::PARSER_DHCP6); testParser(txt, Parser6Context::PARSER_DHCP6, false);
} }
// Tests if bash (#) comments can start anywhere, not just in the first line.
TEST(ParserTest, bashCommentsInline) { TEST(ParserTest, bashCommentsInline) {
string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
@@ -176,9 +181,10 @@ TEST(ParserTest, bashCommentsInline) {
" \"interface\": \"eth0\"" " \"interface\": \"eth0\""
" } ]," " } ],"
"\"valid-lifetime\": 4000 } }"; "\"valid-lifetime\": 4000 } }";
testParser2(txt, Parser6Context::PARSER_DHCP6); testParser(txt, Parser6Context::PARSER_DHCP6, false);
} }
// Tests if multi-line C style comments are handled correctly.
TEST(ParserTest, multilineComments) { TEST(ParserTest, multilineComments) {
string txt= "{ \"Dhcp6\": { \"interfaces-config\": {" string txt= "{ \"Dhcp6\": { \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]" " \"interfaces\": [ \"*\" ]"
@@ -193,17 +199,29 @@ TEST(ParserTest, multilineComments) {
" \"interface\": \"eth0\"" " \"interface\": \"eth0\""
" } ]," " } ],"
"\"valid-lifetime\": 4000 } }"; "\"valid-lifetime\": 4000 } }";
testParser2(txt, Parser6Context::PARSER_DHCP6); testParser(txt, Parser6Context::PARSER_DHCP6, false);
} }
/// @brief Loads specified example config file
void testFile(const std::string& fname, bool print) { ///
/// 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 legacy support for comments is very limited).
///
/// @param fname name of the file to be loaded
void testFile(const std::string& fname) {
ElementPtr reference_json; ElementPtr reference_json;
ConstElementPtr test_json; ConstElementPtr test_json;
cout << "Attempting to load file " << fname << endl; string decommented = decommentJSONfile(fname);
EXPECT_NO_THROW(reference_json = Element::fromJSONFile(fname, true)); cout << "Parsing file " << fname << "(" << decommented << ")" << endl;
EXPECT_NO_THROW(reference_json = Element::fromJSONFile(decommented, true));
// remove the temporary file
EXPECT_NO_THROW(::remove(decommented.c_str()));
EXPECT_NO_THROW( EXPECT_NO_THROW(
try { try {
@@ -217,9 +235,7 @@ void testFile(const std::string& fname, bool print) {
ASSERT_TRUE(reference_json); ASSERT_TRUE(reference_json);
ASSERT_TRUE(test_json); ASSERT_TRUE(test_json);
compareJSON(reference_json, test_json, print); compareJSON(reference_json, test_json);
} }
// This test loads all available existing files. Each config is loaded // This test loads all available existing files. Each config is loaded
@@ -243,10 +259,15 @@ TEST(ParserTest, file) {
configs.push_back("stateless.json"); configs.push_back("stateless.json");
for (int i = 0; i<configs.size(); i++) { for (int i = 0; i<configs.size(); i++) {
testFile(string(CFG_EXAMPLES) + "/" + configs[i], false); testFile(string(CFG_EXAMPLES) + "/" + configs[i]);
} }
} }
/// @brief Tests error conditions in Dhcp6Parser
///
/// @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, void testError(const std::string& txt,
Parser6Context::ParserType parser_type, Parser6Context::ParserType parser_type,
const std::string& msg) const std::string& msg)
@@ -265,7 +286,7 @@ void testError(const std::string& txt,
} }
} }
// Check errors // Verify that error conditions are handled correctly.
TEST(ParserTest, errors) { TEST(ParserTest, errors) {
// no input // no input
testError("", Parser6Context::PARSER_JSON, testError("", Parser6Context::PARSER_JSON,
@@ -507,9 +528,13 @@ TEST(ParserTest, unicodeEscapes) {
ASSERT_EQ(Element::string, result->getType()); ASSERT_EQ(Element::string, result->getType());
EXPECT_EQ(ins, result->stringValue()); EXPECT_EQ(ins, result->stringValue());
} }
}
// This test checks that all representations of a slash is recognized properly.
TEST(ParserTest, unicodeSlash) {
// check the 4 possible encodings of solidus '/' // check the 4 possible encodings of solidus '/'
json = "\"/\\/\\u002f\\u002F\""; ConstElementPtr result;
string json = "\"/\\/\\u002f\\u002F\"";
ASSERT_NO_THROW( ASSERT_NO_THROW(
try { try {
Parser6Context ctx; Parser6Context ctx;
@@ -520,6 +545,8 @@ TEST(ParserTest, unicodeEscapes) {
}); });
ASSERT_EQ(Element::string, result->getType()); ASSERT_EQ(Element::string, result->getType());
EXPECT_EQ("////", result->stringValue()); EXPECT_EQ("////", result->stringValue());
} }
}; };
};
};

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -47,6 +47,16 @@ uint32_t CfgMACSource::MACSourceFromText(const std::string& name) {
isc_throw(BadValue, "Can't convert '" << name << "' to any known MAC source."); isc_throw(BadValue, "Can't convert '" << name << "' to any known MAC source.");
} }
void CfgMACSource::add(uint32_t source) {
for (CfgMACSources::const_iterator it = mac_sources_.begin();
it != mac_sources_.end(); ++it) {
if (*it == source) {
isc_throw(InvalidParameter, "mac-source paramter " << source
<< "' specified twice.");
}
}
mac_sources_.push_back(source);
}
}; };
}; };

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2014-2015,2017 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -52,10 +52,8 @@ class CfgMACSource {
/// @param source MAC source (see constants in Pkt::HWADDR_SOURCE_*) /// @param source MAC source (see constants in Pkt::HWADDR_SOURCE_*)
/// ///
/// Specified source is being added to the mac_sources_ array. /// Specified source is being added to the mac_sources_ array.
/// @todo implement add(string) version of this method. /// @throw InvalidParameter if such a source is already defined.
void add(uint32_t source) { void add(uint32_t source);
mac_sources_.push_back(source);
}
/// @brief Provides access to the configure MAC/Hardware address sources. /// @brief Provides access to the configure MAC/Hardware address sources.
/// ///

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -177,62 +177,48 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
// ******************** MACSourcesListConfigParser ************************* // ******************** MACSourcesListConfigParser *************************
MACSourcesListConfigParser::
MACSourcesListConfigParser(const std::string& param_name,
ParserContextPtr global_context)
: param_name_(param_name), global_context_(global_context) {
if (param_name_ != "mac-sources") {
isc_throw(BadValue, "Internal error. MAC sources configuration "
"parser called for the wrong parameter: " << param_name);
}
}
void void
MACSourcesListConfigParser::build(ConstElementPtr value) { MACSourcesListConfigParser::parse(CfgMACSource& mac_sources, ConstElementPtr value) {
CfgIface cfg_iface; CfgIface cfg_iface;
uint32_t source = 0; uint32_t source = 0;
size_t cnt = 0;
// By default, there's only one source defined: ANY. // By default, there's only one source defined: ANY.
// If user specified anything, we need to get rid of that default. // If user specified anything, we need to get rid of that default.
CfgMgr::instance().getStagingCfg()->getMACSources().clear(); mac_sources.clear();
BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) { BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) {
std::string source_str = source_elem->stringValue(); std::string source_str = source_elem->stringValue();
try { try {
source = CfgMACSource::MACSourceFromText(source_str); source = CfgMACSource::MACSourceFromText(source_str);
CfgMgr::instance().getStagingCfg()->getMACSources().add(source); mac_sources.add(source);
++cnt;
} catch (const InvalidParameter& ex) {
isc_throw(DhcpConfigError, "The mac-sources value '" << source_str
<< "' was specified twice (" << value->getPosition() << ")");
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "Failed to convert '" isc_throw(DhcpConfigError, "Failed to convert '"
<< source_str << "' to any recognized MAC source:" << source_str << "' to any recognized MAC source:"
<< ex.what() << " (" << value->getPosition() << ")"); << ex.what() << " (" << value->getPosition() << ")");
} }
} }
}
void if (!cnt) {
MACSourcesListConfigParser::commit() { isc_throw(DhcpConfigError, "If specified, MAC Sources cannot be empty");
// Nothing to do. }
} }
// ******************** ControlSocketParser ************************* // ******************** ControlSocketParser *************************
ControlSocketParser::ControlSocketParser(const std::string& param_name) { void ControlSocketParser::parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value) {
if (param_name != "control-socket") { if (!value) {
isc_throw(BadValue, "Internal error. Control socket parser called " isc_throw(DhcpConfigError, "Logic error: specified control-socket is null");
" for wrong parameter:" << param_name);
} }
}
void ControlSocketParser::build(isc::data::ConstElementPtr value) {
if (value->getType() != Element::map) { if (value->getType() != Element::map) {
isc_throw(BadValue, "Specified control-socket is expected to be a map" isc_throw(DhcpConfigError, "Specified control-socket is expected to be a map"
", i.e. a structure defined within { }"); ", i.e. a structure defined within { }");
} }
CfgMgr::instance().getStagingCfg()->setControlSocketInfo(value); srv_cfg.setControlSocketInfo(value);
}
/// @brief Does nothing.
void ControlSocketParser::commit() {
// Nothing to do.
} }
// ******************** HooksLibrariesParser ************************* // ******************** HooksLibrariesParser *************************
@@ -804,66 +790,66 @@ OptionDefListParser::parse(CfgOptionDefPtr storage, ConstElementPtr option_def_l
} }
//****************************** RelayInfoParser ******************************** //****************************** RelayInfoParser ********************************
RelayInfoParser::RelayInfoParser(const std::string&, RelayInfoParser::RelayInfoParser(const Option::Universe& family)
const isc::dhcp::Subnet::RelayInfoPtr& relay_info, : family_(family) {
const Option::Universe& family)
:storage_(relay_info), local_(isc::asiolink::IOAddress(
family == Option::V4 ? "0.0.0.0" : "::")),
string_values_(new StringStorage()), family_(family) {
if (!relay_info) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error: "
<< "relay-info storage may not be NULL");
}
}; };
void void
RelayInfoParser::build(ConstElementPtr relay_info) { RelayInfoParser::parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
ConstElementPtr relay_info) {
// Let's start with some sanity checks.
if (!relay_info || !cfg) {
isc_throw(DhcpConfigError, "Logic error: RelayInfoParser::parse() called "
"with at least one NULL parameter.");
}
if (relay_info->getType() != Element::map) {
isc_throw(DhcpConfigError, "Configuration error: RelayInfoParser::parse() "
"called with non-map parameter");
}
// Now create the default value.
isc::asiolink::IOAddress ip(family_ == Option::V4 ? IOAddress::IPV4_ZERO_ADDRESS()
: IOAddress::IPV6_ZERO_ADDRESS());
// Now iterate over all parameters. Currently there's only one supported
// parameter, so it should be an easy thing to check.
bool ip_address_specified = false;
BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) { BOOST_FOREACH(ConfigPair param, relay_info->mapValue()) {
ParserPtr parser(createConfigParser(param.first)); if (param.first == "ip-address") {
parser->build(param.second); ip_address_specified = true;
parser->commit();
try {
ip = asiolink::IOAddress(param.second->stringValue());
} catch (...) {
isc_throw(DhcpConfigError, "Failed to parse ip-address "
"value: " << param.second
<< " (" << param.second->getPosition() << ")");
}
// Check if the address family matches.
if ( (ip.isV4() && family_ != Option::V4) ||
(ip.isV6() && family_ != Option::V6) ) {
isc_throw(DhcpConfigError, "ip-address field " << ip.toText()
<< " does not have IP address of expected family type: "
<< (family_ == Option::V4 ? "IPv4" : "IPv6")
<< " (" << param.second->getPosition() << ")");
}
} else {
isc_throw(NotImplemented,
"parser error: RelayInfoParser parameter not supported: "
<< param.second);
}
} }
// Get the IP address if (!ip_address_specified) {
boost::scoped_ptr<asiolink::IOAddress> ip; isc_throw(DhcpConfigError, "'relay' specified, but mandatory 'ip-address' "
try { "paramter in it is missing");
ip.reset(new asiolink::IOAddress(string_values_->getParam("ip-address")));
} catch (...) {
isc_throw(DhcpConfigError, "Failed to parse ip-address "
"value: " << string_values_->getParam("ip-address")
<< " (" << string_values_->getPosition("ip-address") << ")");
} }
if ( (ip->isV4() && family_ != Option::V4) || // Ok, we're done with parsing. Let's store the result in the structure
(ip->isV6() && family_ != Option::V6) ) { // we were given as configuration storage.
isc_throw(DhcpConfigError, "ip-address field " << ip->toText() *cfg = isc::dhcp::Subnet::RelayInfo(ip);
<< " does not have IP address of expected family type: "
<< (family_ == Option::V4 ? "IPv4" : "IPv6")
<< " (" << string_values_->getPosition("ip-address") << ")");
}
local_.addr_ = *ip;
}
isc::dhcp::ParserPtr
RelayInfoParser::createConfigParser(const std::string& parameter) {
DhcpConfigParser* parser = NULL;
if (parameter.compare("ip-address") == 0) {
parser = new StringParser(parameter, string_values_);
} else {
isc_throw(NotImplemented,
"parser error: RelayInfoParser parameter not supported: "
<< parameter);
}
return (isc::dhcp::ParserPtr(parser));
}
void
RelayInfoParser::commit() {
*storage_ = local_;
} }
//****************************** PoolsListParser ******************************** //****************************** PoolsListParser ********************************
@@ -1069,6 +1055,12 @@ SubnetConfigParser::build(ConstElementPtr subnet) {
continue; continue;
} }
if (param.first == "relay") {
RelayInfoParser parser(global_context_->universe_);
parser.parse(relay_info_, param.second);
continue;
}
ParserPtr parser; ParserPtr parser;
// When unsupported parameter is specified, the function called // When unsupported parameter is specified, the function called
// below will thrown an exception. We have to catch this exception // below will thrown an exception. We have to catch this exception

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC") // Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
// //
// This Source Code Form is subject to the terms of the Mozilla Public // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -16,6 +16,8 @@
#include <dhcpsrv/cfg_option.h> #include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/subnet.h> #include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfg_option_def.h> #include <dhcpsrv/cfg_option_def.h>
#include <dhcpsrv/cfg_mac_source.h>
#include <dhcpsrv/srv_config.h>
#include <dhcpsrv/parsers/dhcp_config_parser.h> #include <dhcpsrv/parsers/dhcp_config_parser.h>
#include <cc/simple_parser.h> #include <cc/simple_parser.h>
#include <hooks/libinfo.h> #include <hooks/libinfo.h>
@@ -372,56 +374,33 @@ private:
/// It contains a list of MAC/hardware acquisition source, i.e. methods how /// It contains a list of MAC/hardware acquisition source, i.e. methods how
/// MAC address can possibly by obtained in DHCPv6. For a currently supported /// MAC address can possibly by obtained in DHCPv6. For a currently supported
/// methods, see @ref isc::dhcp::Pkt::getMAC. /// methods, see @ref isc::dhcp::Pkt::getMAC.
class MACSourcesListConfigParser : public DhcpConfigParser { class MACSourcesListConfigParser : public isc::data::SimpleParser {
public: public:
/// @brief constructor
///
/// As this is a dedicated parser, it must be used to parse
/// "mac-sources" parameter only. All other types will throw exception.
///
/// @param param_name name of the configuration parameter being parsed
/// @param global_context Global parser context.
/// @throw BadValue if supplied parameter name is not "mac-sources"
MACSourcesListConfigParser(const std::string& param_name,
ParserContextPtr global_context);
/// @brief parses parameters value /// @brief parses parameters value
/// ///
/// Parses configuration entry (list of sources) and adds each element /// Parses configuration entry (list of sources) and adds each element
/// to the sources list. /// to the sources list.
/// ///
/// @param value pointer to the content of parsed values /// @param value pointer to the content of parsed values
virtual void build(isc::data::ConstElementPtr value); void parse(CfgMACSource& mac_sources, isc::data::ConstElementPtr value);
/// @brief Does nothing.
virtual void commit();
private:
// Parsed parameter name
std::string param_name_;
/// Global parser context.
ParserContextPtr global_context_;
}; };
/// @brief Parser for the control-socket structure /// @brief Parser for the control-socket structure
/// ///
/// It does not parse anything, simply stores the element in /// It does not parse anything, simply stores the element in
/// the staging config. /// the staging config.
class ControlSocketParser : public DhcpConfigParser { class ControlSocketParser : public isc::data::SimpleParser {
public: public:
/// @brief "Parses" control-socket structure
ControlSocketParser(const std::string& param_name);
/// @brief Stores contents of the control-socket map in the staging config.
/// ///
/// Since the SrvConfig structure takes the socket definition
/// as ConstElementPtr, there's really nothing to parse here.
/// It only does basic sanity checks and throws DhcpConfigError
/// if the value is null or is not a map.
///
/// @param srv_cfg parsed values will be stored here
/// @param value pointer to the content of parsed values /// @param value pointer to the content of parsed values
virtual void build(isc::data::ConstElementPtr value); void parse(SrvConfig& srv_cfg, isc::data::ConstElementPtr value);
/// @brief Does nothing.
virtual void commit();
}; };
/// @brief Parser for hooks library list /// @brief Parser for hooks library list
@@ -849,48 +828,24 @@ protected:
/// is expected that the number of parameters will increase over time. /// is expected that the number of parameters will increase over time.
/// ///
/// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters. /// It is useful for parsing Dhcp<4/6>/subnet<4/6>[x]/relay parameters.
class RelayInfoParser : public DhcpConfigParser { class RelayInfoParser : public isc::data::SimpleParser {
public: public:
/// @brief constructor /// @brief constructor
/// @param unused first argument is ignored, all Parser constructors
/// accept string as first argument.
/// @param relay_info is the storage in which to store the parsed
/// @param family specifies protocol family (IPv4 or IPv6) /// @param family specifies protocol family (IPv4 or IPv6)
RelayInfoParser(const std::string& unused, RelayInfoParser(const isc::dhcp::Option::Universe& family);
const isc::dhcp::Subnet::RelayInfoPtr& relay_info,
const isc::dhcp::Option::Universe& family);
/// @brief parses the actual relay parameters /// @brief parses the actual relay parameters
/// @param relay_info JSON structure holding relay parameters to parse
virtual void build(isc::data::ConstElementPtr relay_info);
/// @brief stores parsed info in relay_info
virtual void commit();
protected:
/// @brief Creates a parser for the given "relay" member element id.
/// ///
/// The elements currently supported are: /// The elements currently supported are:
/// -# ip-address /// -# ip-address
/// ///
/// @param parser is the "item_name" for a specific member element of /// @param cfg configuration will be stored here
/// the "relay" specification. /// @param relay_info JSON structure holding relay parameters to parse
/// void parse(const isc::dhcp::Subnet::RelayInfoPtr& cfg,
/// @return returns a pointer to newly created parser. isc::data::ConstElementPtr relay_info);
isc::dhcp::ParserPtr
createConfigParser(const std::string& parser);
/// Parsed data will be stored there on commit()
isc::dhcp::Subnet::RelayInfoPtr storage_;
/// Local storage information (for temporary values)
isc::dhcp::Subnet::RelayInfo local_;
/// Storage for subnet-specific string values.
StringStoragePtr string_values_;
protected:
/// Protocol family (IPv4 or IPv6) /// Protocol family (IPv4 or IPv6)
Option::Universe family_; Option::Universe family_;
}; };

View File

@@ -1,4 +1,4 @@
// 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 // 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 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -226,48 +226,95 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
EXPECT_EQ(test_value, actual_value); EXPECT_EQ(test_value, actual_value);
} }
/// @brief Check MACSourcesListConfigParser basic functionality /// Verifies the code that parses mac sources and adds them to CfgMgr
/// TEST_F(DhcpParserTest, MacSources) {
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Does not allow name other than "mac-sources"
/// 3. Parses list of mac sources and adds them to CfgMgr
TEST_F(DhcpParserTest, MacSourcesListConfigParserTest) {
const std::string valid_name = "mac-sources";
const std::string bogus_name = "bogus-name";
ParserContextPtr parser_context(new ParserContext(Option::V6));
// Verify that parser constructor fails if parameter name isn't "mac-sources"
EXPECT_THROW(MACSourcesListConfigParser(bogus_name, parser_context),
isc::BadValue);
// That's an equivalent of the following snippet: // That's an equivalent of the following snippet:
// "mac-sources: [ \"duid\", \"ipv6\" ]"; // "mac-sources: [ \"duid\", \"ipv6\" ]";
ElementPtr config = Element::createList(); ElementPtr values = Element::createList();
config->add(Element::create("duid")); values->add(Element::create("duid"));
config->add(Element::create("ipv6-link-local")); values->add(Element::create("ipv6-link-local"));
boost::scoped_ptr<MACSourcesListConfigParser> // Let's grab server configuration from CfgMgr
parser(new MACSourcesListConfigParser(valid_name, parser_context));
// This should parse the configuration and add eth0 and eth1 to the list
// of interfaces that server should listen on.
EXPECT_NO_THROW(parser->build(config));
EXPECT_NO_THROW(parser->commit());
// Use CfgMgr instance to check if eth0 and eth1 was added, and that
// eth2 was not added.
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg(); SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
ASSERT_TRUE(cfg); ASSERT_TRUE(cfg);
CfgMACSources configured_sources = cfg->getMACSources().get(); CfgMACSource& sources = cfg->getMACSources();
// This should parse the configuration and check that it doesn't throw.
MACSourcesListConfigParser parser;
EXPECT_NO_THROW(parser.parse(sources, values));
// Finally, check the sources that were configured
CfgMACSources configured_sources = cfg->getMACSources().get();
ASSERT_EQ(2, configured_sources.size()); ASSERT_EQ(2, configured_sources.size());
EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]); EXPECT_EQ(HWAddr::HWADDR_SOURCE_DUID, configured_sources[0]);
EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]); EXPECT_EQ(HWAddr::HWADDR_SOURCE_IPV6_LINK_LOCAL, configured_sources[1]);
} }
/// @brief Check MACSourcesListConfigParser rejecting empty list
///
/// Verifies that the code rejects an empty mac-sources list.
TEST_F(DhcpParserTest, MacSourcesEmpty) {
// That's an equivalent of the following snippet:
// "mac-sources: [ \"duid\", \"ipv6\" ]";
ElementPtr values = Element::createList();
// Let's grab server configuration from CfgMgr
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
ASSERT_TRUE(cfg);
CfgMACSource& sources = cfg->getMACSources();
// This should throw, because if specified, at least one MAC source
// has to be specified.
MACSourcesListConfigParser parser;
EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
}
/// @brief Check MACSourcesListConfigParser rejecting empty list
///
/// Verifies that the code rejects fake mac source.
TEST_F(DhcpParserTest, MacSourcesBogus) {
// That's an equivalent of the following snippet:
// "mac-sources: [ \"duid\", \"ipv6\" ]";
ElementPtr values = Element::createList();
values->add(Element::create("from-ebay"));
values->add(Element::create("just-guess-it"));
// Let's grab server configuration from CfgMgr
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
ASSERT_TRUE(cfg);
CfgMACSource& sources = cfg->getMACSources();
// This should throw, because these are not valid sources.
MACSourcesListConfigParser parser;
EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
}
/// Verifies the code that properly catches duplicate entries
/// in mac-sources definition.
TEST_F(DhcpParserTest, MacSourcesDuplicate) {
// That's an equivalent of the following snippet:
// "mac-sources: [ \"duid\", \"ipv6\" ]";
ElementPtr values = Element::createList();
values->add(Element::create("ipv6-link-local"));
values->add(Element::create("duid"));
values->add(Element::create("duid"));
values->add(Element::create("duid"));
// Let's grab server configuration from CfgMgr
SrvConfigPtr cfg = CfgMgr::instance().getStagingCfg();
ASSERT_TRUE(cfg);
CfgMACSource& sources = cfg->getMACSources();
// This should parse the configuration and check that it throws.
MACSourcesListConfigParser parser;
EXPECT_THROW(parser.parse(sources, values), DhcpConfigError);
}
/// @brief Test Fixture class which provides basic structure for testing /// @brief Test Fixture class which provides basic structure for testing
/// configuration parsing. This is essentially the same structure provided /// configuration parsing. This is essentially the same structure provided
/// by dhcp servers. /// by dhcp servers.
@@ -2416,6 +2463,19 @@ TEST_F(ParseConfigTest, validRelayInfo4) {
" }"; " }";
ElementPtr json = Element::fromJSON(config_str); ElementPtr json = Element::fromJSON(config_str);
// We need to set the default ip-address to something.
Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0")));
RelayInfoParser parser(Option::V4);
// Subnet4 parser will pass 0.0.0.0 to the RelayInfoParser
EXPECT_NO_THROW(parser.parse(result, json));
EXPECT_EQ("192.0.2.1", result->addr_.toText());
}
/// @brief Checks that a bogus relay info structure for IPv4 is rejected.
TEST_F(ParseConfigTest, bogusRelayInfo4) {
// Invalid config (wrong family type of the ip-address field) // Invalid config (wrong family type of the ip-address field)
std::string config_str_bogus1 = std::string config_str_bogus1 =
" {" " {"
@@ -2430,24 +2490,25 @@ TEST_F(ParseConfigTest, validRelayInfo4) {
" }"; " }";
ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2); ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
// Invalid config (ip-address is mandatory)
std::string config_str_bogus3 =
" {"
" }";
ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
// We need to set the default ip-address to something. // We need to set the default ip-address to something.
Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("0.0.0.0"))); Subnet::RelayInfoPtr result(new Subnet::RelayInfo(IOAddress::IPV4_ZERO_ADDRESS()));
boost::shared_ptr<RelayInfoParser> parser; RelayInfoParser parser(Option::V4);
// Subnet4 parser will pass 0.0.0.0 to the RelayInfoParser // wrong family type
EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result, EXPECT_THROW(parser.parse(result, json_bogus1), DhcpConfigError);
Option::V4)));
EXPECT_NO_THROW(parser->build(json));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ("192.0.2.1", result->addr_.toText()); // Too large byte values in pseudo-IPv4 addr
EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
// Let's check negative scenario (wrong family type) // Mandatory ip-address is missing. What a pity.
EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError); EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
// Let's check negative scenario (too large byte values in pseudo-IPv4 addr)
EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
} }
/// @brief Checks that a valid relay info structure for IPv6 can be handled /// @brief Checks that a valid relay info structure for IPv6 can be handled
@@ -2460,6 +2521,18 @@ TEST_F(ParseConfigTest, validRelayInfo6) {
" }"; " }";
ElementPtr json = Element::fromJSON(config_str); ElementPtr json = Element::fromJSON(config_str);
// We need to set the default ip-address to something.
Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
RelayInfoParser parser(Option::V6);
// Subnet4 parser will pass :: to the RelayInfoParser
EXPECT_NO_THROW(parser.parse(result, json));
EXPECT_EQ("2001:db8::1", result->addr_.toText());
}
/// @brief Checks that a valid relay info structure for IPv6 can be handled
TEST_F(ParseConfigTest, bogusRelayInfo6) {
// Invalid config (wrong family type of the ip-address field // Invalid config (wrong family type of the ip-address field
std::string config_str_bogus1 = std::string config_str_bogus1 =
" {" " {"
@@ -2474,23 +2547,25 @@ TEST_F(ParseConfigTest, validRelayInfo6) {
" }"; " }";
ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2); ElementPtr json_bogus2 = Element::fromJSON(config_str_bogus2);
// Missing mandatory ip-address field.
std::string config_str_bogus3 =
" {"
" }";
ElementPtr json_bogus3 = Element::fromJSON(config_str_bogus3);
// We need to set the default ip-address to something. // We need to set the default ip-address to something.
Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::"))); Subnet::RelayInfoPtr result(new Subnet::RelayInfo(asiolink::IOAddress("::")));
boost::shared_ptr<RelayInfoParser> parser; RelayInfoParser parser(Option::V6);
// Subnet4 parser will pass :: to the RelayInfoParser
EXPECT_NO_THROW(parser.reset(new RelayInfoParser("ignored", result,
Option::V6)));
EXPECT_NO_THROW(parser->build(json));
EXPECT_NO_THROW(parser->commit());
EXPECT_EQ("2001:db8::1", result->addr_.toText()); // Negative scenario (wrong family type)
EXPECT_THROW(parser.parse(result, json_bogus1), DhcpConfigError);
// Let's check negative scenario (wrong family type) // Looks like IPv6 address, but has too many colons
EXPECT_THROW(parser->build(json_bogus1), DhcpConfigError); EXPECT_THROW(parser.parse(result, json_bogus2), DhcpConfigError);
// Unparseable text that looks like IPv6 address, but has too many colons // Mandatory ip-address is missing. What a pity.
EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError); EXPECT_THROW(parser.parse(result, json_bogus3), DhcpConfigError);
} }
// There's no test for ControlSocketParser, as it is tested in the DHCPv4 code // There's no test for ControlSocketParser, as it is tested in the DHCPv4 code

View File

@@ -4,6 +4,7 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <exceptions/exceptions.h>
#include <testutils/io_utils.h> #include <testutils/io_utils.h>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <fstream> #include <fstream>
@@ -36,7 +37,78 @@ std::string readFile(const std::string& file_path) {
return (output.str()); return (output.str());
} }
std::string decommentJSONfile(const std::string& input_file) {
using namespace std;
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);
}
}; // end of isc::dhcp::test namespace }; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace }; // end of isc::dhcp namespace
}; // end of isc namespace }; // end of isc namespace

View File

@@ -26,6 +26,20 @@ bool fileExists(const std::string& file_path);
/// @return File contents. /// @return File contents.
std::string readFile(const std::string& file_path); std::string readFile(const std::string& file_path);
/// @brief Removes comments from a JSON file
///
/// Removes //, # and /* */ comments from the input file and writes its content
/// to another file. The comments are replaced with spaces, so the original
/// token locations should remain unaffected. 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 filename of a new file that has comments stripped from it
/// @throw BadValue if the input file cannot be opened
std::string decommentJSONfile(const std::string& input_file);
}; // end of isc::dhcp::test namespace }; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace }; // end of isc::dhcp namespace
}; // end of isc namespace }; // end of isc namespace