#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/option_vendor.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_host_operations.h>
#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/cfg_shared_networks.h>
#include <dhcpsrv/cfg_subnets4.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <boost/algorithm/string/join.hpp>
#include <boost/bind.hpp>
#include <boost/foreach.hpp>
+#include <boost/pointer_cast.hpp>
#include <boost/shared_ptr.hpp>
#include <iomanip>
// performance hit should be acceptable. If it turns out to
// be significant, we will have to cache server identifiers
// when sockets are opened.
- return (IfaceMgr::instance().hasOpenSocket(server_id));
+ if (IfaceMgr::instance().hasOpenSocket(server_id)) {
+ return (true);
+ }
+
+ // There are some cases when an administrator explicitly sets server
+ // identifier (option 54) that should be used for a given, subnet,
+ // network etc. It doesn't have to be an address assigned to any of
+ // the server interfaces. Thus, we have to check if the server
+ // identifier received is the one that we explicitly set in the
+ // server configuration. At this point, we don't know which subnet
+ // the client belongs to so we can't match the server id with any
+ // subnet. We simply check if this server identifier is configured
+ // anywhere. This should be good enough to eliminate exchanges
+ // with other servers in the same network.
+
+ /// @todo Currently we only check subnet identifiers configured on the
+ /// subnet level, shared network level and global level. This should
+ /// be sufficient for most of cases. At this point, trying to support
+ /// server identifiers on the class level seems to be an overkill and
+ /// is probably not needed. Same with host reservations. In fact,
+ /// at this point we don't know the reservations for the client
+ /// communicating with the server. We may revise some of these choices
+ /// in the future.
+
+ SrvConfigPtr cfg = CfgMgr::instance().getCurrentCfg();
+
+ // Check if there is at least one subnet configured with this server
+ // identifier.
+ ConstCfgSubnets4Ptr cfg_subnets = cfg->getCfgSubnets4();
+ if (cfg_subnets->hasSubnetWithServerId(server_id)) {
+ return (true);
+ }
+
+ // This server identifier is not configured for any of the subnets, so
+ // check on the shared network level.
+ CfgSharedNetworks4Ptr cfg_networks = cfg->getCfgSharedNetworks4();
+ if (cfg_networks->hasSubnetWithServerId(server_id)) {
+ return (true);
+ }
+
+ // Finally, it is possible that the server identifier is specified
+ // on the global level.
+ ConstCfgOptionPtr cfg_global_options = cfg->getCfgOption();
+ OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+ (cfg_global_options->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+
+ return (opt_server_id && (opt_server_id->readAddress() == server_id));
}
void
///
/// - Configuration 7:
/// - Used for testing custom value of dhcp-server-identifier option.
-/// - 1 subnets: 10.0.0.0/24 and 192.0.2.0/24
-/// - Custom server identifier specified for each subnet.
+/// - 3 subnets: 10.0.0.0/24, 192.0.2.0/26 and 192.0.2.64/26
+/// - Custom server identifier specified for 2 subnets subnet.
+/// - Custom server identifier specified at global level.
///
/// - Configuration 8:
/// - Simple configuration with a single subnet and single pool
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
+ "\"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"3.4.5.6\""
+ " }"
+ "],"
"\"subnet4\": ["
" {"
" \"subnet\": \"10.0.0.0/24\", "
" ]"
" },"
" {"
- " \"subnet\": \"192.0.2.0/24\", "
- " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.100\" } ],"
+ " \"subnet\": \"192.0.2.0/26\", "
+ " \"pools\": [ { \"pool\": \"192.0.2.10-192.0.2.63\" } ],"
" \"interface\": \"eth1\","
" \"option-data\": ["
" {"
" \"data\": \"2.3.4.5\""
" }"
" ]"
-
+ " },"
+ " {"
+ " \"subnet\": \"192.0.2.64/26\", "
+ " \"pools\": [ { \"pool\": \"192.0.2.65-192.0.2.100\" } ],"
+ " \"relay\": {"
+ " \"ip-address\": \"10.2.3.4\""
+ " }"
" }"
"]"
"}",
// Repeat the test for different subnet.
Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
client2.setIfaceName("eth1");
+
ASSERT_NO_THROW(client2.doDORA());
ASSERT_TRUE(client2.getContext().response_);
resp = client2.getContext().response_;
ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
+
+ // Create relayed client which will be assigned a lease from the third
+ // subnet. This subnet inherits server identifier value from the global
+ // scope.
+ Dhcp4Client client3(client1.getServer(), Dhcp4Client::SELECTING);
+ client3.useRelay(true, IOAddress("10.2.3.4"));
+
+ ASSERT_NO_THROW(client3.doDORA());
+ ASSERT_TRUE(client3.getContext().response_);
+ resp = client3.getContext().response_;
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ EXPECT_EQ("3.4.5.6", client3.config_.serverid_.toText());
}
// Starting tests which require MySQL backend availability. Those tests
" ]"
"}",
+// Configuration #15
+// - two shared networks, each comes with its own server identifier.
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"1.2.3.4\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"dog\","
+ " \"interface\": \"eth0\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dhcp-server-identifier\","
+ " \"data\": \"2.3.4.5\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"10.0.0.0/26\","
+ " \"id\": 1000,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"10.0.0.1 - 10.0.0.63\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}"
+
};
/// @Brief Test fixture class for DHCPv4 server using shared networks.
});
}
+// This test verifies that custom server identifier can be specified for a
+// shared network.
+TEST_F(Dhcpv4SharedNetworkTest, customServerIdentifier) {
+ Dhcp4Client client1(Dhcp4Client::SELECTING);
+ client1.setIfaceName("eth1");
+
+ // Configure DHCP server.
+ ASSERT_NO_THROW(configure(NETWORKS_CONFIG[15], *client1.getServer()));
+
+ testAssigned([this, &client1] {
+ ASSERT_NO_THROW(client1.doDORA());
+ });
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client1.getContext().response_);
+ Pkt4Ptr resp = client1.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // The explicitly configured server identifier should take precedence
+ // over generated server identifier.
+ EXPECT_EQ("1.2.3.4", client1.config_.serverid_.toText());
+
+ // Create another client using different interface.
+ Dhcp4Client client2(client1.getServer(), Dhcp4Client::SELECTING);
+ client2.setIfaceName("eth0");
+
+ testAssigned([this, &client2] {
+ ASSERT_NO_THROW(client2.doDORA());
+ });
+
+ // Make sure that the server responded.
+ ASSERT_TRUE(client2.getContext().response_);
+ resp = client2.getContext().response_;
+ // Make sure that the server has responded with DHCPACK.
+ ASSERT_EQ(DHCPACK, static_cast<int>(resp->getType()));
+ // The explicitly configured server identifier should take precedence
+ // over generated server identifier.
+ EXPECT_EQ("2.3.4.5", client2.config_.serverid_.toText());
+}
+
} // end of anonymous namespace
libkea_dhcpsrv_la_SOURCES += cfg_option.cc cfg_option.h
libkea_dhcpsrv_la_SOURCES += cfg_option_def.cc cfg_option_def.h
libkea_dhcpsrv_la_SOURCES += cfg_rsoo.cc cfg_rsoo.h
-libkea_dhcpsrv_la_SOURCES += cfg_shared_networks.h
+libkea_dhcpsrv_la_SOURCES += cfg_shared_networks.cc cfg_shared_networks.h
libkea_dhcpsrv_la_SOURCES += cfg_subnets4.cc cfg_subnets4.h
libkea_dhcpsrv_la_SOURCES += cfg_subnets6.cc cfg_subnets6.h
libkea_dhcpsrv_la_SOURCES += cfg_mac_source.cc cfg_mac_source.h
--- /dev/null
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <dhcpsrv/cfg_shared_networks.h>
+
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+bool
+CfgSharedNetworks4::hasSubnetWithServerId(const IOAddress& server_id) const {
+ const auto& index = networks_.get<SharedNetworkServerIdIndexTag>();
+ auto network_it = index.find(server_id);
+ return (network_it != index.cend());
+}
+
+
+} // end of namespace isc::dhcp
+} // end of namespace isc
#ifndef CFG_SHARED_NETWORKS_H
#define CFG_SHARED_NETWORKS_H
+#include <asiolink/io_address.h>
#include <cc/cfg_to_element.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
return (&networks_);
}
+ /// @brief Checks if specified server identifier has been specified for
+ /// any network.
+ ///
+ /// @param server_id Server identifier.
+ ///
+ /// @return true if there is a network with a specified server identifier.
+ bool hasSubnetWithServerId(const asiolink::IOAddress& server_id) const;
+
+
};
/// @brief Pointer to the configuration of IPv4 shared networks.
return ((subnet_it != index.cend()) ? (*subnet_it) : ConstSubnet4Ptr());
}
+bool
+CfgSubnets4::hasSubnetWithServerId(const asiolink::IOAddress& server_id) const {
+ const auto& index = subnets_.get<SubnetServerIdIndexTag>();
+ auto subnet_it = index.find(server_id);
+ return (subnet_it != index.cend());
+}
+
Subnet4Ptr
CfgSubnets4::selectSubnet4o6(const SubnetSelector& selector) const {
/// subnet doesn't exist.
ConstSubnet4Ptr getByPrefix(const std::string& subnet_prefix) const;
+ /// @brief Checks if specified server identifier has been specified for
+ /// any subnet.
+ ///
+ /// @param server_id Server identifier.
+ ///
+ /// @return true if there is a subnet with a specified server identifier.
+ bool hasSubnetWithServerId(const asiolink::IOAddress& server_id) const;
+
/// @brief Returns a pointer to the selected subnet.
///
/// This method tries to retrieve the subnet for the client using various
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#include <dhcp/dhcp4.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_space.h>
#include <dhcpsrv/network.h>
+#include <boost/pointer_cast.hpp>
+using namespace isc::asiolink;
using namespace isc::data;
namespace isc {
return (map);
}
+IOAddress
+Network4::getServerId() const {
+ try {
+ OptionCustomPtr opt_server_id = boost::dynamic_pointer_cast<OptionCustom>
+ (cfg_option_->get(DHCP4_OPTION_SPACE, DHO_DHCP_SERVER_IDENTIFIER).option_);
+ if (opt_server_id) {
+ return (opt_server_id->readAddress());
+ }
+ } catch (const std::exception&) {
+ // Ignore any exceptions and simply return empty buffer.
+ }
+
+ return (IOAddress::IPV4_ZERO_ADDRESS());
+}
+
ElementPtr
Network6::toElement() const {
ElementPtr map = Network::toElement();
/// @return A pointer to unparsed network configuration.
virtual data::ElementPtr toElement() const;
+ /// @brief Returns binary representation of the dhcp-server-identifier option (54).
+ ///
+ /// @return Server identifier option as IPv4 address. Zero IPv4 address
+ /// indicates that server identifier hasn't been specified.
+ virtual asiolink::IOAddress getServerId() const;
+
private:
/// @brief Should server use client identifiers for client lease
#ifndef SHARED_NETWORK_H
#define SHARED_NETWORK_H
+#include <asiolink/io_address.h>
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <dhcpsrv/assignable_network.h>
/// @brief A tag for accessing index by shared network name.
struct SharedNetworkNameIndexTag { };
+/// @brief A tag for accessing index by server identifier.
+struct SharedNetworkServerIdIndexTag { };
+
/// @brief Shared network holding IPv4 subnets.
///
/// Specialization of the @ref Network4 class for IPv4 shared networks.
boost::multi_index::tag<SharedNetworkNameIndexTag>,
boost::multi_index::const_mem_fun<SharedNetwork4, std::string,
&SharedNetwork4::getName>
+ >,
+ // Third index allows for access by server identifier specified for the
+ // network.
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SharedNetworkServerIdIndexTag>,
+ boost::multi_index::const_mem_fun<Network4, asiolink::IOAddress,
+ &Network4::getServerId>
>
+
>
> SharedNetwork4Collection;
/// @brief Tag for the index for searching by subnet prefix.
struct SubnetPrefixIndexTag { };
-/// @brief Multi index container holding subnets.
+/// @brief Tag for the index for searching by server identifier.
+struct SubnetServerIdIndexTag { };
+
+/// @brief A collection of @c Subnet4 objects
+///
+/// This container provides a set of indexes which can be used to retrieve
+/// subnets by various properties.
///
-/// This multi index container can hold pointers to @ref Subnet4 or
-/// @ref Subnet6 objects representing subnets. It provides indexes for
-/// subnet lookups using subnet properties such as: subnet identifier
-/// or subnet prefix. It also provides a random access index which
-/// allows for using the container like a vector.
+/// This multi index container can hold pointers to @ref Subnet4
+/// objects representing subnets. It provides indexes for subnet lookups
+/// using subnet properties such as: subnet identifier,
+/// subnet prefix or server identifier specified for a subnet. It also
+/// provides a random access index which allows for using the container
+/// like a vector.
///
/// The random access index is used by the DHCP servers which perform
/// a full scan on subnets to find the one that matches some specific
/// @todo We should consider optimizing subnet selection by leveraging
/// the indexing capabilities of this container, e.g. searching for
/// a subnet by interface name, relay address etc.
-///
-/// @tparam SubnetType Type of the subnet: @ref Subnet4 or @ref Subnet6.
-template<typename SubnetType>
-using SubnetCollection = boost::multi_index_container<
+typedef boost::multi_index_container<
// Multi index container holds pointers to the subnets.
- boost::shared_ptr<SubnetType>,
+ Subnet4Ptr,
// The following holds all indexes.
boost::multi_index::indexed_by<
// First is the random access index allowing for accessing
boost::multi_index::ordered_unique<
boost::multi_index::tag<SubnetPrefixIndexTag>,
boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >,
+
+ // Fourth index allows for searching using an output from getServerId
+ boost::multi_index::ordered_non_unique<
+ boost::multi_index::tag<SubnetServerIdIndexTag>,
+ boost::multi_index::const_mem_fun<Network4, asiolink::IOAddress,
+ &Network4::getServerId>
>
>
->;
-
-/// @brief A collection of @c Subnet4 objects
-///
-/// This container provides a set of indexes which can be used to retrieve
-/// subnets by various properties.
-typedef SubnetCollection<Subnet4> Subnet4Collection;
+> Subnet4Collection;
/// @brief A collection of @c Subnet6 objects
///
/// This container provides a set of indexes which can be used to retrieve
/// subnets by various properties.
-typedef SubnetCollection<Subnet6> Subnet6Collection;
+///
+/// This multi index container can hold pointers to @ref Subnet6 objects
+/// representing subnets. It provides indexes for subnet lookups using
+/// subnet properties such as: subnet identifier or subnet prefix. It
+/// also provides a random access index which allows for using the
+/// container like a vector.
+///
+/// The random access index is used by the DHCP servers which perform
+/// a full scan on subnets to find the one that matches some specific
+/// criteria for subnet selection.
+///
+/// The remaining indexes are used for searching for a specific subnet
+/// as a result of receiving a command over the control API, e.g.
+/// when 'subnet-get' command is received.
+///
+/// @todo We should consider optimizing subnet selection by leveraging
+/// the indexing capabilities of this container, e.g. searching for
+/// a subnet by interface name, relay address etc.
+typedef boost::multi_index_container<
+ // Multi index container holds pointers to the subnets.
+ Subnet6Ptr,
+ // The following holds all indexes.
+ boost::multi_index::indexed_by<
+ // First is the random access index allowing for accessing
+ // objects just like we'd do with a vector.
+ boost::multi_index::random_access<
+ boost::multi_index::tag<SubnetRandomAccessIndexTag>
+ >,
+ // Second index allows for searching using subnet identifier.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetSubnetIdIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, SubnetID, &Subnet::getID>
+ >,
+ // Third index allows for searching using an output from toText function.
+ boost::multi_index::ordered_unique<
+ boost::multi_index::tag<SubnetPrefixIndexTag>,
+ boost::multi_index::const_mem_fun<Subnet, std::string, &Subnet::toText>
+ >
+ >
+> Subnet6Collection;
//@}
#include <config.h>
#include <dhcp/classify.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
+#include <dhcp/option_space.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/cfg_subnets4.h>
EXPECT_EQ(Subnet4Ptr(), cfg.getSubnet(400)); // no such subnet
}
+// This test verifies that hasSubnetWithServerId returns correct value.
+TEST(CfgSubnets4Test, hasSubnetWithServerId) {
+ CfgSubnets4 cfg;
+
+ // Initially, there is no server identifier option present.
+ EXPECT_FALSE(cfg.hasSubnetWithServerId(IOAddress("1.2.3.4")));
+
+ OptionDefinitionPtr def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ OptionCustomPtr opt_server_id(new OptionCustom(*def, Option::V4));
+ opt_server_id->writeAddress(IOAddress("1.2.3.4"));
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, 100));
+ subnet->getCfgOption()->add(opt_server_id, false, DHCP4_OPTION_SPACE);
+ cfg.add(subnet);
+
+ EXPECT_TRUE(cfg.hasSubnetWithServerId(IOAddress("1.2.3.4")));
+ EXPECT_FALSE(cfg.hasSubnetWithServerId(IOAddress("2.3.4.5")));
+}
+
} // end of anonymous namespace
#include <config.h>
#include <asiolink/io_address.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_definition.h>
#include <dhcp/dhcp6.h>
#include <dhcp/option_space.h>
#include <dhcpsrv/shared_network.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
+#include <boost/pointer_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <limits>
EXPECT_THROW(subnet->addPool(pool5), BadValue);
}
+// Tests if correct value of server identifier is returned when getServerId is
+// called.
+TEST(Subnet4Test, getServerId) {
+ // Initially, the subnet has no server identifier.
+ Subnet4 subnet(IOAddress("192.2.0.0"), 16, 1, 2, 3);
+ EXPECT_TRUE(subnet.getServerId().isV4Zero());
+
+ // Add server identifier.
+ OptionDefinitionPtr option_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE,
+ DHO_DHCP_SERVER_IDENTIFIER);
+ OptionCustomPtr option_server_id(new OptionCustom(*option_def, Option::V4));
+ option_server_id->writeAddress(IOAddress("1.2.3.4"));
+
+ CfgOptionPtr cfg_option = subnet.getCfgOption();
+ cfg_option->add(option_server_id, false, DHCP4_OPTION_SPACE);
+
+ // Verify that the server identifier returned by the Subnet4 object is
+ // correct.
+ OptionBuffer server_id_buf = { 1, 2, 3, 4 };
+ EXPECT_EQ("1.2.3.4", subnet.getServerId().toText());
+}
+
// Tests for Subnet6
TEST(Subnet6Test, constructor) {