2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

[master] Merge branch 'trac1555'

Conflicts:
	src/bin/dhcp6/dhcp6_messages.mes
	src/bin/dhcp6/dhcp6_srv.cc
	src/bin/dhcp6/dhcp6_srv.h
This commit is contained in:
Marcin Siodelski
2013-07-22 08:39:06 +02:00
28 changed files with 1154 additions and 349 deletions

View File

@@ -3599,7 +3599,7 @@ $</screen>
will be available. It will look similar to this:
<screen>
&gt; <userinput>config show Dhcp4</userinput>
Dhcp4/interface/ list (default)
Dhcp4/interfaces/ list (default)
Dhcp4/renew-timer 1000 integer (default)
Dhcp4/rebind-timer 2000 integer (default)
Dhcp4/valid-lifetime 4000 integer (default)
@@ -3686,6 +3686,60 @@ Dhcp4/subnet4 [] list (default)
</note>
</section>
<section id="dhcp4-interface-selection">
<title>Interface selection</title>
<para>
When DHCPv4 server starts up, by default it will listen to the DHCP
traffic and respond to it on all interfaces detected during startup.
However, in many cases it is desired to configure the server to listen and
respond on selected interfaces only. The sample commands in this section
show how to make interface selection using bindctl.
</para>
<para>
The default configuration can be presented with the following command:
<screen>
&gt; <userinput>config show Dhcp4/interfaces</userinput>
<userinput>Dhcp4/interfaces[0] "*" string</userinput></screen>
An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
</para>
<para>
In order to override the default configuration, the existing entry can be replaced
with the actual interface name:
<screen>
&gt; <userinput>config set Dhcp4/interfaces[0] eth1</userinput>
&gt; <userinput>config commit</userinput></screen>
Other interface names can be added on one-by-one basis:
<screen>
&gt; <userinput>config add Dhcp4/interfaces eth2</userinput>
&gt; <userinput>config commit</userinput></screen>
Configuration will now contain two interfaces which can be presented as follows:
<screen>
&gt; <userinput>config show Dhcp4/interfaces</userinput>
<userinput>Dhcp4/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp4/interfaces[1] "eth2" string</userinput></screen>
When configuration gets committed, the server will start to listen on
eth1 and eth2 interfaces only.
</para>
<para>
It is possible to use wildcard interface name (asterisk) concurrently with explicit
interface names:
<screen>
&gt; <userinput>config add Dhcp4/interfaces *</userinput>
&gt; <userinput>config commit</userinput></screen>
This will result in the following configuration:
<screen>
&gt; <userinput>config show Dhcp4/interfaces</userinput>
<userinput>Dhcp4/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp4/interfaces[1] "eth2" string</userinput>
<userinput>Dhcp4/interfaces[2] "*" string</userinput></screen>
The presence of the wildcard name implies that server will listen on all interfaces.
In order to fall back to the previous configuration when server listens on eth1 and eth2:
<screen>
&gt; <userinput>config remove Dhcp4/interfaces[2]</userinput>
&gt; <userinput>config commit</userinput></screen>
</para>
</section>
<section id="dhcp4-address-config">
<title>Configuration of Address Pools</title>
<para>
@@ -4366,7 +4420,7 @@ Dhcp4/renew-timer 1000 integer (default)
will be available. It will look similar to this:
<screen>
&gt; <userinput>config show Dhcp6</userinput>
Dhcp6/interface/ list (default)
Dhcp6/interfaces/ list (default)
Dhcp6/renew-timer 1000 integer (default)
Dhcp6/rebind-timer 2000 integer (default)
Dhcp6/preferred-lifetime 3000 integer (default)
@@ -4459,6 +4513,59 @@ Dhcp6/subnet6/ list
</note>
</section>
<section id="dhcp6-interface-selection">
<title>Interface selection</title>
<para>
When DHCPv6 server starts up, by default it will listen to the DHCP
traffic and respond to it on all interfaces detected during startup.
However, in many cases it is desired to configure the server to listen and
respond on selected interfaces only. The sample commands in this section
show how to make interface selection using bindctl.
</para>
<para>
The default configuration can be presented with the following command:
<screen>
&gt; <userinput>config show Dhcp6/interfaces</userinput>
<userinput>Dhcp6/interfaces[0] "*" string</userinput></screen>
An asterisk sign plays a role of the wildcard and means "listen on all interfaces".
</para>
<para>
In order to override the default configuration, the existing entry can be replaced
with the actual interface name:
<screen>
&gt; <userinput>config set Dhcp6/interfaces[0] eth1</userinput>
&gt; <userinput>config commit</userinput></screen>
Other interface names can be added on one-by-one basis:
<screen>
&gt; <userinput>config add Dhcp6/interfaces eth2</userinput>
&gt; <userinput>config commit</userinput></screen>
Configuration will now contain two interfaces which can be presented as follows:
<screen>
&gt; <userinput>config show Dhcp6/interfaces</userinput>
<userinput>Dhcp6/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp6/interfaces[1] "eth2" string</userinput></screen>
When configuration gets committed, the server will start to listen on
eth1 and eth2 interfaces only.
</para>
<para>
It is possible to use wildcard interface name (asterisk) concurrently with explicit
interface names:
<screen>
&gt; <userinput>config add Dhcp6/interfaces *</userinput>
&gt; <userinput>config commit</userinput></screen>
This will result in the following configuration:
<screen>
&gt; <userinput>config show Dhcp6/interfaces</userinput>
<userinput>Dhcp6/interfaces[0] "eth1" string</userinput>
<userinput>Dhcp6/interfaces[1] "eth2" string</userinput>
<userinput>Dhcp6/interfaces[2] "*" string</userinput></screen>
The presence of the wildcard name implies that server will listen on all interfaces.
In order to fall back to the previous configuration when server listens on eth1 and eth2:
<screen>
&gt; <userinput>config remove Dhcp6/interfaces[2]</userinput>
&gt; <userinput>config commit</userinput></screen>
</para>
</section>
<section>
<title>Subnet and Address Pool</title>

