- CommandSocket, UNIXCommandSocket and ConnectionSocket classes added.
- Updated unit-test to use
libkea_cfgclient_la_SOURCES = config_data.h config_data.cc
libkea_cfgclient_la_SOURCES += module_spec.h module_spec.cc
libkea_cfgclient_la_SOURCES += command_mgr.cc command_mgr.h
+libkea_cfgclient_la_SOURCES += command_socket.cc command_socket.h
libkea_cfgclient_la_SOURCES += command_socket_factory.cc command_socket_factory.h
libkea_cfgclient_la_SOURCES += config_log.h config_log.cc
boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
}
-int CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
- if (socket_info_) {
+CommandSocketPtr
+CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
+ if (socket_) {
isc_throw(SocketError, "There is already a control socket open");
}
socket_ = CommandSocketFactory::create(socket_info);
- socket_info_ = 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::closeCommandSocket() {
- if (socket_info_) {
+ // First, let's close the socket for incoming new connections.
+ if (socket_) {
+ socket_->close();
+ socket_.reset();
+ }
+
+ // Now let's close all existing connections that we may have.
+ for (std::list<CommandSocketPtr>::iterator conn = connections_.begin();
+ conn != connections_.end(); ++conn) {
+ (*conn)->close();
+ }
+ connections_.clear();
+}
+
- isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_);
+void CommandMgr::addConnection(const CommandSocketPtr& conn) {
+ connections_.push_back(conn);
+}
+
+bool CommandMgr::closeConnection(int fd) {
+
+ // Let's iterate over all currently registered connections.
+ for (std::list<CommandSocketPtr>::iterator conn = connections_.begin();
+ conn != connections_.end(); ++conn) {
- CommandSocketFactory::close(socket_, socket_info_);
- socket_ = 0;
- socket_info_.reset();
+ // If found, close it.
+ if ((*conn)->getFD() == fd) {
+ (*conn)->close();
+ connections_.erase(conn);
+ return (true);
+ }
}
+
+ return (false);
}
CommandMgr&
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));
-
- // Remember this socket descriptor. It will be needed when we shut down the
- // server.
- instance().connections_.push_back(fd2);
-
- LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED).arg(fd2).arg(sockfd);
-}
-
void
CommandMgr::commandReader(int 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);
-
// Remove it from the active connections list.
- instance().connections_.remove(sockfd);
+ instance().closeConnection(sockfd);
return;
}
#define COMMAND_MGR_H
#include <cc/data.h>
+#include <config/command_socket.h>
#include <boost/noncopyable.hpp>
#include <boost/function.hpp>
#include <string>
/// @throw SocketError if command socket is already open.
///
/// @param socket_info describes control socket parameters
- /// @return socket descriptor of the socket created
- int openCommandSocket(const isc::data::ConstElementPtr& socket_info);
+ /// @return object representing a socket
+ CommandSocketPtr
+ openCommandSocket(const isc::data::ConstElementPtr& socket_info);
/// @brief Shuts down any open control sockets
void closeCommandSocket();
/// 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
/// handled at all times.
void deregisterAll();
+ /// @brief Adds an information about opened connection socket
+ ///
+ /// @param conn Connection socket to be stored
+ void addConnection(const CommandSocketPtr& conn);
+
+ /// @brief Closes connection with a specific socket descriptor
+ ///
+ /// @param fd socket descriptor
+ /// @return true if closed successfully, false if not found
+ bool closeConnection(int fd);
/// @brief Returns control socket descriptor
///
/// This method should be used only in tests.
int getControlSocketFD() const {
- return (socket_);
+ return (socket_->getFD());
}
private:
/// @brief Container for command handlers
HandlerContainer handlers_;
- /// @brief Socket file descriptor
- int socket_;
-
- /// @brief Parameters for control socket
- isc::data::ConstElementPtr socket_info_;
+ /// @brief Control socket structure
+ ///
+ /// This is the socket that accepts incoming connections. There can be at
+ /// most one (if command channel is configured).
+ CommandSocketPtr socket_;
- /// @brief Socket descriptors for open connections
- std::list<int> connections_;
+ /// @brief Sockets for open connections
+ ///
+ /// These are the sockets that are dedicated to handle a specific connection.
+ /// Their number is equal to number of current control connections.
+ std::list<CommandSocketPtr> connections_;
};
}; // end of isc::config namespace
--- /dev/null
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/command_socket.h>
+#include <config/command_mgr.h>
+#include <config/config_log.h>
+#include <dhcp/iface_mgr.h>
+#include <boost/bind.hpp>
+#include <unistd.h>
+
+namespace isc {
+namespace config {
+
+ConnectionSocket::ConnectionSocket(int sockfd)
+ :CommandSocket(isc::data::ConstElementPtr()) {
+ sockfd_ = sockfd;
+
+ // 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(sockfd,
+ boost::bind(&ConnectionSocket::receiveHandler, this));
+ }
+
+void ConnectionSocket::close() {
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_CLOSED).arg(sockfd_);
+
+ // Unregister this callback
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(sockfd_);
+
+ // We're closing a connection, not the whole socket. It's ok to just
+ // close the connection and don't delete anything.
+ ::close(sockfd_);
+}
+
+void ConnectionSocket::receiveHandler() {
+ CommandMgr::instance().commandReader(sockfd_);
+}
+
+};
+};
--- /dev/null
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef COMMAND_SOCKET_H
+#define COMMAND_SOCKET_H
+
+#include <cc/data.h>
+#include <unistd.h>
+
+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) { };
+};
+
+/// @brief Abstract base class that represents an open command socket
+///
+/// This class is not expected to be instantiated directly. Derived classes
+/// are expected to handle specific socket types (e.g. UNIX or https).
+///
+/// For derived classes, see @ref UnixCommandSocket for a socket that
+/// accepts connections over UNIX socket and @ref ConnectionSocket that
+/// handles established connections (currently over UNIX sockets, but
+/// should be generic).
+class CommandSocket {
+public:
+ /// @brief Default constructor
+ ///
+ /// @param socket_info socket information from the config
+ CommandSocket(const isc::data::ConstElementPtr& socket_info)
+ :socket_info_(socket_info) {
+ }
+
+ /// @brief Method used to handle incoming data
+ ///
+ /// This may be registered in @ref isc::dhcp::IfaceMgr
+ virtual void receiveHandler() = 0;
+
+ /// @brief General method for closing socket.
+ ///
+ /// This is the default implementation that simply closes
+ /// the socket. Derived classes may do additional steps
+ /// to terminate the connection.
+ virtual void close() {
+ ::close(sockfd_);
+ }
+
+ /// @brief Virtual destructor.
+ virtual ~CommandSocket() {
+ close();
+ }
+
+ /// @brief Returns socket descriptor.
+ int getFD() const {
+ return (sockfd_);
+ }
+
+protected:
+ /// Stores socket descriptor.
+ int sockfd_;
+
+ /// Stores socket information.
+ isc::data::ConstElementPtr socket_info_;
+};
+
+/// Pointer to a command socket object
+typedef boost::shared_ptr<CommandSocket> CommandSocketPtr;
+
+/// @brief This class represents a straming socket for handling connections
+///
+/// Initially a socket (e.g. UNIX) is opened (represented by other classes, e.g.
+/// @ref UnixCommandSocket). Once incoming connection is detected, that class
+/// calls accept(), which returns a new socket dedicated to handling that
+/// specific connection. That socket is represented by this class.
+class ConnectionSocket : public CommandSocket {
+public:
+ /// @brief Default constructor
+ ///
+ /// This constructor is used in methods that call accept on existing
+ /// sockets. accept() returns a socket descriptor. Hence only one
+ /// parameter here.
+ ///
+ /// @param sockfd socket descriptor
+ ConnectionSocket(int sockfd);
+
+ /// @brief Method used to handle incoming data
+ ///
+ /// This method calls isc::config::CommandMgr::commandReader method.
+ virtual void receiveHandler();
+
+ /// @brief Closes socket.
+ ///
+ /// This method closes the socket, prints appropriate log message and
+ /// unregisters callback from @ref isc::dhcp::IfaceMgr.
+ virtual void close();
+};
+
+};
+};
+
+#endif
#include <config/command_socket_factory.h>
#include <config/config_log.h>
-#include <cstdio>
+#include <config/command_mgr.h>
+#include <dhcp/iface_mgr.h>
+#include <boost/bind.hpp>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
-#include <unistd.h>
+#include <cstdio>
#include <fcntl.h>
-using namespace isc::config;
-
-namespace {
-
-int createUnixSocket(const std::string& file_name) {
-
- int fd = socket(AF_UNIX, SOCK_STREAM, 0);
- if (fd == -1) {
- isc_throw(isc::config::SocketError, "Failed to create AF_UNIX socket.");
- }
-
- // 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;
- strncpy(addr.sun_path, file_name.c_str(), sizeof(addr.sun_path)-1);
- if (bind(fd, (struct sockaddr*)&addr, sizeof(addr))) {
- isc_throw(isc::config::SocketError, "Failed to bind socket " << fd
- << " 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 {
namespace config {
-int
-CommandSocketFactory::create(const isc::data::ConstElementPtr& socket_info) {
- if(!socket_info) {
- isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
+/// @brief Wrapper for UNIX stream sockets
+///
+/// There are two UNIX socket types: datagram-based (equivalent of UDP) and
+/// stream-based (equivalent of TCP). This class represents stream-based
+/// sockets. It opens up a unix-socket and waits for incoming connections.
+/// Once incoming connection is detected, accept() system call is called
+/// and a new socket for that particular connection is returned. A new
+/// object of @ref ConnectionSocket is created.
+class UnixCommandSocket : public CommandSocket {
+public:
+ /// @brief Default constructor
+ ///
+ /// This socket_info map is expected to have at least one parameter:
+ /// 'socket-name' that specifies UNIX socket path. It's typically
+ /// a file somewhere in /tmp or /var/run/kea directory.
+ ///
+ /// @param socket_info socket description from the configuration
+ UnixCommandSocket(const isc::data::ConstElementPtr& socket_info)
+ : CommandSocket(socket_info) {
+
+ ConstElementPtr name = socket_info->get("socket-name");
+ if (!name) {
+ isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
+ }
+ filename_ = name->stringValue();
+
+ sockfd_ = createUnixSocket(filename_);
+
+ // Install this socket in Interface Manager.
+ isc::dhcp::IfaceMgr::instance().addExternalSocket(sockfd_,
+ boost::bind(&UnixCommandSocket::receiveHandler, this));
}
- ConstElementPtr type = socket_info->get("socket-type");
- if (!type) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
+private:
+
+ /// @brief Auxiliary method for creating a UNIX socket
+ ///
+ /// @param file_name specifies socket file path
+ int createUnixSocket(const std::string& file_name) {
+
+ int fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd == -1) {
+ isc_throw(isc::config::SocketError, "Failed to create AF_UNIX socket.");
+ }
+
+ // 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;
+ strncpy(addr.sun_path, file_name.c_str(), sizeof(addr.sun_path)-1);
+ if (bind(fd, (struct sockaddr*)&addr, sizeof(addr))) {
+ ::close(fd);
+ remove(file_name.c_str());
+ isc_throw(isc::config::SocketError, "Failed to bind socket " << fd
+ << " 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 the number of parallel connections configurable.
+ int status = listen(fd, 1);
+ if (status < 0) {
+ ::close(fd);
+ remove(file_name.c_str());
+ isc_throw(isc::config::SocketError, "Failed to listen on socket fd="
+ << fd << ", filename=" << file_name);
+ }
+
+ // Woohoo! Socket opened, let's log it!
+ LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_OPEN).arg(fd).arg(file_name);
+
+ return (fd);
}
- ConstElementPtr name = socket_info->get("socket-name");
- if (!name) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
+ /// CommandMgr::connectionAcceptor(int sockfd) {
+ /// @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.
+ void receiveHandler() {
+
+ // This method is specific to receiving data over UNIX socket, so using
+ // sockaddr_un instead of sockaddr_storage here is ok.
+ 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_, static_cast<struct sockaddr*>(&client_addr),
+ &client_addr_len);
+
+ // And now create an object that represents that new connection.
+ CommandSocketPtr conn(new ConnectionSocket(fd2));
+
+ // Not sure if this is really needed, but let's set it to non-blocking
+ // mode.
+ fcntl(fd2, F_SETFL, O_NONBLOCK);
+
+ // Remember this socket descriptor. It will be needed when we shut down
+ // the server.
+ CommandMgr::instance().addConnection(conn);
+
+ LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED).arg(fd2)
+ .arg(sockfd_);
}
- if (type->stringValue() == "unix") {
- return (createUnixSocket(name->stringValue()));
- } else {
- isc_throw(BadSocketInfo, "Specified socket type ('" + type->stringValue()
- + "') is not supported.");
+ // This method is called when we shutdown the connection.
+ void close() {
+ LOG_INFO(command_logger, COMMAND_SOCKET_UNIX_CLOSE).arg(sockfd_)
+ .arg(filename_);
+
+ isc::dhcp::IfaceMgr::instance().deleteExternalSocket(sockfd_);
+
+ ::close(sockfd_);
+ remove(filename_.c_str());
}
-}
-void CommandSocketFactory::close(int socket_fd,
- const isc::data::ConstElementPtr& socket_info) {
+ std::string filename_;
+};
+CommandSocketPtr
+CommandSocketFactory::create(const isc::data::ConstElementPtr& socket_info) {
if(!socket_info) {
isc_throw(BadSocketInfo, "Missing socket_info parameters, can't create socket.");
}
isc_throw(BadSocketInfo, "Mandatory 'socket-type' parameter missing");
}
- ConstElementPtr name = socket_info->get("socket-name");
- if (!name) {
- isc_throw(BadSocketInfo, "Mandatory 'socket-name' parameter missing");
- }
-
if (type->stringValue() == "unix") {
- return (closeUnixSocket(socket_fd, name->stringValue()));
+ return (CommandSocketPtr(new UnixCommandSocket(socket_info)));
} else {
isc_throw(BadSocketInfo, "Specified socket type ('" + type->stringValue()
+ "') is not supported.");
#define COMMAND_SOCKET_FACTORY_H
#include <cc/data.h>
+#include <config/command_socket.h>
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) { };
-};
-
-
/// A factory class for opening command socket
///
/// This class provides an interface for opening command socket.
///
/// @param socket_info structure that describes the socket
/// @return socket descriptor
- static int create(const isc::data::ConstElementPtr& socket_info);
-
- /// @brief Closes specified socket
- ///
- /// In most cases it will be a simple close() call, but in more complex
- /// (e.g. https) it may perform certain shutdown operations before
- /// closing.
- /// @param socket_fd file descriptor of the socket
- /// @param socket_info structure that was used to open the socket
- static void close(int socket_fd, const isc::data::ConstElementPtr& socket_info);
+ static CommandSocketPtr create(const isc::data::ConstElementPtr& socket_info);
};
-
};
};
#include <cc/data.h>
#include <config/command_mgr.h>
+#include <config/command_socket.h>
#include <config/command_socket_factory.h>
#include <cstdio>
isc::config::BadSocketInfo);
socket_info->set("socket-name", Element::create(SOCKET_NAME));
- int fd;
- EXPECT_NO_THROW(fd = CommandSocketFactory::create(socket_info));
- EXPECT_NE(-1, fd);
+ CommandSocketPtr sock;
+ EXPECT_NO_THROW(sock = CommandSocketFactory::create(socket_info));
+ ASSERT_TRUE(sock);
+ EXPECT_NE(-1, sock->getFD());
// It should be possible to close the socket.
- EXPECT_NO_THROW(CommandSocketFactory::close(fd, socket_info));
+ EXPECT_NO_THROW(sock->close());
}