Commit 3e1e56f1 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[5357] Addressed comments after review:

 - many more parameters are now inherited from shared-network to subnet4
 - parameters now passed as const reference
 - it is no longer possible to specify echo-client-id on shared-network
   level (because it's a global parameter)
 - example config commented properly
parent de94f82b
// This is an example configuration file for DHCPv4 server in Kea.
// It demonstrates an advanced feature called shared network. Typically, for
// each physical link there is one IPv4 subnet that the server is expected
// to manage. However, in some cases there is a need to configure more subnets
// in the same physical location. The most common use case is an existing
// subnet that grew past its original assumptions and ran out of addresses,
// so the sysadmin needs to add another subnet on top of existing one.
{
"Dhcp4": {
// As with any other configuration, you need to tell Kea the interface
// names, so it would listen to incoming traffic.
"interfaces-config": {
"interfaces": [ "eth0" ]
},
// You also need to tell where to store lease information.
// memfile is the backend that is easiest to set up.
"lease-database": {
"type": "memfile",
"lfc-interval": 3600
},
// Here the shared networks definition starts. shared-networks can
// contain a list of shared networks. There are many parameters
// that can be specfied here, so this example may be overwhelming
// at first, but the only mandatory parameter for each shared
// network is name. It must be unique. Typically, each shared
// subnet also needs to have at least two subnets to be functional,
// but if you really want to, you can define a degraded shared
// network that has 1 or even 0 subnets. This may come in handy
// when migrating between regular subnets and shared networks
// or when debugging a problem. It is not recommended to use
// 1 subnet per shared network, as there is extra processing
// overhead for shared networks.
"shared-networks": [
{
// Name of the shared network. It may be an arbitrary string
// and it must be unique among all shared networks.
"name": "frog",
// You may specify interface name if the shared network is
// reachable directly from the server.
"interface": "eth1",
// You can specify many parameters that are allowed in subnet scope
// here. It's useful to put them here if they apply to all subnets
// in this shared network. It's likely that the most common
// parameter here will be option values defined with option-data.
"match-client-id": false,
"name": "frog",
"option-data": [ ],
"rebind-timer": 150,
// If all the traffic coming from that shared subnet is reachable
// via relay and that relay always use the same IP address, you
// can specify that relay address here. Since this example shows
// a shared network reachable directly, we put 0.0.0.0 here.
// It would be better to skip the relay scope altogether, but
// it was left here for demonstration purposes.
"relay": {
"ip-address": "0.0.0.0"
},
// Timer values can be overridden here.
"renew-timer": 100,
"reservation-mode": "all",
// This starts a list of subnets allowed in this shared network.
// In our example, there are two subnets.
"subnet4": [
{
"4o6-interface": "",
"4o6-interface-id": "",
"4o6-subnet": "",
"id": 1,
"match-client-id": true,
"next-server": "0.0.0.0",
"option-data": [ ],
"pools": [ ],
"rebind-timer": 20,
// You can override the value inherited from shared-network
// here if your relay uses different IP addresses for
// each subnet.
"relay": {
"ip-address": "0.0.0.0"
},
......@@ -42,18 +88,12 @@
"valid-lifetime": 30
},
{
"4o6-interface": "",
"4o6-interface-id": "",
"4o6-subnet": "",
"id": 2,
"match-client-id": true,
"next-server": "0.0.0.0",
"option-data": [ ],
"pools": [ ],
"rebind-timer": 20,
"relay": {
"ip-address": "0.0.0.0"
},
"renew-timer": 10,
"reservation-mode": "all",
"subnet": "192.0.2.0/24",
......@@ -63,6 +103,10 @@
"valid-lifetime": 200
} ], // end of shared-networks
// It is likely that in your network you'll have a mix or regular,
// "plain" subnets and shared networks. It is perfectly valid to mix
// them in the same config file.
//
// This is regular subnet. It's not part of any shared-network.
"subnet4": [
{
......
......@@ -1002,7 +1002,6 @@ shared_network_param: name
| next_server
| relay
| reservation_mode
| echo_client_id
| client_classes
| valid_lifetime
| unknown_map_entry
......
......@@ -72,7 +72,7 @@ public:
///
/// @throw DhcpConfigError if parameters are missing or
/// or having incorrect values.
void parse(SrvConfigPtr cfg, ConstElementPtr global) {
void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
// Set whether v4 server is supposed to echo back client-id
// (yes = RFC6842 compatible, no = backward compatibility)
......@@ -96,7 +96,7 @@ public:
/// @throw BadValue if any pointer is missing
/// @throw DhcpConfigError if there are duplicates (or other subnet defects)
void
copySubnets4(const CfgSubnets4Ptr dest, const CfgSharedNetworks4Ptr from) {
copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
if (!dest || !from) {
isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
......@@ -136,7 +136,7 @@ public:
/// @param global global Dhcp4 scope
/// @throw DhcpConfigError in case of issues found
void
sanityChecks(SrvConfigPtr cfg, ConstElementPtr global) {
sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
/// Shared network sanity checks
const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
......
......@@ -611,17 +611,23 @@ public:
/// @param t1 expected renew-timer value
/// @param t2 expected rebind-timer value
/// @param valid expected valid-lifetime value
void
/// @return the subnet that was examined
Subnet4Ptr
checkSubnet(const Subnet4Collection& col, std::string subnet,
uint32_t t1, uint32_t t2, uint32_t valid) {
const auto& index = col.get<SubnetPrefixIndexTag>();
auto subnet_it = index.find(subnet);
ASSERT_NE(subnet_it, index.cend());
if (subnet_it == index.cend()) {
ADD_FAILURE() << "Unable to find expected subnet " << subnet;
return (Subnet4Ptr());
}
Subnet4Ptr s = *subnet_it;
EXPECT_EQ(t1, s->getT1());
EXPECT_EQ(t2, s->getT2());
EXPECT_EQ(valid, s->getValid());
return (s);
}
/// @brief This utility method attempts to configure using specified
......@@ -5209,13 +5215,34 @@ TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
// - shared network to subnet
// Also, it tests that more than one shared network can be defined.
TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
// We need to fake the interfaces present, because we want to test
// interface names inheritance. However, there are sanity checks
// on subnet level that would refuse the value if the interface
// is not present.
IfaceMgrTestConfig iface_config(true);
// This config is structured in a way that the first shared
// subnet have many parameters defined. The first subnet
// should inherit them. The second subnet overrides all
// values and those values should be used, not those from
// shared network scope.
string config = "{\n"
"\"renew-timer\": 1, \n"
"\"renew-timer\": 1, \n" // global values here
"\"rebind-timer\": 2, \n"
"\"valid-lifetime\": 4, \n"
"\"shared-networks\": [ {\n"
" \"name\": \"foo\"\n,"
" \"name\": \"foo\"\n," // shared network values here
" \"interface\": \"eth0\",\n"
" \"match-client-id\": false,\n"
" \"next-server\": \"1.2.3.4\",\n"
" \"relay\": {\n"
" \"ip-address\": \"5.6.7.8\"\n"
" },\n"
" \"reservation-mode\": \"out-of-pool\",\n"
" \"renew-timer\": 10,\n"
" \"rebind-timer\": 20,\n"
" \"valid-lifetime\": 40,\n"
" \"subnet4\": [\n"
" { \n"
" \"subnet\": \"192.0.1.0/24\",\n"
......@@ -5224,10 +5251,17 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
" { \n"
" \"subnet\": \"192.0.2.0/24\",\n"
" \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
" \"renew-timer\": 100\n"
" \"renew-timer\": 100,\n"
" \"rebind-timer\": 200,\n"
" \"valid-lifetime\": 400,\n"
" \"match-client-id\": true,\n"
" \"next-server\": \"11.22.33.44\",\n"
" \"relay\": {\n"
" \"ip-address\": \"55.66.77.88\"\n"
" },\n"
" \"reservation-mode\": \"disabled\"\n"
" }\n"
" ]\n"
" },\n"
"{ // second shared-network starts here\n"
" \"name\": \"bar\",\n"
......@@ -5265,13 +5299,28 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
// derived from shared-network level. Other parameters a derived
// from global scope to shared-network level and later again to
// subnet4 level.
checkSubnet(*subs, "192.0.1.0/24", 10, 2, 4);
Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 10, 20, 40);
ASSERT_TRUE(s);
// These are values derived from shared network scope:
EXPECT_EQ("eth0", s->getIface());
EXPECT_EQ(false, s->getMatchClientId());
EXPECT_EQ(IOAddress("1.2.3.4"), s->getSiaddr());
EXPECT_EQ(IOAddress("5.6.7.8"), s->getRelayInfo().addr_);
EXPECT_EQ(Network::HR_OUT_OF_POOL, s->getHostReservationMode());
// For the second subnet, the renew-timer should be 100, because it
// was specified explicitly. Other parameters a derived
// from global scope to shared-network level and later again to
// subnet4 level.
checkSubnet(*subs, "192.0.2.0/24", 100, 2, 4);
s = checkSubnet(*subs, "192.0.2.0/24", 100, 200, 400);
// These are values derived from shared network scope:
EXPECT_EQ("eth0", s->getIface());
EXPECT_EQ(true, s->getMatchClientId());
EXPECT_EQ(IOAddress("11.22.33.44"), s->getSiaddr());
EXPECT_EQ(IOAddress("55.66.77.88"), s->getRelayInfo().addr_);
EXPECT_EQ(Network::HR_DISABLED, s->getHostReservationMode());
// Ok, now check the second shared subnet.
net = nets->at(1);
......@@ -5282,9 +5331,13 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
EXPECT_EQ(1, subs->size());
// This subnet should derive its renew-timer from global scope.
checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
// All other parameters should have default values.
s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
EXPECT_EQ("", s->getIface());
EXPECT_EQ(true, s->getMatchClientId());
EXPECT_EQ(IOAddress("0.0.0.0"), s->getSiaddr());
EXPECT_EQ(IOAddress("0.0.0.0"), s->getRelayInfo().addr_);
EXPECT_EQ(Network::HR_ALL, s->getHostReservationMode());
}
}
......@@ -982,6 +982,7 @@ shared_network_params: shared_network_param
shared_network_param: name
| subnet6_list
| interface
| interface_id
| renew_timer
| rebind_timer
| option_data_list
......
......@@ -322,18 +322,24 @@ public:
/// @param t2 expected rebind-timer value
/// @param preferred expected preferred-lifetime value
/// @param valid expected valid-lifetime value
void
/// @return the subnet that was examined
Subnet6Ptr
checkSubnet(const Subnet6Collection& col, std::string subnet,
uint32_t t1, uint32_t t2, uint32_t pref, uint32_t valid) {
const auto& index = col.get<SubnetPrefixIndexTag>();
auto subnet_it = index.find(subnet);
ASSERT_NE(subnet_it, index.cend());
if (subnet_it == index.cend()) {
ADD_FAILURE() << "Unable to find expected subnet " << subnet;
return (Subnet6Ptr());
}
Subnet6Ptr s = *subnet_it;
EXPECT_EQ(t1, s->getT1());
EXPECT_EQ(t2, s->getT2());
EXPECT_EQ(pref, s->getPreferred());
EXPECT_EQ(valid, s->getValid());
return (s);
}
/// @brief Returns an interface configuration used by the most of the
......@@ -5558,6 +5564,21 @@ TEST_F(Dhcp6ParserTest, sharedNetworks3subnets) {
// - shared network to subnet
// Also, it tests that more than one shared network can be defined.
TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
// We need to fake the interfaces present, because we want to test
// interface names inheritance. However, there are sanity checks
// on subnet level that would refuse the value if the interface
// is not present.
IfaceMgrTestConfig iface_config(true);
// Build some expected interface-id values.
const string text1 = "oneone";
const string text2 = "twotwo";
OptionBuffer buffer1 = OptionBuffer(text1.begin(), text1.end());
OptionBuffer buffer2 = OptionBuffer(text2.begin(), text2.end());
Option iface_id1(Option::V6, D6O_INTERFACE_ID, buffer1);
Option iface_id2(Option::V6, D6O_INTERFACE_ID, buffer2);
string config = "{\n"
"\"renew-timer\": 1, \n"
"\"rebind-timer\": 2, \n"
......@@ -5566,6 +5587,121 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
"\"shared-networks\": [ {\n"
" \"name\": \"foo\"\n,"
" \"renew-timer\": 10,\n"
" \"rebind-timer\": 20, \n"
" \"preferred-lifetime\": 30,\n"
" \"valid-lifetime\": 40, \n"
" \"interface-id\": \"oneone\",\n"
" \"relay\": {\n"
" \"ip-address\": \"1111::1\"\n"
" },\n"
" \"rapid-commit\": true,\n"
" \"reservation-mode\": \"disabled\",\n"
" \"subnet6\": [\n"
" { \n"
" \"subnet\": \"2001:db1::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
" },\n"
" { \n"
" \"subnet\": \"2001:db2::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
" \"renew-timer\": 100\n,"
" \"rebind-timer\": 200, \n"
" \"preferred-lifetime\": 300,\n"
" \"relay\": {\n"
" \"ip-address\": \"2222::2\"\n"
" },\n"
" \"valid-lifetime\": 400, \n"
" \"interface-id\": \"twotwo\",\n"
" \"rapid-commit\": false,\n"
" \"reservation-mode\": \"out-of-pool\"\n"
" }\n"
" ]\n"
" },\n"
"{ // second shared-network starts here\n"
" \"name\": \"bar\",\n"
" \"subnet6\": [\n"
" {\n"
" \"subnet\": \"2001:db3::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
" }\n"
" ]\n"
"} ]\n"
"} \n";
configure(config, CONTROL_RESULT_SUCCESS, "");
// Now verify that the shared network was indeed configured.
CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
->getCfgSharedNetworks6();
// Two shared networks are expeced.
ASSERT_TRUE(cfg_net);
const SharedNetwork6Collection* nets = cfg_net->getAll();
ASSERT_TRUE(nets);
ASSERT_EQ(2, nets->size());
// Let's check the first one.
SharedNetwork6Ptr net = nets->at(0);
ASSERT_TRUE(net);
const Subnet6Collection * subs = net->getAllSubnets();
ASSERT_TRUE(subs);
EXPECT_EQ(2, subs->size());
// For the first subnet, the renew-timer should be 10, because it was
// derived from shared-network level. Other parameters a derived
// from global scope to shared-network level and later again to
// subnet6 level.
Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 10, 20, 30, 40);
ASSERT_TRUE(s);
ASSERT_TRUE(s->getInterfaceId());
EXPECT_TRUE(iface_id1.equals(s->getInterfaceId()));
EXPECT_EQ(IOAddress("1111::1"), s->getRelayInfo().addr_);
EXPECT_EQ(true, s->getRapidCommit());
EXPECT_EQ(Network::HR_DISABLED, s->getHostReservationMode());
// For the second subnet, the renew-timer should be 100, because it
// was specified explicitly. Other parameters a derived
// from global scope to shared-network level and later again to
// subnet6 level.
s = checkSubnet(*subs, "2001:db2::/48", 100, 200, 300, 400);
ASSERT_TRUE(s->getInterfaceId());
EXPECT_TRUE(iface_id2.equals(s->getInterfaceId()));
EXPECT_EQ(IOAddress("2222::2"), s->getRelayInfo().addr_);
EXPECT_EQ(false, s->getRapidCommit());
EXPECT_EQ(Network::HR_OUT_OF_POOL, s->getHostReservationMode());
// Ok, now check the second shared subnet.
net = nets->at(1);
ASSERT_TRUE(net);
subs = net->getAllSubnets();
ASSERT_TRUE(subs);
EXPECT_EQ(1, subs->size());
// This subnet should derive its renew-timer from global scope.
s = checkSubnet(*subs, "2001:db3::/48", 1, 2, 3, 4);
EXPECT_FALSE(s->getInterfaceId());
EXPECT_EQ(IOAddress("::"), s->getRelayInfo().addr_);
EXPECT_EQ(false, s->getRapidCommit());
EXPECT_EQ(Network::HR_ALL, s->getHostReservationMode());
}
// Since it is not allowed to define both interface-id and interface
// for the same subnet, we need dedicated test that will check
// interface separately.
TEST_F(Dhcp6ParserTest, sharedNetworksDeriveInterfaces) {
// We need to fake the interfaces present, because we want to test
// interface names inheritance. However, there are sanity checks
// on subnet level that would refuse the value if the interface
// is not present.
IfaceMgrTestConfig iface_config(true);
string config = "{\n"
"\"shared-networks\": [ {\n"
" \"name\": \"foo\"\n,"
" \"interface\": \"eth0\",\n"
" \"subnet6\": [\n"
" { \n"
" \"subnet\": \"2001:db1::/48\",\n"
......@@ -5574,7 +5710,7 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
" { \n"
" \"subnet\": \"2001:db2::/48\",\n"
" \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
" \"renew-timer\": 100\n"
" \"interface\": \"eth0\"\n"
" }\n"
" ]\n"
" },\n"
......@@ -5613,13 +5749,16 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
// derived from shared-network level. Other parameters a derived
// from global scope to shared-network level and later again to
// subnet6 level.
checkSubnet(*subs, "2001:db1::/48", 10, 2, 3, 4);
Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 900, 1800, 3600, 7200);
ASSERT_TRUE(s);
EXPECT_EQ("eth0", s->getIface());
// For the second subnet, the renew-timer should be 100, because it
// was specified explicitly. Other parameters a derived
// from global scope to shared-network level and later again to
// subnet6 level.
checkSubnet(*subs, "2001:db2::/48", 100, 2, 3, 4);
checkSubnet(*subs, "2001:db2::/48", 900, 1800, 3600, 7200);
EXPECT_EQ("eth0", s->getIface());
// Ok, now check the second shared subnet.
net = nets->at(1);
......@@ -5630,7 +5769,8 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
EXPECT_EQ(1, subs->size());
// This subnet should derive its renew-timer from global scope.
checkSubnet(*subs, "2001:db3::/48", 1, 2, 3, 4);
s = checkSubnet(*subs, "2001:db3::/48", 900, 1800, 3600, 7200);
EXPECT_EQ("", s->getIface());
}
};
......@@ -7,6 +7,7 @@
#include <dhcpsrv/parsers/simple_parser4.h>
#include <cc/data.h>
#include <boost/foreach.hpp>
#include <iostream>
using namespace isc::data;
......@@ -66,6 +67,12 @@ const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = {
};
/// @brief This table defines default values for each IPv4 subnet.
///
/// Note: When updating this array, please also update SHARED_SUBNET4_DEFAULTS
/// below. In most cases, those two should be kept in sync, except cases
/// where a parameter can be derived from shared-networks, but is not
/// defined on global level. Currently there are two such parameters:
/// interface and reservation-mode
const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = {
{ "id", Element::integer, "0" }, // 0 means autogenerate
{ "interface", Element::string, "" },
......@@ -76,24 +83,51 @@ const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = {
{ "4o6-subnet", Element::string, "" },
};
/// @brief This table defines default values for each IPv4 subnet that is
/// part of a shared network
///
/// This is mostly the same as @ref SUBNET4_DEFAULTS, except two parameters
/// that can be derived from shared-network, but cannot from global scope.
/// Those are: interface and reservation-mode.
const SimpleDefaults SimpleParser4::SHARED_SUBNET4_DEFAULTS = {
{ "id", Element::integer, "0" }, // 0 means autogenerate
{ "client-class", Element::string, "" },
{ "4o6-interface", Element::string, "" },
{ "4o6-interface-id", Element::string, "" },
{ "4o6-subnet", Element::string, "" },
};
/// @brief This table defines default values for each IPv4 shared network.
const SimpleDefaults SimpleParser4::SHARED_NETWORK4_DEFAULTS = {
{ "interface", Element::string, "" },
{ "reservation-mode", Element::string, "all" }
};
/// @brief This table defines default values for interfaces for DHCPv4.
const SimpleDefaults SimpleParser4::IFACE4_DEFAULTS = {
{ "re-detect", Element::boolean, "true" }
};
/// @brief List of parameters that can be inherited from the global to subnet4 scope.
/// @brief List of parameters that can be inherited to subnet4 scope.
///
/// Some parameters may be defined on both global (directly in Dhcp4) and
/// subnet (Dhcp4/subnet4/...) scope. If not defined in the subnet scope,
/// the value is being inherited (derived) from the global scope. This
/// array lists all of such parameters.
const ParamsList SimpleParser4::INHERIT_GLOBAL_TO_SUBNET4 = {
"renew-timer",
"rebind-timer",
"valid-lifetime",
///
/// This list is also used for inheriting from global to shared networks
/// and from shared networks to subnets within it.
const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
"interface",
"match-client-id",
"next-server"
"next-server",
"rebind-timer",
"relay",
"renew-timer",
"reservation-mode",
"valid-lifetime"
};
/// @}
/// ---------------------------------------------------------------------------
......@@ -137,9 +171,12 @@ size_t SimpleParser4::setAllDefaults(isc::data::ElementPtr global) {
ConstElementPtr shared = global->get("shared-networks");
if (shared) {
BOOST_FOREACH(ElementPtr net, shared->listValue()) {
cnt += setDefaults(net, SHARED_NETWORK4_DEFAULTS);
ConstElementPtr subs = net->get("subnet4");