Kea 2.6.2
ca_command_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2017-2024 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>
11#include <agent/ca_controller.h>
12#include <agent/ca_log.h>
13#include <agent/ca_process.h>
15#include <asiolink/io_service.h>
18#include <cc/data.h>
19#include <cc/json_feed.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
39 static CtrlAgentCommandMgr command_mgr;
40 return (command_mgr);
41}
42
43CtrlAgentCommandMgr::CtrlAgentCommandMgr()
45}
46
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
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 }
75 .arg(cmd_name)
76 .arg(remote_addr_);
77
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.
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) {
162 try {
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) {
173 .arg(cmd_name).arg(ex.what());
175 }
176
177 answer_list->add(boost::const_pointer_cast<Element>(answer));
178 }
179 }
180
181 return (answer_list);
182}
183
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.
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
260 try {
261 answer = received_feed->toElement();
262
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
it forwards queries to a single upstream resolver and passes the answers back to the client It is constructed with the address of the forward server Queries are initiated with the question to ask the forward a buffer into which to write the answer
Definition asiodns.dox:60
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition data.cc:249
@ list
Definition data.h:146
@ string
Definition data.h:144
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:299
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Exception thrown when an error occurred during control command forwarding.
Command Manager for Control Agent.
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.
static CtrlAgentCommandMgr & instance()
Returns sole instance of the Command Manager.
virtual isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
static process::DControllerBasePtr & instance()
Static singleton instance method.
virtual isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
Command Manager which can delegate commands to a hook library.
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.
bool delegateCommandToHookLibrary(const std::string &cmd_name, const isc::data::ConstElementPtr &params, const isc::data::ConstElementPtr &original_cmd, isc::data::ElementPtr &answer)
Handles the command within the hooks libraries.
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition macros.h:14
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARD_BEGIN
Definition ca_messages.h:12
boost::shared_ptr< CtrlAgentCfgContext > CtrlAgentCfgContextPtr
Pointer to a configuration context.
Definition ca_cfg_mgr.h:23
boost::shared_ptr< CtrlAgentProcess > CtrlAgentProcessPtr
Defines a shared pointer to CtrlAgentProcess.
Definition ca_process.h:150
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARDED
Definition ca_messages.h:11
isc::log::Logger agent_logger("ctrl-agent")
Control Agent logger.
Definition ca_log.h:18
boost::shared_ptr< CtrlAgentCfgMgr > CtrlAgentCfgMgrPtr
Defines a shared pointer to CtrlAgentCfgMgr.
Definition ca_cfg_mgr.h:311
const isc::log::MessageID CTRL_AGENT_COMMAND_RECEIVED
Definition ca_messages.h:14
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARD_FAILED
Definition ca_messages.h:13
boost::shared_ptr< const JSONFeed > ConstJSONFeedPtr
Pointer to the const JSONFeed.
Definition json_feed.h:27
const char * CONTROL_TEXT
String used for storing textual description ("text")
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Parses a standard config/command level answer and returns arguments or text status code.
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
const int CONTROL_RESULT_COMMAND_UNSUPPORTED
Status code indicating that the specified command is not supported.
constexpr long TIMEOUT_AGENT_FORWARD_COMMAND
Timeout for the Control Agent to forward command to a Kea server, e.g.
Definition timeouts.h:31
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
boost::shared_ptr< Element > ElementPtr
Definition data.h:28
const int DBGLVL_COMMAND
This debug level is reserved for logging the exchange of messages/commands between processes,...
Defines the logger used by the top-level component of kea-lfc.