Commit ffdc326d authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[1555] Implemented configuration parameter to select interfaces for DHCPv4

parent dd01c785
......@@ -347,7 +347,7 @@ DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
(config_id.compare("rebind-timer") == 0)) {
parser = new Uint32Parser(config_id,
globalContext()->uint32_values_);
} else if (config_id.compare("interface") == 0) {
} else if (config_id.compare("interfaces") == 0) {
parser = new InterfaceListConfigParser(config_id);
} else if (config_id.compare("subnet4") == 0) {
parser = new Subnets4ListConfigParser(config_id);
......@@ -397,6 +397,7 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
ParserCollection independent_parsers;
ParserPtr subnet_parser;
ParserPtr option_parser;
ParserPtr iface_parser;
// The subnet parsers implement data inheritance by directly
// accessing global storage. For this reason the global data
......@@ -428,6 +429,11 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
subnet_parser = parser;
} else if (config_pair.first == "option-data") {
option_parser = parser;
} else if (config_pair.first == "interfaces") {
// The interface parser is independent from any other
// parser and can be run here before any other parsers.
iface_parser = parser;
parser->build(config_pair.second);
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
......@@ -483,6 +489,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
if (subnet_parser) {
subnet_parser->commit();
}
if (iface_parser) {
iface_parser->commit();
}
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
......
......@@ -3,7 +3,7 @@
"module_name": "Dhcp4",
"module_description": "DHCPv4 server daemon",
"config_data": [
{ "item_name": "interface",
{ "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
"item_default": [ "all" ],
......
......@@ -51,6 +51,7 @@ public:
// deal with sockets here, just check if configuration handling
// is sane.
srv_.reset(new Dhcpv4Srv(0));
CfgMgr::instance().deleteActiveIfaces();
}
// Checks if global parameter of name have expected_value
......@@ -138,7 +139,7 @@ public:
/// describing an option.
std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ],"
stream << "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
......@@ -245,7 +246,7 @@ public:
void resetConfiguration() {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
......@@ -322,7 +323,7 @@ TEST_F(Dhcp4ParserTest, emptySubnet) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
Element::fromJSON("{ \"interface\": [ \"all\" ],"
Element::fromJSON("{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ ], "
......@@ -342,7 +343,7 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
......@@ -372,7 +373,7 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
......@@ -403,7 +404,7 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
......@@ -427,7 +428,7 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
......@@ -949,7 +950,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
// configuration does not include options configuration.
TEST_F(Dhcp4ParserTest, optionDataDefaults) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
......@@ -1022,7 +1023,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
// The definition is not required for the option that
// belongs to the 'dhcp4' option space as it is the
// standard option.
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
......@@ -1100,7 +1101,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
// at the very end (when all other parameters are configured).
// Starting stage 1. Configure sub-options and their definitions.
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
......@@ -1149,7 +1150,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
// the configuration from the stage 2 is repeated because BIND
// configuration manager sends whole configuration for the lists
// where at least one element is being modified or added.
config = "{ \"interface\": [ \"all\" ],"
config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
......@@ -1245,7 +1246,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
// option setting.
TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
......@@ -1317,7 +1318,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
// for multiple subnets.
TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
......@@ -1597,7 +1598,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
// In the first stahe we create definitions of suboptions
// that we will add to the base option.
// Let's create some dummy options: foo and foo2.
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
......@@ -1650,7 +1651,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
// We add our dummy options to this option space and thus
// they should be included as sub-options in the 'vendor-opts'
// option.
config = "{ \"interface\": [ \"all\" ],"
config = "{ \"interfaces\": [ \"all\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
......@@ -1749,5 +1750,69 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
EXPECT_FALSE(desc.option->getOption(3));
}
// This test verifies that it is possible to select subset of interfaces
// on which server should listen.
TEST_F(Dhcp4ParserTest, selectedInterfaces) {
ConstElementPtr x;
string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000 }";
};
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Make sure the config manager is clean and there is no hanging
// interface configuration.
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
// Apply configuration.
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// eth0 and eth1 were explicitly selected. eth2 was not.
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
EXPECT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
}
// This test verifies that it is possible to configure the server in such a way
// that it listens on all interfaces.
TEST_F(Dhcp4ParserTest, allInterfaces) {
ConstElementPtr x;
// This configuration specifies two interfaces on which server should listen
// but it also includes keyword 'all'. This keyword switches server into the
// mode when it listens on all interfaces regardless of what interface names
// were specified in the "interfaces" parameter.
string config = "{ \"interfaces\": [ \"eth0\",\"all\",\"eth1\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
ConstElementPtr status;
// Make sure there is no old configuration.
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
// Apply configuration.
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
// All interfaces should be now active.
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
}
}
......@@ -3,7 +3,7 @@
"module_name": "Dhcp6",
"module_description": "DHCPv6 server daemon",
"config_data": [
{ "item_name": "interface",
{ "item_name": "interfaces",
"item_type": "list",
"item_optional": false,
"item_default": [ "all" ],
......
......@@ -266,9 +266,61 @@ std::string CfgMgr::getDataDir() {
return (datadir_);
}
void
CfgMgr::addActiveIface(const std::string& iface) {
if (isIfaceListedActive(iface)) {
isc_throw(DuplicateListeningIface,
"attempt to add duplicate interface '" << iface << "'"
" to the set of interfaces on which server listens");
}
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_IFACE)
.arg(iface);
active_ifaces_.push_back(iface);
}
void
CfgMgr::activateAllIfaces() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE);
all_ifaces_active_ = true;
}
void
CfgMgr::deleteActiveIfaces() {
LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES);
active_ifaces_.clear();
all_ifaces_active_ = false;
}
bool
CfgMgr::isActiveIface(const std::string& iface) const {
// @todo Verify that the interface with the specified name is
// present in the system.
// If all interfaces are marked active, there is no need to check that
// the name of this interface has been explicitly listed.
if (all_ifaces_active_) {
return (true);
}
return (isIfaceListedActive(iface));
}
bool
CfgMgr::isIfaceListedActive(const std::string& iface) const {
for (ActiveIfacesCollection::const_iterator it = active_ifaces_.begin();
it != active_ifaces_.end(); ++it) {
if (iface == *it) {
return (true);
}
}
return (false);
}
CfgMgr::CfgMgr()
:datadir_(DHCP_DATA_DIR) {
: datadir_(DHCP_DATA_DIR),
all_ifaces_active_(false) {
// DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
// Note: the definition of DHCP_DATA_DIR needs to include quotation marks
// See AM_CPPFLAGS definition in Makefile.am
......
......@@ -30,10 +30,21 @@
#include <map>
#include <string>
#include <vector>
#include <list>
namespace isc {
namespace dhcp {
/// @brief Exception thrown when the same interface has been specified twice.
///
/// In particular, this exception is thrown when adding interface to the set
/// of interfaces on which server is supposed to listen.
class DuplicateListeningIface : public Exception {
public:
DuplicateListeningIface(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Configuration Manager
///
......@@ -237,6 +248,36 @@ public:
/// @return data directory
std::string getDataDir();
/// @brief Adds the name of the interface to the set of interfaces on which
/// server should listen.
///
/// @param iface A name of the interface being added to the listening set.
void addActiveIface(const std::string& iface);
/// @brief Configures the server to listen on all interfaces.
///
/// This function configrues the server to listen on all available
/// interfaces regardless of the interfaces specified with
/// @c CfgMgr::addListeningInterface.
void activateAllIfaces();
/// @brief Clear the collection of the interfaces that server is configured
/// to use to listen for incoming requests.
///
/// Apart from clearing the list of interfaces specified with
/// @c CfgMgr::addListeningInterface, it also disables listening on all
/// interfaces if it has been enabled using @c CfgMgr::listenAllInterfaces.
void deleteActiveIfaces();
/// @brief Check if server is configured to listen on the interface which
/// name is specified as an argument to this function.
///
/// @param iface A name of the interface to be checked.
///
/// @return true if the specified interface belongs to the set of the
/// interfaces on which server is configured to listen.
bool isActiveIface(const std::string& iface) const;
protected:
/// @brief Protected constructor.
......@@ -268,6 +309,20 @@ protected:
private:
/// @brief Checks if the specified interface is listed as active.
///
/// This function searches for the specified interface name on the list of
/// active interfaces: @c CfgMgr::active_ifaces_. It does not take into
/// account @c CfgMgr::all_ifaces_active_ flag. If this flag is set to true
/// but the specified interface does not belong to
/// @c CfgMgr::active_ifaces_, it will return false.
///
/// @param iface interface name.
///
/// @return true if specified interface belongs to
/// @c CfgMgr::active_ifaces_.
bool isIfaceListedActive(const std::string& iface) const;
/// @brief A collection of option definitions.
///
/// A collection of option definitions that can be accessed
......@@ -283,6 +338,16 @@ private:
/// @brief directory where data files (e.g. server-id) are stored
std::string datadir_;
/// @name A collection of interface names on which server listens.
//@{
typedef std::list<std::string> ActiveIfacesCollection;
std::list<std::string> active_ifaces_;
//@}
/// A flag which indicates that server should listen on all available
/// interfaces.
bool all_ifaces_active_;
};
} // namespace isc::dhcp
......
......@@ -33,6 +33,10 @@ using namespace isc::data;
namespace isc {
namespace dhcp {
namespace {
const char* ALL_IFACES_KEYWORD = "all";
}
// *********************** ParserContext *************************
ParserContext::ParserContext(Option::Universe universe):
......@@ -140,33 +144,83 @@ template <> void ValueParser<std::string>::build(ConstElementPtr value) {
// ******************** InterfaceListConfigParser *************************
InterfaceListConfigParser::InterfaceListConfigParser(const std::string&
param_name) {
if (param_name != "interface") {
InterfaceListConfigParser::
InterfaceListConfigParser(const std::string& param_name)
: activate_all_(false),
param_name_(param_name) {
if (param_name_ != "interfaces") {
isc_throw(BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
void
void
InterfaceListConfigParser::build(ConstElementPtr value) {
// First, we iterate over all specified entries and add it to the
// local container so as we can do some basic validation, e.g. eliminate
// duplicates.
BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
interfaces_.push_back(iface->str());
std::string iface_name = iface->stringValue();
if (iface_name != ALL_IFACES_KEYWORD) {
// Let's eliminate duplicates. We could possibly allow duplicates,
// but if someone specified duplicated interface name it is likely
// that he mistyped the configuration. Failing here should draw his
// attention.
if (isIfaceAdded(iface_name)) {
isc_throw(isc::dhcp::DhcpConfigError, "duplicate interface"
<< " name '" << iface_name << "' specified in '"
<< param_name_ << "' configuration parameter");
}
// @todo check that this interface exists in the system!
// The IfaceMgr exposes mechanisms to check this.
// Add the interface name if ok.
interfaces_.push_back(iface_name);
} else {
activate_all_ = true;
}
}
}
void
void
InterfaceListConfigParser::commit() {
/// @todo: Implement per interface listening. Currently always listening
/// on all interfaces.
CfgMgr& cfg_mgr = CfgMgr::instance();
// Remove active interfaces and clear a flag which marks all interfaces
// active
cfg_mgr.deleteActiveIfaces();
if (activate_all_) {
// Activate all interfaces. There is not need to add their names
// explicitly.
cfg_mgr.activateAllIfaces();
} else {
// Explicitly add names of the interfaces which server should listen on.
BOOST_FOREACH(std::string iface, interfaces_) {
cfg_mgr.addActiveIface(iface);
}
}
}
bool
InterfaceListConfigParser::isIfaceAdded(const std::string& iface) const {
for (IfaceListStorage::const_iterator it = interfaces_.begin();
it != interfaces_.end(); ++it) {
if (iface == *it) {
return (true);
}
}
return (false);
}
// **************************** OptionDataParser *************************
OptionDataParser::OptionDataParser(const std::string&, OptionStoragePtr options,
ParserContextPtr global_context)
: boolean_values_(new BooleanStorage()),
string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
options_(options), option_descriptor_(false),
: boolean_values_(new BooleanStorage()),
string_values_(new StringStorage()), uint32_values_(new Uint32Storage()),
options_(options), option_descriptor_(false),
global_context_(global_context) {
if (!options_) {
isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
......
......@@ -302,8 +302,23 @@ public:
virtual void commit();
private:
/// @brief Check that specified interface exists in
/// @c InterfaceListConfigParser::interfaces_.
///
/// @param iface A name of the interface.
///
/// @return true if specified interface name was found.
bool isIfaceAdded(const std::string& iface) const;
/// contains list of network interfaces
std::vector<std::string> interfaces_;
typedef std::list<std::string> IfaceListStorage;
IfaceListStorage interfaces_;
// Should server listen on all interfaces.
bool activate_all_;
// Parsed parameter name
std::string param_name_;
};
......
......@@ -54,6 +54,10 @@ consider reducing the lease lifetime. In this way, addresses allocated
to clients that are no longer active on the network will become available
available sooner.
% DHCPSRV_CFGMGR_ADD_IFACE adding listening interface %1
A debug message issued when new interface is being added to the collection of
interfaces on which server listens to DHCP messages.
% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
A debug message reported when the DHCP configuration manager is adding the
specified IPv4 subnet to its database.
......@@ -62,6 +66,16 @@ specified IPv4 subnet to its database.
A debug message reported when the DHCP configuration manager is adding the
specified IPv6 subnet to its database.
% DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE enabling listening on all interfaces
A debug message issued when server is being configured to listen on all
interfaces.
% DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES stop listening on all interfaces
A debug message issued when configuration manager clears the internal list
of active interfaces. This doesn't prevent the server from listening to
the DHCP traffic through open sockets, but will rather be used by Interface
Manager to select active interfaces when sockets are re-opened.
% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
A debug message noting that the DHCP configuration manager has deleted all IPv4
subnets in its database.
......
......@@ -165,6 +165,7 @@ public:
CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().deleteOptionDefs();
CfgMgr::instance().deleteActiveIfaces();
}
/// @brief generates interface-id option based on provided text
......@@ -573,6 +574,50 @@ TEST_F(CfgMgrTest, optionSpace6) {
// @todo decide if a duplicate vendor space is allowed.
}
// This test verifies that it is possible to specify interfaces that server
// should listen on.
TEST_F(CfgMgrTest, addActiveIface) {
CfgMgr& cfg_mgr = CfgMgr::instance();
cfg_mgr.addActiveIface("eth0");
cfg_mgr.addActiveIface("eth1");
EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
cfg_mgr.deleteActiveIfaces();
EXPECT_FALSE(cfg_mgr.isActiveIface("eth0"));
EXPECT_FALSE(cfg_mgr.isActiveIface("eth1"));
EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
}
// This test verifies that it is possible to set the flag which configures the
// server to listen on all interfaces.