View File

@@ -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());

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -20,17 +20,17 @@
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
#include <dhcp4/config_parser.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
#include <cassert>
#include <iostream>
#include <sstream>
using namespace isc::asiolink;
using namespace isc::cc;
@@ -101,7 +101,27 @@ ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
}
// Configure the server.
return (configureDhcp4Server(*server_, merged_config));
ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
// Check that configuration was successful. If not, do not reopen sockets.
int rcode = 0;
parseAnswer(rcode, answer);
if (rcode != 0) {
return (answer);
}
// Configuration may change active interfaces. Therefore, we have to reopen
// sockets according to new configuration. This operation is not exception
// safe and we really don't want to emit exceptions to the callback caller.
// Instead, catch an exception and create appropriate answer.
try {
server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
} catch (std::exception& ex) {
std::ostringstream err;
err << "failed to open sockets after server reconfiguration: " << ex.what();
answer = isc::config::createAnswer(1, err.str());
}
return (answer);
}
ConstElementPtr
@@ -172,8 +192,13 @@ void ControlledDhcpv4Srv::establishSession() {
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
// Configuration may disable or enable interfaces so we have to
// reopen sockets according to new configuration.
openActiveSockets(getPort(), useBroadcast());
} catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
}
/// Integrate the asynchronous I/O model of BIND 10 configuration
@@ -228,6 +253,5 @@ ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
}
}
};
};

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -130,6 +130,7 @@ protected:
/// when there is a new command or configuration sent over msgq.
static void sessionReader(void);
/// @brief IOService object, used for all ASIO operations.
isc::asiolink::IOService io_service_;

View File

@@ -3,16 +3,16 @@
"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" ],
"item_default": [ "*" ],
"list_item_spec":
{
"item_name": "interface_name",
"item_type": "string",
"item_optional": false,
"item_default": "all"
"item_default": "*"
}
} ,

View File

@@ -14,6 +14,11 @@
$NAMESPACE isc::dhcp
% DHCP4_ACTIVATE_INTERFACE activating interface %1
This message is printed when DHCPv4 server enabled an interface to be used
to receive DHCPv4 traffic. IPv4 socket on this interface will be opened once
Interface Manager starts up procedure of opening sockets.
% DHCP4_CCSESSION_STARTED control channel session started on socket %1
A debug message issued during startup after the IPv4 DHCP server has
successfully established a session with the BIND 10 control channel.
@@ -60,6 +65,11 @@ This informational message is printed every time DHCPv4 server is started
and gives both the type and name of the database being used to store
lease and other information.
% DHCP4_DEACTIVATE_INTERFACE deactivate interface %1
This message is printed when DHCPv4 server disables an interface from being
used to receive DHCPv4 traffic. Sockets on this interface will not be opened
by the Interface Manager until interface is enabled.
% DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
This debug message indicates that the server successfully advertised
a lease. It is up to the client to choose one server out of othe advertised
@@ -82,6 +92,11 @@ specified client after receiving a REQUEST message from it. There are many
possible reasons for such a failure. Additional messages will indicate the
reason.
% DHCP4_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
This warning message is issued when current server configuration specifies
no interfaces that server should listen on, or specified interfaces are not
configured to receive the traffic.
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.

View File

@@ -58,7 +58,8 @@ static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
// grants those options and a single, fixed, hardcoded lease.
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
const bool direct_response_desired) {
const bool direct_response_desired)
: port_(port), use_bcast_(use_bcast) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
@@ -73,7 +74,7 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
if (port) {
// open sockets only if port is non-zero. Port 0 is used
// for non-socket related testing.
IfaceMgr::instance().openSockets4(port, use_bcast);
IfaceMgr::instance().openSockets4(port_, use_bcast_);
}
string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
@@ -820,5 +821,49 @@ Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
}
}
void
Dhcpv4Srv::openActiveSockets(const uint16_t port,
const bool use_bcast) {
IfaceMgr::instance().closeSockets();
// Get the reference to the collection of interfaces. This reference should
// be valid as long as the program is run because IfaceMgr is a singleton.
// Therefore we can safely iterate over instances of all interfaces and
// modify their flags. Here we modify flags which indicate whether socket
// should be open for a particular interface or not.
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
if (iface_ptr == NULL) {
isc_throw(isc::Unexpected, "Interface Manager returned NULL"
<< " instance of the interface when DHCPv4 server was"
<< " trying to reopen sockets after reconfiguration");
}
if (CfgMgr::instance().isActiveIface(iface->getName())) {
iface_ptr->inactive4_ = false;
LOG_INFO(dhcp4_logger, DHCP4_ACTIVATE_INTERFACE)
.arg(iface->getFullName());
} else {
// For deactivating interface, it should be sufficient to log it
// on the debug level because it is more useful to know what
// interface is activated which is logged on the info level.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC,
DHCP4_DEACTIVATE_INTERFACE).arg(iface->getName());
iface_ptr->inactive4_ = true;
}
}
// Let's reopen active sockets. openSockets4 will check internally whether
// sockets are marked active or inactive.
// @todo Optimization: we should not reopen all sockets but rather select
// those that have been affected by the new configuration.
if (!IfaceMgr::instance().openSockets4(port, use_bcast)) {
LOG_WARN(dhcp4_logger, DHCP4_NO_SOCKETS_OPEN);
}
}
} // namespace dhcp
} // namespace isc

View File

@@ -113,6 +113,46 @@ public:
/// be freed by the caller.
static const char* serverReceivedPacketName(uint8_t type);
///
/// @name Public accessors returning values required to (re)open sockets.
///
/// These accessors must be public because sockets are reopened from the
/// static configuration callback handler. This callback handler invokes
/// @c ControlledDhcpv4Srv::openActiveSockets which requires parameters
/// which has to be retrieved from the @c ControlledDhcpv4Srv object.
/// They are retrieved using these public functions
//@{
///
/// @brief Get UDP port on which server should listen.
///
/// Typically, server listens on UDP port number 67. Other ports are used
/// for testing purposes only.
///
/// @return UDP port on which server should listen.
uint16_t getPort() const {
return (port_);
}
/// @brief Return bool value indicating that broadcast flags should be set
/// on sockets.
///
/// @return A bool value indicating that broadcast should be used (if true).
bool useBroadcast() const {
return (use_bcast_);
}
//@}
/// @brief Open sockets which are marked as active in @c CfgMgr.
///
/// This function reopens sockets according to the current settings in the
/// Configuration Manager. It holds the list of the interfaces which server
/// should listen on. This function will open sockets on these interfaces
/// only. This function is not exception safe.
///
/// @param port UDP port on which server should listen.
/// @param use_bcast should broadcast flags be set on the sockets.
static void openActiveSockets(const uint16_t port, const bool use_bcast);
protected:
/// @brief verifies if specified packet meets RFC requirements
@@ -310,6 +350,9 @@ private:
/// during normal operation (e.g. to use different allocators)
boost::shared_ptr<AllocEngine> alloc_engine_;
uint16_t port_; ///< UDP port number on which server listens.
bool use_bcast_; ///< Should broadcast be enabled on sockets (if true).
};
}; // namespace isc::dhcp

View File

