Commit 253936cc authored by Marcin Siodelski's avatar Marcin Siodelski

[5306] Subnet selection for shared networks implemented.

parent 4e8ac9fc
......@@ -9,6 +9,7 @@
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet_id.h>
#include <dhcpsrv/addr_utilities.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
// 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);
}
}
}
......
......@@ -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
......
......@@ -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();
......
......@@ -394,6 +394,20 @@ public:
const Triplet<uint32_t>& 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<uint32_t>& 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.
......
......@@ -7,6 +7,7 @@
#include <config.h>
#include <dhcp/classify.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_id.h>
......@@ -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) {
......
......@@ -10,6 +10,7 @@
#include <dhcp/option.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment