]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[3880] Callback for handling UNIX connections implemented.
authorTomek Mrugalski <tomasz@isc.org>
Tue, 2 Jun 2015 20:04:30 +0000 (22:04 +0200)
committerTomek Mrugalski <tomasz@isc.org>
Wed, 10 Jun 2015 11:41:59 +0000 (13:41 +0200)
src/lib/Makefile.am
src/lib/config/Makefile.am
src/lib/config/command_mgr.cc
src/lib/config/command_mgr.h
src/lib/config/command_socket_factory.cc
src/lib/config/config_log.cc
src/lib/config/config_log.h
src/lib/config/config_messages.mes
src/lib/config/tests/Makefile.am

index 364e34ccb7a803f12ea0b79af2a67d9b5a1d5e0d..5c20c3d32dfa764fdb2ea0c18b8e00937e3be007 100644 (file)
@@ -1,3 +1,3 @@
 # The following build order must be maintained.
-SUBDIRS = exceptions util log hooks cryptolink dns cc stats config \
-          asiolink asiodns testutils dhcp dhcp_ddns dhcpsrv
+SUBDIRS = exceptions util log hooks cryptolink dns cc stats dhcp config \
+          asiolink asiodns testutils dhcp_ddns dhcpsrv
index 9922bf1c0ffbe69af8f15dd8c3973f33914675de..7e1d2c66d7c6832aae5220d55506deda52b70359 100644 (file)
@@ -25,6 +25,7 @@ libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
 libkea_cfgclient_la_LIBADD = $(top_builddir)/src/lib/cc/libkea-cc.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
+libkea_cfgclient_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 
 libkea_cfgclient_la_LDFLAGS = -no-undefined -version-info 1:0:1
 
index 814aa567786e30b41859921335ac1f0ba1d093b9..7d9fd00c6b1931e96cdb77bf12d5acdace5b695f 100644 (file)
 #include <config/command_mgr.h>
 #include <config/command_socket_factory.h>
 #include <cc/data.h>
+#include <dhcp/iface_mgr.h>
+#include <config/config_log.h>
 #include <boost/bind.hpp>
 
