From: Marcin Siodelski Date: Thu, 7 Sep 2017 08:16:06 +0000 (+0200) Subject: [5306] Subnet selection for shared networks implemented. X-Git-Tag: trac5363_base~21^2~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=253936cc9a2a754a2bde4f72bf79b14b10b220ba;p=thirdparty%2Fkea.git [5306] Subnet selection for shared networks implemented. --- diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc index 6c4b4f9afb..001e6981b7 100644 --- a/src/lib/dhcpsrv/cfg_subnets4.cc +++ b/src/lib/dhcpsrv/cfg_subnets4.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -119,17 +120,29 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const { } // If relayed message has been received, try to match the giaddr with the - // relay address specified for a subnet. It is also possible that the relay - // address will not match with any of the relay addresses across all - // subnets, but we need to verify that for all subnets before we can try - // to use the giaddr to match with the subnet prefix. + // relay address specified for a subnet and/or shared network. It is also + // possible that the relay address will not match with any of the relay + // addresses across all subnets, but we need to verify that for all subnets + // before we can try to use the giaddr to match with the subnet prefix. if (!selector.giaddr_.isV4Zero()) { for (Subnet4Collection::const_iterator subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) { - // Check if the giaddr is equal to the one defined for the subnet. - if (selector.giaddr_ != (*subnet)->getRelayInfo().addr_) { - continue; + // If relay information specified for this subnet it must match. + // Otherwise, we ignore this subnet. + if (!(*subnet)->getRelayInfo().addr_.isV4Zero()) { + if (selector.giaddr_ != (*subnet)->getRelayInfo().addr_) { + continue; + } + + } else { + // Relay information is not specified on the subnet level, + // so let's try matching on the shared network level. + SharedNetwork4Ptr network; + (*subnet)->getSharedNetwork(network); + if (!network || (selector.giaddr_ != network->getRelayInfo().addr_)) { + continue; + } } // If a subnet meets the client class criteria return it. @@ -198,29 +211,40 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const { Subnet4Ptr CfgSubnets4::selectSubnet(const std::string& iface, - const ClientClasses& client_classes) const { + const ClientClasses& client_classes) const { for (Subnet4Collection::const_iterator subnet = subnets_.begin(); subnet != subnets_.end(); ++subnet) { - // If there's no interface specified for this subnet, proceed to - // the next subnet. - if ((*subnet)->getIface().empty()) { - continue; - } + Subnet4Ptr subnet_selected; - // If it's specified, but does not match, proceed to the next - // subnet. - if ((*subnet)->getIface() != iface) { - continue; + // First, try subnet specific interface name. + if (!(*subnet)->getIface().empty()) { + if ((*subnet)->getIface() == iface) { + subnet_selected = (*subnet); + } + + } else { + // Interface not specified for a subnet, so let's try if + // we can match with shared network specific setting of + // the interface. + SharedNetwork4Ptr network; + (*subnet)->getSharedNetwork(network); + if (network && !network->getIface().empty() && + (network->getIface() == iface)) { + subnet_selected = (*subnet); + } } - // If a subnet meets the client class criteria return it. - if ((*subnet)->clientSupported(client_classes)) { - LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, - DHCPSRV_CFGMGR_SUBNET4_IFACE) - .arg((*subnet)->toText()) - .arg(iface); - return (*subnet); + if (subnet_selected) { + + // If a subnet meets the client class criteria return it. + if (subnet_selected->clientSupported(client_classes)) { + LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, + DHCPSRV_CFGMGR_SUBNET4_IFACE) + .arg((*subnet)->toText()) + .arg(iface); + return (subnet_selected); + } } } diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h index 9c91cc54cc..1bda9cb19d 100644 --- a/src/lib/dhcpsrv/network.h +++ b/src/lib/dhcpsrv/network.h @@ -165,7 +165,7 @@ public: /// /// @param client_classes list of all classes the client belongs to /// @return true if client can be supported, false otherwise - bool + virtual bool clientSupported(const isc::dhcp::ClientClasses& client_classes) const; /// @brief Adds class class_name to the list of supported classes diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index f48e42b468..69ba8f0739 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -176,6 +176,17 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length, setValid(valid_lifetime); } +bool +Subnet4::clientSupported(const isc::dhcp::ClientClasses& client_classes) const { + NetworkPtr network; + getSharedNetwork(network); + if (network && !network->clientSupported(client_classes)) { + return (false); + } + + return (Network4::clientSupported(client_classes)); +} + void Subnet4::setSiaddr(const isc::asiolink::IOAddress& siaddr) { if (!siaddr.isV4()) { isc_throw(BadValue, "Can't set siaddr to non-IPv4 address " @@ -439,6 +450,17 @@ void Subnet6::checkType(Lease::Type type) const { } } +bool +Subnet6::clientSupported(const isc::dhcp::ClientClasses& client_classes) const { + NetworkPtr network; + getSharedNetwork(network); + if (network && !network->clientSupported(client_classes)) { + return (false); + } + + return (Network6::clientSupported(client_classes)); +} + data::ElementPtr Subnet::toElement() const { ElementPtr map = Element::createMap(); diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h index 3f81e43c59..991ec503b9 100644 --- a/src/lib/dhcpsrv/subnet.h +++ b/src/lib/dhcpsrv/subnet.h @@ -394,6 +394,20 @@ public: const Triplet& valid_lifetime, const SubnetID id = 0); + /// @brief Checks whether this subnet and parent shared network supports + /// the client that belongs to specified classes. + /// + /// This method extends the @ref Network::clientSupported method with + /// additional checks whether shared network owning this class supports + /// the client belonging to specified classes. If the class doesn't + /// belong to a shared network this method only checks if the subnet + /// supports specified classes. + /// + /// @param client_classes List of classes the client belongs to. + /// @return true if client can be supported, false otherwise. + virtual bool + clientSupported(const isc::dhcp::ClientClasses& client_classes) const; + /// @brief Sets siaddr for the Subnet4 /// /// Will be used for siaddr field (the next server) that typically is used @@ -482,6 +496,20 @@ public: const Triplet& valid_lifetime, const SubnetID id = 0); + /// @brief Checks whether this subnet and parent shared network supports + /// the client that belongs to specified classes. + /// + /// This method extends the @ref Network::clientSupported method with + /// additional checks whether shared network owning this class supports + /// the client belonging to specified classes. If the class doesn't + /// belong to a shared network this method only checks if the subnet + /// supports specified classes. + /// + /// @param client_classes List of classes the client belongs to. + /// @return true if client can be supported, false otherwise. + virtual bool + clientSupported(const isc::dhcp::ClientClasses& client_classes) const; + /// @brief Unparse a subnet object. /// /// @return A pointer to unparsed subnet configuration. diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index 696c477f33..9cd887584f 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -179,6 +180,76 @@ TEST(CfgSubnets4Test, selectSubnetByIface) { EXPECT_EQ(subnet3, selected); } +// This test verifies that it is possible to select subnet by interface +// name specified on the shared network level. +TEST(CfgSubnets4Test, selectSharedNetworkByIface) { + // The IfaceMgrTestConfig object initializes fake interfaces: + // eth0, eth1 and lo on the configuration manager. The CfgSubnets4 + // object uses interface names to select the appropriate subnet. + IfaceMgrTestConfig config(true); + + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("172.16.2.0"), 24, 1, 2, 3, + SubnetID(1))); + Subnet4Ptr subnet2(new Subnet4(IOAddress("10.1.2.0"), 24, 1, 2, 3, + SubnetID(2))); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.3.4.0"), 24, 1, 2, 3, + SubnetID(3))); + subnet2->setIface("lo"); + + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + SharedNetwork4Ptr network(new SharedNetwork4("network_eth1")); + network->setIface("eth1"); + ASSERT_NO_THROW(network->add(subnet1)); + ASSERT_NO_THROW(network->add(subnet2)); + + // Make sure that initially the subnets don't exist. + SubnetSelector selector; + // Set an interface to a name that is not defined in the config. + // Subnet selection should fail. + selector.iface_name_ = "eth0"; + ASSERT_FALSE(cfg.selectSubnet(selector)); + + // Now select an interface name that matches. Selection should succeed + // and return subnet3. + selector.iface_name_ = "eth1"; + Subnet4Ptr selected = cfg.selectSubnet(selector); + ASSERT_TRUE(selected); + SharedNetwork4Ptr network_returned; + selected->getSharedNetwork(network_returned); + ASSERT_TRUE(network_returned); + + const Subnet4Collection* subnets_eth1 = network_returned->getAllSubnets(); + EXPECT_EQ(2, subnets_eth1->size()); + ASSERT_TRUE(network_returned->getSubnet(SubnetID(1))); + ASSERT_TRUE(network_returned->getSubnet(SubnetID(2))); + + // Make sure that it is still possible to select subnet2 which is + // outside of a shared network. + selector.iface_name_ = "lo"; + selected = cfg.selectSubnet(selector); + ASSERT_TRUE(selected); + EXPECT_EQ(2, selected->getID()); + + // Try selecting by eth1 again, but this time set subnet specific + // interface name to eth0. Subnet selection should fail. + selector.iface_name_ = "eth1"; + subnet1->setIface("eth0"); + subnet3->setIface("eth0"); + selected = cfg.selectSubnet(selector); + ASSERT_FALSE(selected); + + // It should be possible to select by eth0, though. + selector.iface_name_ = "eth0"; + selected = cfg.selectSubnet(selector); + ASSERT_TRUE(selected); +} + // This test verifies that when the classification information is specified for // subnets, the proper subnets are returned by the subnet configuration. TEST(CfgSubnets4Test, selectSubnetByClasses) { @@ -253,6 +324,63 @@ TEST(CfgSubnets4Test, selectSubnetByClasses) { EXPECT_FALSE(cfg.selectSubnet(selector)); } +// This test verifies that shared network can be selected based on client +// classification. +TEST(CfgSubnets4Test, selectSharedNetworkByClasses) { + IfaceMgrTestConfig config(true); + + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3)); + + // Add them to the configuration. + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + // Create first network and add first two subnets to it. + SharedNetwork4Ptr network1(new SharedNetwork4("network1")); + network1->setIface("eth1"); + network1->allowClientClass("device-type1"); + ASSERT_NO_THROW(network1->add(subnet1)); + ASSERT_NO_THROW(network1->add(subnet2)); + + // Create second network and add last subnet there. + SharedNetwork4Ptr network2(new SharedNetwork4("network2")); + network2->setIface("eth1"); + network2->allowClientClass("device-type2"); + ASSERT_NO_THROW(network2->add(subnet3)); + + // Use interface name as a selector. This guarantees that subnet + // selection will be made based on the classification. + SubnetSelector selector; + selector.iface_name_ = "eth1"; + + // If the client has "device-type2" class, it is expected that the + // second network will be used. This network has only one subnet + // in it, i.e. subnet3. + ClientClasses client_classes; + client_classes.insert("device-type2"); + selector.client_classes_ = client_classes; + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); + + // Switch to device-type1 and expect that we're assigned a subnet from + // another shared network. + client_classes.clear(); + client_classes.insert("device-type1"); + selector.client_classes_ = client_classes; + + Subnet4Ptr subnet = cfg.selectSubnet(selector); + ASSERT_TRUE(subnet); + SharedNetwork4Ptr network; + subnet->getSharedNetwork(network); + ASSERT_TRUE(network); + EXPECT_EQ("network1", network->getName()); +} + // This test verifies the option selection can be used and is only // used when present. TEST(CfgSubnets4Test, selectSubnetByOptionSelect) { @@ -329,6 +457,41 @@ TEST(CfgSubnets4Test, selectSubnetByRelayAddress) { EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); } +// This test verifies that the relay information specified on the shared +// network level can be used to select a subnet. +TEST(CfgSubnets4Test, selectSharedNetworkByRelayAddress) { + CfgSubnets4 cfg; + + // Create 3 subnets. + Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3)); + Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3)); + Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3)); + + // Add them to the configuration. + cfg.add(subnet1); + cfg.add(subnet2); + cfg.add(subnet3); + + SharedNetwork4Ptr network(new SharedNetwork4("network")); + network->add(subnet2); + + SubnetSelector selector; + + // Now specify relay info. Note that for the second subnet we specify + // relay info on the network level. + subnet1->setRelayInfo(IOAddress("10.0.0.1")); + network->setRelayInfo(IOAddress("10.0.0.2")); + subnet3->setRelayInfo(IOAddress("10.0.0.3")); + + // And try again. This time relay-info is there and should match. + selector.giaddr_ = IOAddress("10.0.0.1"); + EXPECT_EQ(subnet1, cfg.selectSubnet(selector)); + selector.giaddr_ = IOAddress("10.0.0.2"); + EXPECT_EQ(subnet2, cfg.selectSubnet(selector)); + selector.giaddr_ = IOAddress("10.0.0.3"); + EXPECT_EQ(subnet3, cfg.selectSubnet(selector)); +} + // This test verifies that the subnet can be selected for the client // using a source address if the client hasn't set the ciaddr. TEST(CfgSubnets4Test, selectSubnetNoCiaddr) { diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc index eb24713d66..8081d11c9a 100644 --- a/src/lib/dhcpsrv/tests/subnet_unittest.cc +++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -292,6 +293,13 @@ TEST(Subnet4Test, clientClasses) { three_classes.insert("bar"); three_classes.insert("baz"); + // This client belongs to foo, bar, baz and network classes. + isc::dhcp::ClientClasses four_classes; + four_classes.insert("foo"); + four_classes.insert("bar"); + four_classes.insert("baz"); + four_classes.insert("network"); + // No class restrictions defined, any client should be supported EXPECT_EQ(0, subnet->getClientClasses().size()); EXPECT_TRUE(subnet->clientSupported(no_class)); @@ -307,6 +315,20 @@ TEST(Subnet4Test, clientClasses) { EXPECT_FALSE(subnet->clientSupported(foo_class)); EXPECT_TRUE(subnet->clientSupported(bar_class)); EXPECT_TRUE(subnet->clientSupported(three_classes)); + + // Add shared network which can only be selected when the client + // class is "network". + SharedNetwork4Ptr network(new SharedNetwork4("network")); + network->allowClientClass("network"); + ASSERT_NO_THROW(network->add(subnet)); + + // This time, if the client doesn't support network class, + // the subnets from the shared network can't be selected. + EXPECT_FALSE(subnet->clientSupported(bar_class)); + EXPECT_FALSE(subnet->clientSupported(three_classes)); + + // If the classes include "network", the subnet is selected. + EXPECT_TRUE(subnet->clientSupported(four_classes)); } // Tests whether Subnet4 object is able to store and process properly @@ -743,6 +765,13 @@ TEST(Subnet6Test, clientClasses) { three_classes.insert("bar"); three_classes.insert("baz"); + // This client belongs to foo, bar, baz and network classes. + isc::dhcp::ClientClasses four_classes; + four_classes.insert("foo"); + four_classes.insert("bar"); + four_classes.insert("baz"); + four_classes.insert("network"); + // No class restrictions defined, any client should be supported EXPECT_EQ(0, subnet->getClientClasses().size()); EXPECT_TRUE(subnet->clientSupported(no_class)); @@ -758,6 +787,20 @@ TEST(Subnet6Test, clientClasses) { EXPECT_FALSE(subnet->clientSupported(foo_class)); EXPECT_TRUE(subnet->clientSupported(bar_class)); EXPECT_TRUE(subnet->clientSupported(three_classes)); + + // Add shared network which can only be selected when the client + // class is "network". + SharedNetwork6Ptr network(new SharedNetwork6("network")); + network->allowClientClass("network"); + ASSERT_NO_THROW(network->add(subnet)); + + // This time, if the client doesn't support network class, + // the subnets from the shared network can't be selected. + EXPECT_FALSE(subnet->clientSupported(bar_class)); + EXPECT_FALSE(subnet->clientSupported(three_classes)); + + // If the classes include "network", the subnet is selected. + EXPECT_TRUE(subnet->clientSupported(four_classes)); } // Tests whether Subnet6 object is able to store and process properly