Browse Source

[5306] Subnet selection for shared networks implemented.

Marcin Siodelski 7 years ago
parent
commit
253936cc9a

+ 48 - 24
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -9,6 +9,7 @@
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
@@ -119,17 +120,29 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
     }
     }
 
 
     // If relayed message has been received, try to match the giaddr with the
     // 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()) {
     if (!selector.giaddr_.isV4Zero()) {
         for (Subnet4Collection::const_iterator subnet = subnets_.begin();
         for (Subnet4Collection::const_iterator subnet = subnets_.begin();
              subnet != subnets_.end(); ++subnet) {
              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.
             // If a subnet meets the client class criteria return it.
@@ -198,29 +211,40 @@ CfgSubnets4::selectSubnet(const SubnetSelector& selector) const {
 
 
 Subnet4Ptr
 Subnet4Ptr
 CfgSubnets4::selectSubnet(const std::string& iface,
 CfgSubnets4::selectSubnet(const std::string& iface,
-                 const ClientClasses& client_classes) const {
+                          const ClientClasses& client_classes) const {
     for (Subnet4Collection::const_iterator subnet = subnets_.begin();
     for (Subnet4Collection::const_iterator subnet = subnets_.begin();
          subnet != subnets_.end(); ++subnet) {
          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);
+            }
         }
         }
     }
     }
 
 

+ 1 - 1
src/lib/dhcpsrv/network.h

@@ -165,7 +165,7 @@ public:
     ///
     ///
     /// @param client_classes list of all classes the client belongs to
     /// @param client_classes list of all classes the client belongs to
     /// @return true if client can be supported, false otherwise
     /// @return true if client can be supported, false otherwise
-    bool
+    virtual bool
     clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
     clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
 
 
     /// @brief Adds class class_name to the list of supported classes
     /// @brief Adds class class_name to the list of supported classes

+ 22 - 0
src/lib/dhcpsrv/subnet.cc

@@ -176,6 +176,17 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
     setValid(valid_lifetime);
     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) {
 void Subnet4::setSiaddr(const isc::asiolink::IOAddress& siaddr) {
     if (!siaddr.isV4()) {
     if (!siaddr.isV4()) {
         isc_throw(BadValue, "Can't set siaddr to non-IPv4 address "
         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
 data::ElementPtr
 Subnet::toElement() const {
 Subnet::toElement() const {
     ElementPtr map = Element::createMap();
     ElementPtr map = Element::createMap();

+ 28 - 0
src/lib/dhcpsrv/subnet.h

@@ -394,6 +394,20 @@ public:
             const Triplet<uint32_t>& valid_lifetime,
             const Triplet<uint32_t>& valid_lifetime,
             const SubnetID id = 0);
             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
     /// @brief Sets siaddr for the Subnet4
     ///
     ///
     /// Will be used for siaddr field (the next server) that typically is used
     /// Will be used for siaddr field (the next server) that typically is used
@@ -482,6 +496,20 @@ public:
             const Triplet<uint32_t>& valid_lifetime,
             const Triplet<uint32_t>& valid_lifetime,
             const SubnetID id = 0);
             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.
     /// @brief Unparse a subnet object.
     ///
     ///
     /// @return A pointer to unparsed subnet configuration.
     /// @return A pointer to unparsed subnet configuration.

+ 163 - 0
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 #include <config.h>
 #include <dhcp/classify.h>
 #include <dhcp/classify.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/subnet_id.h>
@@ -179,6 +180,76 @@ TEST(CfgSubnets4Test, selectSubnetByIface) {
     EXPECT_EQ(subnet3, selected);
     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
 // This test verifies that when the classification information is specified for
 // subnets, the proper subnets are returned by the subnet configuration.
 // subnets, the proper subnets are returned by the subnet configuration.
 TEST(CfgSubnets4Test, selectSubnetByClasses) {
 TEST(CfgSubnets4Test, selectSubnetByClasses) {
@@ -253,6 +324,63 @@ TEST(CfgSubnets4Test, selectSubnetByClasses) {
     EXPECT_FALSE(cfg.selectSubnet(selector));
     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
 // This test verifies the option selection can be used and is only
 // used when present.
 // used when present.
 TEST(CfgSubnets4Test, selectSubnetByOptionSelect) {
 TEST(CfgSubnets4Test, selectSubnetByOptionSelect) {
@@ -329,6 +457,41 @@ TEST(CfgSubnets4Test, selectSubnetByRelayAddress) {
     EXPECT_EQ(subnet3, cfg.selectSubnet(selector));
     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
 // This test verifies that the subnet can be selected for the client
 // using a source address if the client hasn't set the ciaddr.
 // using a source address if the client hasn't set the ciaddr.
 TEST(CfgSubnets4Test, selectSubnetNoCiaddr) {
 TEST(CfgSubnets4Test, selectSubnetNoCiaddr) {

+ 43 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/option.h>
 #include <dhcp/option.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/dhcp6.h>
 #include <dhcp/option_space.h>
 #include <dhcp/option_space.h>
+#include <dhcpsrv/shared_network.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -292,6 +293,13 @@ TEST(Subnet4Test, clientClasses) {
     three_classes.insert("bar");
     three_classes.insert("bar");
     three_classes.insert("baz");
     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
     // No class restrictions defined, any client should be supported
     EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
     EXPECT_TRUE(subnet->clientSupported(no_class));
@@ -307,6 +315,20 @@ TEST(Subnet4Test, clientClasses) {
     EXPECT_FALSE(subnet->clientSupported(foo_class));
     EXPECT_FALSE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
     EXPECT_TRUE(subnet->clientSupported(three_classes));
     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
 // 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("bar");
     three_classes.insert("baz");
     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
     // No class restrictions defined, any client should be supported
     EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_EQ(0, subnet->getClientClasses().size());
     EXPECT_TRUE(subnet->clientSupported(no_class));
     EXPECT_TRUE(subnet->clientSupported(no_class));
@@ -758,6 +787,20 @@ TEST(Subnet6Test, clientClasses) {
     EXPECT_FALSE(subnet->clientSupported(foo_class));
     EXPECT_FALSE(subnet->clientSupported(foo_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
     EXPECT_TRUE(subnet->clientSupported(bar_class));
     EXPECT_TRUE(subnet->clientSupported(three_classes));
     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
 // Tests whether Subnet6 object is able to store and process properly