Kea  1.5.0
command_mgr.cc
Go to the documentation of this file.
1 // Copyright (C) 2015-2018 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 
11 #include <asiolink/io_service.h>
15 #include <config/command_mgr.h>
16 #include <cc/data.h>
17 #include <cc/command_interpreter.h>
18 #include <cc/json_feed.h>
19 #include <dhcp/iface_mgr.h>
20 #include <config/config_log.h>
21 #include <config/timeouts.h>
22 #include <util/watch_socket.h>
23 #include <boost/bind.hpp>
24 #include <boost/enable_shared_from_this.hpp>
25 #include <array>
26 #include <unistd.h>
27 
28 using namespace isc;
29 using namespace isc::asiolink;
30 using namespace isc::config;
31 using namespace isc::data;
32 
33 namespace {
34 
36 const size_t BUF_SIZE = 32768;
37 
38 class ConnectionPool;
39 
44 class Connection : public boost::enable_shared_from_this<Connection> {
45 public:
46 
64  Connection(const IOServicePtr& io_service,
65  const boost::shared_ptr<UnixDomainSocket>& socket,
66  ConnectionPool& connection_pool,
67  const long timeout)
68  : socket_(socket), timeout_timer_(*io_service), timeout_(timeout),
69  buf_(), response_(), connection_pool_(connection_pool), feed_(),
70  response_in_progress_(false), watch_socket_(new util::WatchSocket()) {
71 
72  LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_OPENED)
73  .arg(socket_->getNative());
74 
75  // Callback value of 0 is used to indicate that callback function is
76  // not installed.
77  isc::dhcp::IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
78  isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
79 
80  // Initialize state model for receiving and preparsing commands.
81  feed_.initModel();
82 
83  // Start timer for detecting timeouts.
84  scheduleTimer();
85  }
86 
90  ~Connection() {
91  timeout_timer_.cancel();
92  }
93 
95  void scheduleTimer() {
96  timeout_timer_.setup(boost::bind(&Connection::timeoutHandler, this),
97  timeout_, IntervalTimer::ONE_SHOT);
98  }
99 
106  void stop() {
107  if (!response_in_progress_) {
108  LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_CLOSED)
109  .arg(socket_->getNative());
110 
111  isc::dhcp::IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
112  isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
113 
114  // Close watch socket and log errors if occur.
115  std::string watch_error;
116  if (!watch_socket_->closeSocket(watch_error)) {
117  LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLOSE_ERROR)
118  .arg(watch_error);
119  }
120 
121  socket_->close();
122  timeout_timer_.cancel();
123  }
124  }
125 
130  void terminate();
131 
137  void doReceive() {
138  socket_->asyncReceive(&buf_[0], sizeof(buf_),
139  boost::bind(&Connection::receiveHandler,
140  shared_from_this(), _1, _2));
141  }
142 
150  void doSend() {
151  size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
152  socket_->asyncSend(&response_[0], chunk_size,
153  boost::bind(&Connection::sendHandler, shared_from_this(), _1, _2));
154 
155  // Asynchronous send has been scheduled and we need to indicate this
156  // to break the synchronous select(). The handler should clear this
157  // status when invoked.
158  try {
159  watch_socket_->markReady();
160 
161  } catch (const std::exception& ex) {
162  LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_MARK_READY_ERROR)
163  .arg(ex.what());
164  }
165  }
166 
175  //
179  void receiveHandler(const boost::system::error_code& ec,
180  size_t bytes_transferred);
181 
182 
191  void sendHandler(const boost::system::error_code& ec,
192  size_t bytes_transferred);
193 
198  void timeoutHandler();
199 
200 private:
201 
203  boost::shared_ptr<UnixDomainSocket> socket_;
204 
206  IntervalTimer timeout_timer_;
207 
209  long timeout_;
210 
212  std::array<char, BUF_SIZE> buf_;
213 
215  std::string response_;
216 
218  ConnectionPool& connection_pool_;
219 
222  JSONFeed feed_;
223 
226  bool response_in_progress_;
227 
230  util::WatchSocketPtr watch_socket_;
231 };
232 
234 typedef boost::shared_ptr<Connection> ConnectionPtr;
235 
237 class ConnectionPool {
238 public:
239 
243  void start(const ConnectionPtr& connection) {
244  connection->doReceive();
245  connections_.insert(connection);
246  }
247 
251  void stop(const ConnectionPtr& connection) {
252  try {
253  connection->stop();
254  connections_.erase(connection);
255  } catch (const std::exception& ex) {
256  LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CLOSE_FAIL)
257  .arg(ex.what());
258  }
259  }
260 
262  void stopAll() {
263  for (auto conn = connections_.begin(); conn != connections_.end();
264  ++conn) {
265  (*conn)->stop();
266  }
267  connections_.clear();
268  }
269 
270 private:
271 
273  std::set<ConnectionPtr> connections_;
274 
275 };
276 
277 void
278 Connection::terminate() {
279  try {
280  socket_->shutdown();
281 
282  } catch (const std::exception& ex) {
283  LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL)
284  .arg(ex.what());
285  }
286 }
287 
288 void
289 Connection::receiveHandler(const boost::system::error_code& ec,
290  size_t bytes_transferred) {
291  if (ec) {
292  if (ec.value() == boost::asio::error::eof) {
293  std::stringstream os;
294  if (feed_.getProcessedText().empty()) {
295  os << "no input data to discard";
296  }
297  else {
298  os << "discarding partial command of "
299  << feed_.getProcessedText().size() << " bytes";
300  }
301 
302  // Foreign host has closed the connection. We should remove it from the
303  // connection pool.
304  LOG_INFO(command_logger, COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST)
305  .arg(socket_->getNative()).arg(os.str());
306  } else if (ec.value() != boost::asio::error::operation_aborted) {
307  LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL)
308  .arg(ec.value()).arg(socket_->getNative());
309  }
310 
311  connection_pool_.stop(shared_from_this());
312  return;
313 
314  } else if (bytes_transferred == 0) {
315  // Nothing received. Close the connection.
316  connection_pool_.stop(shared_from_this());
317  return;
318  }
319 
320  LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ)
321  .arg(bytes_transferred).arg(socket_->getNative());
322 
323  // Reschedule the timer because the transaction is ongoing.
324  scheduleTimer();
325 
326  ConstElementPtr rsp;
327 
328  try {
329  // Received some data over the socket. Append them to the JSON feed
330  // to see if we have reached the end of command.
331  feed_.postBuffer(&buf_[0], bytes_transferred);
332  feed_.poll();
333  // If we haven't yet received the full command, continue receiving.
334  if (feed_.needData()) {
335  doReceive();
336  return;
337  }
338 
339  // Received entire command. Parse the command into JSON.
340  if (feed_.feedOk()) {
341  ConstElementPtr cmd = feed_.toElement();
342  response_in_progress_ = true;
343 
344  // Cancel the timer to make sure that long lasting command
345  // processing doesn't cause the timeout.
346  timeout_timer_.cancel();
347 
348  // If successful, then process it as a command.
349  rsp = CommandMgr::instance().processCommand(cmd);
350 
351  response_in_progress_ = false;
352 
353  } else {
354  // Failed to parse command as JSON or process the received command.
355  // This exception will be caught below and the error response will
356  // be sent.
357  isc_throw(BadValue, feed_.getErrorMessage());
358  }
359 
360  } catch (const Exception& ex) {
361  LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
362  rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
363  }
364 
365  // No response generated. Connection will be closed.
366  if (!rsp) {
367  LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
369  "internal server error: no response generated");
370 
371  } else {
372 
373  // Reschedule the timer as it may be either canceled or need to be
374  // updated to not timeout before we manage to the send the reply.
375  scheduleTimer();
376 
377  // Let's convert JSON response to text. Note that at this stage
378  // the rsp pointer is always set.
379  response_ = rsp->str();
380 
381  doSend();
382  return;
383  }
384 
385  // Close the connection if we have sent the entire response.
386  connection_pool_.stop(shared_from_this());
387 }
388 
389 void
390 Connection::sendHandler(const boost::system::error_code& ec,
391  size_t bytes_transferred) {
392  // Clear the watch socket so as the future send operation can mark it
393  // again to interrupt the synchronous select() call.
394  try {
395  watch_socket_->clearReady();
396 
397  } catch (const std::exception& ex) {
398  LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLEAR_ERROR)
399  .arg(ex.what());
400  }
401 
402  if (ec) {
403  // If an error occurred, log this error and stop the connection.
404  if (ec.value() != boost::asio::error::operation_aborted) {
405  LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
406  .arg(socket_->getNative()).arg(ec.message());
407  }
408 
409  } else {
410 
411  // Reschedule the timer because the transaction is ongoing.
412  scheduleTimer();
413 
414  // No error. We are in a process of sending a response. Need to
415  // remove the chunk that we have managed to sent with the previous
416  // attempt.
417  response_.erase(0, bytes_transferred);
418 
419  LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE)
420  .arg(bytes_transferred).arg(response_.size())
421  .arg(socket_->getNative());
422 
423  // Check if there is any data left to be sent and sent it.
424  if (!response_.empty()) {
425  doSend();
426  return;
427  }
428 
429  // Gracefully shutdown the connection and close the socket if
430  // we have sent the whole response.
431  terminate();
432  }
433 
434  // All data sent or an error has occurred. Close the connection.
435  connection_pool_.stop(shared_from_this());
436 }
437 
438 void
439 Connection::timeoutHandler() {
440  LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_TIMEOUT)
441  .arg(socket_->getNative());
442 
443  try {
444  socket_->cancel();
445 
446  } catch (const std::exception& ex) {
447  LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CANCEL_FAIL)
448  .arg(socket_->getNative())
449  .arg(ex.what());
450  }
451 
452  std::stringstream os;
453  os << "Connection over control channel timed out";
454  if (!feed_.getProcessedText().empty()) {
455  os << ", discarded partial command of "
456  << feed_.getProcessedText().size() << " bytes";
457  }
458 
460  response_ = rsp->str();
461  doSend();
462 }
463 
464 
465 }
466 
467 namespace isc {
468 namespace config {
469 
472 public:
473 
476  : io_service_(), acceptor_(), socket_(), socket_name_(),
477  connection_pool_(), timeout_(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND) {
478  }
479 
485  void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
486 
488  void doAccept();
489 
493 
495  boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
496 
498  boost::shared_ptr<UnixDomainSocket> socket_;
499 
503  std::string socket_name_;
504 
506  ConnectionPool connection_pool_;
507 
509  long timeout_;
510 };
511 
512 void
513 CommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
514  socket_name_.clear();
515 
516  if(!socket_info) {
517  isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
518  }
519 
520  ConstElementPtr type = socket_info->get("socket-type");
521  if (!type) {
522  isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
523  }
524 
525  // Only supporting unix sockets right now.
526  if (type->stringValue() != "unix") {
527  isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
528  << type->stringValue());
529  }
530 
531  // UNIX socket is requested. It takes one parameter: socket-name that
532  // specifies UNIX path of the socket.
533  ConstElementPtr name = socket_info->get("socket-name");
534  if (!name) {
535  isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
536  }
537 
538  if (name->getType() != Element::string) {
539  isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
540  }
541 
542  socket_name_ = name->stringValue();
543 
544  LOG_INFO(command_logger, COMMAND_ACCEPTOR_START)
545  .arg(socket_name_);
546 
547  try {
548  // Start asynchronous acceptor service.
549  acceptor_.reset(new UnixDomainSocketAcceptor(*io_service_));
550  UnixDomainSocketEndpoint endpoint(socket_name_);
551  acceptor_->open(endpoint);
552  acceptor_->bind(endpoint);
553  acceptor_->listen();
554 
555  // Install this socket in Interface Manager.
556  isc::dhcp::IfaceMgr::instance().addExternalSocket(acceptor_->getNative(), 0);
557 
558  doAccept();
559 
560  } catch (const std::exception& ex) {
561  isc_throw(SocketError, ex.what());
562  }
563 }
564 
565 void
566 CommandMgrImpl::doAccept() {
567  // Create a socket into which the acceptor will accept new connection.
568  socket_.reset(new UnixDomainSocket(*io_service_));
569  acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
570  if (!ec) {
571  // New connection is arriving. Start asynchronous transmission.
572  ConnectionPtr connection(new Connection(io_service_, socket_,
573  connection_pool_,
574  timeout_));
575  connection_pool_.start(connection);
576 
577  } else if (ec.value() != boost::asio::error::operation_aborted) {
578  LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
579  .arg(acceptor_->getNative()).arg(ec.message());
580  }
581 
582  // Unless we're stopping the service, start accepting connections again.
583  if (ec.value() != boost::asio::error::operation_aborted) {
584  doAccept();
585  }
586  });
587 }
588 
589 CommandMgr::CommandMgr()
590  : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
591 }
592 
593 void
595  impl_->openCommandSocket(socket_info);
596 }
597 
599  // Close acceptor if the acceptor is open.
600  if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
601  isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
602  impl_->acceptor_->close();
603  static_cast<void>(::remove(impl_->socket_name_.c_str()));
604  }
605 
606  // Stop all connections which can be closed. The only connection that won't
607  // be closed is the one over which we have received a request to reconfigure
608  // the server. This connection will be held until the CommandMgr responds to
609  // such request.
610  impl_->connection_pool_.stopAll();
611 }
612 
613 int
615  return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
616 }
617 
618 
619 CommandMgr&
621  static CommandMgr cmd_mgr;
622  return (cmd_mgr);
623 }
624 
625 void
627  impl_->io_service_ = io_service;
628 }
629 
630 void
631 CommandMgr::setConnectionTimeout(const long timeout) {
632  impl_->timeout_ = timeout;
633 }
634 
635 
636 }; // end of isc::config
637 }; // end of isc
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
std::string socket_name_
Path to the unix domain socket descriptor.
Definition: command_mgr.cc:503
An exception indicating a problem with socket operation.
Definition: command_mgr.h:28
ConnectionPool connection_pool_
Pool of connections.
Definition: command_mgr.cc:506
#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)
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition: iface_mgr.cc:317
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
const int DBG_COMMAND
Definition: config_log.h:24
boost::shared_ptr< WatchSocket > WatchSocketPtr
Defines a smart pointer to an instance of a WatchSocket.
Definition: watch_socket.h:138
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
An exception indicating that specified socket parameters are invalid.
Definition: command_mgr.h:21
constexpr long TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND
Timeout for the DHCP server to receive command over the unix domain socket.
Definition: timeouts.h:17
Defines the class, WatchSocket.
Command Manager which can delegate commands to a hook library.
Implementation of the CommandMgr.
Definition: command_mgr.cc:471
long timeout_
Connection timeout.
Definition: command_mgr.cc:509
boost::shared_ptr< UnixDomainSocketAcceptor > acceptor_
Pointer to the acceptor service.
Definition: command_mgr.cc:495
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
isc::log::Logger command_logger("commands")
Command processing Logger.
Definition: config_log.h:21
void closeCommandSocket()
Shuts down any open control sockets.
Definition: command_mgr.cc:598
void deleteExternalSocket(int socketfd)
Deletes external socket.
Definition: iface_mgr.cc:339
IOServicePtr io_service_
Pointer to the IO service used by the server process for running asynchronous tasks.
Definition: command_mgr.cc:492
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
void setConnectionTimeout(const long timeout)
Override default connection timeout.
Definition: command_mgr.cc:631
void setIOService(const asiolink::IOServicePtr &io_service)
Sets IO service to be used by the command manager.
Definition: command_mgr.cc:626
State model for asynchronous read of data in JSON format.
Definition: json_feed.h:68
This is a base class for exceptions thrown from the DNS library module.
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens control socket with parameters specified in socket_info.
Definition: command_mgr.cc:594
Defines the logger used by the top-level component of kea-dhcp-ddns.
boost::shared_ptr< UnixDomainSocket > socket_
Pointer to the socket into which the new connection is accepted.
Definition: command_mgr.cc:498
Commands Manager implementation for the Kea servers.
Definition: command_mgr.h:41
int getControlSocketFD()
Returns control socket descriptor.
Definition: command_mgr.cc:614
This file contains several functions and constants that are used for handling commands and responses ...
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
static CommandMgr & instance()
CommandMgr is a singleton class.
Definition: command_mgr.cc:620