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

[master] Merge branch 'trac5100'

parents 3b8641e4 8ef97bd6
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -292,6 +292,39 @@ to the end of this list.
expired leases will remain in the database and their recovery will
be attempted during the next reclaim cycle.
@subsection dhcpv4HooksControlCommandReceive control_command_receive
- @b Arguments:
- name: @b command, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
- name: @b response, type: isc::data::ConstElementPtr, direction: <b>in/out</b>
- @b Description: this callout is executed when DHCPv4 server receives a
control command over the command channel (typically unix domain socket).
The "command" argument is a pointer to the parsed JSON structure
including command name and command arguments. If the callout implements
the specified command, it handles the command and creates appropriate
response. The response should be returned in the "response" argument.
In most cases, the callout which handles the command will set the next
step action to SKIP, to prevent the server from trying to handle the
command on its own and overriding the response created by the callouts.
A notable exception is the 'list-commands' command for which the callouts
should not set the next step action to SKIP. The server has a special
code path for this command which combines the list of commands returned
by the callouts with the list of commands supported by the server. If
the callout sets the next step action to SKIP in this case, the server
will only return the list of commands supported by the hook library.
The callout can modify the command arguments to influence the command
processing by the Command Manager. For example, it may freely modify
the configuration received in 'set-config' before it is processed by
the server. The SKIP action is not set in this case.
- <b>Next step status</b>: if any callout sets the next step action to SKIP,
the server will assume that the command has been handled by the callouts
and will expect that the response is provided in the "response" argument.
The server will not handle the command in this case but simply return the
response returned by the callout to the caller.
@section dhcpv4HooksOptionsAccess Accessing DHCPv4 Options within a Packet
When the server constructs a response message to a client it includes
DHCP options configured for this client in a response message. Apart
......
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -334,6 +334,39 @@ to the end of this list.
expired leases will remain in the database and their recovery will
be attempted during the next reclaim cycle.
@subsection dhcpv6HooksControlCommandReceive control_command_receive
- @b Arguments:
- name: @b command, type: ConstElementPtr, direction: <b>in/out</b>
- name: @b response, type: ConstElementPtr, direction: <b>in/out</b>
- @b Description: this callout is executed when DHCPv4 server receives a
control command over the command channel (typically unix domain socket).
The "command" argument is a pointer to the parsed JSON structure
including command name and command arguments. If the callout implements
the specified command, it handles the command and creates appropriate
response. The response should be returned in the "response" argument.
In most cases, the callout which handles the command will set the next
step action to SKIP, to prevent the server from trying to handle the
command on its own and overriding the response created by the callouts.
A notable exception is the 'list-commands' command for which the callouts
should not set the next step action to SKIP. The server has a special
code path for this command which combines the list of commands returned
by the callouts with the list of commands supported by the server. If
the callout sets the next step action to SKIP in this case, the server
will only return the list of commands supported by the hook library.
The callout can modify the command arguments to influence the command
processing by the Command Manager. For example, it may freely modify
the configuration received in 'set-config' before it is processed by
the server. The SKIP action is not set in this case.
- <b>Next step status</b>: if any callout sets the next step action to SKIP,
the server will assume that the command has been handled by the callouts
and will expect that the response is provided in the "response" argument.
The server will not handle the command in this case but simply return the
response returned by the callout to the caller.
@section dhcpv6HooksOptionsAccess Accessing DHCPv6 Options within a Packet
When the server constructs a response message to a client it includes
DHCP options configured for this client in a response message. Apart
......
......@@ -15,16 +15,19 @@ BUILT_SOURCES = config_messages.h config_messages.cc
lib_LTLIBRARIES = libkea-cfgclient.la
libkea_cfgclient_la_SOURCES = config_data.h config_data.cc
libkea_cfgclient_la_SOURCES += module_spec.h module_spec.cc
libkea_cfgclient_la_SOURCES += base_command_mgr.cc base_command_mgr.h
libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
libkea_cfgclient_la_SOURCES += command_socket.cc command_socket.h
libkea_cfgclient_la_SOURCES += command_socket_factory.cc command_socket_factory.h
libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
libkea_cfgclient_la_SOURCES += hooked_command_mgr.cc hooked_command_mgr.h
libkea_cfgclient_la_LIBADD = $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
......
// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <cc/command_interpreter.h>
#include <config/base_command_mgr.h>
#include <config/config_log.h>
#include <boost/bind.hpp>
#include <set>
using namespace isc::data;
namespace isc {
namespace config {
BaseCommandMgr::BaseCommandMgr() {
registerCommand("list-commands", boost::bind(&BaseCommandMgr::listCommandsHandler,
this, _1, _2));
}
void
BaseCommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
if (!handler) {
isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
}
HandlerContainer::const_iterator it = handlers_.find(cmd);
if (it != handlers_.end()) {
isc_throw(InvalidCommandName, "Handler for command '" << cmd
<< "' is already installed.");
}
handlers_.insert(make_pair(cmd, handler));
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
}
void
BaseCommandMgr::deregisterCommand(const std::string& cmd) {
if (cmd == "list-commands") {
isc_throw(InvalidCommandName,
"Can't uninstall internal command 'list-commands'");
}
HandlerContainer::iterator it = handlers_.find(cmd);
if (it == handlers_.end()) {
isc_throw(InvalidCommandName, "Handler for command '" << cmd
<< "' not found.");
}
handlers_.erase(it);
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
}
void
BaseCommandMgr::deregisterAll() {
// No need to log anything here. deregisterAll is not used in production
// code, just in tests.
handlers_.clear();
registerCommand("list-commands",
boost::bind(&BaseCommandMgr::listCommandsHandler, this, _1, _2));
}
isc::data::ConstElementPtr
BaseCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
if (!cmd) {
return (createAnswer(CONTROL_RESULT_ERROR,
"Command processing failed: NULL command parameter"));
}
try {
ConstElementPtr arg;
std::string name = parseCommand(arg, cmd);
LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
return (handleCommand(name, arg));
} catch (const Exception& e) {
LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
return (createAnswer(CONTROL_RESULT_ERROR,
std::string("Error during command processing: ")
+ e.what()));
}
}
ConstElementPtr
BaseCommandMgr::combineCommandsLists(const ConstElementPtr& response1,
const ConstElementPtr& response2) const {
// Usually when this method is called there should be two non-null
// responses. If there is just a single response, return this
// response.
if (!response1 && response2) {
return (response2);
} else if (response1 && !response2) {
return (response1);
} else if (!response1 && !response2) {
return (ConstElementPtr());
} else {
// Both responses are non-null so we need to combine the lists
// of supported commands if the status codes are 0.
int status_code;
ConstElementPtr args1 = parseAnswer(status_code, response1);
if (status_code != 0) {
return (response1);
}
ConstElementPtr args2 = parseAnswer(status_code, response2);
if (status_code != 0) {
return (response2);
}
const std::vector<ElementPtr> vec1 = args1->listValue();
const std::vector<ElementPtr> vec2 = args2->listValue();
// Storing command names in a set guarantees that the non-unique
// command names are aggregated.
std::set<std::string> combined_set;
for (auto v = vec1.cbegin(); v != vec1.cend(); ++v) {
combined_set.insert((*v)->stringValue());
}
for (auto v = vec2.cbegin(); v != vec2.cend(); ++v) {
combined_set.insert((*v)->stringValue());
}
// Create a combined list of commands.
ElementPtr combined_list = Element::createList();
for (auto s = combined_set.cbegin(); s != combined_set.cend(); ++s) {
combined_list->add(Element::create(*s));
}
return (createAnswer(CONTROL_RESULT_SUCCESS, combined_list));
}
}
ConstElementPtr
BaseCommandMgr::handleCommand(const std::string& cmd_name,
const ConstElementPtr& params) {
auto it = handlers_.find(cmd_name);
if (it == handlers_.end()) {
// Ok, there's no such command.
return (createAnswer(CONTROL_RESULT_ERROR,
"'" + cmd_name + "' command not supported."));
}
// Call the actual handler and return whatever it returned
return (it->second(cmd_name, params));
}
isc::data::ConstElementPtr
BaseCommandMgr::listCommandsHandler(const std::string& name,
const isc::data::ConstElementPtr& ) {
using namespace isc::data;
ElementPtr commands = Element::createList();
for (HandlerContainer::const_iterator it = handlers_.begin();
it != handlers_.end(); ++it) {
commands->add(Element::create(it->first));
}
return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
}
} // namespace isc::config
} // namespace isc
// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef BASE_COMMAND_MGR_H
#define BASE_COMMAND_MGR_H
#include <cc/data.h>
#include <exceptions/exceptions.h>
#include <boost/function.hpp>
#include <map>
#include <string>
namespace isc {
namespace config {
/// @brief Exception indicating that the handler specified is not valid
class InvalidCommandHandler : public Exception {
public:
InvalidCommandHandler(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Exception indicating that the command name is not valid
class InvalidCommandName : public Exception {
public:
InvalidCommandName(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Commands Manager, responsible for processing external commands.
///
/// Commands Manager is a generic interface for handling external commands.
/// Commands are received over control sockets. Derivations of this class
/// provide implementations of the control socket layers, e.g. unix domain
/// sockets, TCP sockets etc. This base class merely provides methods to manage
/// command handling functions, i.e. register commands, deregister commands.
/// It also includes a @ref BaseCommandMgr::processCommand method which
/// uses the command as an input and invokes appropriate handlers.
///
/// The commands and responses are formatted using JSON.
/// See http://kea.isc.org/wiki/StatsDesign for details.
///
/// Below is an example of the command using JSON format:
/// @code
/// {
/// "command": "statistic-get",
/// "arguments": {
/// "name": "received-packets"
/// }
/// }
/// @endcode
///
/// And the response is:
///
/// @code
/// {
/// "result": 0,
/// "arguments": {
/// "received-packets": [ [ 1234, "2015-04-15 12:34:45.123" ] ]
/// }
/// }
/// @endcode
///
/// BaseCommandsMgr does not implement the commands (except one,
/// "list-commands") itself, but rather provides an interface
/// (see @ref registerCommand, @ref deregisterCommand, @ref processCommand)
/// for other components to use it.
class BaseCommandMgr {
public:
/// @brief Defines command handler type
///
/// Command handlers are expected to use this format.
///
/// @param name name of the commands
/// @param params parameters specific to the command
/// @return response (created with createAnswer())
typedef boost::function<isc::data::ConstElementPtr (const std::string& name,
const isc::data::ConstElementPtr& params)> CommandHandler;
/// @brief Constructor.
///
/// Registers "list-commands" command.
BaseCommandMgr();
/// @brief Destructor.
virtual ~BaseCommandMgr() { };
/// @brief Triggers command processing.
///
/// This method processes specified command. The command is specified using
/// a single Element. See @ref BaseCommandMgr for description of its syntax.
///
/// @param cmd Pointer to the data element representing command in JSON
/// format.
isc::data::ConstElementPtr
processCommand(const isc::data::ConstElementPtr& cmd);
/// @brief Registers specified command handler for a given command
///
/// @param cmd Name of the command to be handled.
/// @param handler Pointer to the method that will handle the command.
void registerCommand(const std::string& cmd, CommandHandler handler);
/// @brief Deregisters specified command handler.
///
/// @param cmd Name of the command that's no longer handled.
void deregisterCommand(const std::string& cmd);
/// @brief Auxiliary method that removes all installed commands.
///
/// The only unwipeable method is list-commands, which is internally
/// handled at all times.
void deregisterAll();
protected:
/// @brief Combines lists of commands carried in two responses.
///
/// This method is used to combine list of commands returned by the
/// hook library with the commands supported by the local Command
/// Manager. This method should also be used within the hook library
/// to combine commands supported by this hook library with the
/// commands returned by other hook libraries attached to the server
/// at the same time.
///
/// If the same command appears in two responses only a single
/// instance is returned in the combined response.
///
/// @param response1 First command response.
/// @param response2 Second command response.
///
/// @return Pointer to the 'list-commands' response holding combined
/// list of commands.
isc::data::ConstElementPtr
combineCommandsLists(const isc::data::ConstElementPtr& response1,
const isc::data::ConstElementPtr& response2) const;
/// @brief Handles the command having a given name and arguments.
///
/// This method can be overridden in the derived classes to provide
/// custom logic for processing commands. For example, the
/// @ref HookedCommandMgr extends this method to delegate commands
/// processing to a hook library.
///
/// @param cmd_name Command name.
/// @param params Command arguments.
///
/// @return Pointer to the const data element representing response
/// to a command.
virtual isc::data::ConstElementPtr
handleCommand(const std::string& cmd_name,
const isc::data::ConstElementPtr& params);
/// @brief Type of the container for command handlers.
typedef std::map<std::string, CommandHandler> HandlerContainer;
/// @brief Container for command handlers.
HandlerContainer handlers_;
private:
/// @brief 'list-commands' command handler.
///
/// This method implements command 'list-commands'. It returns a list of all
/// currently supported commands.
///
/// @param name Name of the command (should always be 'list-commands').
/// @param params Additional parameters (ignored).
///
/// @return Pointer to the structure that includes all currently supported
/// commands.
isc::data::ConstElementPtr
listCommandsHandler(const std::string& name,
const isc::data::ConstElementPtr& params);
};
} // end of namespace isc::config
} // end of namespace isc
#endif
......@@ -18,9 +18,8 @@ using namespace isc::data;
namespace isc {
namespace config {
CommandMgr::CommandMgr() {
registerCommand("list-commands",
boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
CommandMgr::CommandMgr()
: HookedCommandMgr() {
}
CommandSocketPtr
......@@ -77,48 +76,6 @@ CommandMgr::instance() {
return (cmd_mgr);
}
void CommandMgr::registerCommand(const std::string& cmd, CommandHandler handler) {
if (!handler) {
isc_throw(InvalidCommandHandler, "Specified command handler is NULL");
}
HandlerContainer::const_iterator it = handlers_.find(cmd);
if (it != handlers_.end()) {
isc_throw(InvalidCommandName, "Handler for command '" << cmd
<< "' is already installed.");
}
handlers_.insert(make_pair(cmd, handler));
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
}
void CommandMgr::deregisterCommand(const std::string& cmd) {
if (cmd == "list-commands") {
isc_throw(InvalidCommandName,
"Can't uninstall internal command 'list-commands'");
}
HandlerContainer::iterator it = handlers_.find(cmd);
if (it == handlers_.end()) {
isc_throw(InvalidCommandName, "Handler for command '" << cmd
<< "' not found.");
}
handlers_.erase(it);
LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
}
void CommandMgr::deregisterAll() {
// No need to log anything here. deregisterAll is not used in production
// code, just in tests.
handlers_.clear();
registerCommand("list-commands",
boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
}
void
CommandMgr::commandReader(int sockfd) {
......@@ -217,48 +174,5 @@ CommandMgr::commandReader(int sockfd) {
}
}
isc::data::ConstElementPtr
CommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
if (!cmd) {
return (createAnswer(CONTROL_RESULT_ERROR,
"Command processing failed: NULL command parameter"));
}
try {
ConstElementPtr arg;
std::string name = parseCommand(arg, cmd);
LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
HandlerContainer::const_iterator it = handlers_.find(name);
if (it == handlers_.end()) {
// Ok, there's no such command.
return (createAnswer(CONTROL_RESULT_ERROR,
"'" + name + "' command not supported."));
}
// Call the actual handler and return whatever it returned
return (it->second(name, arg));
} catch (const Exception& e) {
LOG_WARN(command_logger, COMMAND_PROCESS_ERROR2).arg(e.what());
return (createAnswer(CONTROL_RESULT_ERROR,
std::string("Error during command processing:")
+ e.what()));
}
}
isc::data::ConstElementPtr
CommandMgr::listCommandsHandler(const std::string& name,
const isc::data::ConstElementPtr& params) {
using namespace isc::data;
ElementPtr commands = Element::createList();
for (HandlerContainer::const_iterator it = handlers_.begin();
it != handlers_.end(); ++it) {
commands->add(Element::create(it->first));
}
return (createAnswer(CONTROL_RESULT_SUCCESS, commands));
}
}; // end of isc::config
}; // end of isc
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
......@@ -8,72 +8,21 @@
#define COMMAND_MGR_H
#include <cc/data.h>
#include <config/hooked_command_mgr.h>
#include <config/command_socket.h>
#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
#include <string>
#include <list>
#include <map>
namespace isc {
namespace config {
/// @brief CommandMgr exception indicating that the handler specified is not valid
class InvalidCommandHandler : public Exception {
public:
InvalidCommandHandler(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief CommandMgr exception indicating that the command name is not valid
class InvalidCommandName : public Exception {
public:
InvalidCommandName(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
/// @brief Commands Manager, responsible for processing external commands
/// @brief Commands Manager implementation for the Kea servers.
///
/// Commands Manager is a generic interface for handling external commands.
/// Commands can be received over control sockets. Currently unix socket is
/// supported, but additional type (udp, tcp, https etc.) may be added later.
/// The commands and responses are sent in JSON format.
/// See http://kea.isc.org/wiki/StatsDesign for details.
///