@@ -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\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -245,7 +246,7 @@ public:
void resetConfiguration() {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ ], "
@@ -342,7 +343,7 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -372,7 +373,7 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -403,7 +404,7 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
@@ -427,7 +428,7 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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\": [ \"*\" ],"
"\"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 asterisk. The asterisk 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\", \"*\", \"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"));
}
}

View File

@@ -413,7 +413,7 @@ DhcpConfigParser* createGlobal6DhcpConfigParser(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("subnet6") == 0) {
parser = new Subnets6ListConfigParser(config_id);
@@ -463,6 +463,7 @@ configureDhcp6Server(Dhcpv6Srv&, 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
@@ -495,6 +496,11 @@ configureDhcp6Server(Dhcpv6Srv&, 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 other parsers.
parser->build(config_pair.second);
iface_parser = parser;
} else {
// Those parsers should be started before other
// parsers so we can call build straight away.
@@ -548,6 +554,10 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (subnet_parser) {
subnet_parser->commit();
}
if (iface_parser) {
iface_parser->commit();
}
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -20,6 +20,7 @@
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
@@ -100,7 +101,27 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
}
// Configure the server.
return (configureDhcp6Server(*server_, merged_config));
ConstElementPtr answer = configureDhcp6Server(*server_, merged_config);
// Check that configuration was successful. If not, do not reopen sockets.
int rcode = 0;
parseAnswer(rcode, answer);
if (rcode != 0) {
return (answer);
}
// Configuration may change active interfaces. Therefore, we have to reopen
// sockets according to new configuration. This operation is not exception
// safe and we really don't want to emit exceptions to the callback caller.
// Instead, catch an exception and create appropriate answer.
try {
server_->openActiveSockets(server_->getPort());
} catch (const std::exception& ex) {
std::ostringstream err;
err << "failed to open sockets after server reconfiguration: " << ex.what();
answer = isc::config::createAnswer(1, err.str());
}
return (answer);
}
ConstElementPtr
@@ -172,8 +193,13 @@ void ControlledDhcpv6Srv::establishSession() {
try {
// Pull the full configuration out from the session.
configureDhcp6Server(*this, config_session_->getFullConfig());
// Configuration may disable or enable interfaces so we have to
// reopen sockets according to new configuration.
openActiveSockets(getPort());
} catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
}
/// Integrate the asynchronous I/O model of BIND 10 configuration
@@ -228,6 +254,5 @@ ControlledDhcpv6Srv::execDhcpv6ServerCommand(const std::string& command_id,
}
}
};
};

View File

@@ -3,16 +3,16 @@
"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" ],
"item_default": [ "*" ],
"list_item_spec":
{
"item_name": "interface_name",
"item_type": "string",
"item_optional": false,
"item_default": "all"
"item_default": "*"
}
} ,

View File

@@ -14,6 +14,11 @@
$NAMESPACE isc::dhcp
% DHCP6_ACTIVATE_INTERFACE activating interface %1
This message is printed when DHCPv6 server enabled an interface to be used
to receive DHCPv6 traffic. IPv6 socket on this interface will be opened once
Interface Manager starts up procedure of opening sockets.
% DHCP6_CCSESSION_STARTED control channel session started on socket %1
A debug message issued during startup after the IPv6 DHCP server has
successfully established a session with the BIND 10 control channel.
@@ -65,6 +70,11 @@ This informational message is printed every time the IPv6 DHCP server
is started. It indicates what database backend type is being to store
lease and other information.
% DHCP6_DEACTIVATE_INTERFACE deactivate interface %1
This message is printed when DHCPv6 server disables an interface from being
used to receive DHCPv6 traffic. Sockets on this interface will not be opened
by the Interface Manager until interface is enabled.
% DHCP6_HOOK_PACKET_RCVD_SKIP received DHCPv6 packet was dropped, because a callout set skip flag.
This debug message is printed when a callout installed on pkt6_received
hook point sets skip flag. For this particular hook point, the setting
@@ -120,6 +130,11 @@ IPv6 DHCP server but it is not running.
During startup the IPv6 DHCP server failed to detect any network
interfaces and is therefore shutting down.
% DHCP6_NO_SOCKETS_OPEN no interface configured to listen to DHCP traffic
This warning message is issued when current server configuration specifies
no interfaces that server should listen on, or specified interfaces are not
configured to receive the traffic.
% DHCP6_OPEN_SOCKET opening sockets on port %1
A debug message issued during startup, this indicates that the IPv6 DHCP
server is about to open sockets on the specified port.

