Kea  2.1.7-git
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>
17 #include <cc/command_interpreter.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 
28 using namespace isc::asiolink;
29 using namespace isc::config;
30 using namespace isc::data;
31 using namespace isc::hooks;
32 using namespace isc::process;
33 
34 namespace isc {
35 namespace agent {
36 
37 CtrlAgentCommandMgr&
38 CtrlAgentCommandMgr::instance() {
39  static CtrlAgentCommandMgr command_mgr;
40  return (command_mgr);
41 }
42 
43 CtrlAgentCommandMgr::CtrlAgentCommandMgr()
44  : HookedCommandMgr() {
45 }
46 
48 CtrlAgentCommandMgr::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 
64 CtrlAgentCommandMgr::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 
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 {
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());
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 
185 CtrlAgentCommandMgr::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();
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 
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
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARD_BEGIN
Definition: ca_messages.h:12
Exception thrown when an error occurred during control command forwarding.
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARD_FAILED
Definition: ca_messages.h:13
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
boost::shared_ptr< Element > ElementPtr
Definition: data.h:24
boost::shared_ptr< CtrlAgentCfgMgr > CtrlAgentCfgMgrPtr
Defines a shared pointer to CtrlAgentCfgMgr.
Definition: ca_cfg_mgr.h:311
Represents client side connection over the unix domain socket.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
boost::shared_ptr< CtrlAgentProcess > CtrlAgentProcessPtr
Defines a shared pointer to CtrlAgentProcess.
Definition: ca_process.h:150
isc::log::Logger agent_logger("ctrl-agent")
Control Agent logger.
Definition: ca_log.h:18
Command Manager which can delegate commands to a hook library.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
CtrlAgentProcessPtr getCtrlAgentProcess()
Returns pointer to an instance of the underlying process object.
Command Manager for Control Agent.
const isc::log::MessageID CTRL_AGENT_COMMAND_RECEIVED
Definition: ca_messages.h:14
const char * CONTROL_TEXT
String used for storing textual description ("text")
Process Controller for Control Agent Process.
Definition: ca_controller.h:21
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
constexpr long TIMEOUT_AGENT_FORWARD_COMMAND
Timeout for the Control Agent to forward command to a Kea server, e.g.
Definition: timeouts.h:31
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
Defines the logger used by the top-level component of kea-lfc.
boost::shared_ptr< const JSONFeed > ConstJSONFeedPtr
Pointer to the const JSONFeed.
Definition: json_feed.h:27
const isc::log::MessageID CTRL_AGENT_COMMAND_FORWARDED
Definition: ca_messages.h:11
This file contains several functions and constants that are used for handling commands and responses ...
const int DBGLVL_COMMAND
This debug level is reserved for logging the exchange of messages/commands between processes...
Definition: log_dbglevels.h:54
The Element class represents a piece of data, used by the command channel and configuration parts...
Definition: data.h:70
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
boost::shared_ptr< CtrlAgentCfgContext > CtrlAgentCfgContextPtr
Pointer to a configuration context.
Definition: ca_cfg_mgr.h:21
Encapsulates timeout value.
void start(const SocketPath &socket_path, const ControlCommand &command, Handler handler, const Timeout &timeout=Timeout(5000))
Starts asynchronous transaction with a remote endpoint.
const int CONTROL_RESULT_COMMAND_UNSUPPORTED
Status code indicating that the specified command is not supported.