Kea 3.3.3
unix_command_mgr.cc
Go to the documentation of this file.
1// Copyright (C) 2015-2026 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>
18#include <cc/data.h>
20#include <cc/json_feed.h>
21#include <dhcp/iface_mgr.h>
22#include <config/config_log.h>
23#include <config/timeouts.h>
24#include <util/filesystem.h>
25#include <util/watch_socket.h>
26#include <boost/enable_shared_from_this.hpp>
27#include <array>
28#include <functional>
29#include <unistd.h>
30#include <sys/file.h>
31
32using namespace isc;
33using namespace isc::asiolink;
34using namespace isc::config;
35using namespace isc::data;
36using namespace isc::dhcp;
37namespace ph = std::placeholders;
38
39namespace {
40
42const size_t BUF_SIZE = 32768;
43
44class ConnectionPool;
45
46class Connection;
47
49typedef boost::shared_ptr<Connection> ConnectionPtr;
50
55class Connection : public boost::enable_shared_from_this<Connection> {
56public:
57
76 Connection(const IOServicePtr& io_service,
77 const boost::shared_ptr<UnixDomainSocket>& socket,
78 ConnectionPool& connection_pool,
79 const long timeout,
80 bool use_external)
81 : io_service_(io_service), socket_(socket), timeout_timer_(io_service_),
82 timeout_(timeout), buf_(), response_(), position_(0),
83 connection_pool_(connection_pool), feed_(), watch_socket_(),
84 use_external_(use_external), defer_shutdown_(false) {
85
87 .arg(socket_->getNative());
88
89 // Callback value of 0 is used to indicate that callback function is
90 // not installed.
91 if (use_external_) {
92 watch_socket_.reset(new util::WatchSocket());
93 IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
94 IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
95 }
96
97 // Initialize state model for receiving and preparsing commands.
98 feed_.initModel();
99
100 // Start timer for detecting timeouts.
101 scheduleTimer();
102 }
103
107 ~Connection() {
108 timeout_timer_.cancel();
109 }
110
112 void scheduleTimer() {
113 timeout_timer_.setup(std::bind(&Connection::timeoutHandler, this),
114 timeout_, IntervalTimer::ONE_SHOT);
115 }
116
123 void stop() {
124 if (defer_shutdown_) {
125 io_service_->post(std::bind([](ConnectionPtr c) { c->stop(); }, shared_from_this()));
126 return;
127 }
128
130 .arg(socket_->getNative());
131
132 if (use_external_) {
133 IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
134 IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
135
136 // Close watch socket and log errors if occur.
137 std::string watch_error;
138 if (!watch_socket_->closeSocket(watch_error)) {
140 .arg(watch_error);
141 }
142 }
143
144 socket_->close();
145 timeout_timer_.cancel();
146 }
147
152 void terminate();
153
159 void doReceive() {
160 socket_->asyncReceive(&buf_[0], sizeof(buf_),
161 std::bind(&Connection::receiveHandler,
162 shared_from_this(), ph::_1, ph::_2));
163 }
164
171 void doSend() {
172 // doSend is called only when there is something to send so
173 // remaining is guaranteed to be greater than 0 and position_
174 // to point inside the buffer.
175 size_t remaining = response_.size() - position_;
176 socket_->asyncSend(&response_[position_], remaining,
177 std::bind(&Connection::sendHandler, shared_from_this(), ph::_1, ph::_2));
178
179 if (use_external_) {
180 // Asynchronous send has been scheduled and we need to indicate this
181 // to break the synchronous select(). The handler should clear this
182 // status when invoked.
183 try {
184 watch_socket_->markReady();
185 } catch (const std::exception& ex) {
187 .arg(ex.what());
188 }
189 }
190 }
191
200 //
204 void receiveHandler(const boost::system::error_code& ec,
205 size_t bytes_transferred);
206
215 void sendHandler(const boost::system::error_code& ec,
216 size_t bytes_transferred);
217
222 void timeoutHandler();
223
224private:
225
227 IOServicePtr io_service_;
228
230 boost::shared_ptr<UnixDomainSocket> socket_;
231
233 IntervalTimer timeout_timer_;
234
236 long timeout_;
237
239 std::array<char, BUF_SIZE> buf_;
240
242 std::string response_;
243
245 size_t position_;
246
248 ConnectionPool& connection_pool_;
249
252 JSONFeed feed_;
253
256 util::WatchSocketPtr watch_socket_;
257
259 bool use_external_;
260
263 bool defer_shutdown_;
264};
265
267class ConnectionPool {
268public:
269
273 void start(const ConnectionPtr& connection) {
274 connection->doReceive();
275 connections_.insert(connection);
276 }
277
281 void stop(const ConnectionPtr& connection) {
282 try {
283 connection->stop();
284 connections_.erase(connection);
285 } catch (const std::exception& ex) {
287 .arg(ex.what());
288 }
289 }
290
292 void stopAll() {
293 for (auto const& conn : connections_) {
294 conn->stop();
295 }
296 connections_.clear();
297 }
298
299private:
300
302 std::set<ConnectionPtr> connections_;
303
304};
305
306void
307Connection::terminate() {
308 try {
309 socket_->shutdown();
310
311 } catch (const std::exception& ex) {
313 .arg(ex.what());
314 }
315}
316
317void
318Connection::receiveHandler(const boost::system::error_code& ec,
319 size_t bytes_transferred) {
320 if (ec) {
321 if (ec.value() == boost::asio::error::eof) {
322 std::stringstream os;
323 if (feed_.getProcessedText().empty()) {
324 os << "no input data to discard";
325 } else {
326 os << "discarding partial command of "
327 << feed_.getProcessedText().size() << " bytes";
328 }
329
330 // Foreign host has closed the connection. We should remove it from the
331 // connection pool.
333 .arg(socket_->getNative()).arg(os.str());
334 } else if (ec.value() != boost::asio::error::operation_aborted) {
336 .arg(ec.value()).arg(socket_->getNative());
337 }
338
339 connection_pool_.stop(shared_from_this());
340 return;
341
342 } else if (bytes_transferred == 0) {
343 // Nothing received. Close the connection.
344 connection_pool_.stop(shared_from_this());
345 return;
346 }
347
349 .arg(bytes_transferred).arg(socket_->getNative());
350
351 // Reschedule the timer because the transaction is ongoing.
352 scheduleTimer();
353
354 ConstElementPtr cmd;
355 ConstElementPtr rsp;
356
357 try {
358 // Received some data over the socket. Append them to the JSON feed
359 // to see if we have reached the end of command.
360 feed_.postBuffer(&buf_[0], bytes_transferred);
361 feed_.poll();
362 // If we haven't yet received the full command, continue receiving.
363 if (feed_.needData()) {
364 doReceive();
365 return;
366 }
367
368 // Received entire command. Parse the command into JSON.
369 if (feed_.feedOk()) {
370 cmd = feed_.toElement();
371
372 defer_shutdown_ = true;
373
374 std::unique_ptr<Connection, void (*)(Connection*)> p(this, [](Connection* c) {
375 c->defer_shutdown_ = false;
376 });
377
378 // Cancel the timer to make sure that long lasting command
379 // processing doesn't cause the timeout.
380 timeout_timer_.cancel();
381
382 // If successful, then process it as a command.
384
385 } else {
386 // Failed to parse command as JSON or process the received command.
387 // This exception will be caught below and the error response will
388 // be sent.
389 isc_throw(BadValue, feed_.getErrorMessage());
390 }
391
392 } catch (const Exception& ex) {
394 rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
395 }
396
397 // No response generated. Connection will be closed.
398 if (!rsp || rsp->str().empty()) {
400 .arg(cmd ? cmd->str() : "unknown");
402 "internal server error: no response generated");
403
404 } else {
405
406 // Reschedule the timer as it may be either canceled or need to be
407 // updated to not timeout before we manage to the send the reply.
408 scheduleTimer();
409
410 // Let's convert JSON response to text. Note that at this stage
411 // the rsp pointer is always set and its rendering not empty.
412 response_ = rsp->str();
413
414 doSend();
415 return;
416 }
417
418 // Close the connection if we have sent the entire response.
419 connection_pool_.stop(shared_from_this());
420}
421
422void
423Connection::sendHandler(const boost::system::error_code& ec,
424 size_t bytes_transferred) {
425 if (use_external_) {
426 // Clear the watch socket so as the future send operation can mark it
427 // again to interrupt the synchronous select() call.
428 try {
429 watch_socket_->clearReady();
430 } catch (const std::exception& ex) {
432 .arg(ex.what());
433 }
434 }
435
436 if (ec) {
437 // If an error occurred, log this error and stop the connection.
438 if (ec.value() != boost::asio::error::operation_aborted) {
440 .arg(socket_->getNative()).arg(ec.message());
441 }
442
443 } else {
444
445 // Reschedule the timer because the transaction is ongoing.
446 scheduleTimer();
447
448 // No error. We are in a process of sending a response. Need to
449 // advance the to send position in the response.
450 if (bytes_transferred >= response_.size() - position_) {
451 position_ = response_.size();
452 } else {
453 position_ += bytes_transferred;
454 }
455
457 .arg(bytes_transferred)
458 .arg(response_.size() - position_)
459 .arg(socket_->getNative());
460
461 // Check if there is any data left to be sent and sent it.
462 if (position_ < response_.size()) {
463 doSend();
464 return;
465 }
466
467 // Gracefully shutdown the connection and close the socket if
468 // we have sent the whole response.
469 terminate();
470 }
471
472 // All data sent or an error has occurred. Close the connection.
473 connection_pool_.stop(shared_from_this());
474}
475
476void
477Connection::timeoutHandler() {
479 .arg(socket_->getNative());
480
481 try {
482 socket_->cancel();
483
484 } catch (const std::exception& ex) {
486 .arg(socket_->getNative())
487 .arg(ex.what());
488 }
489
490 std::stringstream os;
491 os << "Connection over control channel timed out";
492 if (!feed_.getProcessedText().empty()) {
493 os << ", discarded partial command of "
494 << feed_.getProcessedText().size() << " bytes";
495 }
496
498 response_ = rsp->str();
499 doSend();
500}
501
502}
503
504namespace isc {
505namespace config {
506
509public:
510
515
522
531
536
540 void closeCommandSockets(bool remove = true);
541
546
555
559
561 ConnectionPool connection_pool_;
562
564 std::map<std::string, UnixSocketInfoPtr> sockets_;
565
568
571};
572
573void
575 if (!config) {
576 isc_throw(BadSocketInfo, "Missing config parameters, can't create socket.");
577 }
578
579 if (config->getType() != Element::list) {
580 isc_throw(DhcpConfigError, "expected list type ("
581 << config->getPosition() << ")");
582 }
583
584 for (auto const& socket : config->listValue()) {
585 openCommandSocket(socket);
586 }
587
588 auto copy = sockets_;
589 for (auto const& data : copy) {
590 if (data.second->usable_) {
591 // If the connection can be used (just created) or reused, keep it
592 // in the list and clear the flag. It will be marked again on next
593 // configuration event if needed.
594 data.second->usable_ = false;
595 } else {
596 // If the connection can not be reused, stop it and remove it from the list.
597 closeCommandSocket(data.second);
598 }
599 }
600}
601
602void
604 if (!config) {
605 isc_throw(BadSocketInfo, "Missing config parameters, can't create socket.");
606 }
607
609
610 // Search for the specific connection and reuse the existing one if found.
611 auto it = sockets_.find(cmd_config->getSocketName());
612 if (it != sockets_.end()) {
613 // If the connection can be reused, mark it as usable.
614 it->second->usable_ = true;
615 return;
616 }
617
618 // Connection not found so it needs to be created.
619 // First let's open lock file.
620 std::string lock_name = cmd_config->getLockName();
621 int lock_fd = open(lock_name.c_str(), O_RDONLY | O_CREAT, 0600);
622 if (lock_fd == -1) {
623 std::string errmsg = strerror(errno);
624 isc_throw(SocketError, "cannot create socket lockfile, "
625 << lock_name << ", : " << errmsg);
626 }
627
628 // Try to acquire lock. If we can't somebody else is actively
629 // using it.
630 int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
631 if (ret != 0) {
632 std::string errmsg = strerror(errno);
633 close(lock_fd);
634 isc_throw(SocketError, "cannot lock socket lockfile, "
635 << lock_name << ", : " << errmsg);
636 }
637
638 // We have the lock, so let's remove the pre-existing socket
639 // file if it exists.
640 static_cast<void>(::remove(cmd_config->getSocketName().c_str()));
641
643 .arg(cmd_config->getSocketName());
644
645 UnixSocketInfoPtr socket_info(new UnixSocketInfo());
646 socket_info->config_ = cmd_config;
647 socket_info->lock_fd_ = lock_fd;
648
649 try {
650 // Start asynchronous acceptor service.
651 socket_info->acceptor_.reset(new UnixDomainSocketAcceptor(io_service_));
652 UnixDomainSocketEndpoint endpoint(cmd_config->getSocketName());
653 socket_info->acceptor_->open(endpoint);
654 // Kea umask (0027) is too restrictive: the bind() system call
655 // creates the socket file with 0777 (or on some systems fchmod
656 // provided value) masked by the current umask. The 'connect'
657 // system call requires write access to socket file.
658 // So relaxing the umask to allow group write access.
659 {
661 socket_info->acceptor_->bind(endpoint);
662 }
663 socket_info->acceptor_->listen();
664 if (use_external_) {
665 // Install this socket in Interface Manager.
666 IfaceMgr::instance().addExternalSocket(socket_info->acceptor_->getNative(), 0);
667 }
668
669 doAccept(socket_info);
670
671 } catch (const std::exception& ex) {
672 close(lock_fd);
673 static_cast<void>(::remove(cmd_config->getLockName().c_str()));
674 isc_throw(SocketError, ex.what());
675 }
676
677 sockets_[cmd_config->getSocketName()] = socket_info;
678}
679
680void
682 if (info) {
683 // Close acceptor if the acceptor is open.
684 if (info->acceptor_ && info->acceptor_->isOpen()) {
685 if (use_external_) {
686 IfaceMgr::instance().deleteExternalSocket(info->acceptor_->getNative());
687 }
688 info->acceptor_->close();
689 static_cast<void>(::remove(info->config_->getSocketName().c_str()));
690 static_cast<void>(::remove(info->config_->getLockName().c_str()));
691 }
692
693 // Stop all connections which can be closed. The only connection that won't
694 // be closed is the one over which we have received a request to reconfigure
695 // the server. This connection will be held until the UnixCommandMgr
696 // responds to such request.
697 connection_pool_.stopAll();
698 if (info->lock_fd_ != -1) {
699 close(info->lock_fd_);
700 info->lock_fd_ = -1;
701 }
702 auto it = sockets_.find(info->config_->getSocketName());
703 if (it != sockets_.end()) {
704 sockets_.erase(it);
705 }
706 try {
707 io_service_->pollOne();
708 } catch (...) {
709 }
710 } else {
711 closeCommandSockets(false);
712 }
713}
714
715void
717 auto copy = sockets_;
718 for (auto const& data : copy) {
719 closeCommandSocket(data.second);
720 }
721 if (remove) {
722 sockets_.clear();
723 }
724}
725
726void
728 // Create a socket into which the acceptor will accept new connection.
729 info->socket_.reset(new UnixDomainSocket(io_service_));
730 info->acceptor_->asyncAccept(*info->socket_,
731 [this, info](const boost::system::error_code& ec) {
732 if (!ec) {
733 // New connection is arriving. Start asynchronous transmission.
734 ConnectionPtr connection(new Connection(io_service_, info->socket_,
735 connection_pool_,
736 timeout_,
737 use_external_));
738 connection_pool_.start(connection);
739
740 } else if (ec.value() != boost::asio::error::operation_aborted) {
741 LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
742 .arg(info->acceptor_->getNative()).arg(ec.message());
743 }
744
745 // Unless we're stopping the service, start accepting connections again.
746 if (ec.value() != boost::asio::error::operation_aborted) {
747 doAccept(info);
748 }
749 });
750}
751
752int
754 // Return the most recent listener or null.
755 if (info) {
756 auto const& it = sockets_.find(info->config_->getSocketName());
757 if (it != sockets_.end()) {
758 return (it->second->acceptor_->getNative());
759 }
760 } else if (sockets_.size()) {
761 return (sockets_.begin()->second->acceptor_->getNative());
762 }
763 return (-1);
764}
765
766UnixCommandMgr::UnixCommandMgr() : impl_(new UnixCommandMgrImpl()) {
767}
768
769void
773
774void
778
779void
781 impl_->closeCommandSocket(info);
782}
783
784void
786 impl_->closeCommandSockets();
787}
788
789int
791 return (impl_->getControlSocketFD(info));
792}
793
796 static UnixCommandMgr cmd_mgr;
797 return (cmd_mgr);
798}
799
800void
802 impl_->io_service_ = io_service;
803}
804
805void
807 impl_->timeout_ = timeout;
808}
809
810void
812 impl_->use_external_ = use_external;
813}
814
815} // end of isc::config
816} // end of isc
@ list
Definition data.h:159
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:17
virtual isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr &cmd)
Triggers command processing.
static CommandMgr & instance()
CommandMgr is a singleton class.
An exception indicating a problem with socket operation.
UNIX command config aka UNIX control socket info class.
Implementation of the UnixCommandMgr.
void closeCommandSocket(UnixSocketInfoPtr info)
Shuts down any open unix control sockets.
void openCommandSockets(const isc::data::ConstElementPtr config)
Opens acceptor service allowing the control clients to connect.
long timeout_
Connection timeout.
ConnectionPool connection_pool_
Pool of connections.
bool use_external_
Use external sockets flag.
void doAccept(UnixSocketInfoPtr info)
Asynchronously accepts next connection.
void closeCommandSockets(bool remove=true)
Shuts down any open unix control sockets.
std::map< std::string, UnixSocketInfoPtr > sockets_
The UNIX socket data (configuration, acceptor, etc.).
void openCommandSocket(const isc::data::ConstElementPtr config)
Opens acceptor service allowing the control clients to connect.
IOServicePtr io_service_
Pointer to the IO service used by the server process for running asynchronous tasks.
int getControlSocketFD(UnixSocketInfoPtr info)
Returns unix control socket descriptor.
Unix Commands Manager implementation for the Kea servers.
static UnixCommandMgr & instance()
UnixCommandMgr is a singleton class.
void setIOService(const asiolink::IOServicePtr &io_service)
Sets IO service to be used by the unix command manager.
int getControlSocketFD(UnixSocketInfoPtr info=UnixSocketInfoPtr())
Returns unix control socket descriptor.
void openCommandSockets(const isc::data::ConstElementPtr config)
Opens unix control socket with parameters specified in socket_info (required parameters: socket-type:...
void closeCommandSockets()
Shuts down any open unix control sockets.
void addExternalSockets(bool use_external=true)
Use external sockets flag.
void closeCommandSocket(UnixSocketInfoPtr info=UnixSocketInfoPtr())
Shuts down any open unix control sockets.
void setConnectionTimeout(const long timeout)
Override default connection timeout.
void openCommandSocket(const isc::data::ConstElementPtr config)
Opens unix control socket with parameters specified in socket_info (required parameters: socket-type:...
To be removed. Please use ConfigError instead.
void deleteExternalSocket(int socketfd)
Deletes external socket.
Definition iface_mgr.cc:398
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:52
void addExternalSocket(int socketfd, SocketCallback callback)
Adds external socket and a callback.
Definition iface_mgr.cc:367
RAII device to relax umask (adding group write for sockets).
Definition filesystem.h:105
This file contains several functions and constants that are used for handling commands and responses ...
if(!(yy_init))
Definition d2_lexer.cc:1515
#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
boost::shared_ptr< UnixSocketInfo > UnixSocketInfoPtr
Pointer to a UnixSocketInfo object.
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
boost::shared_ptr< UnixCommandConfig > UnixCommandConfigPtr
Pointer to a UnixCommandConfig object.
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
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
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
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
ElementPtr copy(ConstElementPtr from, unsigned level)
Copy the data up to a nesting level.
Definition data.cc:1543
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:30
@ info
Definition db_log.h:129
boost::shared_ptr< WatchSocket > WatchSocketPtr
Defines a smart pointer to an instance of a WatchSocket.
Defines the logger used by the top-level component of kea-lfc.
Structure used to store UNIX connection data.
Defines the class, WatchSocket.