From: Tomek Mrugalski Date: Tue, 2 Jun 2015 20:04:30 +0000 (+0200) Subject: [3880] Callback for handling UNIX connections implemented. X-Git-Tag: trac3830_base~5^2~18 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4a2f0998c3cd96df9c6dad3f910841207db2708f;p=thirdparty%2Fkea.git [3880] Callback for handling UNIX connections implemented. --- diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 364e34ccb7..5c20c3d32d 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -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 diff --git a/src/lib/config/Makefile.am b/src/lib/config/Makefile.am index 9922bf1c0f..7e1d2c66d7 100644 --- a/src/lib/config/Makefile.am +++ b/src/lib/config/Makefile.am @@ -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 diff --git a/src/lib/config/command_mgr.cc b/src/lib/config/command_mgr.cc index 814aa56778..7d9fd00c6b 100644 --- a/src/lib/config/command_mgr.cc +++ b/src/lib/config/command_mgr.cc @@ -15,8 +15,12 @@ #include #include #include +#include +#include #include +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 diff --git a/src/lib/config/command_mgr.h b/src/lib/config/command_mgr.h index 27b59d86c8..0a506bc48d 100644 --- a/src/lib/config/command_mgr.h +++ b/src/lib/config/command_mgr.h @@ -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 diff --git a/src/lib/config/command_socket_factory.cc b/src/lib/config/command_socket_factory.cc index 3c1af33b76..d072def82a 100644 --- a/src/lib/config/command_socket_factory.cc +++ b/src/lib/config/command_socket_factory.cc @@ -13,9 +13,17 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include +#include #include #include #include +#include +#include + +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 { diff --git a/src/lib/config/config_log.cc b/src/lib/config/config_log.cc index 672b9f1cba..ba95313cd4 100644 --- a/src/lib/config/config_log.cc +++ b/src/lib/config/config_log.cc @@ -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 diff --git a/src/lib/config/config_log.h b/src/lib/config/config_log.h index 14f681e616..2181211a71 100644 --- a/src/lib/config/config_log.h +++ b/src/lib/config/config_log.h @@ -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,20 +21,24 @@ 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 diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes index c5736cabb7..7f3f55f9fe 100644 --- a/src/lib/config/config_messages.mes +++ b/src/lib/config/config_messages.mes @@ -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 @@ -14,6 +14,66 @@ $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 diff --git a/src/lib/config/tests/Makefile.am b/src/lib/config/tests/Makefile.am index 5eeab24127..626dc67eb2 100644 --- a/src/lib/config/tests/Makefile.am +++ b/src/lib/config/tests/Makefile.am @@ -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