+using namespace isc::data;
+
 namespace isc {
 namespace config {
 
@@ -33,12 +37,18 @@ int CommandMgr::openCtrlSocket(const isc::data::ConstElementPtr& socket_info) {
     socket_ = CommandSocketFactory::create(socket_info);
     socket_info_ = socket_info;
 
-    /// @todo: install socket in IfaceMgr
-    ///CommandSocketFactory::install(socket_, socket_info);
+    // Install this socket in Interface Manager.
+    isc::dhcp::IfaceMgr::instance().addExternalSocket(socket_,
+        boost::bind(&isc::config::CommandMgr::connectionAcceptor, socket_));
+
+    return (socket_);
 }
 
 void CommandMgr::closeCtrlSocket() {
     if (socket_info_) {
+
+        isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_);
+
         CommandSocketFactory::close(socket_, socket_info_);
         socket_ = 0;
         socket_info_.reset();
@@ -64,6 +74,8 @@ void CommandMgr::registerCommand(const std::string& cmd, CommandHandler handler)
     }
 
     handlers_.insert(make_pair(cmd, handler));
+
+    LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
 }
 
 void CommandMgr::deregisterCommand(const std::string& cmd) {
@@ -78,14 +90,116 @@ void CommandMgr::deregisterCommand(const std::string& cmd) {
                   << "' not found.");
     }
     handlers_.erase(it);
+
+    LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_DEREGISTERED).arg(cmd);
 }
 
 void CommandMgr::deregisterAll() {
+
+    // No need to log anything here. deregisterAll is not used in production
+    // code, just in tests.
     handlers_.clear();
     registerCommand("list-commands",
         boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
 }
 
+void
+CommandMgr::connectionAcceptor(int sockfd) {
+
+    /// @todo: Either make this generic or rename this method
+    /// to CommandSocketFactory::unixConnectionAcceptor
+    struct sockaddr_un client_addr;
+    socklen_t client_addr_len;
+    client_addr_len = sizeof(client_addr);
+
+    // Accept incoming connection. This will create a separate socket for
+    // handling this specific connection.
+    int fd2 = accept(sockfd, (struct sockaddr*) &client_addr, &client_addr_len);
+
+    // Not sure if this is really needed, but let's set it to non-blocking mode.
+    fcntl(fd2, F_SETFL, O_NONBLOCK);
+
+    // Install commandReader callback. When there's any data incoming on this
+    // socket, commandReader will be called and process it. It may also
+    // eventually close this socket.
+    isc::dhcp::IfaceMgr::instance().addExternalSocket(fd2,
+        boost::bind(&isc::config::CommandMgr::commandReader, fd2));
+
+    LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED).arg(fd2).arg(sockfd);
+}
+
+void
+CommandMgr::commandReader(int sockfd) {
+
+    // We should not expect commands bigger than 64K.
+    char buf[65536];
+    memset(buf, 0, sizeof(buf));
+    ConstElementPtr cmd, rsp;
+
+    // Read incoming data.
+    int rval = read(sockfd, buf, sizeof(buf));
+    if (rval < 0) {
+        // Read failed
+        LOG_WARN(command_logger, COMMAND_SOCKET_READ_FAIL).arg(rval).arg(sockfd);
+        return;
+    } else if (rval == 0) {
+
+        // read of 0 bytes means end-of-file. In other words the connection is
+        // being closed.
+
+        LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_CLOSED).arg(sockfd);
+
+        // Unregister this callback
+        isc::dhcp::IfaceMgr::instance().deleteExternalSocket(sockfd);
+
+        // Close the socket.
+        close(sockfd);
+        return;
+    }
+
+    LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_READ).arg(rval).arg(sockfd);
+
+    // Ok, we received something. Let's see if we can make any sense of it.
+    try {
+
+        // Try to interpret it as JSON.
+        cmd = Element::fromJSON(std::string(buf), true);
+
+        // If successful, then process it as a command.
+        rsp = CommandMgr::instance().processCommand(cmd);
+    } catch (const Exception& ex) {
+        LOG_WARN(command_logger, COMMAND_PROCESS_ERROR).arg(ex.what());
+        rsp = createAnswer(CONTROL_RESULT_ERROR, std::string(ex.what()));
+    }
+
+    if (!rsp) {
+        LOG_WARN(command_logger, COMMAND_RESPONSE_ERROR);
+        return;
+    }
+
+    // Let's convert JSON response to text. Note that at this stage
+    // the rsp pointer is always set.
+    std::string txt = rsp->str();
+    size_t len = txt.length();
+    if (len > 65535) {
+        // Hmm, our response is too large. Let's send the first
+        // 64KB and hope for the best.
+        len = 65535;
+    }
+
+    // Send the data back over socket.
+    rval = write(sockfd, txt.c_str(), len);
+
+    LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_SOCKET_WRITE).arg(len).arg(sockfd);
+
+    if (rval < 0) {
+        // Response transmission failed. Since the response failed, it doesn't
+        // make sense to send any status codes. Let's log it and be done with
+        // it.
+        LOG_WARN(command_logger, COMMAND_SOCKET_WRITE_FAIL).arg(len).arg(sockfd);
+    }
+}
+
 isc::data::ConstElementPtr
 CommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
     if (!cmd) {
@@ -94,9 +208,11 @@ CommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
     }
 
     try {
-        isc::data::ConstElementPtr arg;
+        ConstElementPtr arg;
         std::string name = parseCommand(arg, cmd);
 
+        LOG_INFO(command_logger, COMMAND_RECEIVED).arg(name);
+
         HandlerContainer::const_iterator it = handlers_.find(name);
         if (it == handlers_.end()) {
             // Ok, there's no such command.
@@ -112,7 +228,6 @@ CommandMgr::processCommand(const isc::data::ConstElementPtr& cmd) {
                              std::string("Error during command processing:")
                              + e.what()));
     }
-
 }
 
 isc::data::ConstElementPtr
