#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>
}
// 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.
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);
+ }
}
}
///
/// @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
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 "
}
}
+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();
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
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.
#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>
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) {
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) {
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) {
#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>
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));
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
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));
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