Commit 4a2f0998 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3880] Callback for handling UNIX connections implemented.

parent 16ccf1ae
# 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
......@@ -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
......
......@@ -15,8 +15,12 @@
#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
......
......@@ -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
......
......@@ -13,9 +13,17 @@
// 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 {
......
// 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
// 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
......
# 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
......
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment