Commit 62f0e9a5 authored by Marcin Siodelski's avatar Marcin Siodelski

[5376] Accept explicitly configured server identifiers in inbound msgs.

parent 730c4de2
......@@ -11,6 +11,7 @@
#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>
......@@ -27,6 +28,7 @@
#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>
......@@ -62,6 +64,7 @@
#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>
......@@ -2824,7 +2827,53 @@ Dhcpv4Srv::acceptServerId(const Pkt4Ptr& query) const {
// 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
......
......@@ -88,8 +88,9 @@ namespace {
///
/// - 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
......@@ -291,6 +292,12 @@ const char* DORA_CONFIGS[] = {
" \"interfaces\": [ \"*\" ]"
"},"
"\"valid-lifetime\": 600,"
"\"option-data\": ["
" {"
" \"name\": \"dhcp-server-identifier\","
" \"data\": \"3.4.5.6\""
" }"
"],"
"\"subnet4\": ["
" {"
" \"subnet\": \"10.0.0.0/24\", "
......@@ -304,8 +311,8 @@ const char* DORA_CONFIGS[] = {
" ]"
" },"
" {"
" \"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\": ["
" {"
......@@ -313,7 +320,13 @@ const char* DORA_CONFIGS[] = {
" \"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\""
" }"
" }"
"]"
"}",
......@@ -1625,11 +1638,24 @@ TEST_F(DORATest, customServerIdentifier) {
// 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
......
......@@ -808,6 +808,59 @@ const char* NETWORKS_CONFIG[] = {
" ]"
"}",
// 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.
......@@ -1661,4 +1714,44 @@ TEST_F(Dhcpv4SharedNetworkTest, sharedNetworkSelectedByClass) {
});
}
// 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
......@@ -101,7 +101,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h
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
......
// 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
......@@ -7,6 +7,7 @@
#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>
......@@ -112,6 +113,15 @@ public:
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.
......
......@@ -68,6 +68,13 @@ CfgSubnets4::getByPrefix(const std::string& subnet_text) const {
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 {
......
......@@ -91,6 +91,14 @@ public:
/// 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
......
......@@ -4,8 +4,13 @@
// 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 {
......@@ -112,6 +117,21 @@ Network4::toElement() const {
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();
......
......@@ -337,6 +337,12 @@ public:
/// @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
......
......@@ -7,6 +7,7 @@
#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>
......@@ -30,6 +31,9 @@ struct SharedNetworkRandomAccessIndexTag { };
/// @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.
......@@ -149,7 +153,15 @@ typedef boost::multi_index_container<
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;
......
......@@ -613,13 +613,20 @@ struct SubnetSubnetIdIndexTag { };
/// @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
......@@ -632,12 +639,9 @@ struct SubnetPrefixIndexTag { };
/// @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
......@@ -654,21 +658,61 @@ using SubnetCollection = boost::multi_index_container<
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;
//@}
......
......@@ -6,6 +6,10 @@
#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>
......@@ -856,4 +860,23 @@ TEST(CfgSubnets4Test, getSubnet) {
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
......@@ -7,13 +7,19 @@
#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>
......@@ -497,6 +503,28 @@ TEST(Subnet4Test, PoolType) {
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) {
......
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