Commit 7e9fdfa6 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰

Merge branch 'trac3400'

parents fccc9c59 5625dbfd
7XX. [func]* tomek
b10-dhcp6: New parameter added to configure: --with-kea-config.
It allows selecting configuration backend and accepts one of two
values: BIND10, which uses BIND10 framework as Kea 0.8 did, or
JSON, which reads configuration from a JSON file.
(Trac #3400, git TBD)
782. [func] tmark
Added sender-ip, sender-port, and max-queue-size parameters to
the dhcp-ddns configuration section of both b10-dhcp4 and b10-dhcp6.
......
......@@ -565,6 +565,9 @@ AC_SUBST(PYCOVERAGE)
AC_SUBST(PYCOVERAGE_RUN)
AC_SUBST(USE_PYCOVERAGE)
enable_gtest="no"
GTEST_INCLUDES=
......@@ -1282,6 +1285,34 @@ AC_SUBST(PERL)
AC_PATH_PROGS(AWK, gawk awk)
AC_SUBST(AWK)
# Kea configuration backend section
# Currently there are 2 backends available: BUNDY and JSON
# It is possible that we may extend this to accept additional backends.
AC_ARG_WITH(kea-config,
AC_HELP_STRING([--with-kea-config],
[Selects configuration backend; currently available options are: BUNDY (default,
Kea reads configuration and commands from Bundy framework) or JSON (Kea reads
configuration from a JSON file from disk)]),
[CONFIG_BACKEND="$withval"],
[CONFIG_BACKEND=BUNDY])
AM_CONDITIONAL(CONFIG_BACKEND_BUNDY, test "x$CONFIG_BACKEND" = "xBUNDY")
AM_CONDITIONAL(CONFIG_BACKEND_JSON, test "x$CONFIG_BACKEND" = "xJSON")
if test "x$CONFIG_BACKEND" = "xBUNDY"; then
AC_DEFINE(CONFIG_BACKEND_BUNDY, 1, [Define to 1 if Kea config was set to BUNDY])
fi
if test "x$CONFIG_BACKEND" = "xJSON"; then
AC_DEFINE(CONFIG_BACKEND_JSON, 1, [Define to 1 if Kea config was set to JSON])
fi
# Let's sanity check if the specified backend value is allowed
if test "x$CONFIG_BACKEND" != "xBUNDY" && test "x$CONFIG_BACKEND" != "xJSON"; then
AC_MSG_ERROR("Invalid configuration backend specified: $CONFIG_BACKEND. The only supported are: BUNDY JSON")
fi
AC_ARG_ENABLE(generate_docs, [AC_HELP_STRING([--enable-generate-docs],
[regenerate documentation using Docbook [default=no]])],
enable_generate_docs=$enableval, enable_generate_docs=no)
......@@ -1620,6 +1651,9 @@ SQLite:
SQLITE_VERSION: ${SQLITE_VERSION}
SQLITE_CFLAGS: ${SQLITE_CFLAGS}
SQLITE_LIBS: ${SQLITE_LIBS}
Kea config backend:
CONFIG_BACKEND: ${CONFIG_BACKEND}
END
# Avoid confusion on DNS/DHCP and only mention MySQL if it
......
......@@ -63,6 +63,7 @@
* - @subpage dhcpv6DDNSIntegration
* - @subpage dhcpv6OptionsParse
* - @subpage dhcpv6Classifier
* - @subpage dhcpv6ConfigBackend
* - @subpage dhcpv6Other
* - @subpage d2
* - @subpage d2CPL
......
# This is an example configuration file for DHCPv6 server in Kea.
# It's a basic scenario with four IPv6 subnets configured. In each
# subnet, there's a smaller pool of dynamic addresses.
{ "Dhcp6":
{
# Kea is told to listen on eth0 interface only.
"interfaces": [ "eth0" ],
# Addresses will be assigned with preferred and valid lifetimes
# being 3000 and 4000, respectively. 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).
"preferred-lifetime": 3000,
"valid-lifetime": 4000,
"renew-timer": 1000,
"rebind-timer": 2000,
# The following list defines subnets. Each subnet consists of at
# least subnet and pool entries.
"subnet6": [
{ "pool": [ "2001:db8:1::/80" ],
"subnet": "2001:db8:1::/64" },
{ "pool": [ "2001:db8:2::/80" ],
"subnet": "2001:db8:2::/64" },
{ "pool": [ "2001:db8:3::/80" ],
"subnet": "2001:db8:3::/64" },
{ "pool": [ "2001:db8:4::/80" ],
"subnet": "2001:db8:4::/64" } ]
}
}
......@@ -710,7 +710,9 @@ as a dependency earlier -->
<note>
<para>
For additional instructions concerning the building and installation of
Kea, see <xref linkend="dhcp-install-configure"/>.
Kea for various databases, see <xref linkend="dhcp-install-configure"/>.
For additional instructions concerning configuration backends, see
<xref linkend="dhcp-config-backend" />.
</para>
</note>
</para>
......@@ -1864,6 +1866,46 @@ address, but the usual ones don't." mean? -->
The DHCP-DDNS server details are covered in <xref linkend="dhcp-ddns-server"/>
</para>
<section id="dhcp-config-backend">
<title>Selecting configuration backend</title>
<para>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. This is currently only supported by DHCPv6 component.</para>
<variablelist>
<varlistentry>
<term>BIND10</term>
<listitem>
<simpara>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 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.</simpara>
</listitem>
</varlistentry>
<varlistentry>
<term>JSON</term>
<listitem>
<simpara>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.</simpara>
</listitem>
</varlistentry>
</variablelist>
</section>
<section id="dhcp-install-configure">
<title>DHCP Database Installation and Configuration</title>
<para>
......
......@@ -52,10 +52,18 @@ BUILT_SOURCES = spec_config.h dhcp6_messages.h dhcp6_messages.cc
pkglibexec_PROGRAMS = b10-dhcp6
b10_dhcp6_SOURCES = main.cc
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
b10_dhcp6_SOURCES += config_parser.cc config_parser.h
b10_dhcp6_SOURCES += dhcp6_log.cc dhcp6_log.h
b10_dhcp6_SOURCES += dhcp6_srv.cc dhcp6_srv.h
b10_dhcp6_SOURCES += ctrl_dhcp6_srv.cc ctrl_dhcp6_srv.h
b10_dhcp6_SOURCES += json_config_parser.cc json_config_parser.h
if CONFIG_BACKEND_BUNDY
b10_dhcp6_SOURCES += bundy_controller.cc
endif
if CONFIG_BACKEND_JSON
b10_dhcp6_SOURCES += kea_controller.cc
endif
nodist_b10_dhcp6_SOURCES = dhcp6_messages.h dhcp6_messages.cc
EXTRA_DIST += dhcp6_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 <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 <exceptions/exceptions.h>
#include <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
#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 dhcp6ConfigHandler 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
dhcp6StubConfigHandler(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 (!ControlledDhcpv6Srv::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(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
.arg(merged_config->str());
}
// Configure the server.
return (ControlledDhcpv6Srv::processConfig(merged_config));
}
void
ControlledDhcpv6Srv::init(const std::string& /* config_file*/) {
// This is Bundy configuration backed. It established control session
// that is used to connect to Bundy framework.
//
// Creates session that will be used to receive commands and updated
// configuration from cfgmgr (or indirectly from user via bindctl).
string specfile;
if (getenv("B10_FROM_BUILD")) {
specfile = string(getenv("B10_FROM_BUILD")) +
"/src/bin/dhcp6/dhcp6.spec";
} else {
specfile = string(DHCP6_SPECFILE_LOCATION);
}
/// @todo: Check if session is not established already. Throw, if it is.
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_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_,
dhcp6StubConfigHandler,
processCommand, false);
config_session_->start();
// The constructor already pulled the configuration that had
// been created in the previous session thanks to the dummy
// handler. We can switch to the handler that will be
// parsing future changes to the configuration.
config_session_->setConfigHandler(bundyConfigHandler);
try {
// Pull the full configuration out from the session.
processConfig(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());
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
}
/// Integrate the asynchronous I/O model of Bundy configuration
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv6Session.
int ctrl_socket = cc_session_->getSocketDesc();
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_CCSESSION_STARTED)
.arg(ctrl_socket);
IfaceMgr::instance().addExternalSocket(ctrl_socket, sessionReader);
return;
}
void ControlledDhcpv6Srv::cleanup() {
if (config_session_) {
delete config_session_;
config_session_ = NULL;
}
if (cc_session_) {
int ctrl_socket = cc_session_->getSocketDesc();
cc_session_->disconnect();
// deregister session socket
IfaceMgr::instance().deleteExternalSocket(ctrl_socket);
delete cc_session_;
cc_session_ = NULL;
}
}
void
Daemon::loggerInit(const char* log_name, bool verbose, bool stand_alone) {
isc::log::initLogger(log_name,
(verbose ? isc::log::DEBUG : isc::log::INFO),
isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
}
}; // end of isc::dhcp namespace
}; // end of isc namespace
// Copyright (C) 2012-2013 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,35 +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 <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/spec_config.h>
#include <exceptions/exceptions.h>
#include <hooks/hooks_manager.h>
#include <util/buffer.h>
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
#include <dhcp6/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 {
......@@ -50,73 +29,105 @@ namespace dhcp {
ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
ConstElementPtr
ControlledDhcpv6Srv::dhcp6StubConfigHandler(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."));
ControlledDhcpv6Srv::commandShutdownHandler(const string&, ConstElementPtr) {
if (ControlledDhcpv6Srv::server_) {
ControlledDhcpv6Srv::server_->shutdown();
} else {
LOG_WARN(dhcp6_logger, DHCP6_NOT_RUNNING);
ConstElementPtr answer = isc::config::createAnswer(1, "Shutdown failure.");
return (answer);
}
ConstElementPtr answer = isc::config::createAnswer(0, "Shutting down.");
return (answer);
}
ConstElementPtr
ControlledDhcpv6Srv::dhcp6ConfigHandler(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.");
ControlledDhcpv6Srv::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(dhcp6_logger, DHCP6_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
ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) {
return (processConfig(args));
}
// 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.
isc::data::ConstElementPtr
ControlledDhcpv6Srv::processCommand(const std::string& command,
isc::data::ConstElementPtr args) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)
.arg(command).arg(args->str());
// 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());
ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
// Merge an existing and new configuration.
isc::data::merge(merged_config, new_config);
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_UPDATE)
.arg(merged_config->str());
if (!srv) {
ConstElementPtr no_srv = isc::config::createAnswer(1,
"Server object not initialized, can't process command '" +
command + "'.");
return (no_srv);
}
// Configure the server.
ConstElementPtr answer = configureDhcp6Server(*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) {
return (answer);
} else if (command == "libreload") {
return (srv->commandLibReloadHandler(command, args));
} else if (command == "config-reload") {
return (srv->commandConfigReloadHandler(command, args));
}
return (isc::config::createAnswer(1, "Unrecognized command:"
+ command));
} catch (const Exception& ex) {
return (isc::config::createAnswer(1, "Error while processing command '"
+ command + "':" + ex.what()));
}
}
isc::data::ConstElementPtr
ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
if (!srv) {
ConstElementPtr no_srv = isc::config::createAnswer(1,
"Server object not initialized, can't process config.");
return (no_srv);
}
ConstElementPtr answer = configureDhcp6Server(*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) {
return (isc::config::createAnswer(1, "Failed to process configuration:"
+ string(ex.what())));
}
// 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 "
......@@ -129,141 +140,22 @@ ControlledDhcpv6Srv::dhcp6ConfigHandler(ConstElementPtr new_config) {
// 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());
srv->openActiveSockets(srv->getPort());
} catch (const std::exception& ex) {
std::ostringstream err;
err << "failed to open sockets after server reconfiguration: " << ex.what();
answer = isc::config::createAnswer(1, err.str());
}
return (answer);
}
ConstElementPtr
ControlledDhcpv6Srv::dhcp6CommandHandler(const string& command, ConstElementPtr args) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_COMMAND_RECEIVED)