View File

@@ -95,7 +95,7 @@ static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
:alloc_engine_(), serverid_(), shutdown_(true), hook_index_pkt6_receive_(-1),
hook_index_subnet6_select_(-1), hook_index_pkt6_send_(-1)
hook_index_subnet6_select_(-1), hook_index_pkt6_send_(-1), port_(port)
{
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET).arg(port);
@@ -111,7 +111,7 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port)
LOG_ERROR(dhcp6_logger, DHCP6_NO_INTERFACES);
return;
}
IfaceMgr::instance().openSockets6(port);
IfaceMgr::instance().openSockets6(port_);
}
string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
@@ -1255,5 +1255,47 @@ isc::hooks::CalloutHandlePtr Dhcpv6Srv::getCalloutHandle(const Pkt6Ptr& pkt) {
return (callout_handle);
}
void
Dhcpv6Srv::openActiveSockets(const uint16_t port) {
IfaceMgr::instance().closeSockets();
// Get the reference to the collection of interfaces. This reference should be
// valid as long as the program is run because IfaceMgr is a singleton.
// Therefore we can safely iterate over instances of all interfaces and modify
// their flags. Here we modify flags which indicate wheter socket should be
// open for a particular interface or not.
const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
iface != ifaces.end(); ++iface) {
Iface* iface_ptr = IfaceMgr::instance().getIface(iface->getName());
if (iface_ptr == NULL) {
isc_throw(isc::Unexpected, "Interface Manager returned NULL"
<< " instance of the interface when DHCPv6 server was"
<< " trying to reopen sockets after reconfiguration");
}
if (CfgMgr::instance().isActiveIface(iface->getName())) {
iface_ptr->inactive4_ = false;
LOG_INFO(dhcp6_logger, DHCP6_ACTIVATE_INTERFACE)
.arg(iface->getFullName());
} else {
// For deactivating interface, it should be sufficient to log it
// on the debug level because it is more useful to know what
// interface is activated which is logged on the info level.
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC,
DHCP6_DEACTIVATE_INTERFACE).arg(iface->getName());
iface_ptr->inactive6_ = true;
}
}
// Let's reopen active sockets. openSockets6 will check internally whether
// sockets are marked active or inactive.
// @todo Optimization: we should not reopen all sockets but rather select
// those that have been affected by the new configuration.
if (!IfaceMgr::instance().openSockets6(port)) {
LOG_WARN(dhcp6_logger, DHCP6_NO_SOCKETS_OPEN);
}
}
};
};

View File

@@ -88,6 +88,32 @@ public:
/// @brief Instructs the server to shut down.
void shutdown();
/// @brief Get UDP port on which server should listen.
///
/// Typically, server listens on UDP port 547. Other ports are only
/// used for testing purposes.
///
/// This accessor must be public because sockets are reopened from the
/// static configuration callback handler. This callback handler invokes
/// @c ControlledDhcpv4Srv::openActiveSockets which requires port parameter
/// which has to be retrieved from the @c ControlledDhcpv4Srv object.
/// They are retrieved using this public function.
///
/// @return UDP port on which server should listen.
uint16_t getPort() const {
return (port_);
}
/// @brief Open sockets which are marked as active in @c CfgMgr.
///
/// This function reopens sockets according to the current settings in the
/// Configuration Manager. It holds the list of the interfaces which server
/// should listen on. This function will open sockets on these interfaces
/// only. This function is not exception safe.
///
/// @param port UDP port on which server should listen.
static void openActiveSockets(const uint16_t port);
protected:
/// @brief verifies if specified packet meets RFC requirements
@@ -361,6 +387,9 @@ private:
int hook_index_pkt6_receive_;
int hook_index_subnet6_select_;
int hook_index_pkt6_send_;
/// UDP port number on which server listens.
uint16_t port_;
};
}; // namespace isc::dhcp

View File

@@ -66,11 +66,12 @@ public:
<< " while the test assumes that it doesn't, to execute"
<< " some negative scenarios. Can't continue this test.";
}
// Reset configuration for each test.
resetConfiguration();
}
~Dhcp6ParserTest() {
// Reset configuration database after each test.
resetConfiguration();
};
// Checks if config_result (result of DHCP server configuration) has
@@ -133,7 +134,7 @@ public:
std::string>& params)
{
std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ],"
stream << "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -173,13 +174,13 @@ public:
///
/// This function resets configuration data base by
/// removing all subnets and option-data. Reset must
/// be performed after each test to make sure that
/// be performed before each test to make sure that
/// contents of the database do not affect result of
/// subsequent tests.
/// the test being executed.
void resetConfiguration() {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -213,6 +214,12 @@ public:
<< " after the test. Configuration function returned"
<< " error code " << rcode_ << std::endl;
}
// The default setting is to listen on all interfaces. In order to
// properly test interface configuration we disable listening on
// all interfaces before each test and later check that this setting
// has been overriden by the configuration used in the test.
CfgMgr::instance().deleteActiveIfaces();
}
/// @brief Test invalid option parameter value.
@@ -324,7 +331,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_,
Element::fromJSON("{ \"interface\": [ \"all\" ],"
Element::fromJSON("{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -343,7 +350,7 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -377,7 +384,7 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -415,7 +422,7 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
// There should be at least one interface
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -448,7 +455,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
// There should be at least one interface
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -479,7 +486,7 @@ TEST_F(Dhcp6ParserTest, interfaceGlobal) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -549,7 +556,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
// parameter.
TEST_F(Dhcp6ParserTest, interfaceIdGlobal) {
const string config = "{ \"interface\": [ \"all\" ],"
const string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -604,7 +611,7 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
ConstElementPtr status;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -632,7 +639,7 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -1152,7 +1159,7 @@ TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
// configuration does not include options configuration.
TEST_F(Dhcp6ParserTest, optionDataDefaults) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
@@ -1234,7 +1241,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
// The definition is not required for the option that
// belongs to the 'dhcp6' option space as it is the
// standard option.
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1312,7 +1319,7 @@ TEST_F(Dhcp6ParserTest, 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\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1361,7 +1368,7 @@ TEST_F(Dhcp6ParserTest, 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\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1455,7 +1462,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
// for multiple subnets.
TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -1698,7 +1705,7 @@ TEST_F(Dhcp6ParserTest, 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\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1751,7 +1758,7 @@ TEST_F(Dhcp6ParserTest, 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\": [ \"*\" ],"
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
@@ -1850,4 +1857,77 @@ TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
EXPECT_FALSE(desc.option->getOption(112));
}
// This test verifies that it is possible to select subset of interfaces on
// which server should listen.
TEST_F(Dhcp6ParserTest, selectedInterfaces) {
// Make sure there is no garbage interface configuration in the CfgMgr.
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
ConstElementPtr status;
string config = "{ \"interfaces\": [ \"eth0\", \"eth1\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value must be 1 (values error)
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(0, rcode_);
// 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 to listen on
// all interfaces.
TEST_F(Dhcp6ParserTest, allInterfaces) {
// Make sure there is no garbage interface configuration in the CfgMgr.
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth2"));
ConstElementPtr status;
// This configuration specifies two interfaces on which server should listen
// bu 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\", \"eth1\", \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// returned value must be 1 (values error)
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
EXPECT_EQ(0, rcode_);
// All interfaces should be now active.
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth0"));
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth1"));
EXPECT_TRUE(CfgMgr::instance().isActiveIface("eth2"));
}
};

View File

@@ -632,7 +632,7 @@ TEST_F(Dhcpv6SrvTest, DUID) {
// and the requested options are actually assigned.
TEST_F(Dhcpv6SrvTest, advertiseOptions) {
ConstElementPtr x;
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -2409,7 +2409,7 @@ TEST_F(HooksDhcpv6SrvTest, subnet6_select) {
// Configure 2 subnets, both directly reachable over local interface
// (let's not complicate the matter with relays)
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
@@ -2477,7 +2477,7 @@ TEST_F(HooksDhcpv6SrvTest, subnet_select_change) {
// Configure 2 subnets, both directly reachable over local interface
// (let's not complicate the matter with relays)
string config = "{ \"interface\": [ \"all\" ],"
string config = "{ \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "

View File

@@ -51,7 +51,8 @@ IfaceMgr::instance() {
Iface::Iface(const std::string& name, int ifindex)
:name_(name), ifindex_(ifindex), mac_len_(0), hardware_type_(0),
flag_loopback_(false), flag_up_(false), flag_running_(false),
flag_multicast_(false), flag_broadcast_(false), flags_(0)
flag_multicast_(false), flag_broadcast_(false), flags_(0),
inactive4_(false), inactive6_(false)
{
memset(mac_, 0, sizeof(mac_));
}
@@ -295,7 +296,8 @@ bool IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast) {
if (iface->flag_loopback_ ||
!iface->flag_up_ ||
!iface->flag_running_) {
!iface->flag_running_ ||
iface->inactive4_) {
continue;
}
@@ -361,7 +363,8 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
if (iface->flag_loopback_ ||
!iface->flag_up_ ||
!iface->flag_running_) {
!iface->flag_running_ ||
iface->inactive6_) {
continue;
}

View File

@@ -309,6 +309,14 @@ public:
/// Interface flags (this value is as is returned by OS,
/// it may mean different things on different OSes).
uint32_t flags_;
/// Indicates that IPv4 sockets should (true) or should not (false)
/// be opened on this interface.
bool inactive4_;
/// Indicates that IPv6 sockets should (true) or should not (false)
/// be opened on this interface.
bool inactive6_;
};
/// @brief Handles network interfaces, transmission and reception.

View File

@@ -267,9 +267,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

View File

@@ -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
///
@@ -249,6 +260,43 @@ 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 Sets the flag which indicates that server is supposed to listen
/// on all available interfaces.
///
/// This function does not close or open sockets. It simply marks that
/// server should start to listen on all interfaces the next time sockets
/// are reopened. Server should examine this flag when it gets reconfigured
/// and configuration changes the interfaces that server should listen on.
void activateAllIfaces();
/// @brief Clear the collection of the interfaces that server should listen
/// on.
///
/// 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::activateAllInterfaces.
/// Likewise @c CfgMgr::activateAllIfaces, this function does not close or
/// open sockets. It marks all interfaces inactive for DHCP traffic.
/// Server should examine this new setting when it attempts to
/// reopen sockets (as a result of reconfiguration).
void deleteActiveIfaces();
/// @brief Check if specified interface should be used to listen to DHCP
/// traffic.
///
/// @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.
@@ -280,6 +328,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
@@ -295,6 +357,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

View File

@@ -33,6 +33,10 @@ using namespace isc::data;
namespace isc {
namespace dhcp {
namespace {
const char* ALL_IFACES_KEYWORD = "*";
}
// *********************** ParserContext *************************
ParserContext::ParserContext(Option::Universe universe):
@@ -140,9 +144,11 @@ 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);
}
@@ -150,15 +156,63 @@ InterfaceListConfigParser::InterfaceListConfigParser(const std::string&
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
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 *************************

View File

@@ -71,7 +71,7 @@ class ValueStorage {
/// @param name is the name of the parameter for which the data
/// value is desired.
///
/// @return The paramater's data value of type <ValueType>.
/// @return The paramater's data value of type @c ValueType.
/// @throw DhcpConfigError if the parameter is not found.
ValueType getParam(const std::string& name) const {
typename std::map<std::string, ValueType>::const_iterator param
@@ -199,7 +199,7 @@ public:
}
/// @brief Parse a given element into a value of type <ValueType>
/// @brief Parse a given element into a value of type @c ValueType
///
/// @param value a value to be parsed.
///
@@ -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_;
};
@@ -336,7 +351,7 @@ public:
/// @param global_context is a pointer to the global context which
/// stores global scope parameters, options, option defintions.
/// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
OptionDataParser(const std::string&, OptionStoragePtr options,
OptionDataParser(const std::string& dummy, OptionStoragePtr options,
ParserContextPtr global_context);
/// @brief Parses the single option data.
@@ -439,14 +454,14 @@ class OptionDataListParser : public DhcpConfigParser {
public:
/// @brief Constructor.
///
/// @param string& nominally would be param name, this is always ignored.
/// @param dummy nominally would be param name, this is always ignored.
/// @param options parsed option storage for options in this list
/// @param global_context is a pointer to the global context which
/// stores global scope parameters, options, option defintions.
/// @param optionDataParserFactory factory method for creating individual
/// option parsers
/// @throw isc::dhcp::DhcpConfigError if options or global_context are null.
OptionDataListParser(const std::string&, OptionStoragePtr options,
OptionDataListParser(const std::string& dummy, OptionStoragePtr options,
ParserContextPtr global_context,
OptionDataParserFactory *optionDataParserFactory);
@@ -498,7 +513,7 @@ public:
/// @param storage is the definition storage in which to store the parsed
/// definition upon "commit".
/// @throw isc::dhcp::DhcpConfigError if storage is null.
OptionDefParser(const std::string&, OptionDefStoragePtr storage);
OptionDefParser(const std::string& dummy, OptionDefStoragePtr storage);
/// @brief Parses an entry that describes single option definition.
///
@@ -549,7 +564,7 @@ public:
/// @param storage is the definition storage in which to store the parsed
/// definitions in this list
/// @throw isc::dhcp::DhcpConfigError if storage is null.
OptionDefListParser(const std::string&, OptionDefStoragePtr storage);
OptionDefListParser(const std::string& dummy, OptionDefStoragePtr storage);
/// @brief Parse configuration entries.
///
@@ -587,14 +602,13 @@ class PoolParser : public DhcpConfigParser {
public:
/// @brief constructor.
///
/// @param dummy first argument is ignored, all Parser constructors
/// accept string as first argument.
/// @param pools is the storage in which to store the parsed pool
/// upon "commit".
/// @throw isc::dhcp::DhcpConfigError if storage is null.
PoolParser(const std::string&, PoolStoragePtr pools);
PoolParser(const std::string& dummy, PoolStoragePtr pools);
/// @brief parses the actual list
///
@@ -614,7 +628,7 @@ protected:
///
/// @param addr is the IP prefix of the pool.
/// @param len is the prefix length.
/// @param ignored dummy parameter to provide symmetry between
/// @param ptype is the type of pool to create.
/// @return returns a PoolPtr to the new Pool object.
virtual PoolPtr poolMaker(isc::asiolink::IOAddress &addr, uint32_t len,
int32_t ptype=0) = 0;

View File

@@ -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.

View File

@@ -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.
TEST_F(CfgMgrTest, activateAllIfaces) {
CfgMgr& cfg_mgr = CfgMgr::instance();
cfg_mgr.addActiveIface("eth0");
cfg_mgr.addActiveIface("eth1");
ASSERT_TRUE(cfg_mgr.isActiveIface("eth0"));
ASSERT_TRUE(cfg_mgr.isActiveIface("eth1"));
ASSERT_FALSE(cfg_mgr.isActiveIface("eth2"));
cfg_mgr.activateAllIfaces();
EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
EXPECT_TRUE(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"));
}
// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)

View File

@@ -42,6 +42,7 @@ public:
/// @brief Constructor
///
DhcpParserTest() {
CfgMgr::instance().deleteActiveIfaces();
}
};
@@ -197,21 +198,52 @@ TEST_F(DhcpParserTest, uint32ParserTest) {
///
/// Verifies that the parser:
/// 1. Does not allow empty for storage.
/// 2. Does not allow name other than "interface"
///
/// InterfaceListParser doesn't do very much, this test will need to
/// expand once it does.
/// 2. Does not allow name other than "interfaces"
/// 3. Parses list of interfaces and adds them to CfgMgr
/// 4. Parses wildcard interface name and sets a CfgMgr flag which indicates
/// that server will listen on all interfaces.
TEST_F(DhcpParserTest, interfaceListParserTest) {
const std::string name = "interface";
const std::string name = "interfaces";
// Verify that parser constructor fails if parameter name isn't "interface"
EXPECT_THROW(InterfaceListConfigParser("bogus_name"), isc::BadValue);
InterfaceListConfigParser parser(name);
boost::scoped_ptr<InterfaceListConfigParser>
parser(new InterfaceListConfigParser(name));
ElementPtr list_element = Element::createList();
list_element->add(Element::create("eth0"));
list_element->add(Element::create("eth1"));
// Make sure there are no interfaces added yet.
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth0"));
ASSERT_FALSE(CfgMgr::instance().isActiveIface("eth1"));
// This should parse the configuration and add eth0 and eth1 to the list
// of interfaces that server should listen on.
parser->build(list_element);
parser->commit();
// Use CfgMgr instance to check if eth0 and eth1 was added, and that
// eth2 was not added.
CfgMgr& cfg_mgr = CfgMgr::instance();
EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
EXPECT_FALSE(cfg_mgr.isActiveIface("eth2"));
// Add keyword all to the configuration. This should activate all
// interfaces, including eth2, even though it has not been explicitly
// added.
list_element->add(Element::create("*"));
// Reset parser's state.
parser.reset(new InterfaceListConfigParser(name));
parser->build(list_element);
parser->commit();
EXPECT_TRUE(cfg_mgr.isActiveIface("eth0"));
EXPECT_TRUE(cfg_mgr.isActiveIface("eth1"));
EXPECT_TRUE(cfg_mgr.isActiveIface("eth2"));
}
/// @brief Test Implementation of abstract OptionDataParser class. Allows