Kea: ca_command_mgr.cc Source File

| Kea 2.2.0

ca_command_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2022 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8
9#include <agent/ca_cfg_mgr.h>
10#include <agent/ca_command_mgr.h>
11#include <agent/ca_controller.h>
12#include <agent/ca_log.h>
13#include <agent/ca_process.h>
14#include <asiolink/asio_wrapper.h>
15#include <asiolink/io_service.h>
16#include <asiolink/unix_domain_socket.h>
17#include <cc/command_interpreter.h>
18#include <cc/data.h>
19#include <cc/json_feed.h>
20#include <config/client_connection.h>
21#include <config/timeouts.h>
22#include <boost/pointer_cast.hpp>
23#include <iterator>
24#include <sstream>
25#include <string>
26#include <vector>
27
28using namespace isc::asiolink;
29using namespace isc::config;
30using namespace isc::data;
31using namespace isc::hooks;
32using namespace isc::process;
33
34namespace isc {
35namespace agent {
36
37CtrlAgentCommandMgr&
38CtrlAgentCommandMgr::instance() {
39 static CtrlAgentCommandMgr command_mgr;
40 return (command_mgr);
41}
42
43CtrlAgentCommandMgr::CtrlAgentCommandMgr()
44 : HookedCommandMgr() {
45}
46
47 isc::data::ConstElementPtr
48CtrlAgentCommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
49 ConstElementPtr answer = HookedCommandMgr::processCommand(cmd);
50
51 // Responses from the Kea Control Agent must be always wrapped
52 // in a list because in general they contain responses from
53 // multiple daemons.
54 if (answer->getType() == Element::list) {
55 return (answer);
56 }
57 ElementPtr answer_list = Element::createList();
58 answer_list->add(boost::const_pointer_cast<Element>(answer));
59
60 return (answer_list);
61}
62
63 ConstElementPtr
64CtrlAgentCommandMgr::handleCommand(const std::string& cmd_name,
65 const isc::data::ConstElementPtr& params,
66 const isc::data::ConstElementPtr& original_cmd) {
67
68 ConstElementPtr raddr_ptr = original_cmd->get("remote-address");
69 if (raddr_ptr && (raddr_ptr->getType() == Element::string)) {
70 remote_addr_ = raddr_ptr->stringValue();
71 } else {
72 remote_addr_ = "(unknown)";
73 }
74 LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_RECEIVED)
75 .arg(cmd_name)
76 .arg(remote_addr_);
77
78 ConstElementPtr services = Element::createList();
79
80 // Retrieve 'service' parameter to determine if we should forward the
81 // command or handle it on our own.
82 if (original_cmd && original_cmd->contains("service")) {
83 services = original_cmd->get("service");
84 // If 'service' value is not a list, this is a fatal error. We don't want
85 // to try processing commands that don't adhere to the required format.
86 if (services->getType() != Element::list) {
87 return (createAnswer(CONTROL_RESULT_ERROR, "service value must be a list"));
88 }
89 }
90
91 // 'service' parameter hasn't been specified which indicates that the command
92 // is intended to be processed by the CA. The following command will try to
93 // process the command with hooks libraries (if available) or by one of the
94 // CA's native handlers.
95 if (services->empty()) {
96
97 // It is frequent user error to not include the 'service' parameter in
98 // the commands that should be forwarded to Kea servers. If the command
99 // lacks this parameter the CA will try to process it and often fail
100 // because it is not supported by the CA. In the future we may want to
101 // make this parameter mandatory. For now, we're going to improve the
102 // situation by clearly explaining to the controlling client that the
103 // command is not supported by the CA, but it is possible that he may
104 // achieve what he wants by providing the 'service' parameter.
105
106 // Our interface is very restrictive so we walk around this by const
107 // casting the returned pointer. It is certainly easier to do than
108 // changing the whole data interface.
109 ElementPtr answer = boost::const_pointer_cast<Element>
110 (HookedCommandMgr::handleCommand(cmd_name, params, original_cmd));
111
112 try {
113 // Check what error code was returned by the handler.
114 int rcode = 0;
115 ConstElementPtr text = parseAnswer(rcode, answer);
116
117 // There is a dedicated error code for unsupported command case.
118 if (rcode == CONTROL_RESULT_COMMAND_UNSUPPORTED) {
119
120 // Append the explanatory text to the text reported by the handler.
121 // Smart, eh?
122 std::ostringstream s;
123 s << text->stringValue();
124 s << " You did not include \"service\" parameter in the command,"
125 " which indicates that Kea Control Agent should process this"
126 " command rather than forward it to one or more Kea servers. If you"
127 " aimed to send this command to one of the Kea servers you"
128 " should include the \"service\" parameter in your request, e.g."
129 " \"service\": [ \"dhcp4\" ] to forward the command to the DHCPv4"
130 " server, or \"service\": [ \"dhcp4\", \"dhcp6\", \"d2\" ] to forward it to"
131 " DHCPv4, DHCPv6 and D2 servers etc.";
132
133 answer->set(CONTROL_TEXT, Element::create(s.str()));
134 }
135
136 } catch (...) {
137 // Exceptions are not really possible assuming that the BaseCommandMgr
138 // creates the response correctly.
139 }
140
141 return (answer);
142 }
143
144 ElementPtr answer_list = Element::createList();
145
146 // Before the command is forwarded we check if there are any hooks libraries
147 // which would process the command.
148 if (HookedCommandMgr::delegateCommandToHookLibrary(cmd_name, params, original_cmd,
149 answer_list)) {
150 // The command has been processed by hooks library. Return the result.
151 return (answer_list);
152 }
153
154 // We don't know whether the hooks libraries modified the value of the
155 // answer list, so let's be safe and re-create the answer_list.
156 answer_list = Element::createList();
157
158 // For each value within 'service' we have to try forwarding the command.
159 for (unsigned i = 0; i < services->size(); ++i) {
160 if (original_cmd) {
161 ConstElementPtr answer;
162 try {
163 LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
164 CTRL_AGENT_COMMAND_FORWARD_BEGIN)
165 .arg(cmd_name).arg(services->get(i)->stringValue());
166
167 answer = forwardCommand(services->get(i)->stringValue(),
168 cmd_name, original_cmd);
169
170 } catch (const CommandForwardingError& ex) {
171 LOG_DEBUG(agent_logger, isc::log::DBGLVL_COMMAND,
172 CTRL_AGENT_COMMAND_FORWARD_FAILED)
173 .arg(cmd_name).arg(ex.what());
174 answer = createAnswer(CONTROL_RESULT_ERROR, ex.what());
175 }
176
177 answer_list->add(boost::const_pointer_cast<Element>(answer));
178 }
179 }
180
181 return (answer_list);
182}
183
184 ConstElementPtr
185CtrlAgentCommandMgr::forwardCommand(const std::string& service,
186 const std::string& cmd_name,
187 const isc::data::ConstElementPtr& command) {
188 // Context will hold the server configuration.
189 CtrlAgentCfgContextPtr ctx;
190
191 // There is a hierarchy of the objects through which we need to pass to get
192 // the configuration context. We may simplify this at some point but since
193 // we're in the singleton we want to make sure that we're using most current
194 // configuration.
195 boost::shared_ptr<CtrlAgentController> controller =
196 boost::dynamic_pointer_cast<CtrlAgentController>(CtrlAgentController::instance());
197 if (controller) {
198 CtrlAgentProcessPtr process = controller->getCtrlAgentProcess();
199 if (process) {
200 CtrlAgentCfgMgrPtr cfgmgr = process->getCtrlAgentCfgMgr();
201 if (cfgmgr) {
202 ctx = cfgmgr->getCtrlAgentCfgContext();
203 }
204 }
205 }
206
207 // This is highly unlikely but keep the checks just in case someone messes up
208 // in the code.
209 if (!ctx) {
210 isc_throw(CommandForwardingError, "internal server error: unable to retrieve"
211 " Control Agent configuration information");
212 }
213
214 // Now that we know what service it should be forwarded to, we should
215 // find a matching forwarding socket. If this socket is not configured,
216 // we have to communicate it to the client.
217 ConstElementPtr socket_info = ctx->getControlSocketInfo(service);
218 if (!socket_info) {
219 isc_throw(CommandForwardingError, "forwarding socket is not configured"
220 " for the server type " << service);
221 }
222
223 // If the configuration does its job properly the socket-name must be
224 // specified and must be a string value.
225 std::string socket_name = socket_info->get("socket-name")->stringValue();
226
227 // Forward command and receive reply.
228 IOServicePtr io_service(new IOService());;
229 ClientConnection conn(*io_service);
230 boost::system::error_code received_ec;
231 ConstJSONFeedPtr received_feed;
232 conn.start(ClientConnection::SocketPath(socket_name),
233 ClientConnection::ControlCommand(command->toWire()),
234 [&io_service, &received_ec, &received_feed]
235 (const boost::system::error_code& ec, ConstJSONFeedPtr feed) {
236 // Capture error code and parsed data.
237 received_ec = ec;
238 received_feed = feed;
239 // Got the IO service so stop IO service. This causes to
240 // stop IO service when all handlers have been invoked.
241 io_service->stopWork();
242 }, ClientConnection::Timeout(TIMEOUT_AGENT_FORWARD_COMMAND));
243 io_service->run();
244
245 if (received_ec) {
246 isc_throw(CommandForwardingError, "unable to forward command to the "
247 << service << " service: " << received_ec.message()
248 << ". The server is likely to be offline");
249 }
250
251 // This shouldn't happen because the fact that there was no time out indicates
252 // that the whole response has been read and it should be stored within the
253 // feed. But, let's check to prevent assertions.
254 if (!received_feed) {
255 isc_throw(CommandForwardingError, "internal server error: empty response"
256 " received from the unix domain socket");
257 }
258
259 ConstElementPtr answer;
260 try {
261 answer = received_feed->toElement();
262
263 LOG_INFO(agent_logger, CTRL_AGENT_COMMAND_FORWARDED)
264 .arg(cmd_name)
265 .arg(service)
266 .arg(remote_addr_);
267
268 } catch (const std::exception& ex) {
269 isc_throw(CommandForwardingError, "internal server error: unable to parse"
270 " server's answer to the forwarded message: " << ex.what());
271 }
272
273 return (answer);
274}
275
276
277} // end of namespace isc::agent
278} // end of namespace isc
asio_wrapper.h
ca_cfg_mgr.h
ca_command_mgr.h
ca_controller.h
ca_log.h
ca_process.h
isc::Exception::what
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Definition: exceptions/exceptions.cc:32
isc::agent::CommandForwardingError
Exception thrown when an error occurred during control command forwarding.
Definition: ca_command_mgr.h:20
isc::agent::CtrlAgentCommandMgr
Command Manager for Control Agent.
Definition: ca_command_mgr.h:38
isc::agent::CtrlAgentCommandMgr::handleCommand
virtual isc::data::ConstElementPtr handleCommand(const std::string &cmd_name, const isc::data::ConstElementPtr &params, const isc::data::ConstElementPtr &original_cmd)
Handles the command having a given name and arguments.
Definition: ca_command_mgr.cc:64
isc::agent::CtrlAgentCommandMgr::instance
static CtrlAgentCommandMgr & instance()
Returns sole instance of the Command Manager.
Definition: ca_command_mgr.cc:38
isc::agent::CtrlAgentCommandMgr::processCommand
virtual isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
Definition: ca_command_mgr.cc:48
isc::agent::CtrlAgentController::instance
static process::DControllerBasePtr & instance()
Static singleton instance method.
Definition: ca_controller.cc:30
isc::asiolink::IOService
The IOService class is a wrapper for the ASIO io_service class.
Definition: io_service.h:33
isc::config::ClientConnection
Represents client side connection over the unix domain socket.
Definition: client_connection.h:70
isc::config::HookedCommandMgr
Command Manager which can delegate commands to a hook library.
Definition: hooked_command_mgr.h:39
client_connection.h
command_interpreter.h
This file contains several functions and constants that are used for handling commands and responses ...
data.h
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: exceptions/exceptions.h:210
io_service.h
json_feed.h
LOG_INFO
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
LOG_DEBUG
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
isc::agent::CTRL_AGENT_COMMAND_FORWARD_BEGIN
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARD_BEGIN
Definition: ca_messages.h:12
isc::agent::CtrlAgentCfgContextPtr
boost::shared_ptr< CtrlAgentCfgContext > CtrlAgentCfgContextPtr
Pointer to a configuration context.
Definition: ca_cfg_mgr.h:21
isc::agent::CtrlAgentProcessPtr
boost::shared_ptr< CtrlAgentProcess > CtrlAgentProcessPtr
Defines a shared pointer to CtrlAgentProcess.
Definition: ca_process.h:150
isc::agent::CTRL_AGENT_COMMAND_FORWARDED
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARDED
Definition: ca_messages.h:11
isc::agent::agent_logger
isc::log::Logger agent_logger("ctrl-agent")
Control Agent logger.
Definition: ca_log.h:18
isc::agent::CtrlAgentCfgMgrPtr
boost::shared_ptr< CtrlAgentCfgMgr > CtrlAgentCfgMgrPtr
Defines a shared pointer to CtrlAgentCfgMgr.
Definition: ca_cfg_mgr.h:311
isc::agent::CTRL_AGENT_COMMAND_RECEIVED
const isc::log::MessageID CTRL_AGENT_COMMAND_RECEIVED
Definition: ca_messages.h:14
isc::agent::CTRL_AGENT_COMMAND_FORWARD_FAILED
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARD_FAILED
Definition: ca_messages.h:13
isc::asiolink
Definition: addr_utilities.cc:170
isc::asiolink::IOServicePtr
boost::shared_ptr< IOService > IOServicePtr
Defines a smart pointer to an IOService instance.
Definition: io_service.h:110
isc::config
Definition: command_interpreter.cc:23
isc::config::ConstJSONFeedPtr
boost::shared_ptr< const JSONFeed > ConstJSONFeedPtr
Pointer to the const JSONFeed.
Definition: json_feed.h:27
isc::config::CONTROL_TEXT
const char * CONTROL_TEXT
String used for storing textual description ("text")
Definition: command_interpreter.cc:27
isc::config::CONTROL_RESULT_ERROR
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
Definition: command_interpreter.h:45
isc::config::CONTROL_RESULT_COMMAND_UNSUPPORTED
const int CONTROL_RESULT_COMMAND_UNSUPPORTED
Status code indicating that the specified command is not supported.
Definition: command_interpreter.h:48
isc::config::createAnswer
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
Definition: command_interpreter.cc:34
isc::config::parseAnswer
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Definition: command_interpreter.cc:69
isc::config::TIMEOUT_AGENT_FORWARD_COMMAND
constexpr long TIMEOUT_AGENT_FORWARD_COMMAND
Timeout for the Control Agent to forward command to a Kea server, e.g.
Definition: timeouts.h:31
isc::data
Definition: base_stamped_element.cc:12
isc::data::ConstElementPtr
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
isc::data::ElementPtr
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
isc::hooks
Definition: callout_handle.cc:21
isc::log::DBGLVL_COMMAND
const int DBGLVL_COMMAND
This debug level is reserved for logging the exchange of messages/commands between processes,...
Definition: log_dbglevels.h:54
isc::process
Definition: cb_ctl_base.h:20
isc
Defines the logger used by the top-level component of kea-lfc.
Definition: agent_parser.cc:143
isc::config::ClientConnection::ControlCommand
Encapsulates control command.
Definition: client_connection.h:86
isc::config::ClientConnection::SocketPath
Encapsulates socket path.
Definition: client_connection.h:78
isc::config::ClientConnection::Timeout
Encapsulates timeout value.
Definition: client_connection.h:94
timeouts.h
unix_domain_socket.h