index 27b59d86c8bed9edfdb4b5d3e8a2c54fbd6e6305..0a506bc48d8a247cee8e3aa5832fe1e3f3ad340c 100644 (file)
@@ -121,6 +121,26 @@ public:
     /// it may be called by external code explicitly. Hence this method is public.
     isc::data::ConstElementPtr processCommand(const isc::data::ConstElementPtr& cmd);
 
+    /// @brief Callback used to accept incoming connections.
+    ///
+    /// This callback is used on a control socket. Once called, it will accept
+    /// incoming connection, create new socket for it and install @ref commandReader
+    /// for that new socket in @ref isc::dhcp::IfaceMgr.
+    ///
+    /// @param sockfd socket descriptor of a socket capable of accepting
+    ///               incoming connections
+    static void connectionAcceptor(int sockfd);
+
+    /// @brief Reads data from a socket, parses as JSON command and processes it
+    ///
+    /// This method is used to handle traffic on connected socket. This callback
+    /// is installed by the @ref connectionAcceptor once the incoming connection
+    /// is accepted. If end-of-file is detected, this method will close the socket
+    /// and will uninstall itself from @ref isc::dhcp::IfaceMgr.
+    ///
+    /// @param sockfd socket descriptor of a connected socket
+    static void commandReader(int sockfd);
+
     /// @brief Auxiliary method that removes all installed commands.
     ///
     /// The only unwipeable method is list-commands, which is internally
index 3c1af33b76d182df50a0bee57e5eefcac361980c..d072def82a5734569435dc786a0bf4a7c412b5db 100644 (file)
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <config/command_socket_factory.h>
+#include <config/config_log.h>
+#include <cstdio>
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+using namespace isc::config;
+
+namespace {
 
 int createUnixSocket(const std::string& file_name) {
 
@@ -24,8 +32,15 @@ int createUnixSocket(const std::string& file_name) {
         isc_throw(isc::config::SocketError, "Failed to create AF_UNIX socket.");
     }
 
-    /// @todo: Do we need any setsockopt here?
+    // Let's remove the old file. We don't care about any possible
+    // errors here. The file should not be there if the file was
+    // shut down properly.
+    remove(file_name.c_str());
 
+    // Set this socket to be non-blocking one.
+    fcntl(fd, F_SETFL, O_NONBLOCK);
+
+    // Now bind the socket to the specified path.
     struct sockaddr_un addr;
     memset(&addr, 0, sizeof(addr));
     addr.sun_family = AF_UNIX;
@@ -35,14 +50,30 @@ int createUnixSocket(const std::string& file_name) {
                   << " to " << file_name);
     }
 
+    // One means that we allow at most 1 awaiting connections.
+    // Any additional attempts will get ECONNREFUSED error.
+    // That means that at any given time, there may be at most one controlling
+    // connection.
+    /// @todo: Make this a configurable.
+    int status = listen(fd, 1);
+    if (status < 0) {
+        isc_throw(isc::config::SocketError, "Failed to listen on socket fd="
+                  << fd << ", filename=" << file_name);
+    }
+
+    LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_OPEN).arg(fd).arg(file_name);
+
     return (fd);
 }
 
 void closeUnixSocket(int socket_fd, const std::string& file_name) {
+    LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_CLOSE).arg(socket_fd).arg(file_name);
     close(socket_fd);
     unlink(file_name.c_str());
 }
 
+}
+
 using namespace isc::data;
 
 namespace isc {
index 672b9f1cbadb3bb05b8a1c6911e6442cd9418294..ba95313cd4942bd60476c62e168fca7419b346b3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2015  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -21,6 +21,8 @@ namespace config {
 
 isc::log::Logger config_logger("config");
 
+isc::log::Logger command_logger("commands");
+
 } // namespace nsas
 } // namespace isc
 
