summaryrefslogtreecommitdiffstats
path: root/src/bin/agent/ca_command_mgr.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/agent/ca_command_mgr.cc')
-rw-r--r--src/bin/agent/ca_command_mgr.cc278
1 files changed, 278 insertions, 0 deletions
diff --git a/src/bin/agent/ca_command_mgr.cc b/src/bin/agent/ca_command_mgr.cc
new file mode 100644
index 0000000..848753d
--- /dev/null
+++ b/src/bin/agent/ca_command_mgr.cc
@@ -0,0 +1,278 @@
+// Copyright (C) 2017-2022 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 <config.h>
+
+#include <agent/ca_cfg_mgr.h>
+#include <agent/ca_command_mgr.h>
+#include <agent/ca_controller.h>
+#include <agent/ca_log.h>
+#include <agent/ca_process.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/json_feed.h>
+#include <config/client_connection.h>
+#include <config/timeouts.h>
+#include <boost/pointer_cast.hpp>
+#include <iterator>
+#include <sstream>
+#include <string>
+#include <vector>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::hooks;
+using namespace isc::process;
+
+namespace isc {
+namespace agent {
+
+CtrlAgentCommandMgr&
+CtrlAgentCommandMgr::instance() {
+ static CtrlAgentCommandMgr command_mgr;
+ return (command_mgr);
+}
+
+CtrlAgentCommandMgr::CtrlAgentCommandMgr()
+ : HookedCommandMgr() {
+}
+
+isc::data::ConstElementPtr
+CtrlAgentCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
+ ConstElementPtr answer = HookedCommandMgr::processCommand(cmd);
+
+ // Responses from the Kea Control Agent must be always wrapped
+ // in a list because in general they contain responses from
+ // multiple daemons.
+ if (answer->getType() == Element::list) {
+ return (answer);
+ }
+ ElementPtr answer_list = Element::createList();
+ answer_list->add(boost::const_pointer_cast<Element>(answer));
+
+ return (answer_list);
+}
+
+ConstElementPtr
+CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
+ const isc::data::ConstElementPtr& params,
+ const isc::data::ConstElementPtr& original_cmd) {
+
+ ConstElementPtr raddr_ptr = original_cmd->get("remote-address");
+ if (raddr_ptr && (raddr_ptr->getType() == Element::string)) {
+ remote_addr_ = raddr_ptr->stringValue();
+ } else {
+ remote_addr_ = "(unknown)";
+ }
+ LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_RECEIVED)
+ .arg(cmd_name)
+ .arg(remote_addr_);
+
+ ConstElementPtr services = Element::createList();
+
+ // Retrieve 'service' parameter to determine if we should forward the
+ // command or handle it on our own.
+ if (original_cmd && original_cmd->contains("service")) {
+ services = original_cmd->get("service");
+ // If 'service' value is not a list, this is a fatal error. We don't want
+ // to try processing commands that don't adhere to the required format.
+ if (services->getType() != Element::list) {
+ return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
+ }
+ }
+
+ // 'service' parameter hasn't been specified which indicates that the command
+ // is intended to be processed by the CA. The following command will try to
+ // process the command with hooks libraries (if available) or by one of the
+ // CA's native handlers.
+ if (services->empty()) {
+
+ // It is frequent user error to not include the 'service' parameter in
+ // the commands that should be forwarded to Kea servers. If the command
+ // lacks this parameter the CA will try to process it and often fail
+ // because it is not supported by the CA. In the future we may want to
+ // make this parameter mandatory. For now, we're going to improve the
+ // situation by clearly explaining to the controlling client that the
+ // command is not supported by the CA, but it is possible that he may
+ // achieve what he wants by providing the 'service' parameter.
+
+ // Our interface is very restrictive so we walk around this by const
+ // casting the returned pointer. It is certainly easier to do than
+ // changing the whole data interface.
+ ElementPtr answer = boost::const_pointer_cast<Element>
+ (HookedCommandMgr::handleCommand(cmd_name, params, original_cmd));
+
+ try {
+ // Check what error code was returned by the handler.
+ int rcode = 0;
+ ConstElementPtr text = parseAnswer(rcode, answer);
+
+ // There is a dedicated error code for unsupported command case.
+ if (rcode == CONTROL_RESULT_COMMAND_UNSUPPORTED) {
+
+ // Append the explanatory text to the text reported by the handler.
+ // Smart, eh?
+ std::ostringstream s;
+ s << text->stringValue();
+ s << " You did not include \"service\" parameter in the command,"
+ " which indicates that Kea Control Agent should process this"
+ " command rather than forward it to one or more Kea servers. If you"
+ " aimed to send this command to one of the Kea servers you"
+ " should include the \"service\" parameter in your request, e.g."
+ " \"service\": [ \"dhcp4\" ] to forward the command to the DHCPv4"
+ " server, or \"service\": [ \"dhcp4\", \"dhcp6\", \"d2\" ] to forward it to"
+ " DHCPv4, DHCPv6 and D2 servers etc.";
+
+ answer->set(CONTROL_TEXT, Element::create(s.str()));
+ }
+
+ } catch (...) {
+ // Exceptions are not really possible assuming that the BaseCommandMgr
+ // creates the response correctly.
+ }
+
+ return (answer);
+ }
+
+ ElementPtr answer_list = Element::createList();
+
+ // Before the command is forwarded we check if there are any hooks libraries
+ // which would process the command.
+ if (HookedCommandMgr::delegateCommandToHookLibrary(cmd_name, params, original_cmd,
+ answer_list)) {
+ // The command has been processed by hooks library. Return the result.
+ return (answer_list);
+ }
+
+ // We don't know whether the hooks libraries modified the value of the
+ // answer list, so let's be safe and re-create the answer_list.
+ answer_list = Element::createList();
+
+ // For each value within 'service' we have to try forwarding the command.
+ for (unsigned i = 0; i < services->size(); ++i) {
+ if (original_cmd) {
+ ConstElementPtr answer;
+ try {
+ LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
+ CTRL_AGENT_COMMAND_FORWARD_BEGIN)
+ .arg(cmd_name).arg(services->get(i)->stringValue());
+
+ answer = forwardCommand(services->get(i)->stringValue(),
+ cmd_name, original_cmd);
+
+ } catch (const CommandForwardingError& ex) {
+ LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
+ CTRL_AGENT_COMMAND_FORWARD_FAILED)
+ .arg(cmd_name).arg(ex.what());
+ answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
+ }
+
+ answer_list->add(boost::const_pointer_cast<Element>(answer));
+ }
+ }
+
+ return (answer_list);
+}
+
+ConstElementPtr
+CtrlAgentCommandMgr::forwardCommand(const std::string& service,
+ const std::string& cmd_name,
+ const isc::data::ConstElementPtr& command) {
+ // Context will hold the server configuration.
+ CtrlAgentCfgContextPtr ctx;
+
+ // There is a hierarchy of the objects through which we need to pass to get
+ // the configuration context. We may simplify this at some point but since
+ // we're in the singleton we want to make sure that we're using most current
+ // configuration.
+ boost::shared_ptr<CtrlAgentController> controller =
+ boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
+ if (controller) {
+ CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
+ if (process) {
+ CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
+ if (cfgmgr) {
+ ctx = cfgmgr->getCtrlAgentCfgContext();
+ }
+ }
+ }
+
+ // This is highly unlikely but keep the checks just in case someone messes up
+ // in the code.
+ if (!ctx) {
+ isc_throw(CommandForwardingError, "internal server error: unable to retrieve"
+ " Control Agent configuration information");
+ }
+
+ // Now that we know what service it should be forwarded to, we should
+ // find a matching forwarding socket. If this socket is not configured,
+ // we have to communicate it to the client.
+ ConstElementPtr socket_info = ctx->getControlSocketInfo(service);
+ if (!socket_info) {
+ isc_throw(CommandForwardingError, "forwarding socket is not configured"
+ " for the server type " << service);
+ }
+
+ // If the configuration does its job properly the socket-name must be
+ // specified and must be a string value.
+ std::string socket_name = socket_info->get("socket-name")->stringValue();
+
+ // Forward command and receive reply.
+ IOServicePtr io_service(new IOService());;
+ ClientConnection conn(*io_service);
+ boost::system::error_code received_ec;
+ ConstJSONFeedPtr received_feed;
+ conn.start(ClientConnection::SocketPath(socket_name),
+ ClientConnection::ControlCommand(command->toWire()),
+ [&io_service, &received_ec, &received_feed]
+ (const boost::system::error_code& ec, ConstJSONFeedPtr feed) {
+ // Capture error code and parsed data.
+ received_ec = ec;
+ received_feed = feed;
+ // Got the IO service so stop IO service. This causes to
+ // stop IO service when all handlers have been invoked.
+ io_service->stopWork();
+ }, ClientConnection::Timeout(TIMEOUT_AGENT_FORWARD_COMMAND));
+ io_service->run();
+
+ if (received_ec) {
+ isc_throw(CommandForwardingError, "unable to forward command to the "
+ << service << " service: " << received_ec.message()
+ << ". The server is likely to be offline");
+ }
+
+ // This shouldn't happen because the fact that there was no time out indicates
+ // that the whole response has been read and it should be stored within the
+ // feed. But, let's check to prevent assertions.
+ if (!received_feed) {
+ isc_throw(CommandForwardingError, "internal server error: empty response"
+ " received from the unix domain socket");
+ }
+
+ ConstElementPtr answer;
+ try {
+ answer = received_feed->toElement();
+
+ LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
+ .arg(cmd_name)
+ .arg(service)
+ .arg(remote_addr_);
+
+ } catch (const std::exception& ex) {
+ isc_throw(CommandForwardingError, "internal server error: unable to parse"
+ " server's answer to the forwarded message: " << ex.what());
+ }
+
+ return (answer);
+}
+
+
+} // end of namespace isc::agent
+} // end of namespace isc