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": { "Dhcp4": {
// As with any other configuration, you need to tell Kea the interface
// names, so it would listen to incoming traffic.
"interfaces-config": { "interfaces-config": {
"interfaces": [ "eth0" ] "interfaces": [ "eth0" ]
}, },
// You also need to tell where to store lease information.
// memfile is the backend that is easiest to set up.
"lease-database": { "lease-database": {
"type": "memfile", "type": "memfile",
"lfc-interval": 3600 "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": [ "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", "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, "match-client-id": false,
"name": "frog",
"option-data": [ ], "option-data": [ ],
"rebind-timer": 150, "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": { "relay": {
"ip-address": "0.0.0.0" "ip-address": "0.0.0.0"
}, },
// Timer values can be overridden here.
"renew-timer": 100, "renew-timer": 100,
"reservation-mode": "all", "reservation-mode": "all",
// This starts a list of subnets allowed in this shared network.
// In our example, there are two subnets.
"subnet4": [ "subnet4": [
{ {
"4o6-interface": "",
"4o6-interface-id": "",
"4o6-subnet": "",
"id": 1, "id": 1,
"match-client-id": true, "match-client-id": true,
"next-server": "0.0.0.0", "next-server": "0.0.0.0",
"option-data": [ ], "option-data": [ ],
"pools": [ ], "pools": [ ],
"rebind-timer": 20, "rebind-timer": 20,
// You can override the value inherited from shared-network
// here if your relay uses different IP addresses for
// each subnet.
"relay": { "relay": {
"ip-address": "0.0.0.0" "ip-address": "0.0.0.0"
}, },
...@@ -42,18 +88,12 @@ ...@@ -42,18 +88,12 @@
"valid-lifetime": 30 "valid-lifetime": 30
}, },
{ {
"4o6-interface": "",
"4o6-interface-id": "",
"4o6-subnet": "",
"id": 2, "id": 2,
"match-client-id": true, "match-client-id": true,
"next-server": "0.0.0.0", "next-server": "0.0.0.0",
"option-data": [ ], "option-data": [ ],
"pools": [ ], "pools": [ ],
"rebind-timer": 20, "rebind-timer": 20,
"relay": {
"ip-address": "0.0.0.0"
},
"renew-timer": 10, "renew-timer": 10,
"reservation-mode": "all", "reservation-mode": "all",
"subnet": "192.0.2.0/24", "subnet": "192.0.2.0/24",
...@@ -63,6 +103,10 @@ ...@@ -63,6 +103,10 @@
"valid-lifetime": 200 "valid-lifetime": 200
} ], // end of shared-networks } ], // 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. // This is regular subnet. It's not part of any shared-network.
"subnet4": [ "subnet4": [
{ {
...@@ -72,6 +116,6 @@ ...@@ -72,6 +116,6 @@
"id": 3 "id": 3
} }
] ]
} // end of Dhcp4 } // end of Dhcp4
} }
...@@ -1002,7 +1002,6 @@ shared_network_param: name ...@@ -1002,7 +1002,6 @@ shared_network_param: name
| next_server | next_server
| relay | relay
| reservation_mode | reservation_mode
| echo_client_id
| client_classes | client_classes
| valid_lifetime | valid_lifetime
| unknown_map_entry | unknown_map_entry
......
...@@ -72,7 +72,7 @@ public: ...@@ -72,7 +72,7 @@ public:
/// ///
/// @throw DhcpConfigError if parameters are missing or /// @throw DhcpConfigError if parameters are missing or
/// or having incorrect values. /// 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 // Set whether v4 server is supposed to echo back client-id
// (yes = RFC6842 compatible, no = backward compatibility) // (yes = RFC6842 compatible, no = backward compatibility)
...@@ -96,7 +96,7 @@ public: ...@@ -96,7 +96,7 @@ public:
/// @throw BadValue if any pointer is missing /// @throw BadValue if any pointer is missing
/// @throw DhcpConfigError if there are duplicates (or other subnet defects) /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
void void
copySubnets4(const CfgSubnets4Ptr dest, const CfgSharedNetworks4Ptr from) { copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
if (!dest || !from) { if (!dest || !from) {
isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null"); isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
...@@ -136,7 +136,7 @@ public: ...@@ -136,7 +136,7 @@ public:
/// @param global global Dhcp4 scope /// @param global global Dhcp4 scope
/// @throw DhcpConfigError in case of issues found /// @throw DhcpConfigError in case of issues found
void void
sanityChecks(SrvConfigPtr cfg, ConstElementPtr global) { sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
/// Shared network sanity checks /// Shared network sanity checks
const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll(); const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
......
...@@ -611,17 +611,23 @@ public: ...@@ -611,17 +611,23 @@ public:
/// @param t1 expected renew-timer value /// @param t1 expected renew-timer value
/// @param t2 expected rebind-timer value /// @param t2 expected rebind-timer value
/// @param valid expected valid-lifetime value /// @param valid expected valid-lifetime value
void /// @return the subnet that was examined
Subnet4Ptr
checkSubnet(const Subnet4Collection& col, std::string subnet, checkSubnet(const Subnet4Collection& col, std::string subnet,
uint32_t t1, uint32_t t2, uint32_t valid) { uint32_t t1, uint32_t t2, uint32_t valid) {
const auto& index = col.get<SubnetPrefixIndexTag>(); const auto& index = col.get<SubnetPrefixIndexTag>();
auto subnet_it = index.find(subnet); 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; Subnet4Ptr s = *subnet_it;
EXPECT_EQ(t1, s->getT1()); EXPECT_EQ(t1, s->getT1());
EXPECT_EQ(t2, s->getT2()); EXPECT_EQ(t2, s->getT2());
EXPECT_EQ(valid, s->getValid()); EXPECT_EQ(valid, s->getValid());
return (s);
} }
/// @brief This utility method attempts to configure using specified /// @brief This utility method attempts to configure using specified
...@@ -5209,13 +5215,34 @@ TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) { ...@@ -5209,13 +5215,34 @@ TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
// - shared network to subnet // - shared network to subnet
// Also, it tests that more than one shared network can be defined. // Also, it tests that more than one shared network can be defined.
TEST_F(Dhcp4ParserTest, sharedNetworksDerive) { 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" string config = "{\n"
"\"renew-timer\": 1, \n" "\"renew-timer\": 1, \n" // global values here
"\"rebind-timer\": 2, \n" "\"rebind-timer\": 2, \n"
"\"valid-lifetime\": 4, \n" "\"valid-lifetime\": 4, \n"
"\"shared-networks\": [ {\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" " \"renew-timer\": 10,\n"
" \"rebind-timer\": 20,\n"
" \"valid-lifetime\": 40,\n"
" \"subnet4\": [\n" " \"subnet4\": [\n"
" { \n" " { \n"
" \"subnet\": \"192.0.1.0/24\",\n" " \"subnet\": \"192.0.1.0/24\",\n"
...@@ -5224,10 +5251,17 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) { ...@@ -5224,10 +5251,17 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
" { \n" " { \n"
" \"subnet\": \"192.0.2.0/24\",\n" " \"subnet\": \"192.0.2.0/24\",\n"
" \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\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" " ]\n"
" },\n" " },\n"
"{ // second shared-network starts here\n" "{ // second shared-network starts here\n"
" \"name\": \"bar\",\n" " \"name\": \"bar\",\n"
...@@ -5265,13 +5299,28 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) { ...@@ -5265,13 +5299,28 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
// derived from shared-network level. Other parameters a derived // derived from shared-network level. Other parameters a derived
// from global scope to shared-network level and later again to // from global scope to shared-network level and later again to
// subnet4 level. // 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 // For the second subnet, the renew-timer should be 100, because it
// was specified explicitly. Other parameters a derived // was specified explicitly. Other parameters a derived
// from global scope to shared-network level and later again to // from global scope to shared-network level and later again to
// subnet4 level. // 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. // Ok, now check the second shared subnet.
net = nets->at(1); net = nets->at(1);
...@@ -5282,9 +5331,13 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) { ...@@ -5282,9 +5331,13 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
EXPECT_EQ(1, subs->size()); EXPECT_EQ(1, subs->size());
// This subnet should derive its renew-timer from global scope. // 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 ...@@ -982,6 +982,7 @@ shared_network_params: shared_network_param
shared_network_param: name shared_network_param: name
| subnet6_list | subnet6_list
| interface | interface
| interface_id
| renew_timer | renew_timer
| rebind_timer | rebind_timer
| option_data_list | option_data_list
......
...@@ -322,18 +322,24 @@ public: ...@@ -322,18 +322,24 @@ public:
/// @param t2 expected rebind-timer value /// @param t2 expected rebind-timer value
/// @param preferred expected preferred-lifetime value /// @param preferred expected preferred-lifetime value
/// @param valid expected valid-lifetime value /// @param valid expected valid-lifetime value
void /// @return the subnet that was examined
Subnet6Ptr
checkSubnet(const Subnet6Collection& col, std::string subnet, checkSubnet(const Subnet6Collection& col, std::string subnet,
uint32_t t1, uint32_t t2, uint32_t pref, uint32_t valid) { uint32_t t1, uint32_t t2, uint32_t pref, uint32_t valid) {
const auto& index = col.get<SubnetPrefixIndexTag>(); const auto& index = col.get<SubnetPrefixIndexTag>();
auto subnet_it = index.find(subnet); 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; Subnet6Ptr s = *subnet_it;
EXPECT_EQ(t1, s->getT1()); EXPECT_EQ(t1, s->getT1());
EXPECT_EQ(t2, s->getT2()); EXPECT_EQ(t2, s->getT2());
EXPECT_EQ(pref, s->getPreferred()); EXPECT_EQ(pref, s->getPreferred());
EXPECT_EQ(valid, s->getValid()); EXPECT_EQ(valid, s->getValid());
return (s);
} }
/// @brief Returns an interface configuration used by the most of the /// @brief Returns an interface configuration used by the most of the
...@@ -863,7 +869,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) { ...@@ -863,7 +869,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);
...@@ -5558,6 +5564,21 @@ TEST_F(Dhcp6ParserTest, sharedNetworks3subnets) { ...@@ -5558,6 +5564,21 @@ TEST_F(Dhcp6ParserTest, sharedNetworks3subnets) {
// - shared network to subnet // - shared network to subnet
// Also, it tests that more than one shared network can be defined. // Also, it tests that more than one shared network can be defined.
TEST_F(Dhcp6ParserTest, sharedNetworksDerive) { 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" string config = "{\n"
"\"renew-timer\": 1, \n" "\"renew-timer\": 1, \n"
"\"rebind-timer\": 2, \n" "\"rebind-timer\": 2, \n"
...@@ -5566,6 +5587,121 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) { ...@@ -5566,6 +5587,121 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
"\"shared-networks\": [ {\n" "\"shared-networks\": [ {\n"
" \"name\": \"foo\"\n," " \"name\": \"foo\"\n,"
" \"renew-timer\": 10,\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);