2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 05:27:55 +00:00

[master] Merge branch 'trac3399' [Kea4 reads config from JSON file]

Conflicts:
	ChangeLog
	src/bin/dhcp6/kea_controller.cc
	src/lib/dhcpsrv/daemon.h
This commit is contained in:
Tomek Mrugalski 2014-06-02 19:44:33 +02:00
commit 83816e00c5
36 changed files with 1173 additions and 451 deletions

View File

@ -1,3 +1,10 @@
788. [func] tomek
DHCPv4 server: New parameter added to configure.ac: --with-kea-config.
It allows selecting configuration backend and accepts one of two
values: BUNDY, which uses Bundy (former BIND10) framework as Kea
0.8 did, or JSON, which reads configuration from a JSON file.
(Trac #3399, git 6e4dd3ae58c091ba0fd64c87fa8d7c268210f99b)
787. [func] marcin
DHCPv6 server: Implemented dynamic reconfiguration of the server,
triggered when the SIGHUP signal is received by the server's
@ -31,7 +38,7 @@
(Trac #3268, git bd60252e679f19b062f61926647f661ab169f21c)
783. [func]* tomek
b10-dhcp6: New parameter added to configure: --with-kea-config.
DHCPv6 server: New parameter added to configure: --with-kea-config.
It allows selecting configuration backend and accepts one of two
values: BUNDY, which uses Bundy (former BIND10 framework as Kea
0.8 did, or JSON, which reads configuration from a JSON file.

View File

@ -55,6 +55,7 @@
* - @subpage dhcpv4OptionsParse
* - @subpage dhcpv4DDNSIntegration
* - @subpage dhcpv4Classifier
* - @subpage dhcpv4ConfigBackend
* - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session

View File

@ -0,0 +1,38 @@
# This is an example configuration file for DHCPv4 server in Kea.
# It's a basic scenario with three IPv4 subnets configured. In each
# subnet, there's a smaller pool of dynamic addresses.
{ "Dhcp4":
{
# Kea is told to listen on eth0 interface only.
"interfaces": [ "eth0" ],
# We need to specify lease type. As of May 2014, three backends are supported:
# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
# any prior set up.
"lease-database": {
"type": "memfile"
},
# Addresses will be assigned with the valid lifetimes being 4000.
# Client is told to start renewing after 1000 seconds. If the server
# does not repond after 2000 seconds since the lease was granted, client
# is supposed to start REBIND procedure (emergency renewal that allows
# switching to a different server).
"valid-lifetime": 4000,
"renew-timer": 1000,
"rebind-timer": 2000,
# The following list defines subnets. Each subnet consists of at
# least subnet and pool entries.
"subnet4": [
{ "pool": [ "192.0.2.1 - 192.0.2.200" ],
"subnet": "192.0.2.0/24" },
{ "pool": [ "192.0.3.100 - 192.0.3.200" ],
"subnet": "192.0.3.0/24" },
{ "pool": [ "192.0.4.1 - 192.0.4.254" ],
"subnet": "192.0.4.0/24" } ]
}
}

View File

@ -0,0 +1,34 @@
# This is an example configuration file for the DHCPv4 server in Kea.
# It is a basic scenario with one IPv4 subnet configured. The subnet
# contains a single pool of dynamically allocated addresses.
{ "Dhcp4":
{
# Kea is told to listen on eth0 interface only.
"interfaces": [ "eth0" ],
# We need to specify lease type. As of May 2014, three backends are supported:
# memfile, mysql and pgsql. We'll just use memfile, because it doesn't require
# any prior set up.
"lease-database": {
"type": "memfile"
},
# Addresses will be assigned with valid lifetimes being 4000. Client
# is told to start renewing after 1000 seconds. If the server does not respond
# after 2000 seconds since the lease was granted, client is supposed
# to start REBIND procedure (emergency renewal that allows switching
# to a different server).
"valid-lifetime": 4000,
"renew-timer": 1000,
"rebind-timer": 2000,
# The following list defines subnets. We have only one subnet
# here.
"subnet4": [
{ "pool": [ "192.0.2.1 - 192.0.2.200" ],
"subnet": "192.0.2.0/24" } ]
}
}

View File

@ -51,10 +51,18 @@ pkglibexec_PROGRAMS = b10-dhcp4
b10_dhcp4_SOURCES = main.cc
b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
b10_dhcp4_SOURCES += config_parser.cc config_parser.h
b10_dhcp4_SOURCES += json_config_parser.cc json_config_parser.h
b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
if CONFIG_BACKEND_BUNDY
b10_dhcp4_SOURCES += bundy_controller.cc
endif
if CONFIG_BACKEND_JSON
b10_dhcp4_SOURCES += kea_controller.cc
endif
nodist_b10_dhcp4_SOURCES = dhcp4_messages.h dhcp4_messages.cc
EXTRA_DIST += dhcp4_messages.mes

View File

@ -0,0 +1,219 @@
// Copyright (C) 2012-2014 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <exceptions/exceptions.h>
#include <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::hooks;
using namespace isc::log;
using namespace isc::util;
using namespace std;
namespace isc {
namespace dhcp {
/// @brief Helper session object that represents raw connection to msgq.
isc::cc::Session* cc_session_ = NULL;
/// @brief Session that receives configuration and commands
isc::config::ModuleCCSession* config_session_ = NULL;
/// @brief A dummy configuration handler that always returns success.
///
/// This configuration handler does not perform configuration
/// parsing and always returns success. A dummy handler should
/// be installed using \ref isc::config::ModuleCCSession ctor
/// to get the initial configuration. This initial configuration
/// comprises values for only those elements that were modified
/// the previous session. The \ref dhcp4ConfigHandler can't be
/// used to parse the initial configuration because it needs the
/// full configuration to satisfy dependencies between the
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
/// be later accessed with
/// \ref isc::config::ConfigData::getFullConfig().
///
/// @param new_config new configuration.
///
/// @return success configuration status.
ConstElementPtr
dhcp4StubConfigHandler(ConstElementPtr) {
// This configuration handler is intended to be used only
// when the initial configuration comes in. To receive this
// configuration a pointer to this handler must be passed
// using ModuleCCSession constructor. This constructor will
// invoke the handler and will store the configuration for
// the configuration session when the handler returns success.
// Since this configuration is partial we just pretend to
// parse it and always return success. The function that
// initiates the session must get the configuration on its
// own using getFullConfig.
return (isc::config::createAnswer(0, "Configuration accepted."));
}
ConstElementPtr
bundyConfigHandler(ConstElementPtr new_config) {
if (!ControlledDhcpv4Srv::getInstance() || !config_session_) {
// That should never happen as we install config_handler
// after we instantiate the server.
ConstElementPtr answer =
isc::config::createAnswer(1, "Configuration rejected,"
" server is during startup/shutdown phase.");
return (answer);
}
// The configuration passed to this handler function is partial.
// In other words, it just includes the values being modified.
// In the same time, there are dependencies between various
// DHCP configuration parsers. For example: the option value can
// be set if the definition of this option is set. If someone removes
// an existing option definition then the partial configuration that
// removes that definition is triggered while a relevant option value
// may remain configured. This eventually results in the DHCP server
// configuration being in the inconsistent state.
// In order to work around this problem we need to merge the new
// configuration with the existing (full) configuration.
// Let's create a new object that will hold the merged configuration.
boost::shared_ptr<MapElement> merged_config(new MapElement());
// Let's get the existing configuration.
ConstElementPtr full_config = config_session_->getFullConfig();
// The full_config and merged_config should be always non-NULL
// but to provide some level of exception safety we check that they
// really are (in case we go out of memory).
if (full_config && merged_config) {
merged_config->setValue(full_config->mapValue());
// Merge an existing and new configuration.
isc::data::merge(merged_config, new_config);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
.arg(full_config->str());
}
// Configure the server.
return (ControlledDhcpv4Srv::processConfig(merged_config));
}
void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) {
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
"/src/bin/dhcp4/dhcp4.spec";
} else {
specfile = string(DHCP4_SPECFILE_LOCATION);
}
/// @todo: Check if session is not established already. Throw, if it is.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
// Create a session with the dummy configuration handler.
// Dumy configuration handler is internally invoked by the
// constructor and on success the constructor updates
// the current session with the configuration that had been
// committed in the previous session. If we did not install
// the dummy handler, the previous configuration would have
// been lost.
config_session_ = new ModuleCCSession(specfile, *cc_session_,
dhcp4StubConfigHandler,
processCommand, false);
config_session_->start();
// We initially create ModuleCCSession() without configHandler, as
// the session module is too eager to send partial configuration.
// We want to get the full configuration, so we explicitly call
// getFullConfig() and then pass it to our configHandler.
config_session_->setConfigHandler(bundyConfigHandler);
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
// Server will start DDNS communications if its enabled.
server_->startD2();
// Configuration may disable or enable interfaces so we have to
// reopen sockets according to new configuration.
openActiveSockets(getPort(), useBroadcast());
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
}
/// Integrate the asynchronous I/O model of BIND 10 configuration
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv4Session.
int ctrl_socket = cc_session_->getSocketDesc();
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
.arg(ctrl_socket);
IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
}
void ControlledDhcpv4Srv::cleanup() {
if (config_session_) {
delete config_session_;
config_session_ = NULL;
}
if (cc_session_) {
int ctrl_socket = cc_session_->getSocketDesc();
cc_session_->disconnect();
IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
delete cc_session_;
cc_session_ = NULL;
}
}
void
Daemon::loggerInit(const char* log_name, bool verbose) {
isc::log::initLogger(log_name,
(verbose ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, true);
}
};
};

View File

@ -1,4 +1,4 @@
// Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014 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
@ -13,36 +13,14 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <exceptions/exceptions.h>
#include <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <dhcp4/json_config_parser.h>
#include <cassert>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::hooks;
using namespace isc::log;
using namespace isc::util;
using namespace std;
namespace isc {
@ -51,217 +29,141 @@ namespace dhcp {
ControlledDhcpv4Srv* ControlledDhcpv4Srv::server_ = NULL;
ConstElementPtr
ControlledDhcpv4Srv::dhcp4StubConfigHandler(ConstElementPtr) {
// This configuration handler is intended to be used only
// when the initial configuration comes in. To receive this
// configuration a pointer to this handler must be passed
// using ModuleCCSession constructor. This constructor will
// invoke the handler and will store the configuration for
// the configuration session when the handler returns success.
// Since this configuration is partial we just pretend to
// parse it and always return success. The function that
// initiates the session must get the configuration on its
// own using getFullConfig.
return (isc::config::createAnswer(0, "Configuration accepted."));
ControlledDhcpv4Srv::commandShutdownHandler(const string&, ConstElementPtr) {
if (ControlledDhcpv4Srv::getInstance()) {
ControlledDhcpv4Srv::getInstance()->shutdown();
} else {
LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
ConstElementPtr answer = isc::config::createAnswer(1,
"Shutdown failure.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
return (answer);
}
ConstElementPtr
ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
if (!server_ || !server_->config_session_) {
// That should never happen as we install config_handler
// after we instantiate the server.
ConstElementPtr answer =
isc::config::createAnswer(1, "Configuration rejected,"
" server is during startup/shutdown phase.");
ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
/// @todo delete any stored CalloutHandles referring to the old libraries
/// Get list of currently loaded libraries and reload them.
vector<string> loaded = HooksManager::getLibraryNames();
bool status = HooksManager::loadLibraries(loaded);
if (!status) {
LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
ConstElementPtr answer = isc::config::createAnswer(1,
"Failed to reload hooks libraries.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(0,
"Hooks libraries successfully reloaded.");
return (answer);
}
// The configuration passed to this handler function is partial.
// In other words, it just includes the values being modified.
// In the same time, there are dependencies between various
// DHCP configuration parsers. For example: the option value can
// be set if the definition of this option is set. If someone removes
// an existing option definition then the partial configuration that
// removes that definition is triggered while a relevant option value
// may remain configured. This eventually results in the DHCP server
// configuration being in the inconsistent state.
// In order to work around this problem we need to merge the new
// configuration with the existing (full) configuration.
ConstElementPtr
ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
ConstElementPtr args) {
return (processConfig(args));
}
// Let's create a new object that will hold the merged configuration.
boost::shared_ptr<MapElement> merged_config(new MapElement());
// Let's get the existing configuration.
ConstElementPtr full_config = server_->config_session_->getFullConfig();
// The full_config and merged_config should be always non-NULL
// but to provide some level of exception safety we check that they
// really are (in case we go out of memory).
if (full_config && merged_config) {
merged_config->setValue(full_config->mapValue());
ConstElementPtr
ControlledDhcpv4Srv::processCommand(const string& command,
ConstElementPtr args) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
.arg(command).arg(args->str());
// Merge an existing and new configuration.
isc::data::merge(merged_config, new_config);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
.arg(full_config->str());
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
if (!srv) {
ConstElementPtr no_srv = isc::config::createAnswer(1,
"Server object not initialized, so can't process command '" +
command + "', arguments: '" + args->str() + "'.");
return (no_srv);
}
// Configure the server.
ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
try {
if (command == "shutdown") {
return (srv->commandShutdownHandler(command, args));
// Check that configuration was successful. If not, do not reopen sockets.
int rcode = 0;
parseAnswer(rcode, answer);
if (rcode != 0) {
} else if (command == "libreload") {
return (srv->commandLibReloadHandler(command, args));
} else if (command == "config-reload") {
return (srv->commandConfigReloadHandler(command, args));
}
ConstElementPtr answer = isc::config::createAnswer(1,
"Unrecognized command:" + command);
return (answer);
} catch (const Exception& ex) {
return (isc::config::createAnswer(1, "Error while processing command '"
+ command + "':" + ex.what() +
", params: '" + args->str() + "'"));
}
}
isc::data::ConstElementPtr
ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
.arg(config->str());
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
// Single stream instance used in all error clauses
std::ostringstream err;
if (!srv) {
err << "Server object not initialized, can't process config.";
return (isc::config::createAnswer(1, err.str()));
}
ConstElementPtr answer = configureDhcp4Server(*srv, config);
// Check that configuration was successful. If not, do not reopen sockets
// and don't bother with DDNS stuff.
try {
int rcode = 0;
isc::config::parseAnswer(rcode, answer);
if (rcode != 0) {
return (answer);
}
} catch (const std::exception& ex) {
err << "Failed to process configuration:" << ex.what();
return (isc::config::createAnswer(1, err.str()));
}
// Server will start DDNS communications if its enabled.
try {
server_->startD2();
srv->startD2();
} catch (const std::exception& ex) {
std::ostringstream err;
err << "error starting DHCP_DDNS client "
" after server reconfiguration: " << ex.what();
err << "Error starting DHCP_DDNS client after server reconfiguration: "
<< ex.what();
return (isc::config::createAnswer(1, err.str()));
}
// 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.
// safe and we really don't want to emit exceptions to whoever called this
// method. Instead, catch an exception and create appropriate answer.
try {
server_->openActiveSockets(server_->getPort(), server_->useBroadcast());
srv->openActiveSockets(srv->getPort(), getInstance()->useBroadcast());
} catch (std::exception& ex) {
std::ostringstream err;
err << "failed to open sockets after server reconfiguration: " << ex.what();
err << "failed to open sockets after server reconfiguration: "
<< ex.what();
answer = isc::config::createAnswer(1, err.str());
}
return (answer);
}
ConstElementPtr
ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
.arg(command).arg(args->str());
if (command == "shutdown") {
if (ControlledDhcpv4Srv::server_) {
ControlledDhcpv4Srv::server_->shutdown();
} else {
LOG_WARN(dhcp4_logger, DHCP4_NOT_RUNNING);
ConstElementPtr answer = isc::config::createAnswer(1,
"Shutdown failure.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(0,
"Shutting down.");
return (answer);
} else if (command == "libreload") {
// TODO delete any stored CalloutHandles referring to the old libraries
// Get list of currently loaded libraries and reload them.
vector<string> loaded = HooksManager::getLibraryNames();
bool status = HooksManager::loadLibraries(loaded);
if (!status) {
LOG_ERROR(dhcp4_logger, DHCP4_HOOKS_LIBS_RELOAD_FAIL);
ConstElementPtr answer = isc::config::createAnswer(1,
"Failed to reload hooks libraries.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(0,
"Hooks libraries successfully reloaded.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(1,
"Unrecognized command.");
return (answer);
}
void ControlledDhcpv4Srv::sessionReader(void) {
// Process one asio event. If there are more events, iface_mgr will call
// this callback more than once.
if (server_) {
server_->io_service_.run_one();
}
}
void ControlledDhcpv4Srv::establishSession() {
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
"/src/bin/dhcp4/dhcp4.spec";
} else {
specfile = string(DHCP4_SPECFILE_LOCATION);
}
/// @todo: Check if session is not established already. Throw, if it is.
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTING)
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
// Create a session with the dummy configuration handler.
// Dumy configuration handler is internally invoked by the
// constructor and on success the constructor updates
// the current session with the configuration that had been
// committed in the previous session. If we did not install
// the dummy handler, the previous configuration would have
// been lost.
config_session_ = new ModuleCCSession(specfile, *cc_session_,
dhcp4StubConfigHandler,
dhcp4CommandHandler, false);
config_session_->start();
// We initially create ModuleCCSession() without configHandler, as
// the session module is too eager to send partial configuration.
// We want to get the full configuration, so we explicitly call
// getFullConfig() and then pass it to our configHandler.
config_session_->setConfigHandler(dhcp4ConfigHandler);
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
// Server will start DDNS communications if its enabled.
server_->startD2();
// Configuration may disable or enable interfaces so we have to
// reopen sockets according to new configuration.
openActiveSockets(getPort(), useBroadcast());
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
}
/// Integrate the asynchronous I/O model of BIND 10 configuration
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv4Session.
int ctrl_socket = cc_session_->getSocketDesc();
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_CCSESSION_STARTED)
.arg(ctrl_socket);
IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
}
void ControlledDhcpv4Srv::disconnectSession() {
if (config_session_) {
delete config_session_;
config_session_ = NULL;
}
if (cc_session_) {
int ctrl_socket = cc_session_->getSocketDesc();
cc_session_->disconnect();
IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
delete cc_session_;
cc_session_ = NULL;
}
}
ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
:Dhcpv4Srv(port), cc_session_(NULL), config_session_(NULL) {
server_ = this; // remember this instance for use in callback
:Dhcpv4Srv(port) {
if (getInstance()) {
isc_throw(InvalidOperation,
"There is another Dhcpv4Srv instance already.");
}
server_ = this; // remember this instance for later use in handlers
}
void ControlledDhcpv4Srv::shutdown() {
@ -270,22 +172,19 @@ void ControlledDhcpv4Srv::shutdown() {
}
ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
disconnectSession();
server_ = NULL; // forget this instance. There should be no callback anymore
// at this stage anyway.
cleanup();
server_ = NULL; // forget this instance. Noone should call any handlers at
// this stage.
}
isc::data::ConstElementPtr
ControlledDhcpv4Srv::execDhcpv4ServerCommand(const std::string& command_id,
isc::data::ConstElementPtr args) {
try {
return (dhcp4CommandHandler(command_id, args));
} catch (const Exception& ex) {
ConstElementPtr answer = isc::config::createAnswer(1, ex.what());
return (answer);
void ControlledDhcpv4Srv::sessionReader(void) {
// Process one asio event. If there are more events, iface_mgr will call
// this callback more than once.
if (getInstance()) {
getInstance()->io_service_.run_one();
}
}
};
};
}; // end of isc::dhcp namespace
}; // end of isc namespace

View File

@ -1,4 +1,4 @@
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2014 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
@ -26,12 +26,10 @@ namespace dhcp {
/// @brief Controlled version of the DHCPv4 server
///
/// This is a class that is responsible for establishing connection
/// with msqg (receving commands and configuration). This is an extended
/// version of Dhcpv4Srv class that is purely a DHCPv4 server, without
/// external control. ControlledDhcpv4Srv should be used in typical BIND10
/// (i.e. featuring msgq) environment, while Dhcpv4Srv should be used in
/// embedded environments.
/// This is a class that is responsible for DHCPv4 server being controllable.
/// It does various things, depending on the configuration backend.
/// For Bundy backend it establishes a connection with msqg and later receives
/// commands over it. For Kea backend, it reads configuration file from disk.
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
@ -46,44 +44,57 @@ public:
/// @brief Destructor.
~ControlledDhcpv4Srv();
/// @brief Establishes msgq session.
/// @brief Initializes the server.
///
/// Creates session that will be used to receive commands and updated
/// configuration from cfgmgr (or indirectly from user via bindctl).
void establishSession();
/// Depending on the configuration backend, it establishes msgq session,
/// reads the JSON file from disk or may perform any other setup
/// operation. For specific details, see actual implementation in
/// *_backend.cc
///
/// This method may throw if initialization fails. Exception types may be
/// specific to used configuration backend.
void init(const std::string& config_file);
/// @brief Terminates existing msgq session.
/// @brief Performs cleanup, immediately before termination
///
/// This method terminates existing session with msgq. After calling
/// This method performs final clean up, just before the Dhcpv4Srv object
/// is destroyed. The actual behavior is backend dependent. For Bundy
/// backend, it terminates existing session with msgq. After calling
/// it, no further messages over msgq (commands or configuration updates)
/// may be received.
/// may be received. For JSON backend, it is no-op.
///
/// It is ok to call this method when session is disconnected already.
void disconnectSession();
/// For specific details, see actual implementation in *_backend.cc
void cleanup();
/// @brief Initiates shutdown procedure for the whole DHCPv4 server.
void shutdown();
/// @brief Session callback, processes received commands.
/// @brief Command processor
///
/// This method is uniform for all config backends. It processes received
/// command (as a string + JSON arguments). Internally, it's just a
/// wrapper that calls process*Command() methods and catches exceptions
/// in them.
///
/// Currently supported commands are:
/// - shutdown
/// - libreload
/// - config-reload
///
/// @note It never throws.
///
/// @param command Text represenation of the command (e.g. "shutdown")
/// @param args Optional parameters
/// @param command text represenation of the command (e.g. "shutdown")
/// @param args optional parameters
///
/// @return status of the command
static isc::data::ConstElementPtr
execDhcpv4ServerCommand(const std::string& command,
isc::data::ConstElementPtr args);
processCommand(const std::string& command, isc::data::ConstElementPtr args);
protected:
/// @brief Static pointer to the sole instance of the DHCP server.
/// @brief Configuration processor
///
/// This is required for config and command handlers to gain access to
/// the server
static ControlledDhcpv4Srv* server_;
/// @brief A callback for handling incoming configuration updates.
/// This is a method for handling incoming configuration updates.
/// This method should be called by all configuration backends when the
/// server is starting up or when configuration has changed.
///
/// As pointer to this method is used a callback in ASIO used in
/// ModuleCCSession, it has to be static.
@ -92,54 +103,72 @@ protected:
///
/// @return status of the config update
static isc::data::ConstElementPtr
dhcp4ConfigHandler(isc::data::ConstElementPtr new_config);
processConfig(isc::data::ConstElementPtr new_config);
/// @brief A dummy configuration handler that always returns success.
/// @brief Returns pointer to the sole instance of Dhcpv4Srv
///
/// This configuration handler does not perform configuration
/// parsing and always returns success. A dummy handler should
/// be installed using \ref isc::config::ModuleCCSession ctor
/// to get the initial configuration. This initial configuration
/// comprises values for only those elements that were modified
/// the previous session. The \ref dhcp4ConfigHandler can't be
/// used to parse the initial configuration because it needs the
/// full configuration to satisfy dependencies between the
/// various configuration values. Installing the dummy handler
/// that guarantees to return success causes initial configuration
/// to be stored for the session being created and that it can
/// be later accessed with
/// \ref isc::config::ConfigData::getFullConfig().
///
/// @param new_config new configuration.
///
/// @return success configuration status.
static isc::data::ConstElementPtr
dhcp4StubConfigHandler(isc::data::ConstElementPtr new_config);
/// @return server instance (may return NULL, if called before server is spawned)
static ControlledDhcpv4Srv* getInstance() {
return (server_);
}
/// @brief A callback for handling incoming commands.
///
/// @param command textual representation of the command
/// @param args parameters of the command
///
/// @return status of the processed command
static isc::data::ConstElementPtr
dhcp4CommandHandler(const std::string& command, isc::data::ConstElementPtr args);
/// @brief Callback that will be called from iface_mgr when command/config arrives.
protected:
/// @brief Static pointer to the sole instance of the DHCP server.
///
/// This static callback method is called from IfaceMgr::receive4() method,
/// when there is a new command or configuration sent over msgq.
/// This is required for config and command handlers to gain access to
/// the server
static ControlledDhcpv4Srv* server_;
/// @brief Callback that will be called from iface_mgr when data
/// is received over control socket.
///
/// This static callback method is called from IfaceMgr::receive6() method,
/// when there is a new command or configuration sent over control socket
/// (that was sent from msgq if backend is Bundy, or some yet unspecified
/// sender if the backend is JSON file).
static void sessionReader(void);
/// @brief IOService object, used for all ASIO operations.
isc::asiolink::IOService io_service_;
/// @brief Helper session object that represents raw connection to msgq.
isc::cc::Session* cc_session_;
/// @brief Handler for processing 'shutdown' command
///
/// This handler processes shutdown command, which initializes shutdown
/// procedure.
/// @param command (parameter ignored)
/// @param args (parameter ignored)
///
/// @return status of the command
isc::data::ConstElementPtr
commandShutdownHandler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief Session that receives configuration and commands
isc::config::ModuleCCSession* config_session_;
/// @brief Handler for processing 'libreload' command
///
/// This handler processes libreload command, which unloads all hook
/// libraries and reloads them.
///
/// @param command (parameter ignored)
/// @param args (parameter ignored)
///
/// @return status of the command
isc::data::ConstElementPtr
commandLibReloadHandler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief Handler for processing 'config-reload' command
///
/// This handler processes config-reload command, which processes
/// configuration specified in args parameter.
///
/// @param command (parameter ignored)
/// @param args configuration to be processed
///
/// @return status of the command
isc::data::ConstElementPtr
commandConfigReloadHandler(const std::string& command,
isc::data::ConstElementPtr args);
};
}; // namespace isc::dhcp

View File

@ -191,6 +191,46 @@ being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSu
Currently this capability is usable, but the number of scenarios it supports is
limited.
@section dhcpv4ConfigBackend Configuration backend for DHCPv4
There are many theoretical ways in which server configuration can be stored. Kea 0.8 and
earlier versions used BIND10 framework and its internal storage for DHCPv6 server configuration.
The legacy ISC-DHCP implementation uses flat files. Configuration stored in JSON files is
becoming more and more popular among various projects. There are unofficial patches for
ISC-DHCP that keep parts of the configuration in LDAP. It was also suggested that in some
cases it would be convenient to keep configuration in XML files.
Kea 0.9 introduces configuration backends that are switchable during compilation phase.
There is a new parameter for configure script: --with-kea-config. It currently supports
two values: BUNDY and JSON.
BUNDY (which is the default value as of May 2014) means that Kea4 is linked with the
Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works
exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available
RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires
Python3 to be present. That framework is going away with the release of Kea 0.9.
JSON is a new configuration backend that causes Kea to read JSON configuration file from
disk. It does not require any framework and thus is considered more lightweight. It will
allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
API). This configuration backend is expected to be the default for upcoming Kea 0.9. It
requires <tt> -c config-file </tt> command-line option.
Internally, configuration backends are implemented as different implementations of the
isc::dhcp::ControlledDhcpv4Srv class, stored in {kea,bundy}_controller.cc files. Depending on
the choice made by ./configure script, only one of those files is compiled and linked.
There are backend specific tests in src/bin/dhcp4/tests/{kea,bundy}_controller_unittest.cc.
Only tests specific to selected backend are linked and executed during make distcheck.
While it is unlikely that ISC will support more than one backend at any given time, there
are several aspects that make that approach appealing in the long term. First, having
two backends is essential during transition time, where both old and new backend is used.
Second, there are external organizations that develop and presumably maintain LDAP backend
for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if we ever
extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as
a migration tool.
@section dhcpv4Other Other DHCPv4 topics
For hooks API support in DHCPv4, see @ref dhcpv4Hooks.

View File

@ -44,6 +44,10 @@ name was malformed or due to internal server error.
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the DHCPv4 server.
% DHCP4_CONFIG_RECEIVED received configuration %1
A debug message listing the configuration received by the DHCPv4 server.
The source of that configuration depends on used configuration backend.
% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
This is an informational message announcing the successful processing of a
new configuration. It is output during server startup, and when an updated
@ -339,17 +343,11 @@ core component within the DHCPv4 server (the Dhcpv4 server object)
has failed. As a result, the server will exit. The reason for the
failure is given within the message.
% DHCP4_STANDALONE skipping message queue, running standalone
This is a debug message indicating that the DHCPv4 server is running in
standalone mode, not connected to the message queue. Standalone mode
is only useful during program development, and should not be used in a
production environment.
% DHCP4_STARTING server starting
This informational message indicates that the DHCPv4 server has
processed any command-line switches and is starting.
% DHCP4_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
% DHCP4_START_INFO pid: %1, port: %2, verbose: %3
This is a debug message issued during the DHCPv4 server startup.
It lists some information about the parameters with which the server
is running.

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2014 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
@ -26,6 +26,7 @@
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/alloc_engine.h>
#include <hooks/callout_handle.h>
#include <dhcpsrv/daemon.h>
#include <boost/noncopyable.hpp>
@ -57,7 +58,7 @@ public:
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
/// Dhcpv4Srv and other classes, see \ref dhcpv4Session.
class Dhcpv4Srv : public boost::noncopyable {
class Dhcpv4Srv : public Daemon {
public:

View File

@ -17,7 +17,7 @@
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/json_config_parser.h>
#include <dhcpsrv/dbaccess_parser.h>
#include <dhcpsrv/dhcp_parsers.h>
#include <dhcpsrv/option_space_container.h>

View File

@ -0,0 +1,124 @@
// Copyright (C) 2014 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <exceptions/exceptions.h>
#include <string>
using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace std;
namespace isc {
namespace dhcp {
void
ControlledDhcpv4Srv::init(const std::string& file_name) {
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
isc::data::ConstElementPtr json;
isc::data::ConstElementPtr dhcp4;
isc::data::ConstElementPtr result;
// Basic sanity check: file name must not be empty.
try {
if (file_name.empty()) {
// Basic sanity check: file name must not be empty.
isc_throw(BadValue, "JSON configuration file not specified. Please "
"use -c command line option.");
}
// Read contents of the file and parse it as JSON
json = isc::data::Element::fromJSONFile(file_name, true);
if (!json) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
.arg("Config file " + file_name + " missing or empty.");
isc_throw(BadValue, "Unable to process JSON configuration file:"
+ file_name);
}
// Get Dhcp4 component from the config
dhcp4 = json->get("Dhcp4");
if (!dhcp4) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
.arg("Config file " + file_name + " does not include 'Dhcp4' entry.");
isc_throw(BadValue, "Unable to process JSON configuration file:"
+ file_name);
}
// Use parsed JSON structures to configure the server
result = processCommand("config-reload", dhcp4);
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
isc_throw(BadValue, "Unable to process JSON configuration file:"
+ file_name);
}
if (!result) {
// Undetermined status of the configuration. This should never happen,
// but as the configureDhcp4Server returns a pointer, it is theoretically
// possible that it will return NULL.
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
.arg("Configuration failed: Undefined result of processCommand("
"config-reload, " + file_name + ")");
isc_throw(BadValue, "Configuration failed: Undefined result of "
"processCommand('config-reload', " + file_name + ")");
}
// Now check is the returned result is successful (rcode=0) or not
isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
int rcode;
comment = isc::config::parseAnswer(rcode, result);
if (rcode != 0) {
string reason = "";
if (comment) {
reason = string(" (") + comment->stringValue() + string(")");
}
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(reason);
isc_throw(BadValue, "Failed to apply configuration:" << reason);
}
// We don't need to call openActiveSockets() or startD2() as these
// methods are called in processConfig() which is called by
// processCommand("reload-config", ...)
}
void ControlledDhcpv4Srv::cleanup() {
// Nothing to do here. No need to disconnect from anything.
}
/// This is a logger initialization for JSON file backend.
/// For now, it's just setting log messages to be printed on stdout.
/// @todo: Implement this properly (see #3427)
void Daemon::loggerInit(const char*, bool verbose) {
setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
setenv("B10_LOGGER_ROOT", "kea", 0);
setenv("B10_LOGGER_SEVERITY", (verbose ? "DEBUG":"INFO"), 0);
setenv("B10_LOGGER_DBGLEVEL", "99", 0);
setenv("B10_LOGGER_DESTINATION", "stdout", 0);
isc::log::initLogger();
}
};
};

View File

@ -39,13 +39,15 @@ namespace {
const char* const DHCP4_NAME = "b10-dhcp4";
const char* const DHCP4_LOGGER_NAME = "kea";
void
usage() {
cerr << "Usage: " << DHCP4_NAME << " [-v] [-s] [-p number]" << endl;
cerr << "Usage: " << DHCP4_NAME << " [-v] [-p number] [-c file]" << endl;
cerr << " -v: verbose output" << endl;
cerr << " -s: stand-alone mode (don't connect to BIND10)" << endl;
cerr << " -p number: specify non-standard port number 1-65535 "
<< "(useful for testing only)" << endl;
cerr << " -c file: specify configuration file" << endl;
exit(EXIT_FAILURE);
}
} // end of anonymous namespace
@ -55,19 +57,17 @@ main(int argc, char* argv[]) {
int ch;
int port_number = DHCP4_SERVER_PORT; // The default. any other values are
// useful for testing only.
bool stand_alone = false; // Should be connect to BIND10 msgq?
bool verbose_mode = false; // Should server be verbose?
while ((ch = getopt(argc, argv, "vsp:")) != -1) {
// The standard config file
std::string config_file("");
while ((ch = getopt(argc, argv, "vp:c:")) != -1) {
switch (ch) {
case 'v':
verbose_mode = true;
break;
case 's':
stand_alone = true;
break;
case 'p':
try {
port_number = boost::lexical_cast<int>(optarg);
@ -83,6 +83,10 @@ main(int argc, char* argv[]) {
}
break;
case 'c': // config file
config_file = optarg;
break;
default:
usage();
}
@ -93,36 +97,39 @@ main(int argc, char* argv[]) {
usage();
}
// Initialize logging. If verbose, we'll use maximum verbosity.
// If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(DHCP4_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
.arg(stand_alone ? "yes" : "no" );
int ret = EXIT_SUCCESS;
try {
// Initialize logging. If verbose, we'll use maximum verbosity.
// If standalone is enabled, do not buffer initial log messages
Daemon::loggerInit(DHCP4_LOGGER_NAME, verbose_mode);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
// Create the server instance.
ControlledDhcpv4Srv server(port_number);
if (!stand_alone) {
try {
server.establishSession();
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
// Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
// We do need to make sure logging is no longer buffered
// since then it would not print until dhcp6 is stopped
isc::log::LoggerManager log_manager;
log_manager.process();
}
} else {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
try {
// Initialize the server.
server.init(config_file);
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
// We should not continue if were told to configure (either read
// config file or establish Bundy control session).
isc::log::LoggerManager log_manager;
log_manager.process();
cerr << "Failed to initialize server: " << ex.what() << endl;
return (EXIT_FAILURE);
}
// And run the main loop of the server.
server.run();
LOG_INFO(dhcp4_logger, DHCP4_SHUTDOWN);
} catch (const std::exception& ex) {

View File

@ -33,6 +33,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
CLEANFILES += *.json
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_CLANGPP
@ -77,7 +78,7 @@ TESTS += dhcp4_unittests
dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
dhcp4_unittests_SOURCES += ../json_config_parser.cc ../json_config_parser.h
dhcp4_unittests_SOURCES += d2_unittest.h d2_unittest.cc
dhcp4_unittests_SOURCES += dhcp4_test_utils.h
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
@ -89,6 +90,19 @@ dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += config_parser_unittest.cc
dhcp4_unittests_SOURCES += fqdn_unittest.cc
dhcp4_unittests_SOURCES += marker_file.cc
if CONFIG_BACKEND_BUNDY
# For Bundy backend, we only need to run the usual tests. There are no
# Bundy-specific tests yet.
dhcp4_unittests_SOURCES += ../bundy_controller.cc
dhcp4_unittests_SOURCES += bundy_controller_unittest.cc
endif
if CONFIG_BACKEND_JSON
dhcp4_unittests_SOURCES += ../kea_controller.cc
dhcp4_unittests_SOURCES += kea_controller_unittest.cc
endif
nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
nodist_dhcp4_unittests_SOURCES += marker_file.h test_libraries.h
@ -109,4 +123,6 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
endif
noinst_EXTRA_DIST = configs-list.txt
noinst_PROGRAMS = $(TESTS)

View File

@ -0,0 +1,30 @@
// Copyright (C) 2014 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <gtest/gtest.h>
namespace {
/// As of May 2014, maintaining or extending Bundy support is very low
/// prority for Kea team. We are looking for contributors, who would
/// like to maintain this backend.
// Bundy framework specific tests should be added here.
TEST(BundyBackendTest, dummy) {
}
} // End of anonymous namespace

View File

@ -19,7 +19,7 @@
#include <config/ccsession.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>

View File

@ -0,0 +1,5 @@
# This is a list of config files that the unit-tests (specifically
# JSONFileBackendTest.loadAllConfigs) is going to load.
../../../../doc/examples/kea4/single-subnet.json
../../../../doc/examples/kea4/several-subnets.json

View File

@ -85,12 +85,12 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
int rcode = -1;
// Case 1: send bogus command
ConstElementPtr result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("blah", params);
ConstElementPtr result = ControlledDhcpv4Srv::processCommand("blah", params);
ConstElementPtr comment = parseAnswer(rcode, result);
EXPECT_EQ(1, rcode); // expect failure (no such command as blah)
// Case 2: send shutdown command without any parameters
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
result = ControlledDhcpv4Srv::processCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
@ -99,7 +99,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
params->set("pid", x);
// Case 3: send shutdown command with 1 parameter: pid
result = ControlledDhcpv4Srv::execDhcpv4ServerCommand("shutdown", params);
result = ControlledDhcpv4Srv::processCommand("shutdown", params);
comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // expect success
}
@ -107,6 +107,14 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
// Check that the "libreload" command will reload libraries
TEST_F(CtrlDhcpv4SrvTest, libreload) {
// Sending commands for processing now requires a server that can process
// them.
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(
srv.reset(new ControlledDhcpv4Srv(0))
);
// Ensure no marker files to start with.
ASSERT_FALSE(checkMarkerFileExists(LOAD_MARKER_FILE));
ASSERT_FALSE(checkMarkerFileExists(UNLOAD_MARKER_FILE));
@ -137,7 +145,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) {
int rcode = -1;
ConstElementPtr result =
ControlledDhcpv4Srv::execDhcpv4ServerCommand("libreload", params);
ControlledDhcpv4Srv::processCommand("libreload", params);
ConstElementPtr comment = parseAnswer(rcode, result);
EXPECT_EQ(0, rcode); // Expect success

View File

@ -13,7 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/iface_mgr.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/tests/d2_unittest.h>
#include <dhcpsrv/cfgmgr.h>

View File

@ -32,7 +32,7 @@
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/json_config_parser.h>
#include <hooks/server_hooks.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>

View File

@ -13,7 +13,7 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from init import ProcessInfo, parse_args, dump_pid, unlink_pid_file, _BASETIME
from init import ProcessInfo
import unittest
import sys
@ -207,22 +207,5 @@ class TestDhcpv4Daemon(unittest.TestCase):
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(error).count("Failed to parse port number"), 1)
def test_portnumber_nonroot(self):
print("Check that specifying unprivileged port number will work.")
# Check that there is a message about running with an unprivileged port
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP4_OPEN_SOCKET opening sockets on port 10057"), 1)
def test_skip_msgq(self):
print("Check that connection to BIND10 msgq can be disabled.")
# Check that the system outputs a message on one of its streams about running
# standalone.
(returncode, output, error) = self.runCommand(['../b10-dhcp4', '-v', '-s', '-p', '10057'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP4_STANDALONE"), 1)
if __name__ == '__main__':
unittest.main()

View File

@ -17,7 +17,7 @@
#include <asiolink/io_address.h>
#include <cc/data.h>
#include <config/ccsession.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_int_array.h>

View File

@ -19,7 +19,7 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
#include <dhcp4/config_parser.h>
#include <dhcp4/json_config_parser.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <gtest/gtest.h>
#include <string>

View File

@ -0,0 +1,296 @@
// Copyright (C) 2014 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
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <config/ccsession.h>
#include <dhcp/dhcp4.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcpsrv/cfgmgr.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <arpa/inet.h>
#include <unistd.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::hooks;
namespace {
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
public:
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
};
/// @brief test class for Kea configuration backend
///
/// This class is used for testing Kea configuration backend.
/// It is very simple and currently focuses on reading
/// config file from disk. It is expected to be expanded in the
/// near future.
class JSONFileBackendTest : public ::testing::Test {
public:
JSONFileBackendTest() {
}
~JSONFileBackendTest() {
static_cast<void>(unlink(TEST_FILE));
};
/// @brief writes specified content to a well known file
///
/// Writes specified content to TEST_FILE. Tests will
/// attempt to read that file.
///
/// @param content content to be written to file
void writeFile(const std::string& content) {
static_cast<void>(unlink(TEST_FILE));
ofstream out(TEST_FILE, ios::trunc);
EXPECT_TRUE(out.is_open());
out << content;
out.close();
}
/// Name of a config file used during tests
static const char* TEST_FILE;
};
const char* JSONFileBackendTest::TEST_FILE = "test-config.json";
// This test checks if configuration can be read from a JSON file.
TEST_F(JSONFileBackendTest, jsonFile) {
// Prepare configuration file.
string config = "{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\" "
" },"
" {"
" \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
" \"subnet\": \"192.0.3.0/24\", "
" \"id\": 0 "
" },"
" {"
" \"pool\": [ \"192.0.4.101 - 192.0.4.150\" ],"
" \"subnet\": \"192.0.4.0/24\" "
" } ],"
"\"valid-lifetime\": 4000 }"
"}";
writeFile(config);
// Now initialize the server
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(
srv.reset(new ControlledDhcpv4Srv(0))
);
// And configure it using the config file.
EXPECT_NO_THROW(srv->init(TEST_FILE));
// Now check if the configuration has been applied correctly.
const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
ASSERT_TRUE(subnets);
ASSERT_EQ(3, subnets->size()); // We expect 3 subnets.
// Check subnet 1.
EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
EXPECT_EQ(24, subnets->at(0)->get().second);
// Check pools in the first subnet.
const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
ASSERT_EQ(1, pools1.size());
EXPECT_EQ("192.0.2.1", pools1.at(0)->getFirstAddress().toText());
EXPECT_EQ("192.0.2.100", pools1.at(0)->getLastAddress().toText());
EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
// Check subnet 2.
EXPECT_EQ("192.0.3.0", subnets->at(1)->get().first.toText());
EXPECT_EQ(24, subnets->at(1)->get().second);
// Check pools in the second subnet.
const PoolCollection& pools2 = subnets->at(1)->getPools(Lease::TYPE_V4);
ASSERT_EQ(1, pools2.size());
EXPECT_EQ("192.0.3.101", pools2.at(0)->getFirstAddress().toText());
EXPECT_EQ("192.0.3.150", pools2.at(0)->getLastAddress().toText());
EXPECT_EQ(Lease::TYPE_V4, pools2.at(0)->getType());
// And finally check subnet 3.
EXPECT_EQ("192.0.4.0", subnets->at(2)->get().first.toText());
EXPECT_EQ(24, subnets->at(2)->get().second);
// ... and it's only pool.
const PoolCollection& pools3 = subnets->at(2)->getPools(Lease::TYPE_V4);
EXPECT_EQ("192.0.4.101", pools3.at(0)->getFirstAddress().toText());
EXPECT_EQ("192.0.4.150", pools3.at(0)->getLastAddress().toText());
EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
}
// This test checks if configuration can be read from a JSON file.
TEST_F(JSONFileBackendTest, comments) {
string config_hash_comments = "# This is a comment. It should be \n"
"#ignored. Real config starts in line below\n"
"{ \"Dhcp4\": { \"interfaces\": [ \"*\" ],"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, \n"
"# comments in the middle should be ignored, too\n"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.0/24\" ],"
" \"subnet\": \"192.0.2.0/22\" "
" } ],"
"\"valid-lifetime\": 4000 }"
"}";
/// @todo: Implement C++-style (// ...) comments
/// @todo: Implement C-style (/* ... */) comments
writeFile(config_hash_comments);
// Now initialize the server
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(
srv.reset(new ControlledDhcpv4Srv(0))
);
// And configure it using config with comments.
EXPECT_NO_THROW(srv->init(TEST_FILE));
// Now check if the configuration has been applied correctly.
const Subnet4Collection* subnets = CfgMgr::instance().getSubnets4();
ASSERT_TRUE(subnets);
ASSERT_EQ(1, subnets->size());
// Check subnet 1.
EXPECT_EQ("192.0.2.0", subnets->at(0)->get().first.toText());
EXPECT_EQ(22, subnets->at(0)->get().second);
// Check pools in the first subnet.
const PoolCollection& pools1 = subnets->at(0)->getPools(Lease::TYPE_V4);
ASSERT_EQ(1, pools1.size());
EXPECT_EQ("192.0.2.0", pools1.at(0)->getFirstAddress().toText());
EXPECT_EQ("192.0.2.255", pools1.at(0)->getLastAddress().toText());
EXPECT_EQ(Lease::TYPE_V4, pools1.at(0)->getType());
}
// This test checks if configuration detects failure when trying:
// - empty file
// - empty filename
// - no Dhcp4 element
// - Config file that contains Dhcp4 but has a content error
TEST_F(JSONFileBackendTest, configBroken) {
// Empty config is not allowed, because Dhcp4 element is missing
string config_empty = "";
// This config does not have mandatory Dhcp4 element
string config_v4 = "{ \"Dhcp6\": { \"interfaces\": [ \"*\" ],"
"\"preferred-lifetime\": 3000,"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pool\": [ \"2001:db8::/80\" ],"
" \"subnet\": \"2001:db8::/64\" "
" } ]}";
// This has Dhcp4 element, but it's utter nonsense
string config_nonsense = "{ \"Dhcp4\": { \"reviews\": \"are so much fun\" } }";
// Now initialize the server
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(
srv.reset(new ControlledDhcpv4Srv(0))
);
// Try to configure without filename. Should fail.
EXPECT_THROW(srv->init(""), BadValue);
// Try to configure it using empty file. Should fail.
writeFile(config_empty);
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
// Now try to load a config that does not have Dhcp4 component.
writeFile(config_v4);
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
// Now try to load a config with Dhcp4 full of nonsense.
writeFile(config_nonsense);
EXPECT_THROW(srv->init(TEST_FILE), BadValue);
}
/// This unit-test reads all files enumerated in configs-test.txt file, loads
/// each of them and verify that they can be loaded.
///
/// @todo: Unfortunately, we have this test disabled, because all loaded
/// configs use memfile, which attempts to create lease file in
/// /usr/local/var/bind10/kea-leases4.csv. We have couple options here:
/// a) disable persistence in example configs - a very bad thing to do
/// as users will forget to reenable it and then will be surprised when their
/// leases disappear
/// b) change configs to store lease file in /tmp. It's almost as bad as the
/// previous one. Users will then be displeased when all their leases are
/// wiped. (most systems wipe /tmp during boot)
/// c) read each config and rewrite it on the fly, so persistence is disabled.
/// This is probably the way to go, but this is a work for a dedicated ticket.
///
/// Hence I'm leaving the test in, but it is disabled.
TEST_F(JSONFileBackendTest, DISABLED_loadAllConfigs) {
// Create server first
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(
srv.reset(new ControlledDhcpv4Srv(0))
);
const char* configs_list = "configs-list.txt";
fstream configs(configs_list, ios::in);
ASSERT_TRUE(configs.is_open());
std::string config_name;
while (std::getline(configs, config_name)) {
// Ignore empty and commented lines
if (config_name.empty() || config_name[0] == '#') {
continue;
}
// Unit-tests usually do not print out anything, but in this case I
// think printing out tests configs is warranted.
std::cout << "Loading config file " << config_name << std::endl;
try {
srv->init(config_name);
} catch (const std::exception& ex) {
ADD_FAILURE() << "Exception thrown" << ex.what() << endl;
}
}
}
} // End of anonymous namespace

View File

@ -220,10 +220,10 @@ void ControlledDhcpv6Srv::cleanup() {
}
void
Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
Daemon::loggerInit(const char* log_name, bool verbose) {
isc::log::initLogger(log_name,
(verbose ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
isc::log::MAX_DEBUG_LEVEL, NULL, true);
}
}; // end of isc::dhcp namespace

View File

@ -43,8 +43,8 @@ ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
ConstElementPtr
ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
// TODO delete any stored CalloutHandles referring to the old libraries
// Get list of currently loaded libraries and reload them.
/// @todo delete any stored CalloutHandles referring to the old libraries
/// Get list of currently loaded libraries and reload them.
vector<string> loaded = HooksManager::getLibraryNames();
bool status = HooksManager::loadLibraries(loaded);
if (!status) {
@ -99,9 +99,12 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
}
}
isc::data::ConstElementPtr
ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
.arg(config->str());
ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
if (!srv) {

View File

@ -1,4 +1,4 @@
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2014 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
@ -26,12 +26,10 @@ namespace dhcp {
/// @brief Controlled version of the DHCPv6 server
///
/// This is a class that is responsible for establishing connection
/// with msqg (receving commands and configuration). This is an extended
/// version of Dhcpv6Srv class that is purely a DHCPv6 server, without
/// external control. ControlledDhcpv6Srv should be used in typical BIND10
/// (i.e. featuring msgq) environment, while Dhcpv6Srv should be used in
/// embedded environments.
/// This is a class that is responsible for DHCPv6 server being controllable.
/// It does various things, depending on the configuration backend.
/// For Bundy backend it establishes a connection with msqg and later receives
/// commands over it. For Kea backend, it reads configuration file from disk.
///
/// For detailed explanation or relations between main(), ControlledDhcpv6Srv,
/// Dhcpv6Srv and other classes, see \ref dhcpv6Session.
@ -53,7 +51,8 @@ public:
/// operation. For specific details, see actual implementation in
/// *_backend.cc
///
/// @return true if initialization was successful, false if it failed
/// This method may throw if initialization fails. Exception types may be
/// specific to used configuration backend.
void init(const std::string& config_file);
/// @brief Performs cleanup, immediately before termination
@ -77,6 +76,11 @@ public:
/// wrapper that calls process*Command() methods and catches exceptions
/// in them.
///
/// Currently supported commands are:
/// - shutdown
/// - libreload
/// - config-reload
///
/// @note It never throws.
///
/// @param command Text represenation of the command (e.g. "shutdown")
@ -88,7 +92,7 @@ public:
/// @brief configuration processor
///
/// This is a callback for handling incoming configuration updates.
/// This is a method for handling incoming configuration updates.
/// This method should be called by all configuration backends when the
/// server is starting up or when configuration has changed.
///
@ -103,7 +107,7 @@ public:
/// @brief returns pointer to the sole instance of Dhcpv6Srv
///
/// @note may return NULL, if called before server is spawned
/// @return server instance (may return NULL, if called before server is spawned)
static ControlledDhcpv6Srv* getInstance() {
return (server_);
}

View File

@ -234,24 +234,25 @@ cases it would be convenient to keep configuration in XML files.
Kea 0.9 introduces configuration backends that are switchable during compilation phase.
There is a new parameter for configure script: --with-kea-config. It currently supports
two values: BIND10 and JSON.
two values: BUNDY and JSON.
BIND10 (which is the default value as of April 2014) means that Kea6 is linked with the
BIND10 configuration backend that connects to the BIND10 framework and in general works
BUNDY (which is the default value as of April 2014) means that Kea6 is linked with the
Bundy (former BIND10) configuration backend that connects to the BIND10 framework and in general works
exactly the same as Kea 0.8 and earlier versions. The benefits of that backend are uniform
integration with BIND10 framework, easy on-line reconfiguration using bindctl, available
RESTful API. On the other hand, it requires the whole heavy BIND10 framework that requires
Python3 to be present. That backend is likely to go away with the release of Kea 0.9.
integration with Bundy/BIND10 framework, easy on-line reconfiguration using bindctl, available
RESTful API. On the other hand, it requires the whole heavy Bundy framework that requires
Python3 to be present. That framework is going away with the release of Kea 0.9.
JSON is a new configuration backend that causes Kea to read JSON configuration file from
disk. It does not require any framework and thus is considered more lightweight. It will
allow dynamic on-line reconfiguration, but will lack remote capabilities (i.e. no RESTful
API). This configuration backend is expected to be the default for upcoming Kea 0.9.
API). This configuration backend is expected to be the default for upcoming Kea 0.9. It
requires <tt> -c config-file </tt> command-line option.
Internally, configuration backends are implemented as different implementations of the
isc::dhcp::ControlledDhcpv6Srv class, stored in ctrl_*_dhcpv6_srv.cc files. Depending on
isc::dhcp::ControlledDhcpv6Srv class, stored in {kea,bundy}_controller.cc files. Depending on
the choice made by ./configure script, only one of those files is compiled and linked.
There are backend specific tests in src/bin/dhcp6/tests/ctrl_*_dhcpv6_srv_unittest.cc.
There are backend specific tests in src/bin/dhcp6/tests/{kea,bundy}_controller_unittest.cc.
Only tests specific to selected backend are linked and executed during make distcheck.
While it is unlikely that ISC will support more than one backend at any given time, there

View File

@ -40,6 +40,10 @@ address assignment. The most likely cause is a problem with the client.
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv6 DHCP server.
% DHCP6_CONFIG_RECEIVED received configuration: %1
A debug message listing the configuration received by the DHCPv6 server.
The source of that configuration depends on used configuration backend.
% DHCP6_CONFIG_COMPLETE DHCPv6 server has completed configuration: %1
This is an informational message announcing the successful processing of a
new configuration. it is output during server startup, and when an updated
@ -540,7 +544,7 @@ production environment.
This informational message indicates that the IPv6 DHCP server has
processed any command-line switches and is starting.
% DHCP6_START_INFO pid: %1, port: %2, verbose: %3, standalone: %4
% DHCP6_START_INFO pid: %1, port: %2, verbose: %3
This is a debug message issued during the IPv6 DHCP server startup.
It lists some information about the parameters with which the server
is running.

View File

@ -15,35 +15,19 @@
#include <config.h>
#include <asiolink/asiolink.h>
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcp6/json_config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/spec_config.h>
#include <log/logger_level.h>
#include <log/logger_name.h>
#include <log/logger_manager.h>
#include <log/logger_specification.h>
#include <log/logger_support.h>
#include <log/output_option.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
#include <signal.h>
#include <string>
#include <vector>
using namespace isc::asiolink;
using namespace isc::cc;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::log;
using namespace isc::util;
using namespace std;
namespace {
@ -74,7 +58,7 @@ void configure(const std::string& file_name) {
}
// Read contents of the file and parse it as JSON
json = Element::fromJSONFile(file_name, true);
json = isc::data::Element::fromJSONFile(file_name, true);
if (!json) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL)
@ -113,9 +97,9 @@ void configure(const std::string& file_name) {
}
// Now check is the returned result is successful (rcode=0) or not
ConstElementPtr comment; /// see @ref isc::config::parseAnswer
isc::data::ConstElementPtr comment; /// see @ref isc::config::parseAnswer
int rcode;
comment = parseAnswer(rcode, result);
comment = isc::config::parseAnswer(rcode, result);
if (rcode != 0) {
string reason = "";
if (comment) {
@ -151,7 +135,7 @@ void signalHandler(int signo) {
.arg(file);
}
} else if ((signo == SIGTERM) || (signo == SIGINT)) {
ElementPtr params(new isc::data::MapElement());
isc::data::ElementPtr params(new isc::data::MapElement());
ControlledDhcpv6Srv::processCommand("shutdown", params);
}
}
@ -188,7 +172,7 @@ void ControlledDhcpv6Srv::cleanup() {
/// This is a logger initialization for JSON file backend.
/// For now, it's just setting log messages to be printed on stdout.
/// @todo: Implement this properly (see #3427)
void Daemon::loggerInit(const char*, bool verbose, bool ) {
void Daemon::loggerInit(const char*, bool verbose) {
setenv("B10_LOCKFILE_DIR_FROM_BUILD", "/tmp", 1);
setenv("B10_LOGGER_ROOT", "kea", 0);

View File

@ -43,9 +43,8 @@ const char* const DHCP6_LOGGER_NAME = "kea";
void
usage() {
cerr << "Usage: " << DHCP6_NAME << " [-v] [-s] [-p port_number] [-c cfgfile]" << endl;
cerr << "Usage: " << DHCP6_NAME << " [-v] [-p port_number] [-c cfgfile]" << endl;
cerr << " -v: verbose output" << endl;
cerr << " -s: skip configuration (don't connect to BIND10 or don't read config file)" << endl;
cerr << " -p number: specify non-standard port number 1-65535 "
<< "(useful for testing only)" << endl;
cerr << " -c file: specify configuration file" << endl;
@ -58,22 +57,17 @@ main(int argc, char* argv[]) {
int ch;
int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
// useful for testing only.
bool stand_alone = false; // Should be connect to BIND10 msgq?
bool verbose_mode = false; // Should server be verbose?
// The standard config file
std::string config_file("");
while ((ch = getopt(argc, argv, "vsp:c:")) != -1) {
while ((ch = getopt(argc, argv, "vp:c:")) != -1) {
switch (ch) {
case 'v':
verbose_mode = true;
break;
case 's': // stand-alone
stand_alone = true;
break;
case 'p': // port number
try {
port_number = boost::lexical_cast<int>(optarg);
@ -107,39 +101,37 @@ main(int argc, char* argv[]) {
int ret = EXIT_SUCCESS;
try {
// Initialize logging. If verbose, we'll use maximum verbosity.
// If standalone is enabled, do not buffer initial log messages
Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode, stand_alone);
Daemon::loggerInit(DHCP6_LOGGER_NAME, verbose_mode);
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
.arg(stand_alone ? "yes" : "no" );
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no");
LOG_INFO(dhcp6_logger, DHCP6_STARTING);
// Create the server instance.
ControlledDhcpv6Srv server(port_number);
if (!stand_alone) {
try {
// Initialize the server, i.e. establish control session
// if BIND10 backend is used or read a configuration file
server.init(config_file);
try {
// Initialize the server, i.e. establish control session
// if BIND10 backend is used or read a configuration file
// if Kea backend is used.
server.init(config_file);
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_INIT_FAIL).arg(ex.what());
// We should not continue if were told to configure (either read
// config file or establish BIND10 control session).
isc::log::LoggerManager log_manager;
log_manager.process();
// We should not continue if were told to configure (either read
// config file or establish BIND10 control session).
isc::log::LoggerManager log_manager;
log_manager.process();
cerr << "Failed to initialize server: " << ex.what() << endl;
return (EXIT_FAILURE);
}
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
cerr << "Failed to initialize server: " << ex.what() << endl;
return (EXIT_FAILURE);
}
// And run the main loop of the server.
server.run();
LOG_INFO(dhcp6_logger, DHCP6_SHUTDOWN);
} catch (const std::exception& ex) {

View File

@ -18,6 +18,10 @@
namespace {
/// As of May 2014, maintaining or extending Bundy support is very low
/// prority for Kea team. We are looking for contributors, who would
/// like to maintain this backend.
// Bundy framework specific tests should be added here.
TEST(BundyBackendTest, dummy) {

View File

@ -210,22 +210,5 @@ class TestDhcpv6Daemon(unittest.TestCase):
# Check that there is an error message about invalid port number printed on stderr
self.assertEqual( str(error).count("Failed to parse port number"), 1)
def test_portnumber_nonroot(self):
print("Check that specifying unprivileged port number will work.")
# Check that there is a message about running with an unprivileged port
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP6_OPEN_SOCKET opening sockets on port 10547"), 1)
def test_skip_msgq(self):
print("Check that connection to BIND10 msgq can be disabled.")
# Check that the system outputs a message on one of its streams about running
# standalone.
(returncode, output, error) = self.runCommand(['../b10-dhcp6', '-v', '-s', '-p', '10547'])
output_text = str(output) + str(error)
self.assertEqual(output_text.count("DHCP6_STANDALONE"), 1)
if __name__ == '__main__':
unittest.main()

View File

@ -100,8 +100,12 @@ public:
/// Initializes logger
///
/// This method initializes logger.
static void loggerInit(const char* log_name, bool verbose, bool stand_alone);
/// This method initializes logger. Currently its implementation is specific
/// to each configuration backend.
///
/// @param log_name name used in logger initialization
/// @param verbose verbose mode (true usually enables DEBUG messages)
static void loggerInit(const char* log_name, bool verbose);
private: