Commit 83816e00 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

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

Conflicts:
	ChangeLog
	src/bin/dhcp6/kea_controller.cc
	src/lib/dhcpsrv/daemon.h
parents 26542e1c 6e4dd3ae
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.
......
......@@ -55,6 +55,7 @@
* - @subpage dhcpv4OptionsParse
* - @subpage dhcpv4DDNSIntegration
* - @subpage dhcpv4Classifier
* - @subpage dhcpv4ConfigBackend
* - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session
......
# 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" } ]
}
}
# 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" } ]
}
}
......@@ -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
......
// 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);
}
};
};
// 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 <cassert>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <dhcp4/json_config_parser.h>
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.
// 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());
// 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());
}
ConstElementPtr
ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
ConstElementPtr args) {
return (processConfig(args));
}
// Configure the server.
ConstElementPtr answer = configureDhcp4Server(*server_, merged_config);
ConstElementPtr
ControlledDhcpv4Srv::processCommand(const string& command,
ConstElementPtr args) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
.arg(command).arg(args->str());
// Check that configuration was successful. If not, do not reopen sockets.
int rcode = 0;
parseAnswer(rcode, answer);
if (rcode != 0) {
return (answer);
}
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
// Server will start DDNS communications if its enabled.
try {
server_->startD2();
} catch (const std::exception& ex) {
std::ostringstream err;
err << "error starting DHCP_DDNS client "
" after server reconfiguration: " << ex.what();
return (isc::config::createAnswer(1, err.str()));
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);
}
// 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);
}
if (command == "shutdown") {
return (srv->commandShutdownHandler(command, args));
ConstElementPtr
ControlledDhcpv4Srv::dhcp4CommandHandler(const string& command, ConstElementPtr args) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_COMMAND_RECEIVED)
.arg(command).arg(args->str());
} else if (command == "libreload") {
return (srv->commandLibReloadHandler(command, args));
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 == "config-reload") {
return (srv->commandConfigReloadHandler(command, args));
} 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.");
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() + "'"));
}
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();
}
}
isc::data::ConstElementPtr
ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
void ControlledDhcpv4Srv::establishSession() {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
.arg(config->str());
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
"/src/bin/dhcp4/dhcp4.spec";
} else {
specfile = string(DHCP4_SPECFILE_LOCATION);
}
ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
/// @todo: Check if session is not established already. Throw, if it is.
// Single stream instance used in all error clauses
std::ostringstream err;
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