index 14f681e6165e84e8feedfc058209071a1762abb6..2181211a719ff93a70b5553d85325be5600a18d3 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2011  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011,2015  Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 namespace isc {
 namespace config {
 
-/// \brief Config Logging
-///
-/// Defines logger object for config log messages
-
-/// \brief Config Logger
+/// @brief Command processing Logger
 ///
 /// Define the logger used to log messages.  We could define it in multiple
 /// modules, but defining in a single module and linking to it saves time and
 /// space.
 extern isc::log::Logger config_logger;
 
+/// @brief Command processing Logger
+///
+/// Define the logger used to log messages related to command processing.
+extern isc::log::Logger command_logger;
+
 // Enumerate configuration elements as they are processed.
 const int DBG_CONFIG_PROCESS = DBGLVL_TRACE_BASIC;
 
+// Enumerate configuration elements as they are processed.
+const int DBG_COMMAND = DBGLVL_COMMAND;
+
 } // namespace config
 } // namespace isc
 
index c5736cabb7951243a96f351624fd31edd93de5ab..7f3f55f9fe918cfc24e42486be473655b0b81d6d 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (C) 2011, 2014  Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2011, 2014-2015  Internet Systems Consortium, Inc. ("ISC")
 #
 # Permission to use, copy, modify, and/or distribute this software for any
 # purpose with or without fee is hereby granted, provided that the above
 
 $NAMESPACE isc::config
 
+% COMMAND_PROCESS_ERROR Error while processing command: %1
+This warning message indicates that the server encountered an error while
+processing received command. Additional information will be provided, if
+available. Additional log messages may provide more details.
+
+% COMMAND_RECEIVED Received command '%1'
+This informational message indicates that a command was received over command
+socket. The nature of this command and its possible results will be logged
+with separate messages.
+
+% COMMAND_RESPONSE_ERROR Server failed to generate response for command: %1
+This error message indicates that the server failed to generate response for
+specified command. This likely indicates a server logic error, as the server
+is expected to generate valid responses for all commands, even malformed
+ones.
+
+% COMMAND_SOCKET_READ Received %1 bytes over command socket %2
+This debug message indicates that specified number of bytes was received
+over command socket identified by specified file descriptor.
+
+% COMMAND_SOCKET_READ_FAIL Encountered error %1 while reading from command socket %2
+This warning message indicates that an error was encountered while
+reading from command socket.
+
+% COMMAND_SOCKET_WRITE Sent response of %1 bytes over command socket %2
+This debug message indicates that the specified number of bytes was sent
+over command socket identifier by the specified file descriptor.
+
+% COMMAND_SOCKET_WRITE_FAIL Error while writing %1 bytes to command socket %2
+This warning message indicates that an error was encountered while
+attempting to send a response to the command socket.
+
+% COMMAND_SOCKET_UNIX_OPEN Command socket opened: UNIX, fd=%1, path=%2
+This informational message indicates that the daemon opened a command
+processing socket. This is a UNIX socket. It was opened with the file
+descriptor and path specified.
+
+% COMMAND_SOCKET_UNIX_CLOSE Command socket closed: UNIX, fd=%1, path=%2
+This informational message indicates that the daemon closed a command
+processing socket. This was a UNIX socket. It was opened with the file
+descriptor and path specified.
+
+% COMMAND_SOCKET_CONNECTION_OPENED Opened socket %1 for incoming command connection on socket %2
+This is an informational message that a new incoming command connection was
+detected and a dedicated socket was opened for that connection.
+
+% COMMAND_SOCKET_CONNECTION_CLOSED Closed socket %1 for existing command connection
+This is an informational message that the socket created for handling
+client's connection is closed. This usually means that the client disconnected,
+but may also mean a timeout.
+
+% COMMAND_REGISTERED Command %1 registered
+This debug message indicates that the daemon started supporting specified
+command. If the command socket is open, this command can now be issued.
+
+% COMMAND_DEREGISTERED Command %1 deregistered
+This debug message indicates that the daemon stopped supporting specified
+command. This command can no longer be issued. If the command socket is
+open and this command is issued, the daemon will not be able to process it.
+
 % CONFIG_CCSESSION_MSG error in CC session message: %1
 There was a problem with an incoming message on the command and control
 channel. The message does not appear to be a valid command, and is
index 5eeab24127cdf4aed23ee4fed27264ae93246024..626dc67eb260d88c0453173d69a4d5e8ed0e342f 100644 (file)
@@ -30,6 +30,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
 run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
 run_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
 run_unittests_LDADD += $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
 
 endif