summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorFrancis Dupont <fdupont@isc.org>2024-09-01 20:55:56 +0200
committerFrancis Dupont <fdupont@isc.org>2024-09-20 14:55:54 +0200
commit57430023310e59178e5f19cd8c0dba36ee4545d8 (patch)
treec6222f6c6e5c20c2001714a4811938309e67c772 /src
parent[#3477] Typo (diff)
downloadkea-57430023310e59178e5f19cd8c0dba36ee4545d8.tar.xz
kea-57430023310e59178e5f19cd8c0dba36ee4545d8.zip
[#3506] Checkpoint: split UnixCommandMgr
Diffstat (limited to 'src')
-rw-r--r--src/bin/agent/ca_cfg_mgr.h4
-rw-r--r--src/bin/d2/d2_controller.cc3
-rw-r--r--src/bin/d2/d2_process.cc9
-rw-r--r--src/bin/d2/tests/d2_command_unittest.cc17
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.cc5
-rw-r--r--src/bin/dhcp4/json_config_parser.cc5
-rw-r--r--src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc11
-rw-r--r--src/bin/dhcp4/tests/dhcp4_srv_unittest.cc6
-rw-r--r--src/bin/dhcp4/tests/dhcp4_test_utils.h3
-rw-r--r--src/bin/dhcp6/ctrl_dhcp6_srv.cc5
-rw-r--r--src/bin/dhcp6/json_config_parser.cc5
-rw-r--r--src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc13
-rw-r--r--src/bin/dhcp6/tests/dhcp6_srv_unittest.cc5
-rw-r--r--src/bin/dhcp6/tests/dhcp6_test_utils.h3
-rw-r--r--src/lib/config/Makefile.am1
-rw-r--r--src/lib/config/command-socket.dox40
-rw-r--r--src/lib/config/command_mgr.cc640
-rw-r--r--src/lib/config/command_mgr.h60
-rw-r--r--src/lib/config/http_command_mgr.cc3
-rw-r--r--src/lib/config/http_command_mgr.h13
-rw-r--r--src/lib/config/tests/Makefile.am1
-rw-r--r--src/lib/config/tests/command_mgr_unittests.cc94
-rw-r--r--src/lib/config/tests/unix_command_mgr_unittests.cc117
-rw-r--r--src/lib/config/unix_command_mgr.cc663
-rw-r--r--src/lib/config/unix_command_mgr.h88
25 files changed, 955 insertions, 859 deletions
diff --git a/src/bin/agent/ca_cfg_mgr.h b/src/bin/agent/ca_cfg_mgr.h
index 6234bd44f6..1cbfa82837 100644
--- a/src/bin/agent/ca_cfg_mgr.h
+++ b/src/bin/agent/ca_cfg_mgr.h
@@ -50,7 +50,7 @@ public:
/// This method returns Element tree structure that describes the control
/// socket (or null pointer if the socket is not defined for a particular
/// server type). This information is expected to be compatible with
- /// data passed to @ref isc::config::CommandMgr::openCommandSocket.
+ /// data passed to @ref isc::config::UnixCommandMgr::openCommandSocket.
///
/// @param service server being controlled
/// @return pointer to the Element that holds control-socket map (or NULL)
@@ -61,7 +61,7 @@ public:
///
/// This method stores Element tree structure that describes the control
/// socket. This information is expected to be compatible with
- /// data passed to @ref isc::config::CommandMgr::openCommandSocket.
+ /// data passed to @ref isc::config::UnixCommandMgr::openCommandSocket.
///
/// @param control_socket Element that holds control-socket map
/// @param service server being controlled
diff --git a/src/bin/d2/d2_controller.cc b/src/bin/d2/d2_controller.cc
index 9534d0b9bf..0dfd021d2d 100644
--- a/src/bin/d2/d2_controller.cc
+++ b/src/bin/d2/d2_controller.cc
@@ -8,6 +8,7 @@
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <d2/d2_controller.h>
#include <d2/d2_process.h>
#include <d2/parser_context.h>
@@ -105,7 +106,7 @@ void
D2Controller::deregisterCommands() {
try {
// Close command sockets.
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
HttpCommandMgr::instance().close();
// Deregister any registered commands (please keep in alphabetic order)
diff --git a/src/bin/d2/d2_process.cc b/src/bin/d2/d2_process.cc
index 2974e8fb2a..8b0da4cf2d 100644
--- a/src/bin/d2/d2_process.cc
+++ b/src/bin/d2/d2_process.cc
@@ -10,6 +10,7 @@
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <d2/d2_controller.h>
#include <d2/d2_process.h>
#include <d2srv/d2_cfg_mgr.h>
@@ -77,7 +78,7 @@ void
D2Process::init() {
using namespace isc::config;
// Command managers use IO service to run asynchronous socket operations.
- CommandMgr::instance().setIOService(getIOService());
+ UnixCommandMgr::instance().setIOService(getIOService());
HttpCommandMgr::instance().setIOService(getIOService());
// Set the HTTP authentication default realm.
@@ -161,7 +162,7 @@ D2Process::runIO() {
// service is stopped it will return immediately with a cnt of zero.
cnt = getIOService()->runOne();
}
- config::HttpCommandMgr::instance().garbageCollectListeners();
+ HttpCommandMgr::instance().garbageCollectListeners();
return (cnt);
}
@@ -515,13 +516,13 @@ D2Process::reconfigureCommandChannel() {
if (!sock_cfg || !current_control_socket_ || sock_changed) {
// Close the existing socket.
if (current_control_socket_) {
- isc::config::CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
current_control_socket_.reset();
}
// Open the new socket.
if (sock_cfg) {
- isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
+ UnixCommandMgr::instance().openCommandSocket(sock_cfg);
}
}
diff --git a/src/bin/d2/tests/d2_command_unittest.cc b/src/bin/d2/tests/d2_command_unittest.cc
index f9f7604927..a63a505ce7 100644
--- a/src/bin/d2/tests/d2_command_unittest.cc
+++ b/src/bin/d2/tests/d2_command_unittest.cc
@@ -11,6 +11,7 @@
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
#include <config/timeouts.h>
+#include <config/unix_command_mgr.h>
#include <testutils/io_utils.h>
#include <testutils/unix_control_client.h>
#include <d2/d2_controller.h>
@@ -144,7 +145,7 @@ public:
// Reset command manager.
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
+ UnixCommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
}
/// @brief Returns pointer to the server's IO service.
@@ -216,7 +217,7 @@ public:
ASSERT_EQ(0, status) << txt->str();
// Now check that the socket was indeed open.
- ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+ ASSERT_GT(UnixCommandMgr::instance().getControlSocketFD(), -1);
}
/// @brief Conducts a command/response exchange via UnixCommandSocket.
@@ -456,7 +457,7 @@ TEST_F(CtrlChannelD2Test, configure) {
ASSERT_EQ(Element::string, txt->getType());
EXPECT_EQ("'socket-type' parameter is mandatory in control-sockets items",
txt->stringValue());
- EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+ EXPECT_EQ(-1, UnixCommandMgr::instance().getControlSocketFD());
// no name.
string bad3 =
@@ -482,7 +483,7 @@ TEST_F(CtrlChannelD2Test, configure) {
ASSERT_EQ(Element::string, txt->getType());
EXPECT_EQ("Mandatory 'socket-name' parameter missing",
txt->stringValue());
- EXPECT_EQ(-1, CommandMgr::instance().getControlSocketFD());
+ EXPECT_EQ(-1, UnixCommandMgr::instance().getControlSocketFD());
}
// This test checks which commands are registered by the D2 server.
@@ -787,7 +788,7 @@ TEST_F(CtrlChannelD2Test, configTest) {
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
- ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+ ASSERT_GT(UnixCommandMgr::instance().getControlSocketFD(), -1);
// Create a config with invalid content that should fail to parse.
os.str("");
@@ -926,7 +927,7 @@ TEST_F(CtrlChannelD2Test, configSet) {
ASSERT_TRUE(keys);
EXPECT_EQ(1, keys->size());
- ASSERT_GT(CommandMgr::instance().getControlSocketFD(), -1);
+ ASSERT_GT(UnixCommandMgr::instance().getControlSocketFD(), -1);
// Create a config with invalid content that should fail to parse.
os.str("");
@@ -1316,7 +1317,7 @@ TEST_F(CtrlChannelD2Test, connectionTimeoutPartialCommand) {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
- CommandMgr::instance().setConnectionTimeout(timeout);
+ UnixCommandMgr::instance().setConnectionTimeout(timeout);
// Server's response will be assigned to this variable.
string response;
@@ -1369,7 +1370,7 @@ TEST_F(CtrlChannelD2Test, connectionTimeoutNoData) {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
- CommandMgr::instance().setConnectionTimeout(timeout);
+ UnixCommandMgr::instance().setConnectionTimeout(timeout);
// Server's response will be assigned to this variable.
string response;
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 4201b60cb7..f464e0a94c 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -12,6 +12,7 @@
#include <cc/data.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <cryptolink/crypto_hash.h>
#include <dhcp/libdhcp++.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
@@ -1082,7 +1083,7 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t server_port /*= DHCP4_SERVER_P
TimerMgr::instance()->setIOService(getIOService());
// Command managers use IO service to run asynchronous socket operations.
- CommandMgr::instance().setIOService(getIOService());
+ UnixCommandMgr::instance().setIOService(getIOService());
HttpCommandMgr::instance().setIOService(getIOService());
// Set the HTTP authentication default realm.
@@ -1193,7 +1194,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
cleanup();
// Close command sockets.
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
HttpCommandMgr::instance().close();
// Deregister any registered commands (please keep in alphabetic order)
diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc
index 79dec5174a..a244083413 100644
--- a/src/bin/dhcp4/json_config_parser.cc
+++ b/src/bin/dhcp4/json_config_parser.cc
@@ -10,6 +10,7 @@
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <database/database_connection.h>
#include <database/dbaccess_parser.h>
#include <database/backend_selector.h>
@@ -340,14 +341,14 @@ void configureCommandChannel() {
// receive the configuration result.
if (!sock_cfg || !current_sock_cfg || sock_changed) {
// Close the existing socket (if any).
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
if (sock_cfg) {
// This will create a control socket and install the external
// socket in IfaceMgr. That socket will be monitored when
// Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
// callback in CommandMgr will be called, if necessary.
- CommandMgr::instance().openCommandSocket(sock_cfg);
+ UnixCommandMgr::instance().openCommandSocket(sock_cfg);
}
}
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index 169e6aa2e4..632e1df759 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -10,6 +10,7 @@
#include <asiolink/io_service.h>
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <config/timeouts.h>
#include <dhcp/dhcp4.h>
#include <dhcp/libdhcp++.h>
@@ -131,9 +132,9 @@ public:
LeaseMgrFactory::destroy();
StatsMgr::instance().removeAll();
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
+ UnixCommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
server_.reset();
reset();
@@ -222,7 +223,7 @@ public:
ASSERT_EQ(0, status) << txt->str();
// Now check that the socket was indeed open.
- ASSERT_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
+ ASSERT_GT(isc::config::UnixCommandMgr::instance().getControlSocketFD(), -1);
}
/// @brief Reset hooks data
@@ -2254,7 +2255,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, connectionTimeoutPartialCommand) {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
- CommandMgr::instance().setConnectionTimeout(timeout);
+ UnixCommandMgr::instance().setConnectionTimeout(timeout);
// Server's response will be assigned to this variable.
std::string response;
@@ -2308,7 +2309,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, connectionTimeoutNoData) {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
- CommandMgr::instance().setConnectionTimeout(timeout);
+ UnixCommandMgr::instance().setConnectionTimeout(timeout);
// Server's response will be assigned to this variable.
std::string response;
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 0b50668dd4..0dccec9914 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -8,7 +8,7 @@
#include <asiolink/io_address.h>
#include <cc/command_interpreter.h>
-#include <config/command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <config_backend/base_config_backend.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
@@ -2965,10 +2965,10 @@ Dhcpv4SrvTest::loadConfigFile(const string& path) {
TimerMgr::instance()->unregisterTimers();
// Close the command socket (if it exists).
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
// Reset CommandMgr IO service.
- CommandMgr::instance().setIOService(IOServicePtr());
+ UnixCommandMgr::instance().setIOService(IOServicePtr());
// Reset DatabaseConnection IO service.
DatabaseConnection::setIOService(IOServicePtr());
diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.h b/src/bin/dhcp4/tests/dhcp4_test_utils.h
index 269e6906f0..9988e3631c 100644
--- a/src/bin/dhcp4/tests/dhcp4_test_utils.h
+++ b/src/bin/dhcp4/tests/dhcp4_test_utils.h
@@ -28,6 +28,7 @@
#include <asiolink/io_address.h>
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <util/multi_threading_mgr.h>
#include <list>
@@ -136,7 +137,7 @@ public:
dhcp::TimerMgr::instance()->setIOService(getIOService());
- config::CommandMgr::instance().setIOService(getIOService());
+ config::UnixCommandMgr::instance().setIOService(getIOService());
}
/// @brief Returns fixed server identifier assigned to the naked server
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 43eb8392c8..635212eda5 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -12,6 +12,7 @@
#include <cc/data.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <cryptolink/crypto_hash.h>
#include <dhcp/libdhcp++.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
@@ -1106,7 +1107,7 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t server_port /*= DHCP6_SERVER_P
TimerMgr::instance()->setIOService(getIOService());
// Command managers use IO service to run asynchronous socket operations.
- CommandMgr::instance().setIOService(getIOService());
+ UnixCommandMgr::instance().setIOService(getIOService());
HttpCommandMgr::instance().setIOService(getIOService());
// Set the HTTP default socket address to the IPv6 (vs IPv4) loopback.
@@ -1220,7 +1221,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
cleanup();
// Close command sockets.
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
HttpCommandMgr::instance().close();
// Deregister any registered commands (please keep in alphabetic order)
diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc
index b9d26a0a1e..eb5923a5de 100644
--- a/src/bin/dhcp6/json_config_parser.cc
+++ b/src/bin/dhcp6/json_config_parser.cc
@@ -12,6 +12,7 @@
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
#include <config/http_command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <database/database_connection.h>
#include <database/dbaccess_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
@@ -442,14 +443,14 @@ void configureCommandChannel() {
// receive the configuration result.
if (!sock_cfg || !current_sock_cfg || sock_changed) {
// Close the existing socket (if any).
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
if (sock_cfg) {
// This will create a control socket and install the external
// socket in IfaceMgr. That socket will be monitored when
// Dhcp6Srv::receivePacket() calls IfaceMgr::receive6() and
// callback in CommandMgr will be called, if necessary.
- CommandMgr::instance().openCommandSocket(sock_cfg);
+ UnixCommandMgr::instance().openCommandSocket(sock_cfg);
}
}
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index fc1271b070..131bf179aa 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -10,6 +10,7 @@
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
#include <config/timeouts.h>
+#include <config/unix_command_mgr.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/testutils/iface_mgr_test_config.h>
#include <dhcpsrv/cfgmgr.h>
@@ -111,7 +112,7 @@ public:
LeaseMgrFactory::destroy();
StatsMgr::instance().removeAll();
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT);
+ UnixCommandMgr::instance().setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT);
reset();
}
@@ -167,9 +168,9 @@ public:
LeaseMgrFactory::destroy();
StatsMgr::instance().removeAll();
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
+ UnixCommandMgr::instance().setConnectionTimeout(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND);
server_.reset();
reset();
@@ -258,7 +259,7 @@ public:
ASSERT_EQ(0, status) << txt->str();
// Now check that the socket was indeed open.
- ASSERT_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
+ ASSERT_GT(isc::config::UnixCommandMgr::instance().getControlSocketFD(), -1);
}
/// @brief Reset
@@ -2289,7 +2290,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeoutPartialCommand) {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
- CommandMgr::instance().setConnectionTimeout(timeout);
+ UnixCommandMgr::instance().setConnectionTimeout(timeout);
// Server's response will be assigned to this variable.
std::string response;
@@ -2343,7 +2344,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, connectionTimeoutNoData) {
// Set connection timeout to 2s to prevent long waiting time for the
// timeout during this test.
const unsigned short timeout = 2000;
- CommandMgr::instance().setConnectionTimeout(timeout);
+ UnixCommandMgr::instance().setConnectionTimeout(timeout);
// Server's response will be assigned to this variable.
std::string response;
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 0a3d1feefc..d9648bf4ad 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -9,6 +9,7 @@
#include <asiolink/io_address.h>
#include <cc/command_interpreter.h>
#include <config/command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <config_backend/base_config_backend.h>
#include <dhcp6/json_config_parser.h>
#include <dhcp6/tests/dhcp6_test_utils.h>
@@ -329,10 +330,10 @@ Dhcpv6SrvTest::loadConfigFile(const string& path) {
TimerMgr::instance()->unregisterTimers();
// Close the command socket (if it exists).
- CommandMgr::instance().closeCommandSocket();
+ UnixCommandMgr::instance().closeCommandSocket();
// Reset CommandMgr IO service.
- CommandMgr::instance().setIOService(IOServicePtr());
+ UnixCommandMgr::instance().setIOService(IOServicePtr());
// Reset DatabaseConnection IO service.
DatabaseConnection::setIOService(IOServicePtr());
diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.h b/src/bin/dhcp6/tests/dhcp6_test_utils.h
index fd7a11f474..b0e23488a1 100644
--- a/src/bin/dhcp6/tests/dhcp6_test_utils.h
+++ b/src/bin/dhcp6/tests/dhcp6_test_utils.h
@@ -31,6 +31,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <hooks/hooks_manager.h>
#include <config/command_mgr.h>
+#include <config/unix_command_mgr.h>
#include <util/multi_threading_mgr.h>
#include <testutils/log_utils.h>
@@ -145,7 +146,7 @@ public:
dhcp::TimerMgr::instance()->setIOService(getIOService());
- config::CommandMgr::instance().setIOService(getIOService());
+ config::UnixCommandMgr::instance().setIOService(getIOService());
}
/// @brief fakes packet reception
diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am
index fab1236701..b31544365f 100644
--- a/src/lib/config/Makefile.am
+++ b/src/lib/config/Makefile.am
@@ -10,6 +10,7 @@ libkea_cfgclient_la_SOURCES = cmds_impl.h
libkea_cfgclient_la_SOURCES += base_command_mgr.cc base_command_mgr.h
libkea_cfgclient_la_SOURCES += client_connection.cc client_connection.h
libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
+libkea_cfgclient_la_SOURCES += unix_command_mgr.cc unix_command_mgr.h
libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
libkea_cfgclient_la_SOURCES += config_messages.h config_messages.cc
libkea_cfgclient_la_SOURCES += hooked_command_mgr.cc hooked_command_mgr.h
diff --git a/src/lib/config/command-socket.dox b/src/lib/config/command-socket.dox
index 3b5150afa3..7413746576 100644
--- a/src/lib/config/command-socket.dox
+++ b/src/lib/config/command-socket.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2020 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,8 +18,7 @@ envisioned are: reconfiguration, statistics retrieval and manipulation,
and shutdown.
Communication over Control Channel is conducted using JSON structures.
-As of Kea 0.9.2, the only supported communication channel is UNIX stream
-socket, but additional types may be added in the future.
+The supported communication channel is UNIX stream and HTTP/HTTPS sockets.
If configured, Kea will open a socket and will listen for any incoming
connections. A process connecting to this socket is expected to send JSON
@@ -62,9 +61,9 @@ encountered.
- arguments - is a map of additional data values returned by the server, specific to the
command issue. The map is always present, even if it contains no data values.
-@section ctrlSocketClient Using Control Channel
+@section ctrlSocketClient Using UNIX Control Channel
-Here are two examples of how to access the Control Channel:
+Here are two examples of how to access the UNIX Control Channel:
1. Use socat tool, which is available in many Linux and BSD distributions.
See http://www.dest-unreach.org/socat/ for details. To use it:
@@ -147,30 +146,33 @@ There are 3 main methods that are expected to be used by developers:
- @ref isc::config::CommandMgr::processCommand, which allows handling specified
command.
-There are also two methods for managing control sockets. They are not expected
-to be used directly, unless someone implements a new Control Channel (e.g. TCP
-or HTTPS connection):
-
-- @ref isc::config::CommandMgr::openCommandSocket that passes structure defined
- in the configuration file. Currently only two parameters are supported: socket-type
- (which must contain value 'unix') and socket-name (which contains unix path for
- the named socket to be created).
-- @ref isc::config::CommandMgr::closeCommandSocket() - it is used to close the
- socket.
-
Kea servers use @c CommandMgr to register handlers for various commands they
support natively. However, it is possible extend a set of supported commands
using hooks framework. See @ref hooksdgCommandHandlers how to implement support
for your own control commands in Kea.
+@section unixCtrlSocketImpl UNIX Control Channel Implementation
+
+UNIX Control Channel is implemented in @ref isc::config::UnixCommandMgr.
+It is a singleton class providing two methods for managing control sockets.
+They are not expected to be used directly.
+
+- @ref isc::config::UnixCommandMgr::openCommandSocket that passes
+ structure defined in the configuration file. Currently only two
+ parameters are supported: socket-type (which must contain value
+ 'unix') and socket-name (which contains unix path for the named
+ socket to be created).
+- @ref isc::config::UnixCommandMgr::closeCommandSocket() - it is used
+ to close the socket.
+
@section ctrlSocketConnections Accepting connections
-The @ref isc::config::CommandMgr is implemented using boost ASIO and uses
+The @ref isc::config::UnixCommandMgr is implemented using boost ASIO and uses
asynchronous calls to accept new connections and receive commands from the
controlling clients. ASIO uses IO service object to run asynchronous calls.
-Thus, before the server can use the @ref isc::config::CommandMgr it must
+Thus, before the server can use the @ref isc::config::UnixCommandMgr it must
provide it with a common instance of the @ref isc::asiolink::IOService
-object using @ref isc::config::CommandMgr::setIOService. The server's
+object using @ref isc::config::UnixCommandMgr::setIOService. The server's
main loop must contain calls to @ref isc::asiolink::IOService::run or
@ref isc::asiolink::IOService::poll or their variants to invoke Command
Manager's handlers as required for processing control requests.
diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc
index cf21a697ab..13a94e0f0c 100644
--- a/src/lib/config/command_mgr.cc
+++ b/src/lib/config/command_mgr.cc
@@ -6,640 +6,12 @@
#include <config.h>
-#include <asiolink/asio_wrapper.h>
-#include <asiolink/interval_timer.h>
-#include <asiolink/io_service.h>
-#include <asiolink/unix_domain_socket.h>
-#include <asiolink/unix_domain_socket_acceptor.h>
-#include <asiolink/unix_domain_socket_endpoint.h>
#include <config/command_mgr.h>
-#include <cc/data.h>
-#include <cc/command_interpreter.h>
-#include <cc/json_feed.h>
-#include <dhcp/iface_mgr.h>
-#include <config/config_log.h>
-#include <config/timeouts.h>
-#include <util/watch_socket.h>
-#include <boost/enable_shared_from_this.hpp>
-#include <array>
-#include <functional>
-#include <unistd.h>
-#include <sys/file.h>
-
-using namespace isc;
-using namespace isc::asiolink;
-using namespace isc::config;
-using namespace isc::data;
-namespace ph = std::placeholders;
-
-namespace {
-
-/// @brief Maximum size of the data chunk sent/received over the socket.
-const size_t BUF_SIZE = 32768;
-
-class ConnectionPool;
-
-/// @brief Represents a single connection over control socket.
-///
-/// An instance of this object is created when the @c CommandMgr acceptor
-/// receives new connection from a controlling client.
-class Connection : public boost::enable_shared_from_this<Connection> {
-public:
-
- /// @brief Constructor.
- ///
- /// This constructor registers a socket of this connection in the Interface
- /// Manager to cause the blocking call to @c select() to return as soon as
- /// a transmission over the control socket is received.
- ///
- /// It installs two external sockets on the @IfaceMgr to break synchronous
- /// calls to @select(). The @c WatchSocket is used for send operations
- /// over the connection. The native socket is used for signaling reads
- /// over the connection.
- ///
- /// @param io_service IOService object used to handle the asio operations
- /// @param socket Pointer to the object representing a socket which is used
- /// for data transmission.
- /// @param connection_pool Reference to the connection pool to which this
- /// connection belongs.
- /// @param timeout Connection timeout (in seconds).
- Connection(const IOServicePtr& io_service,
- const boost::shared_ptr<UnixDomainSocket>& socket,
- ConnectionPool& connection_pool,
- const long timeout)
- : socket_(socket), timeout_timer_(io_service), timeout_(timeout),
- buf_(), response_(), connection_pool_(connection_pool), feed_(),
- response_in_progress_(false), watch_socket_(new util::WatchSocket()) {
-
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_OPENED)
- .arg(socket_->getNative());
-
- // Callback value of 0 is used to indicate that callback function is
- // not installed.
- isc::dhcp::IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
- isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
-
- // Initialize state model for receiving and preparsing commands.
- feed_.initModel();
-
- // Start timer for detecting timeouts.
- scheduleTimer();
- }
-
- /// @brief Destructor.
- ///
- /// Cancels timeout timer if one is scheduled.
- ~Connection() {
- timeout_timer_.cancel();
- }
-
- /// @brief This method schedules timer or reschedules existing timer.
- void scheduleTimer() {
- timeout_timer_.setup(std::bind(&Connection::timeoutHandler, this),
- timeout_, IntervalTimer::ONE_SHOT);
- }
-
- /// @brief Close current connection.
- ///
- /// Connection is not closed if the invocation of this method is a result of
- /// server reconfiguration. The connection will be closed once a response is
- /// sent to the client. Closing a socket during processing a request would
- /// cause the server to not send a response to the client.
- void stop() {
- if (!response_in_progress_) {
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_CLOSED)
- .arg(socket_->getNative());
-
- isc::dhcp::IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
- isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
-
- // Close watch socket and log errors if occur.
- std::string watch_error;
- if (!watch_socket_->closeSocket(watch_error)) {
- LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLOSE_ERROR)
- .arg(watch_error);
- }
-
- socket_->close();
- timeout_timer_.cancel();
- }
- }
-
- /// @brief Gracefully terminates current connection.
- ///
- /// This method should be called prior to closing the socket to initiate
- /// graceful shutdown.
- void terminate();
-
- /// @brief Start asynchronous read over the unix domain socket.
- ///
- /// This method doesn't block. Once the transmission is received over the
- /// socket, the @c Connection::receiveHandler callback is invoked to
- /// process received data.
- void doReceive() {
- socket_->asyncReceive(&buf_[0], sizeof(buf_),
- std::bind(&Connection::receiveHandler,
- shared_from_this(), ph::_1, ph::_2));
- }
-
- /// @brief Starts asynchronous send over the unix domain socket.
- ///
- /// This method doesn't block. Once the send operation (that covers the whole
- /// data if it's small or first BUF_SIZE bytes if its large) is completed, the
- /// @c Connection::sendHandler callback is invoked. That handler will either
- /// close the connection gracefully if all data has been sent, or will
- /// call @ref doSend() again to send the next chunk of data.
- void doSend() {
- size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
- socket_->asyncSend(&response_[0], chunk_size,
- std::bind(&Connection::sendHandler, shared_from_this(), ph::_1, ph::_2));
-
- // Asynchronous send has been scheduled and we need to indicate this
- // to break the synchronous select(). The handler should clear this
- // status when invoked.
- try {
- watch_socket_->markReady();
-
- } catch (const std::exception& ex) {
- LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_MARK_READY_ERROR)
- .arg(ex.what());
- }
- }
-
- /// @brief Handler invoked when the data is received over the control
- /// socket.
- ///
- /// It collects received data into the @c isc::config::JSONFeed object and
- /// schedules additional asynchronous read of data if this object signals
- /// that command is incomplete. When the entire command is received, the
- /// handler processes this command and asynchronously responds to the
- /// controlling client.
- //
- ///
- /// @param ec Error code.
- /// @param bytes_transferred Number of bytes received.
- void receiveHandler(const boost::system::error_code& ec,
- size_t bytes_transferred);
-
- /// @brief Handler invoked when the data is sent over the control socket.
- ///
- /// If there are still data to be sent, another asynchronous send is
- /// scheduled. When the entire command is sent, the connection is shutdown
- /// and closed.
- ///
- /// @param ec Error code.
- /// @param bytes_transferred Number of bytes sent.
- void sendHandler(const boost::system::error_code& ec,
- size_t bytes_transferred);
-
- /// @brief Handler invoked when timeout has occurred.
- ///
- /// Asynchronously sends a response to the client indicating that the
- /// timeout has occurred.
- void timeoutHandler();
-
-private:
-
- /// @brief Pointer to the socket used for transmission.
- boost::shared_ptr<UnixDomainSocket> socket_;
-
- /// @brief Interval timer used to detect connection timeouts.
- IntervalTimer timeout_timer_;
-
- /// @brief Connection timeout (in milliseconds)
- long timeout_;
-
- /// @brief Buffer used for received data.
- std::array<char, BUF_SIZE> buf_;
-
- /// @brief Response created by the server.
- std::string response_;
-
- /// @brief Reference to the pool of connections.
- ConnectionPool& connection_pool_;
-
- /// @brief State model used to receive data over the connection and detect
- /// when the command ends.
- JSONFeed feed_;
-
- /// @brief Boolean flag indicating if the request to stop connection is a
- /// result of server reconfiguration.
- bool response_in_progress_;
-
- /// @brief Pointer to watch socket instance used to signal that the socket
- /// is ready for read or write.
- util::WatchSocketPtr watch_socket_;
-};
-
-/// @brief Pointer to the @c Connection.
-typedef boost::shared_ptr<Connection> ConnectionPtr;
-
-/// @brief Holds all open connections.
-class ConnectionPool {
-public:
-
- /// @brief Starts new connection.
- ///
- /// @param connection Pointer to the new connection object.
- void start(const ConnectionPtr& connection) {
- connection->doReceive();
- connections_.insert(connection);
- }
-
- /// @brief Stops running connection.
- ///
- /// @param connection Pointer to the new connection object.
- void stop(const ConnectionPtr& connection) {
- try {
- connection->stop();
- connections_.erase(connection);
- } catch (const std::exception& ex) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CLOSE_FAIL)
- .arg(ex.what());
- }
- }
-
- /// @brief Stops all connections which are allowed to stop.
- void stopAll() {
- for (auto const& conn : connections_) {
- conn->stop();
- }
- connections_.clear();
- }
-
-private:
-
- /// @brief Pool of connections.
- std::set<ConnectionPtr> connections_;
-
-};
-
-void
-Connection::terminate() {
- try {
- socket_->shutdown();
-
- } catch (const std::exception& ex) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL)
- .arg(ex.what());
- }
-}
-
-void
-Connection::receiveHandler(const boost::system::error_code& ec,
- size_t bytes_transferred) {
- if (ec) {
- if (ec.value() == boost::asio::error::eof) {
- std::stringstream os;
- if (feed_.getProcessedText().empty()) {
- os << "no input data to discard";
- } else {
- os << "discarding partial command of "
- << feed_.getProcessedText().size() << " bytes";
- }
-
- // Foreign host has closed the connection. We should remove it from the
- // connection pool.
- LOG_INFO(command_logger, COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST)
- .arg(socket_->getNative()).arg(os.str());
- } else if (ec.value() != boost::asio::error::operation_aborted) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL)
- .arg(ec.value()).arg(socket_->getNative());
- }
-
- connection_pool_.stop(shared_from_this());
- return;
-
- } else if (bytes_transferred == 0) {
- // Nothing received. Close the connection.
- connection_pool_.stop(shared_from_this());
- return;
- }
-
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ)
- .arg(bytes_transferred).arg(socket_->getNative());
-
- // Reschedule the timer because the transaction is ongoing.
- scheduleTimer();
-
- ConstElementPtr cmd;
- ConstElementPtr rsp;
-
- try {
- // Received some data over the socket. Append them to the JSON feed
- // to see if we have reached the end of command.
- feed_.postBuffer(&buf_[0], bytes_transferred);
- feed_.poll();
- // If we haven't yet received the full command, continue receiving.
- if (feed_.needData()) {
- doReceive();
- return;
- }
-
- // Received entire command. Parse the command into JSON.
- if (feed_.feedOk()) {
- cmd = feed_.toElement();
- response_in_progress_ = true;
-
- // Cancel the timer to make sure that long lasting command
- // processing doesn't cause the timeout.
- timeout_timer_.cancel();
-
- // If successful, then process it as a command.
- rsp = CommandMgr::instance().processCommand(cmd);
-
- response_in_progress_ = false;
-
- } else {
- // Failed to parse command as JSON or process the received command.
- // This exception will be caught below and the error response will
- // be sent.
- isc_throw(BadValue, feed_.getErrorMessage());
- }
-
- } catch (const Exception& ex) {
- LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
- rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
- }
-
- // No response generated. Connection will be closed.
- if (!rsp) {
- LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR)
- .arg(cmd ? cmd->str() : "unknown");
- rsp = createAnswer(CONTROL_RESULT_ERROR,
- "internal server error: no response generated");
-
- } else {
-
- // Reschedule the timer as it may be either canceled or need to be
- // updated to not timeout before we manage to the send the reply.
- scheduleTimer();
-
- // Let's convert JSON response to text. Note that at this stage
- // the rsp pointer is always set.
- response_ = rsp->str();
-
- doSend();
- return;
- }
-
- // Close the connection if we have sent the entire response.
- connection_pool_.stop(shared_from_this());
-}
-
-void
-Connection::sendHandler(const boost::system::error_code& ec,
- size_t bytes_transferred) {
- // Clear the watch socket so as the future send operation can mark it
- // again to interrupt the synchronous select() call.
- try {
- watch_socket_->clearReady();
-
- } catch (const std::exception& ex) {
- LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLEAR_ERROR)
- .arg(ex.what());
- }
-
- if (ec) {
- // If an error occurred, log this error and stop the connection.
- if (ec.value() != boost::asio::error::operation_aborted) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
- .arg(socket_->getNative()).arg(ec.message());
- }
-
- } else {
-
- // Reschedule the timer because the transaction is ongoing.
- scheduleTimer();
-
- // No error. We are in a process of sending a response. Need to
- // remove the chunk that we have managed to sent with the previous
- // attempt.
- response_.erase(0, bytes_transferred);
-
- LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE)
- .arg(bytes_transferred).arg(response_.size())
- .arg(socket_->getNative());
-
- // Check if there is any data left to be sent and sent it.
- if (!response_.empty()) {
- doSend();
- return;
- }
-
- // Gracefully shutdown the connection and close the socket if
- // we have sent the whole response.
- terminate();
- }
-
- // All data sent or an error has occurred. Close the connection.
- connection_pool_.stop(shared_from_this());
-}
-
-void
-Connection::timeoutHandler() {
- LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_TIMEOUT)
- .arg(socket_->getNative());
-
- try {
- socket_->cancel();
-
- } catch (const std::exception& ex) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CANCEL_FAIL)
- .arg(socket_->getNative())
- .arg(ex.what());
- }
-
- std::stringstream os;
- os << "Connection over control channel timed out";
- if (!feed_.getProcessedText().empty()) {
- os << ", discarded partial command of "
- << feed_.getProcessedText().size() << " bytes";
- }
-
- ConstElementPtr rsp = createAnswer(CONTROL_RESULT_ERROR, os.str());
- response_ = rsp->str();
- doSend();
-}
-
-}
namespace isc {
namespace config {
-/// @brief Implementation of the @c CommandMgr.
-class CommandMgrImpl {
-public:
-
- /// @brief Constructor.
- CommandMgrImpl()
- : io_service_(), acceptor_(), socket_(), socket_name_(),
- connection_pool_(), timeout_(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND) {
- }
-
- /// @brief Opens acceptor service allowing the control clients to connect.
- ///
- /// @param socket_info Configuration information for the control socket.
- /// @throw BadSocketInfo When socket configuration is invalid.
- /// @throw SocketError When socket operation fails.
- void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
-
- /// @brief Asynchronously accepts next connection.
- void doAccept();
-
- /// @brief Returns the lock file name
- std::string getLockName() {
- return (std::string(socket_name_ + ".lock"));
- }
-
- /// @brief Pointer to the IO service used by the server process for running
- /// asynchronous tasks.
- IOServicePtr io_service_;
-
- /// @brief Pointer to the acceptor service.
- boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
-
- /// @brief Pointer to the socket into which the new connection is accepted.
- boost::shared_ptr<UnixDomainSocket> socket_;
-
- /// @brief Path to the unix domain socket descriptor.
- ///
- /// This is used to remove the socket file once the connection terminates.
- std::string socket_name_;
-
- /// @brief Pool of connections.
- ConnectionPool connection_pool_;
-
- /// @brief Connection timeout
- long timeout_;
-};
-
-void
-CommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
- socket_name_.clear();
-
- if(!socket_info) {
- isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
- }
-
- ConstElementPtr type = socket_info->get("socket-type");
- if (!type) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
- }
-
- // Only supporting unix sockets right now.
- if (type->stringValue() != "unix") {
- isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
- << type->stringValue());
- }
-
- // UNIX socket is requested. It takes one parameter: socket-name that
- // specifies UNIX path of the socket.
- ConstElementPtr name = socket_info->get("socket-name");
- if (!name) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
- }
-
- if (name->getType() != Element::string) {
- isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
- }
-
- socket_name_ = name->stringValue();
-
- // First let's open lock file.
- std::string lock_name = getLockName();
- int lock_fd = open(lock_name.c_str(), O_RDONLY | O_CREAT, 0600);
- if (lock_fd == -1) {
- std::string errmsg = strerror(errno);
- isc_throw(SocketError, "cannot create socket lockfile, "
- << lock_name << ", : " << errmsg);
- }
-
- // Try to acquire lock. If we can't somebody else is actively
- // using it.
- int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
- if (ret != 0) {
- std::string errmsg = strerror(errno);
- isc_throw(SocketError, "cannot lock socket lockfile, "
- << lock_name << ", : " << errmsg);
- }
-
- // We have the lock, so let's remove the pre-existing socket
- // file if it exists.
- static_cast<void>(::remove(socket_name_.c_str()));
-
- LOG_INFO(command_logger, COMMAND_ACCEPTOR_START)
- .arg(socket_name_);
-
- try {
- // Start asynchronous acceptor service.
- acceptor_.reset(new UnixDomainSocketAcceptor(io_service_));
- UnixDomainSocketEndpoint endpoint(socket_name_);
- acceptor_->open(endpoint);
- acceptor_->bind(endpoint);
- acceptor_->listen();
- // Install this socket in Interface Manager.
- isc::dhcp::IfaceMgr::instance().addExternalSocket(acceptor_->getNative(), 0);
-
- doAccept();
-
- } catch (const std::exception& ex) {
- isc_throw(SocketError, ex.what());
- }
-}
-
-void
-CommandMgrImpl::doAccept() {
- // Create a socket into which the acceptor will accept new connection.
- socket_.reset(new UnixDomainSocket(io_service_));
- acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
- if (!ec) {
- // New connection is arriving. Start asynchronous transmission.
- ConnectionPtr connection(new Connection(io_service_, socket_,
- connection_pool_,
- timeout_));
- connection_pool_.start(connection);
-
- } else if (ec.value() != boost::asio::error::operation_aborted) {
- LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
- .arg(acceptor_->getNative()).arg(ec.message());
- }
-
- // Unless we're stopping the service, start accepting connections again.
- if (ec.value() != boost::asio::error::operation_aborted) {
- doAccept();
- }
- });
-}
-
-CommandMgr::CommandMgr()
- : HookedCommandMgr(), impl_(new CommandMgrImpl()) {
-}
-
-void
-CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
- impl_->openCommandSocket(socket_info);
-}
-
-void CommandMgr::closeCommandSocket() {
- // Close acceptor if the acceptor is open.
- if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
- isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
- impl_->acceptor_->close();
- static_cast<void>(::remove(impl_->socket_name_.c_str()));
- static_cast<void>(::remove(impl_->getLockName().c_str()));
- }
-
- // Stop all connections which can be closed. The only connection that won't
- // be closed is the one over which we have received a request to reconfigure
- // the server. This connection will be held until the CommandMgr responds to
- // such request.
- impl_->connection_pool_.stopAll();
-}
-
-int
-CommandMgr::getControlSocketFD() {
- return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
+CommandMgr::CommandMgr() : HookedCommandMgr() {
}
CommandMgr&
@@ -648,15 +20,5 @@ CommandMgr::instance() {
return (cmd_mgr);
}
-void
-CommandMgr::setIOService(const IOServicePtr& io_service) {
- impl_->io_service_ = io_service;
-}
-
-void
-CommandMgr::setConnectionTimeout(const long timeout) {
- impl_->timeout_ = timeout;
-}
-
} // end of isc::config
} // end of isc
diff --git a/src/lib/config/command_mgr.h b/src/lib/config/command_mgr.h
index 91f2c8f39a..10f2f0a12d 100644
--- a/src/lib/config/command_mgr.h
+++ b/src/lib/config/command_mgr.h
@@ -7,33 +7,12 @@
#ifndef COMMAND_MGR_H
#define COMMAND_MGR_H
-#include <asiolink/io_service.h>
-#include <cc/data.h>
#include <config/hooked_command_mgr.h>
-#include <exceptions/exceptions.h>
#include <boost/noncopyable.hpp>
-#include <boost/shared_ptr.hpp>
namespace isc {
namespace config {
-/// @brief An exception indicating that specified socket parameters are invalid
-class BadSocketInfo : public Exception {
-public:
- BadSocketInfo(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) { };
-};
-
-/// @brief An exception indicating a problem with socket operation
-class SocketError : public Exception {
-public:
- SocketError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) { };
-};
-
-
-class CommandMgrImpl;
-
/// @brief Commands Manager implementation for the Kea servers.
///
/// This class extends @ref BaseCommandMgr with the ability to receive and
@@ -44,48 +23,13 @@ public:
/// @brief CommandMgr is a singleton class. This method returns reference
/// to its sole instance.
///
- /// @return the only existing instance of the manager
+ /// @return the only existing instance of the manager.
static CommandMgr& instance();
- /// @brief Sets IO service to be used by the command manager.
- ///
- /// The server should use this method to provide the Command Manager with the
- /// common IO service used by the server.
- /// @param io_service Pointer to the IO service.
- void setIOService(const asiolink::IOServicePtr& io_service);
-
- /// @brief Override default connection timeout.
- ///
- /// @param timeout New connection timeout in milliseconds.
- void setConnectionTimeout(const long timeout);
-
- /// @brief Opens control socket with parameters specified in socket_info
- ///
- /// Currently supported types are:
- /// - unix (required parameters: socket-type: unix, socket-name:/unix/path)
- ///
- /// @throw BadSocketInfo When socket configuration is invalid.
- /// @throw SocketError When socket operation fails.
- ///
- /// @param socket_info Configuration information for the control socket.
- void
- openCommandSocket(const isc::data::ConstElementPtr& socket_info);
-
- /// @brief Shuts down any open control sockets
- void closeCommandSocket();
-
- /// @brief Returns control socket descriptor
- ///
- /// This method should be used only in tests.
- int getControlSocketFD();
-
private:
- /// @brief Private constructor
+ /// @brief Private constructor.
CommandMgr();
-
- /// @brief Pointer to the implementation of the @ref CommandMgr.
- boost::shared_ptr<CommandMgrImpl> impl_;
};
} // end of isc::config namespace
diff --git a/src/lib/config/http_command_mgr.cc b/src/lib/config/http_command_mgr.cc
index fafe50eb11..2f396ae83e 100644
--- a/src/lib/config/http_command_mgr.cc
+++ b/src/lib/config/http_command_mgr.cc
@@ -215,8 +215,7 @@ HttpCommandMgr::instance() {
return (http_cmd_mgr);
}
-HttpCommandMgr::HttpCommandMgr()
- : HookedCommandMgr(), impl_(new HttpCommandMgrImpl()) {
+HttpCommandMgr::HttpCommandMgr() : impl_(new HttpCommandMgrImpl()) {
}
void
diff --git a/src/lib/config/http_command_mgr.h b/src/lib/config/http_command_mgr.h
index 9cd51707a6..12b2bf3b9f 100644
--- a/src/lib/config/http_command_mgr.h
+++ b/src/lib/config/http_command_mgr.h
@@ -9,7 +9,6 @@
#include <asiolink/io_service.h>
#include <config/http_command_config.h>
-#include <config/hooked_command_mgr.h>
#include <http/listener.h>
#include <boost/noncopyable.hpp>
@@ -21,8 +20,8 @@ class HttpCommandMgrImpl;
/// @brief HTTP Commands Manager implementation for the Kea servers.
///
-/// Similar to @c CommandMgr but using HTTP/HTTPS instead of UNIX sockets.
-class HttpCommandMgr : public HookedCommandMgr, public boost::noncopyable {
+/// Similar to @c UnixCommandMgr but using HTTP/HTTPS instead of UNIX sockets.
+class HttpCommandMgr : public boost::noncopyable {
public:
/// @brief HttpCommandMgr is a singleton class. This method
@@ -31,7 +30,7 @@ public:
/// @return The only existing instance of the manager.
static HttpCommandMgr& instance();
- /// @brief Sets IO service to be used by the command manager.
+ /// @brief Sets IO service to be used by the http command manager.
///
/// The server should use this method to provide the Command
/// Manager with the common IO service used by the server.
@@ -56,12 +55,12 @@ public:
/// @param use_external True (default) add external sockets.
void addExternalSockets(bool use_external = true);
- /// @brief Configure control socket from configuration.
+ /// @brief Configure http control socket from configuration.
///
- /// @param config Configuration of the control socket.
+ /// @param config Configuration of the control http socket.
void configure(HttpCommandConfigPtr config);
- /// @brief Close control socket.
+ /// @brief Close http control socket.
///
/// @note When remove is false @c garbageCollectListeners must
/// be called after.
diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am
index 41077ce953..3a95de4d2d 100644
--- a/src/lib/config/tests/Makefile.am
+++ b/src/lib/config/tests/Makefile.am
@@ -21,6 +21,7 @@ TESTS += run_unittests
run_unittests_SOURCES = client_connection_unittests.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_SOURCES += command_mgr_unittests.cc
+run_unittests_SOURCES += unix_command_mgr_unittests.cc
run_unittests_SOURCES += cmd_http_listener_unittests.cc
run_unittests_SOURCES += cmd_response_creator_unittests.cc
run_unittests_SOURCES += cmd_response_creator_factory_unittests.cc
diff --git a/src/lib/config/tests/command_mgr_unittests.cc b/src/lib/config/tests/command_mgr_unittests.cc
index 492bcfa825..289548521f 100644
--- a/src/lib/config/tests/command_mgr_unittests.cc
+++ b/src/lib/config/tests/command_mgr_unittests.cc
@@ -8,8 +8,6 @@
#include <gtest/gtest.h>
-#include <testutils/sandbox.h>
-#include <asiolink/io_service.h>
#include <config/base_command_mgr.h>
#include <config/command_mgr.h>
#include <config/hooked_command_mgr.h>
@@ -20,7 +18,6 @@
#include <string>
#include <vector>
-using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::hooks;
@@ -29,14 +26,8 @@ using namespace std;
// Test class for Command Manager
class CommandMgrTest : public ::testing::Test {
public:
- isc::test::Sandbox sandbox;
-
/// Default constructor
- CommandMgrTest()
- : io_service_(new IOService()) {
-
- CommandMgr::instance().setIOService(io_service_);
-
+ CommandMgrTest() {
handler_name_ = "";
handler_params_ = ElementPtr();
handler_called_ = false;
@@ -45,7 +36,6 @@ public:
processed_log_ = "";
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().closeCommandSocket();
resetCalloutIndicators();
}
@@ -53,23 +43,9 @@ public:
/// Default destructor
virtual ~CommandMgrTest() {
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().closeCommandSocket();
resetCalloutIndicators();
}
- /// @brief Returns socket path (using either hardcoded path or env variable)
- /// @return path to the unix socket
- std::string getSocketPath() {
- std::string socket_path;
- const char* env = getenv("KEA_SOCKET_TEST_DIR");
- if (env) {
- socket_path = std::string(env) + "/test-socket";
- } else {
- socket_path = sandbox.join("test-socket");
- }
- return (socket_path);
- }
-
/// @brief Resets indicators related to callout invocation.
///
/// It also removes any registered callouts.
@@ -150,9 +126,6 @@ public:
return (0);
}
- /// @brief IO service used by these tests.
- IOServicePtr io_service_;
-
/// @brief Name of the command (used in my_handler)
static std::string handler_name_;
@@ -427,46 +400,6 @@ TEST_F(CommandMgrTest, delegateListCommands) {
EXPECT_EQ("my-command-bis", command_names_list[2]);
}
-// This test verifies that a Unix socket can be opened properly and that input
-// parameters (socket-type and socket-name) are verified.
-TEST_F(CommandMgrTest, unixCreate) {
- // Null pointer is obviously a bad idea.
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(ConstElementPtr()),
- isc::config::BadSocketInfo);
-
- // So is passing no parameters.
- ElementPtr socket_info = Element::createMap();
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::BadSocketInfo);
-
- // We don't support ipx sockets
- socket_info->set("socket-type", Element::create("ipx"));
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::BadSocketInfo);
-
- socket_info->set("socket-type", Element::create("unix"));
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::BadSocketInfo);
-
- socket_info->set("socket-name", Element::create(getSocketPath()));
- EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
- EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
-
- // It should be possible to close the socket.
- EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
-}
-
-// This test checks that when unix path is too long, the socket cannot be opened.
-TEST_F(CommandMgrTest, unixCreateTooLong) {
- ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
- "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
- "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
- "\" }");
-
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- SocketError);
-}
-
// This test verifies that a registered callout for the command_processed
// hookpoint is invoked and passed the correct information.
TEST_F(CommandMgrTest, commandProcessedHook) {
@@ -542,28 +475,3 @@ TEST_F(CommandMgrTest, commandProcessedHookReplaceResponse) {
"{ \"result\": 2, \"text\": \"'change-response' command not supported.\" }",
processed_log_);
}
-
-// Verifies that a socket cannot be concurrently opened more than once.
-TEST_F(CommandMgrTest, exclusiveOpen) {
- // Pass in valid parameters.
- ElementPtr socket_info = Element::createMap();
- socket_info->set("socket-type", Element::create("unix"));
- socket_info->set("socket-name", Element::create(getSocketPath()));
-
- EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
- EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
-
- // Should not be able to open it twice.
- EXPECT_THROW(CommandMgr::instance().openCommandSocket(socket_info),
- isc::config::SocketError);
-
- // Now let's close it.
- EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
-
- // Should be able to re-open it now.
- EXPECT_NO_THROW(CommandMgr::instance().openCommandSocket(socket_info));
- EXPECT_GE(CommandMgr::instance().getControlSocketFD(), 0);
-
- // Now let's close it.
- EXPECT_NO_THROW(CommandMgr::instance().closeCommandSocket());
-}
diff --git a/src/lib/config/tests/unix_command_mgr_unittests.cc b/src/lib/config/tests/unix_command_mgr_unittests.cc
new file mode 100644
index 0000000000..9b59fb6aee
--- /dev/null
+++ b/src/lib/config/tests/unix_command_mgr_unittests.cc
@@ -0,0 +1,117 @@
+// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <gtest/gtest.h>
+
+#include <testutils/sandbox.h>
+#include <asiolink/io_service.h>
+#include <config/unix_command_mgr.h>
+#include <string>
+
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+using namespace std;
+
+// Test class for Unix Command Manager
+class UnixCommandMgrTest : public ::testing::Test {
+public:
+ isc::test::Sandbox sandbox;
+
+ /// Default constructor
+ UnixCommandMgrTest() : io_service_(new IOService()) {
+ UnixCommandMgr::instance().setIOService(io_service_);
+ UnixCommandMgr::instance().closeCommandSocket();
+ }
+
+ /// Default destructor
+ virtual ~UnixCommandMgrTest() {
+ UnixCommandMgr::instance().closeCommandSocket();
+ }
+
+ /// @brief Returns socket path (using either hardcoded path or env variable)
+ /// @return path to the unix socket
+ std::string getSocketPath() {
+ std::string socket_path;
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
+ if (env) {
+ socket_path = std::string(env) + "/test-socket";
+ } else {
+ socket_path = sandbox.join("test-socket");
+ }
+ return (socket_path);
+ }
+
+ /// @brief IO service used by these tests.
+ IOServicePtr io_service_;
+};
+
+// This test verifies that a Unix socket can be opened properly and that input
+// parameters (socket-type and socket-name) are verified.
+TEST_F(UnixCommandMgrTest, unixCreate) {
+ // Null pointer is obviously a bad idea.
+ EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(ConstElementPtr()),
+ isc::config::BadSocketInfo);
+
+ // So is passing no parameters.
+ ElementPtr socket_info = Element::createMap();
+ EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ // We don't support ipx sockets
+ socket_info->set("socket-type", Element::create("ipx"));
+ EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-type", Element::create("unix"));
+ EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::BadSocketInfo);
+
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+ EXPECT_NO_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(UnixCommandMgr::instance().getControlSocketFD(), 0);
+
+ // It should be possible to close the socket.
+ EXPECT_NO_THROW(UnixCommandMgr::instance().closeCommandSocket());
+}
+
+// This test checks that when unix path is too long, the socket cannot be opened.
+TEST_F(UnixCommandMgrTest, unixCreateTooLong) {
+ ElementPtr socket_info = Element::fromJSON("{ \"socket-type\": \"unix\","
+ "\"socket-name\": \"/tmp/toolongtoolongtoolongtoolongtoolongtoolong"
+ "toolongtoolongtoolongtoolongtoolongtoolongtoolongtoolongtoolong"
+ "\" }");
+
+ EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
+ SocketError);
+}
+
+// Verifies that a socket cannot be concurrently opened more than once.
+TEST_F(UnixCommandMgrTest, exclusiveOpen) {
+ // Pass in valid parameters.
+ ElementPtr socket_info = Element::createMap();
+ socket_info->set("socket-type", Element::create("unix"));
+ socket_info->set("socket-name", Element::create(getSocketPath()));
+
+ EXPECT_NO_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(UnixCommandMgr::instance().getControlSocketFD(), 0);
+
+ // Should not be able to open it twice.
+ EXPECT_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info),
+ isc::config::SocketError);
+
+ // Now let's close it.
+ EXPECT_NO_THROW(UnixCommandMgr::instance().closeCommandSocket());
+
+ // Should be able to re-open it now.
+ EXPECT_NO_THROW(UnixCommandMgr::instance().openCommandSocket(socket_info));
+ EXPECT_GE(UnixCommandMgr::instance().getControlSocketFD(), 0);
+
+ // Now let's close it.
+ EXPECT_NO_THROW(UnixCommandMgr::instance().closeCommandSocket());
+}
diff --git a/src/lib/config/unix_command_mgr.cc b/src/lib/config/unix_command_mgr.cc
new file mode 100644
index 0000000000..94ed6f0a39
--- /dev/null
+++ b/src/lib/config/unix_command_mgr.cc
@@ -0,0 +1,663 @@
+// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <asiolink/io_service.h>
+#include <asiolink/unix_domain_socket.h>
+#include <asiolink/unix_domain_socket_acceptor.h>
+#include <asiolink/unix_domain_socket_endpoint.h>
+#include <config/command_mgr.h>
+#include <config/unix_command_mgr.h>
+#include <cc/data.h>
+#include <cc/command_interpreter.h>
+#include <cc/json_feed.h>
+#include <dhcp/iface_mgr.h>
+#include <config/config_log.h>
+#include <config/timeouts.h>
+#include <util/watch_socket.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <array>
+#include <functional>
+#include <unistd.h>
+#include <sys/file.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::config;
+using namespace isc::data;
+namespace ph = std::placeholders;
+
+namespace {
+
+/// @brief Maximum size of the data chunk sent/received over the socket.
+const size_t BUF_SIZE = 32768;
+
+class ConnectionPool;
+
+/// @brief Represents a single connection over control socket.
+///
+/// An instance of this object is created when the @c CommandMgr acceptor
+/// receives new connection from a controlling client.
+class Connection : public boost::enable_shared_from_this<Connection> {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor registers a socket of this connection in the Interface
+ /// Manager to cause the blocking call to @c select() to return as soon as
+ /// a transmission over the control socket is received.
+ ///
+ /// It installs two external sockets on the @IfaceMgr to break synchronous
+ /// calls to @select(). The @c WatchSocket is used for send operations
+ /// over the connection. The native socket is used for signaling reads
+ /// over the connection.
+ ///
+ /// @param io_service IOService object used to handle the asio operations
+ /// @param socket Pointer to the object representing a socket which is used
+ /// for data transmission.
+ /// @param connection_pool Reference to the connection pool to which this
+ /// connection belongs.
+ /// @param timeout Connection timeout (in seconds).
+ Connection(const IOServicePtr& io_service,
+ const boost::shared_ptr<UnixDomainSocket>& socket,
+ ConnectionPool& connection_pool,
+ const long timeout)
+ : socket_(socket), timeout_timer_(io_service), timeout_(timeout),
+ buf_(), response_(), connection_pool_(connection_pool), feed_(),
+ response_in_progress_(false), watch_socket_(new util::WatchSocket()) {
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_OPENED)
+ .arg(socket_->getNative());
+
+ // Callback value of 0 is used to indicate that callback function is
+ // not installed.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(watch_socket_->getSelectFd(), 0);
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_->getNative(), 0);
+
+ // Initialize state model for receiving and preparsing commands.
+ feed_.initModel();
+
+ // Start timer for detecting timeouts.
+ scheduleTimer();
+ }
+
+ /// @brief Destructor.
+ ///
+ /// Cancels timeout timer if one is scheduled.
+ ~Connection() {
+ timeout_timer_.cancel();
+ }
+
+ /// @brief This method schedules timer or reschedules existing timer.
+ void scheduleTimer() {
+ timeout_timer_.setup(std::bind(&Connection::timeoutHandler, this),
+ timeout_, IntervalTimer::ONE_SHOT);
+ }
+
+ /// @brief Close current connection.
+ ///
+ /// Connection is not closed if the invocation of this method is a result of
+ /// server reconfiguration. The connection will be closed once a response is
+ /// sent to the client. Closing a socket during processing a request would
+ /// cause the server to not send a response to the client.
+ void stop() {
+ if (!response_in_progress_) {
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_CONNECTION_CLOSED)
+ .arg(socket_->getNative());
+
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(watch_socket_->getSelectFd());
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_->getNative());
+
+ // Close watch socket and log errors if occur.
+ std::string watch_error;
+ if (!watch_socket_->closeSocket(watch_error)) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLOSE_ERROR)
+ .arg(watch_error);
+ }
+
+ socket_->close();
+ timeout_timer_.cancel();
+ }
+ }
+
+ /// @brief Gracefully terminates current connection.
+ ///
+ /// This method should be called prior to closing the socket to initiate
+ /// graceful shutdown.
+ void terminate();
+
+ /// @brief Start asynchronous read over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the transmission is received over the
+ /// socket, the @c Connection::receiveHandler callback is invoked to
+ /// process received data.
+ void doReceive() {
+ socket_->asyncReceive(&buf_[0], sizeof(buf_),
+ std::bind(&Connection::receiveHandler,
+ shared_from_this(), ph::_1, ph::_2));
+ }
+
+ /// @brief Starts asynchronous send over the unix domain socket.
+ ///
+ /// This method doesn't block. Once the send operation (that covers the whole
+ /// data if it's small or first BUF_SIZE bytes if its large) is completed, the
+ /// @c Connection::sendHandler callback is invoked. That handler will either
+ /// close the connection gracefully if all data has been sent, or will
+ /// call @ref doSend() again to send the next chunk of data.
+ void doSend() {
+ size_t chunk_size = (response_.size() < BUF_SIZE) ? response_.size() : BUF_SIZE;
+ socket_->asyncSend(&response_[0], chunk_size,
+ std::bind(&Connection::sendHandler, shared_from_this(), ph::_1, ph::_2));
+
+ // Asynchronous send has been scheduled and we need to indicate this
+ // to break the synchronous select(). The handler should clear this
+ // status when invoked.
+ try {
+ watch_socket_->markReady();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_MARK_READY_ERROR)
+ .arg(ex.what());
+ }
+ }
+
+ /// @brief Handler invoked when the data is received over the control
+ /// socket.
+ ///
+ /// It collects received data into the @c isc::config::JSONFeed object and
+ /// schedules additional asynchronous read of data if this object signals
+ /// that command is incomplete. When the entire command is received, the
+ /// handler processes this command and asynchronously responds to the
+ /// controlling client.
+ //
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes received.
+ void receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+ /// @brief Handler invoked when the data is sent over the control socket.
+ ///
+ /// If there are still data to be sent, another asynchronous send is
+ /// scheduled. When the entire command is sent, the connection is shutdown
+ /// and closed.
+ ///
+ /// @param ec Error code.
+ /// @param bytes_transferred Number of bytes sent.
+ void sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred);
+
+ /// @brief Handler invoked when timeout has occurred.
+ ///
+ /// Asynchronously sends a response to the client indicating that the
+ /// timeout has occurred.
+ void timeoutHandler();
+
+private:
+
+ /// @brief Pointer to the socket used for transmission.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Interval timer used to detect connection timeouts.
+ IntervalTimer timeout_timer_;
+
+ /// @brief Connection timeout (in milliseconds)
+ long timeout_;
+
+ /// @brief Buffer used for received data.
+ std::array<char, BUF_SIZE> buf_;
+
+ /// @brief Response created by the server.
+ std::string response_;
+
+ /// @brief Reference to the pool of connections.
+ ConnectionPool& connection_pool_;
+
+ /// @brief State model used to receive data over the connection and detect
+ /// when the command ends.
+ JSONFeed feed_;
+
+ /// @brief Boolean flag indicating if the request to stop connection is a
+ /// result of server reconfiguration.
+ bool response_in_progress_;
+
+ /// @brief Pointer to watch socket instance used to signal that the socket
+ /// is ready for read or write.
+ util::WatchSocketPtr watch_socket_;
+};
+
+/// @brief Pointer to the @c Connection.
+typedef boost::shared_ptr<Connection> ConnectionPtr;
+
+/// @brief Holds all open connections.
+class ConnectionPool {
+public:
+
+ /// @brief Starts new connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void start(const ConnectionPtr& connection) {
+ connection->doReceive();
+ connections_.insert(connection);
+ }
+
+ /// @brief Stops running connection.
+ ///
+ /// @param connection Pointer to the new connection object.
+ void stop(const ConnectionPtr& connection) {
+ try {
+ connection->stop();
+ connections_.erase(connection);
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CLOSE_FAIL)
+ .arg(ex.what());
+ }
+ }
+
+ /// @brief Stops all connections which are allowed to stop.
+ void stopAll() {
+ for (auto const& conn : connections_) {
+ conn->stop();
+ }
+ connections_.clear();
+ }
+
+private:
+
+ /// @brief Pool of connections.
+ std::set<ConnectionPtr> connections_;
+
+};
+
+void
+Connection::terminate() {
+ try {
+ socket_->shutdown();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_SHUTDOWN_FAIL)
+ .arg(ex.what());
+ }
+}
+
+void
+Connection::receiveHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ if (ec) {
+ if (ec.value() == boost::asio::error::eof) {
+ std::stringstream os;
+ if (feed_.getProcessedText().empty()) {
+ os << "no input data to discard";
+ } else {
+ os << "discarding partial command of "
+ << feed_.getProcessedText().size() << " bytes";
+ }
+
+ // Foreign host has closed the connection. We should remove it from the
+ // connection pool.
+ LOG_INFO(command_logger, COMMAND_SOCKET_CLOSED_BY_FOREIGN_HOST)
+ .arg(socket_->getNative()).arg(os.str());
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_READ_FAIL)
+ .arg(ec.value()).arg(socket_->getNative());
+ }
+
+ connection_pool_.stop(shared_from_this());
+ return;
+
+ } else if (bytes_transferred == 0) {
+ // Nothing received. Close the connection.
+ connection_pool_.stop(shared_from_this());
+ return;
+ }
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ)
+ .arg(bytes_transferred).arg(socket_->getNative());
+
+ // Reschedule the timer because the transaction is ongoing.
+ scheduleTimer();
+
+ ConstElementPtr cmd;
+ ConstElementPtr rsp;
+
+ try {
+ // Received some data over the socket. Append them to the JSON feed
+ // to see if we have reached the end of command.
+ feed_.postBuffer(&buf_[0], bytes_transferred);
+ feed_.poll();
+ // If we haven't yet received the full command, continue receiving.
+ if (feed_.needData()) {
+ doReceive();
+ return;
+ }
+
+ // Received entire command. Parse the command into JSON.
+ if (feed_.feedOk()) {
+ cmd = feed_.toElement();
+ response_in_progress_ = true;
+
+ // Cancel the timer to make sure that long lasting command
+ // processing doesn't cause the timeout.
+ timeout_timer_.cancel();
+
+ // If successful, then process it as a command.
+ rsp = CommandMgr::instance().processCommand(cmd);
+
+ response_in_progress_ = false;
+
+ } else {
+ // Failed to parse command as JSON or process the received command.
+ // This exception will be caught below and the error response will
+ // be sent.
+ isc_throw(BadValue, feed_.getErrorMessage());
+ }
+
+ } catch (const Exception& ex) {
+ LOG_WARN(command_logger, COMMAND_PROCESS_ERROR1).arg(ex.what());
+ rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
+ }
+
+ // No response generated. Connection will be closed.
+ if (!rsp) {
+ LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR)
+ .arg(cmd ? cmd->str() : "unknown");
+ rsp = createAnswer(CONTROL_RESULT_ERROR,
+ "internal server error: no response generated");
+
+ } else {
+
+ // Reschedule the timer as it may be either canceled or need to be
+ // updated to not timeout before we manage to the send the reply.
+ scheduleTimer();
+
+ // Let's convert JSON response to text. Note that at this stage
+ // the rsp pointer is always set.
+ response_ = rsp->str();
+
+ doSend();
+ return;
+ }
+
+ // Close the connection if we have sent the entire response.
+ connection_pool_.stop(shared_from_this());
+}
+
+void
+Connection::sendHandler(const boost::system::error_code& ec,
+ size_t bytes_transferred) {
+ // Clear the watch socket so as the future send operation can mark it
+ // again to interrupt the synchronous select() call.
+ try {
+ watch_socket_->clearReady();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_WATCH_SOCKET_CLEAR_ERROR)
+ .arg(ex.what());
+ }
+
+ if (ec) {
+ // If an error occurred, log this error and stop the connection.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_WRITE_FAIL)
+ .arg(socket_->getNative()).arg(ec.message());
+ }
+
+ } else {
+
+ // Reschedule the timer because the transaction is ongoing.
+ scheduleTimer();
+
+ // No error. We are in a process of sending a response. Need to
+ // remove the chunk that we have managed to sent with the previous
+ // attempt.
+ response_.erase(0, bytes_transferred);
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE)
+ .arg(bytes_transferred).arg(response_.size())
+ .arg(socket_->getNative());
+
+ // Check if there is any data left to be sent and sent it.
+ if (!response_.empty()) {
+ doSend();
+ return;
+ }
+
+ // Gracefully shutdown the connection and close the socket if
+ // we have sent the whole response.
+ terminate();
+ }
+
+ // All data sent or an error has occurred. Close the connection.
+ connection_pool_.stop(shared_from_this());
+}
+
+void
+Connection::timeoutHandler() {
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_TIMEOUT)
+ .arg(socket_->getNative());
+
+ try {
+ socket_->cancel();
+
+ } catch (const std::exception& ex) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_CONNECTION_CANCEL_FAIL)
+ .arg(socket_->getNative())
+ .arg(ex.what());
+ }
+
+ std::stringstream os;
+ os << "Connection over control channel timed out";
+ if (!feed_.getProcessedText().empty()) {
+ os << ", discarded partial command of "
+ << feed_.getProcessedText().size() << " bytes";
+ }
+
+ ConstElementPtr rsp = createAnswer(CONTROL_RESULT_ERROR, os.str());
+ response_ = rsp->str();
+ doSend();
+}
+
+}
+
+namespace isc {
+namespace config {
+
+/// @brief Implementation of the @c UnixCommandMgr.
+class UnixCommandMgrImpl {
+public:
+
+ /// @brief Constructor.
+ UnixCommandMgrImpl()
+ : io_service_(), acceptor_(), socket_(), socket_name_(),
+ connection_pool_(), timeout_(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND) {
+ }
+
+ /// @brief Opens acceptor service allowing the control clients to connect.
+ ///
+ /// @param socket_info Configuration information for the control socket.
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ void openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+
+ /// @brief Asynchronously accepts next connection.
+ void doAccept();
+
+ /// @brief Returns the lock file name
+ std::string getLockName() {
+ return (std::string(socket_name_ + ".lock"));
+ }
+
+ /// @brief Pointer to the IO service used by the server process for running
+ /// asynchronous tasks.
+ IOServicePtr io_service_;
+
+ /// @brief Pointer to the acceptor service.
+ boost::shared_ptr<UnixDomainSocketAcceptor> acceptor_;
+
+ /// @brief Pointer to the socket into which the new connection is accepted.
+ boost::shared_ptr<UnixDomainSocket> socket_;
+
+ /// @brief Path to the unix domain socket descriptor.
+ ///
+ /// This is used to remove the socket file once the connection terminates.
+ std::string socket_name_;
+
+ /// @brief Pool of connections.
+ ConnectionPool connection_pool_;
+
+ /// @brief Connection timeout
+ long timeout_;
+};
+
+void
+UnixCommandMgrImpl::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ socket_name_.clear();
+
+ if(!socket_info) {
+ isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
+ }
+
+ ConstElementPtr type = socket_info->get("socket-type");
+ if (!type) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
+ }
+
+ // Only supporting unix sockets right now.
+ if (type->stringValue() != "unix") {
+ isc_throw(BadSocketInfo, "Invalid 'socket-type' parameter value "
+ << type->stringValue());
+ }
+
+ // UNIX socket is requested. It takes one parameter: socket-name that
+ // specifies UNIX path of the socket.
+ ConstElementPtr name = socket_info->get("socket-name");
+ if (!name) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
+ }
+
+ if (name->getType() != Element::string) {
+ isc_throw(BadSocketInfo, "'socket-name' parameter expected to be a string");
+ }
+
+ socket_name_ = name->stringValue();
+
+ // First let's open lock file.
+ std::string lock_name = getLockName();
+ int lock_fd = open(lock_name.c_str(), O_RDONLY | O_CREAT, 0600);
+ if (lock_fd == -1) {
+ std::string errmsg = strerror(errno);
+ isc_throw(SocketError, "cannot create socket lockfile, "
+ << lock_name << ", : " << errmsg);
+ }
+
+ // Try to acquire lock. If we can't somebody else is actively
+ // using it.
+ int ret = flock(lock_fd, LOCK_EX | LOCK_NB);
+ if (ret != 0) {
+ std::string errmsg = strerror(errno);
+ isc_throw(SocketError, "cannot lock socket lockfile, "
+ << lock_name << ", : " << errmsg);
+ }
+
+ // We have the lock, so let's remove the pre-existing socket
+ // file if it exists.
+ static_cast<void>(::remove(socket_name_.c_str()));
+
+ LOG_INFO(command_logger, COMMAND_ACCEPTOR_START)
+ .arg(socket_name_);
+
+ try {
+ // Start asynchronous acceptor service.
+ acceptor_.reset(new UnixDomainSocketAcceptor(io_service_));
+ UnixDomainSocketEndpoint endpoint(socket_name_);
+ acceptor_->open(endpoint);
+ acceptor_->bind(endpoint);
+ acceptor_->listen();
+ // Install this socket in Interface Manager.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(acceptor_->getNative(), 0);
+
+ doAccept();
+
+ } catch (const std::exception& ex) {
+ isc_throw(SocketError, ex.what());
+ }
+}
+
+void
+UnixCommandMgrImpl::doAccept() {
+ // Create a socket into which the acceptor will accept new connection.
+ socket_.reset(new UnixDomainSocket(io_service_));
+ acceptor_->asyncAccept(*socket_, [this](const boost::system::error_code& ec) {
+ if (!ec) {
+ // New connection is arriving. Start asynchronous transmission.
+ ConnectionPtr connection(new Connection(io_service_, socket_,
+ connection_pool_,
+ timeout_));
+ connection_pool_.start(connection);
+
+ } else if (ec.value() != boost::asio::error::operation_aborted) {
+ LOG_ERROR(command_logger, COMMAND_SOCKET_ACCEPT_FAIL)
+ .arg(acceptor_->getNative()).arg(ec.message());
+ }
+
+ // Unless we're stopping the service, start accepting connections again.
+ if (ec.value() != boost::asio::error::operation_aborted) {
+ doAccept();
+ }
+ });
+}
+
+UnixCommandMgr::UnixCommandMgr() : impl_(new UnixCommandMgrImpl()) {
+}
+
+void
+UnixCommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ impl_->openCommandSocket(socket_info);
+}
+
+void
+UnixCommandMgr::closeCommandSocket() {
+ // Close acceptor if the acceptor is open.
+ if (impl_->acceptor_ && impl_->acceptor_->isOpen()) {
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(impl_->acceptor_->getNative());
+ impl_->acceptor_->close();
+ static_cast<void>(::remove(impl_->socket_name_.c_str()));
+ static_cast<void>(::remove(impl_->getLockName().c_str()));
+ }
+
+ // Stop all connections which can be closed. The only connection that won't
+ // be closed is the one over which we have received a request to reconfigure
+ // the server. This connection will be held until the UnixCommandMgr
+ // responds to such request.
+ impl_->connection_pool_.stopAll();
+}
+
+int
+UnixCommandMgr::getControlSocketFD() {
+ return (impl_->acceptor_ ? impl_->acceptor_->getNative() : -1);
+}
+
+UnixCommandMgr&
+UnixCommandMgr::instance() {
+ static UnixCommandMgr cmd_mgr;
+ return (cmd_mgr);
+}
+
+void
+UnixCommandMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->io_service_ = io_service;
+}
+
+void
+UnixCommandMgr::setConnectionTimeout(const long timeout) {
+ impl_->timeout_ = timeout;
+}
+
+} // end of isc::config
+} // end of isc
diff --git a/src/lib/config/unix_command_mgr.h b/src/lib/config/unix_command_mgr.h
new file mode 100644
index 0000000000..1bed1e532e
--- /dev/null
+++ b/src/lib/config/unix_command_mgr.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2015-2024 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef UNIX_COMMAND_MGR_H
+#define UNIX_COMMAND_MGR_H
+
+#include <asiolink/io_service.h>
+#include <cc/data.h>
+#include <exceptions/exceptions.h>
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace config {
+
+/// @brief An exception indicating that specified socket parameters are invalid
+class BadSocketInfo : public Exception {
+public:
+ BadSocketInfo(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief An exception indicating a problem with socket operation
+class SocketError : public Exception {
+public:
+ SocketError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+class UnixCommandMgrImpl;
+
+/// @brief Unix Commands Manager implementation for the Kea servers.
+///
+/// This class receives and responds to commands over unix domain sockets.
+class UnixCommandMgr : public boost::noncopyable {
+public:
+
+ /// @brief UnixCommandMgr is a singleton class. This method
+ /// returns reference to its sole instance.
+ ///
+ /// @return the only existing instance of the manager.
+ static UnixCommandMgr& instance();
+
+ /// @brief Sets IO service to be used by the unix command manager.
+ ///
+ /// The server should use this method to provide the Unix Command
+ /// Manager with the common IO service used by the server.
+ /// @param io_service Pointer to the IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
+ /// @brief Override default connection timeout.
+ ///
+ /// @param timeout New connection timeout in milliseconds.
+ void setConnectionTimeout(const long timeout);
+
+ /// @brief Opens unix control socket with parameters specified in socket_info
+ /// (required parameters: socket-type: unix, socket-name:/unix/path).
+ ///
+ /// @throw BadSocketInfo When socket configuration is invalid.
+ /// @throw SocketError When socket operation fails.
+ ///
+ /// @param socket_info Configuration information for the unix control socket.
+ void
+ openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+
+ /// @brief Shuts down any open unix control sockets
+ void closeCommandSocket();
+
+ /// @brief Returns unix control socket descriptor
+ ///
+ /// This method should be used only in tests.
+ int getControlSocketFD();
+
+private:
+
+ /// @brief Private constructor.
+ UnixCommandMgr();
+
+ /// @brief Pointer to the implementation of the @ref UnixCommandMgr.
+ boost::shared_ptr<UnixCommandMgrImpl> impl_;
+};
+
+} // end of isc::config namespace
+} // end of isc namespace
+#endif