#include <config.h>
#include <asiolink/asio_wrapper.h>
-#include <asiolink/io_service.h>
-#include <config/command_mgr.h>
-#include <cc/data.h>
-#include <cc/command_interpreter.h>
-#include <cc/json_feed.h>
-#include <dhcp/iface_mgr.h>
#include <config/config_log.h>
+#include <config/http_command_mgr.h>
+#include <config/http_command_response_creator_factory.h>
+#include <config/http_command_response_creator.h>
#include <config/timeouts.h>
+#include <sstream>
+#include <vector>
using namespace isc;
using namespace isc::asiolink;
using namespace isc::config;
-using namespace isc::data;
-namespace ph = std::placeholders;
+using namespace isc::http;
+using namespace std;
namespace isc {
namespace config {
+/// @brief Implementation of the @c HttpCommandMgr.
+class HttpCommandMgrImpl {
+public:
+
+ /// @brief Constructor.
+ HttpCommandMgrImpl()
+ : io_service_(), timeout_(TIMEOUT_DHCP_SERVER_RECEIVE_COMMAND),
+ current_config_(), http_listeners_(), active_(0) {
+ }
+
+ /// @brief Configure control socket from configuration.
+ void configure(HttpCommandConfigPtr config);
+
+ /// @brief Close control socket.
+ ///
+ /// @param remove When true remove the listeners immediately.
+ void close(bool remove);
+
+ /// @brief Removes listeners which are no longer in use.
+ void garbageCollectListeners();
+
+ /// @brief Returns a const pointer to the HTTP listener.
+ ConstHttpListenerPtr getHttpListener() const;
+
+ /// @brief Pointer to the IO service.
+ IOServicePtr io_service_;
+
+ /// @brief Connection timeout.
+ long timeout_;
+
+ /// @brief Current config.
+ HttpCommandConfigPtr current_config_;
+
+ /// @brief Active listeners.
+ vector<HttpListenerPtr> http_listeners_;
+
+ /// @brief Number of active listeners (0 or 1).
+ size_t active_;
+};
+
+void
+HttpCommandMgrImpl::configure(HttpCommandConfigPtr config) {
+ // First case: from no config to no config.
+ if (!config && http_listeners_.empty()) {
+ return;
+ }
+
+ // Second case: from config to no config.
+ if (!config && !http_listeners_.empty()) {
+ close(false);
+ return;
+ }
+
+ // Third case: no address or port change.
+ if (config && current_config_ &&
+ (config->getSocketAddress() == current_config_->getSocketAddress()) &&
+ (config->getSocketPort() == current_config_->getSocketPort())) {
+ // Overwrite the authentication setup in the response creator config.
+ current_config_->setAuthConfig(config->getAuthConfig());
+ // Overwrite the emulation flag in the response creator config.
+ current_config_->setEmulateAgentResponse(config->getEmulateAgentResponse());
+ // Check if TLS setup changed.
+ if ((config->getTrustAnchor() != current_config_->getTrustAnchor()) ||
+ (config->getCertFile() != current_config_->getCertFile()) ||
+ (config->getKeyFile() != current_config_->getKeyFile()) ||
+ (config->getCertRequired() != current_config_->getCertRequired())) {
+ LOG_WARN(command_logger, HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES);
+ } else {
+ current_config_ = config;
+ }
+ return;
+ }
+
+ // Last case: from no config, or address or port change.
+ /////
+ current_config_ = config;
+ IOAddress server_address = config->getSocketAddress();
+ uint16_t server_port = config->getSocketPort();
+ bool use_https = false;
+ TlsContextPtr tls_context;
+ if (!config->getCertFile().empty()) {
+ TlsContext::configure(tls_context,
+ TlsRole::SERVER,
+ config->getTrustAnchor(),
+ config->getCertFile(),
+ config->getKeyFile(),
+ config->getCertRequired());
+ use_https = true;
+ }
+ // Create response creator factory first. It will be used to
+ // generate response creators. Each response creator will be used
+ // to generate answer to specific request.
+ HttpResponseCreatorFactoryPtr rfc(new HttpCommandResponseCreatorFactory(config));
+ // Create HTTP listener. It will open up a TCP socket and be
+ // prepared to accept incoming connection.
+ HttpListenerPtr http_listener
+ (new HttpListener(io_service_,
+ server_address,
+ server_port,
+ tls_context,
+ rfc,
+ HttpListener::RequestTimeout(TIMEOUT_AGENT_RECEIVE_COMMAND),
+ HttpListener::IdleTimeout(TIMEOUT_AGENT_IDLE_CONNECTION_TIMEOUT)));
+ // Instruct the HTTP listener to actually open socket, install
+ // callback and start listening.
+ http_listener->start();
+
+ // The new listener is running so add it to the collection of
+ // active listeners. The next step will be to remove all other
+ // active listeners, but we do it inside the main process loop.
+ http_listeners_.push_back(http_listener);
+ active_ = 1;
+
+ // Ok, seems we're good to go.
+ LOG_INFO(command_logger, HTTP_COMMAND_MGR_SERVICE_STARTED)
+ .arg(use_https ? "HTTPS" : "HTTP")
+ .arg(server_address.toText())
+ .arg(server_port);
+}
+
+void
+HttpCommandMgrImpl::close(bool remove) {
+ bool use_https = false;
+ ostringstream ep;
+ if (current_config_) {
+ use_https = !current_config_->getCertFile().empty();
+ ep << " bound to address " << current_config_->getSocketAddress()
+ << " port " << current_config_->getSocketPort();
+ }
+ LOG_INFO(command_logger, HTTP_COMMAND_MGR_SERVICE_STOPPING)
+ .arg(use_https ? "HTTPS" : "HTTP")
+ .arg(ep.str());
+ current_config_.reset();
+ active_ = 0;
+ if (remove) {
+ garbageCollectListeners();
+ }
+}
+
+void
+HttpCommandMgrImpl::garbageCollectListeners() {
+ // We expect only one active listener. If there are more (most likely 2),
+ // it means we have just reconfigured the server and need to shut down all
+ // listeners except the most recently added.
+ if (http_listeners_.size() > active_) {
+ // Stop no longer used listeners.
+ for (auto l = http_listeners_.begin();
+ l != http_listeners_.end() - active_;
+ ++l) {
+ (*l)->stop();
+ }
+ // We have stopped listeners but there may be some pending handlers
+ // related to these listeners. Need to invoke these handlers.
+ try {
+ io_service_->poll();
+ } catch (...) {
+ }
+ // Finally, we're ready to remove no longer used listeners.
+ http_listeners_.erase(http_listeners_.begin(),
+ http_listeners_.end() - active_);
+ }
+}
+
+ConstHttpListenerPtr
+HttpCommandMgrImpl::getHttpListener() const {
+ // Return the most recent listener or null.
+ return (http_listeners_.empty() ? ConstHttpListenerPtr() :
+ http_listeners_.back());
+}
+
+HttpCommandMgr&
+HttpCommandMgr::instance() {
+ static HttpCommandMgr http_cmd_mgr;
+ return (http_cmd_mgr);
+}
+
+HttpCommandMgr::HttpCommandMgr()
+ : HookedCommandMgr(), impl_(new HttpCommandMgrImpl()) {
+}
+
+void
+HttpCommandMgr::setIOService(const IOServicePtr& io_service) {
+ impl_->io_service_ = io_service;
+}
+
+void
+HttpCommandMgr::setConnectionTimeout(const long timeout) {
+ impl_->timeout_ = timeout;
+}
+
+void
+HttpCommandMgr::configure(HttpCommandConfigPtr config) {
+ impl_->configure(config);
+}
+
+void
+HttpCommandMgr::close(bool remove) {
+ impl_->close(remove);
+}
+
+void
+HttpCommandMgr::garbageCollectListeners() {
+ impl_->garbageCollectListeners();
+}
+
+ConstHttpListenerPtr
+HttpCommandMgr::getHttpListener() const {
+ return (impl_->getHttpListener());
+}
+
} // end of isc::config
} // end of isc
#define HTTP_COMMAND_MGR_H
#include <asiolink/io_service.h>
-#include <cc/data.h>
+#include <config/http_command_config.h>
#include <config/hooked_command_mgr.h>
-#include <exceptions/exceptions.h>
+#include <http/listener.h>
#include <boost/noncopyable.hpp>
-#include <boost/shared_ptr.hpp>
namespace isc {
namespace config {
+/// @brief Declaration of the implementation class.
+class HttpCommandMgrImpl;
+
+/// @brief HTTP Commands Manager implementation for the Kea servers.
+///
+/// Similar to @c CommandMgr but using HTTP/HTTPS instead of UNIX sockets.
+class HttpCommandMgr : public HookedCommandMgr, public boost::noncopyable {
+public:
+
+ /// @brief HttpCommandMgr is a singleton class. This method
+ /// returns reference to its sole instance.
+ ///
+ /// @return The only existing instance of the manager.
+ static HttpCommandMgr& instance();
+
+ /// @brief Sets IO service to be used by the command manager.
+ ///
+ /// The server should use this method to provide the Command
+ /// Manager with the common IO service used by the server.
+ /// @param io_service Pointer to the IO service.
+ void setIOService(const asiolink::IOServicePtr& io_service);
+
+ /// @brief Override default connection timeout.
+ ///
+ /// @param timeout New connection timeout in milliseconds.
+ void setConnectionTimeout(const long timeout);
+
+ /// @brief Configure control socket from configuration.
+ ///
+ /// @param config Configuration of the control socket.
+ void configure(HttpCommandConfigPtr config);
+
+ /// @brief Close control socket.
+ ///
+ /// @note When remove is false @c garbageCollectListeners must
+ /// be called after.
+ ///
+ /// @param remove When true remove the listeners immediately.
+ void close(bool remove = true);
+
+ /// @brief Removes listeners which are no longer in use.
+ ///
+ /// This method should be called after server reconfiguration to
+ /// remove listeners used previously (no longer used because the
+ /// listening address and port has changed as a result of the
+ /// reconfiguration). If there are no listeners additional to the
+ /// one that is currently in use, the method has no effect.
+ /// This method is reused to remove all listeners at shutdown time.
+ void garbageCollectListeners();
+
+ /// @brief Returns a const pointer to the HTTP listener.
+ ///
+ /// @return Const pointer to the currently used listener or null pointer if
+ /// we're not listening.
+ isc::http::ConstHttpListenerPtr getHttpListener() const;
+
+private:
+
+ /// @brief Private constructor.
+ HttpCommandMgr();
+
+ /// @brief Pointer to the implementation of the @ref HttpCommandMgr.
+ boost::shared_ptr<HttpCommandMgrImpl> impl_;
+};
+
} // end of isc::config namespace
} // end of isc namespace
#endif