# 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
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
#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 {
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();
}
handlers_.insert(make_pair(cmd, handler));
+
+ LOG_DEBUG(command_logger, DBG_COMMAND, COMMAND_REGISTERED).arg(cmd);
}
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) {
}
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.
std::string("Error during command processing:")
+ e.what()));
}
-
}
isc::data::ConstElementPtr
/// 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
// 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) {
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;
<< " 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 {
-// 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
isc::log::Logger config_logger("config");
+isc::log::Logger command_logger("commands");
+
} // namespace nsas
} // namespace isc
-// 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
-# 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
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