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>
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
28using namespace isc;
29using namespace isc::asiolink;
30using namespace isc::config;
31using namespace isc::data;
32
33namespace {
34
36const size_t BUF_SIZE = 32768;
37
38class ConnectionPool;
39
44class Connection : public boost::enable_shared_from_this<Connection> {
45public:
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());
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
200private:
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
234typedef boost::shared_ptr<Connection> ConnectionPtr;
235
237class ConnectionPool {
238public:
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
270private:
271
273 std::set<ConnectionPtr> connections_;
274
275};
276
277void
278Connection::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
288void
289Connection::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.
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
389void
390Connection::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
438void
439Connection::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
467namespace isc {
468namespace config {
469
472public:
473
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
510};
511
512void
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.
551 acceptor_->open(endpoint);
552 acceptor_->bind(endpoint);
553 acceptor_->listen();
554
555 // Install this socket in Interface Manager.
557
558 doAccept();
559
560 } catch (const std::exception& ex) {
562 }
563}
564
565void
567 // Create a socket into which the acceptor will accept new connection.
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_,
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
589CommandMgr::CommandMgr()
590 : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
591}
592
593void
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
613int
615 return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
616}
617
618
621 static CommandMgr cmd_mgr;
622 return (cmd_mgr);
623}
624
625void
627 impl_->io_service_ = io_service;
628}
629
630void
632 impl_->timeout_ = timeout;
633}
634
635
636}; // end of isc::config
637}; // end of isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
This is a base class for exceptions thrown from the DNS library module.
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
isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
Implementation of the CommandMgr.
Definition: command_mgr.cc:471
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens acceptor service allowing the control clients to connect.
Definition: command_mgr.cc:513
boost::shared_ptr< UnixDomainSocket > socket_
Pointer to the socket into which the new connection is accepted.
Definition: command_mgr.cc:498
boost::shared_ptr< UnixDomainSocketAcceptor > acceptor_
Pointer to the acceptor service.
Definition: command_mgr.cc:495
IOServicePtr io_service_
Pointer to the IO service used by the server process for running asynchronous tasks.
Definition: command_mgr.cc:492
long timeout_
Connection timeout.
Definition: command_mgr.cc:509
void doAccept()
Asynchronously accepts next connection.
Definition: command_mgr.cc:566
std::string socket_name_
Path to the unix domain socket descriptor.
Definition: command_mgr.cc:503
ConnectionPool connection_pool_
Pool of connections.
Definition: command_mgr.cc:506
Commands Manager implementation for the Kea servers.
Definition: command_mgr.h:41
int getControlSocketFD()
Returns control socket descriptor.
Definition: command_mgr.cc:614
void closeCommandSocket()
Shuts down any open control sockets.
Definition: command_mgr.cc:598
static CommandMgr & instance()
CommandMgr is a singleton class.
Definition: command_mgr.cc:620
void setIOService(const asiolink::IOServicePtr &io_service)
Sets IO service to be used by the command manager.
Definition: command_mgr.cc:626
void setConnectionTimeout(const long timeout)
Override default connection timeout.
Definition: command_mgr.cc:631
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens control socket with parameters specified in socket_info.
Definition: command_mgr.cc:594
Command Manager which can delegate commands to a hook library.
State model for asynchronous read of data in JSON format.
Definition: json_feed.h:68
An exception indicating a problem with socket operation.
Definition: command_mgr.h:28
void deleteExternalSocket(int socketfd)
Deletes external socket.
Definition: iface_mgr.cc:339
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition: iface_mgr.cc:53
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition: iface_mgr.cc:317
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_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition: macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition: macros.h:26
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
constexpr long TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND
Timeout for the DHCP server to receive command over the unix domain socket.
Definition: timeouts.h:17
const int DBG_COMMAND
Definition: config_log.h:24
isc::log::Logger command_logger("commands")
Command processing Logger.
Definition: config_log.h:21
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:23
boost::shared_ptr< WatchSocket > WatchSocketPtr
Defines a smart pointer to an instance of a WatchSocket.
Definition: watch_socket.h:138
Defines the logger used by the top-level component of kea-dhcp-ddns.
Defines the class, WatchSocket.