Kea 2.0.3
command_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2015-2021 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/enable_shared_from_this.hpp>
24#include <array>
25#include <functional>
26#include <unistd.h>
27#include <sys/file.h>
28
29using namespace isc;
30using namespace isc::asiolink;
31using namespace isc::config;
32using namespace isc::data;
33namespace ph = std::placeholders;
34
35namespace {
36
38const size_t BUF_SIZE = 32768;
39
40class ConnectionPool;
41
46class Connection : public boost::enable_shared_from_this<Connection> {
47public:
48
66 Connection(const IOServicePtr& io_service,
67 const boost::shared_ptr<UnixDomainSocket>& socket,
68 ConnectionPool& connection_pool,
69 const long timeout)
70 : socket_(socket), timeout_timer_(*io_service), timeout_(timeout),
71 buf_(), response_(), connection_pool_(connection_pool), feed_(),
72 response_in_progress_(false), watch_socket_(new util::WatchSocket()) {
73
75 .arg(socket_->getNative());
76
77 // Callback value of 0 is used to indicate that callback function is
78 // not installed.
79 isc::dhcp::IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
80 isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
81
82 // Initialize state model for receiving and preparsing commands.
83 feed_.initModel();
84
85 // Start timer for detecting timeouts.
86 scheduleTimer();
87 }
88
92 ~Connection() {
93 timeout_timer_.cancel();
94 }
95
97 void scheduleTimer() {
98 timeout_timer_.setup(std::bind(&Connection::timeoutHandler, this),
99 timeout_, IntervalTimer::ONE_SHOT);
100 }
101
108 void stop() {
109 if (!response_in_progress_) {
111 .arg(socket_->getNative());
112
113 isc::dhcp::IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
115
116 // Close watch socket and log errors if occur.
117 std::string watch_error;
118 if (!watch_socket_->closeSocket(watch_error)) {
120 .arg(watch_error);
121 }
122
123 socket_->close();
124 timeout_timer_.cancel();
125 }
126 }
127
132 void terminate();
133
139 void doReceive() {
140 socket_->asyncReceive(&buf_[0], sizeof(buf_),
141 std::bind(&Connection::receiveHandler,
142 shared_from_this(), ph::_1, ph::_2));
143 }
144
152 void doSend() {
153 size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
154 socket_->asyncSend(&response_[0], chunk_size,
155 std::bind(&Connection::sendHandler, shared_from_this(), ph::_1, ph::_2));
156
157 // Asynchronous send has been scheduled and we need to indicate this
158 // to break the synchronous select(). The handler should clear this
159 // status when invoked.
160 try {
161 watch_socket_->markReady();
162
163 } catch (const std::exception& ex) {
165 .arg(ex.what());
166 }
167 }
168
177 //
181 void receiveHandler(const boost::system::error_code& ec,
182 size_t bytes_transferred);
183
184
193 void sendHandler(const boost::system::error_code& ec,
194 size_t bytes_transferred);
195
200 void timeoutHandler();
201
202private:
203
205 boost::shared_ptr<UnixDomainSocket> socket_;
206
208 IntervalTimer timeout_timer_;
209
211 long timeout_;
212
214 std::array<char, BUF_SIZE> buf_;
215
217 std::string response_;
218
220 ConnectionPool& connection_pool_;
221
224 JSONFeed feed_;
225
228 bool response_in_progress_;
229
232 util::WatchSocketPtr watch_socket_;
233};
234
236typedef boost::shared_ptr<Connection> ConnectionPtr;
237
239class ConnectionPool {
240public:
241
245 void start(const ConnectionPtr& connection) {
246 connection->doReceive();
247 connections_.insert(connection);
248 }
249
253 void stop(const ConnectionPtr& connection) {
254 try {
255 connection->stop();
256 connections_.erase(connection);
257 } catch (const std::exception& ex) {
259 .arg(ex.what());
260 }
261 }
262
264 void stopAll() {
265 for (auto conn = connections_.begin(); conn != connections_.end();
266 ++conn) {
267 (*conn)->stop();
268 }
269 connections_.clear();
270 }
271
272private:
273
275 std::set<ConnectionPtr> connections_;
276
277};
278
279void
280Connection::terminate() {
281 try {
282 socket_->shutdown();
283
284 } catch (const std::exception& ex) {
286 .arg(ex.what());
287 }
288}
289
290void
291Connection::receiveHandler(const boost::system::error_code& ec,
292 size_t bytes_transferred) {
293 if (ec) {
294 if (ec.value() == boost::asio::error::eof) {
295 std::stringstream os;
296 if (feed_.getProcessedText().empty()) {
297 os << "no input data to discard";
298 }
299 else {
300 os << "discarding partial command of "
301 << feed_.getProcessedText().size() << " bytes";
302 }
303
304 // Foreign host has closed the connection. We should remove it from the
305 // connection pool.
307 .arg(socket_->getNative()).arg(os.str());
308 } else if (ec.value() != boost::asio::error::operation_aborted) {
310 .arg(ec.value()).arg(socket_->getNative());
311 }
312
313 connection_pool_.stop(shared_from_this());
314 return;
315
316 } else if (bytes_transferred == 0) {
317 // Nothing received. Close the connection.
318 connection_pool_.stop(shared_from_this());
319 return;
320 }
321
323 .arg(bytes_transferred).arg(socket_->getNative());
324
325 // Reschedule the timer because the transaction is ongoing.
326 scheduleTimer();
327
328 ConstElementPtr cmd;
329 ConstElementPtr rsp;
330
331 try {
332 // Received some data over the socket. Append them to the JSON feed
333 // to see if we have reached the end of command.
334 feed_.postBuffer(&buf_[0], bytes_transferred);
335 feed_.poll();
336 // If we haven't yet received the full command, continue receiving.
337 if (feed_.needData()) {
338 doReceive();
339 return;
340 }
341
342 // Received entire command. Parse the command into JSON.
343 if (feed_.feedOk()) {
344 cmd = feed_.toElement();
345 response_in_progress_ = true;
346
347 // Cancel the timer to make sure that long lasting command
348 // processing doesn't cause the timeout.
349 timeout_timer_.cancel();
350
351 // If successful, then process it as a command.
352 rsp = CommandMgr::instance().processCommand(cmd);
353
354 response_in_progress_ = false;
355
356 } else {
357 // Failed to parse command as JSON or process the received command.
358 // This exception will be caught below and the error response will
359 // be sent.
360 isc_throw(BadValue, feed_.getErrorMessage());
361 }
362
363 } catch (const Exception& ex) {
365 rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
366 }
367
368 // No response generated. Connection will be closed.
369 if (!rsp) {
371 .arg(cmd ? cmd->str() : "unknown");
373 "internal server error: no response generated");
374
375 } else {
376
377 // Reschedule the timer as it may be either canceled or need to be
378 // updated to not timeout before we manage to the send the reply.
379 scheduleTimer();
380
381 // Let's convert JSON response to text. Note that at this stage
382 // the rsp pointer is always set.
383 response_ = rsp->str();
384
385 doSend();
386 return;
387 }
388
389 // Close the connection if we have sent the entire response.
390 connection_pool_.stop(shared_from_this());
391}
392
393void
394Connection::sendHandler(const boost::system::error_code& ec,
395 size_t bytes_transferred) {
396 // Clear the watch socket so as the future send operation can mark it
397 // again to interrupt the synchronous select() call.
398 try {
399 watch_socket_->clearReady();
400
401 } catch (const std::exception& ex) {
403 .arg(ex.what());
404 }
405
406 if (ec) {
407 // If an error occurred, log this error and stop the connection.
408 if (ec.value() != boost::asio::error::operation_aborted) {
410 .arg(socket_->getNative()).arg(ec.message());
411 }
412
413 } else {
414
415 // Reschedule the timer because the transaction is ongoing.
416 scheduleTimer();
417
418 // No error. We are in a process of sending a response. Need to
419 // remove the chunk that we have managed to sent with the previous
420 // attempt.
421 response_.erase(0, bytes_transferred);
422
424 .arg(bytes_transferred).arg(response_.size())
425 .arg(socket_->getNative());
426
427 // Check if there is any data left to be sent and sent it.
428 if (!response_.empty()) {
429 doSend();
430 return;
431 }
432
433 // Gracefully shutdown the connection and close the socket if
434 // we have sent the whole response.
435 terminate();
436 }
437
438 // All data sent or an error has occurred. Close the connection.
439 connection_pool_.stop(shared_from_this());
440}
441
442void
443Connection::timeoutHandler() {
445 .arg(socket_->getNative());
446
447 try {
448 socket_->cancel();
449
450 } catch (const std::exception& ex) {
452 .arg(socket_->getNative())
453 .arg(ex.what());
454 }
455
456 std::stringstream os;
457 os << "Connection over control channel timed out";
458 if (!feed_.getProcessedText().empty()) {
459 os << ", discarded partial command of "
460 << feed_.getProcessedText().size() << " bytes";
461 }
462
464 response_ = rsp->str();
465 doSend();
466}
467
468
469}
470
471namespace isc {
472namespace config {
473
476public:
477
482 }
483
489 void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
490
492 void doAccept();
493
495 std::string getLockName() {
496 return (std::string(socket_name_ + ".lock"));
497 }
498
502
504 boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
505
507 boost::shared_ptr<UnixDomainSocket> socket_;
508
512 std::string socket_name_;
513
515 ConnectionPool connection_pool_;
516
519};
520
521void
523 socket_name_.clear();
524
525 if(!socket_info) {
526 isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
527 }
528
529 ConstElementPtr type = socket_info->get("socket-type");
530 if (!type) {
531 isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
532 }
533
534 // Only supporting unix sockets right now.
535 if (type->stringValue() != "unix") {
536 isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
537 << type->stringValue());
538 }
539
540 // UNIX socket is requested. It takes one parameter: socket-name that
541 // specifies UNIX path of the socket.
542 ConstElementPtr name = socket_info->get("socket-name");
543 if (!name) {
544 isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
545 }
546
547 if (name->getType() != Element::string) {
548 isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
549 }
550
551 socket_name_ = name->stringValue();
552
553 // First let's open lock file.
554 std::string lock_name = getLockName();
555 int lock_fd = open(lock_name.c_str(), O_RDONLY | O_CREAT, 0600);
556 if (lock_fd == -1) {
557 std::string errmsg = strerror(errno);
558 isc_throw(SocketError, "cannot create socket lockfile, "
559 << lock_name << ", : " << errmsg);
560 }
561
562 // Try to acquire lock. If we can't somebody else is actively
563 // using it.
564 int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
565 if (ret != 0) {
566 std::string errmsg = strerror(errno);
567 isc_throw(SocketError, "cannot lock socket lockfile, "
568 << lock_name << ", : " << errmsg);
569 }
570
571 // We have the lock, so let's remove the pre-existing socket
572 // file if it exists.
573 static_cast<void>(::remove(socket_name_.c_str()));
574
576 .arg(socket_name_);
577
578 try {
579 // Start asynchronous acceptor service.
582 acceptor_->open(endpoint);
583 acceptor_->bind(endpoint);
584 acceptor_->listen();
585 // Install this socket in Interface Manager.
587
588 doAccept();
589
590 } catch (const std::exception& ex) {
592 }
593}
594
595void
597 // Create a socket into which the acceptor will accept new connection.
599 acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
600 if (!ec) {
601 // New connection is arriving. Start asynchronous transmission.
602 ConnectionPtr connection(new Connection(io_service_, socket_,
604 timeout_));
605 connection_pool_.start(connection);
606
607 } else if (ec.value() != boost::asio::error::operation_aborted) {
609 .arg(acceptor_->getNative()).arg(ec.message());
610 }
611
612 // Unless we're stopping the service, start accepting connections again.
613 if (ec.value() != boost::asio::error::operation_aborted) {
614 doAccept();
615 }
616 });
617}
618
619CommandMgr::CommandMgr()
620 : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
621}
622
623void
625 impl_->openCommandSocket(socket_info);
626}
627
629 // Close acceptor if the acceptor is open.
630 if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
631 isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
632 impl_->acceptor_->close();
633 static_cast<void>(::remove(impl_->socket_name_.c_str()));
634 static_cast<void>(::remove(impl_->getLockName().c_str()));
635 }
636
637 // Stop all connections which can be closed. The only connection that won't
638 // be closed is the one over which we have received a request to reconfigure
639 // the server. This connection will be held until the CommandMgr responds to
640 // such request.
641 impl_->connection_pool_.stopAll();
642}
643
644int
646 return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
647}
648
649
652 static CommandMgr cmd_mgr;
653 return (cmd_mgr);
654}
655
656void
658 impl_->io_service_ = io_service;
659}
660
661void
663 impl_->timeout_ = timeout;
664}
665
666
667}; // end of isc::config
668}; // 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
Implementation of the CommandMgr.
Definition: command_mgr.cc:475
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens acceptor service allowing the control clients to connect.
Definition: command_mgr.cc:522
std::string getLockName()
Returns the lock file name.
Definition: command_mgr.cc:495
boost::shared_ptr< UnixDomainSocket > socket_
Pointer to the socket into which the new connection is accepted.
Definition: command_mgr.cc:507
boost::shared_ptr< UnixDomainSocketAcceptor > acceptor_
Pointer to the acceptor service.
Definition: command_mgr.cc:504
IOServicePtr io_service_
Pointer to the IO service used by the server process for running asynchronous tasks.
Definition: command_mgr.cc:501
long timeout_
Connection timeout.
Definition: command_mgr.cc:518
void doAccept()
Asynchronously accepts next connection.
Definition: command_mgr.cc:596
std::string socket_name_
Path to the unix domain socket descriptor.
Definition: command_mgr.cc:512
ConnectionPool connection_pool_
Pool of connections.
Definition: command_mgr.cc:515
Commands Manager implementation for the Kea servers.
Definition: command_mgr.h:41
int getControlSocketFD()
Returns control socket descriptor.
Definition: command_mgr.cc:645
void closeCommandSocket()
Shuts down any open control sockets.
Definition: command_mgr.cc:628
static CommandMgr & instance()
CommandMgr is a singleton class.
Definition: command_mgr.cc:651
void setIOService(const asiolink::IOServicePtr &io_service)
Sets IO service to be used by the command manager.
Definition: command_mgr.cc:657
void setConnectionTimeout(const long timeout)
Override default connection timeout.
Definition: command_mgr.cc:662
void openCommandSocket(const isc::data::ConstElementPtr &socket_info)
Opens control socket with parameters specified in socket_info.
Definition: command_mgr.cc:624
Command Manager which can delegate commands to a hook library.
State model for asynchronous read of data in JSON format.
Definition: json_feed.h:71
An exception indicating a problem with socket operation.
Definition: command_mgr.h:28
void deleteExternalSocket(int socketfd)
Deletes external socket.
Definition: iface_mgr.cc:347
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:324
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 isc::log::MessageID COMMAND_PROCESS_ERROR1
const isc::log::MessageID COMMAND_SOCKET_READ_FAIL
const isc::log::MessageID COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL
const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSED
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CANCEL_FAIL
const isc::log::MessageID COMMAND_SOCKET_CONNECTION_OPENED
const isc::log::MessageID COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST
const isc::log::MessageID COMMAND_SOCKET_CONNECTION_TIMEOUT
const isc::log::MessageID COMMAND_ACCEPTOR_START
const isc::log::MessageID COMMAND_RESPONSE_ERROR
constexpr long TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND
Timeout for the DHCP server to receive command over the unix domain socket.
Definition: timeouts.h:17
ConstElementPtr createAnswer(const int status_code, const std::string &text, const ConstElementPtr &arg)
const isc::log::MessageID COMMAND_SOCKET_ACCEPT_FAIL
const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL
const isc::log::MessageID COMMAND_SOCKET_CONNECTION_CLOSE_FAIL
const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR
const int DBG_COMMAND
Definition: config_log.h:24
const isc::log::MessageID COMMAND_SOCKET_READ
isc::log::Logger command_logger("commands")
Command processing Logger.
Definition: config_log.h:21
const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR
const isc::log::MessageID COMMAND_SOCKET_WRITE
const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR
boost::shared_ptr< const Element > ConstElementPtr
Definition: data.h:27
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-lfc.
Defines the class, WatchSocket.