From: Francis Dupont Date: Sat, 6 Jul 2024 20:29:52 +0000 (+0200) Subject: [#3477] Checkpoint: did libconfig X-Git-Tag: Kea-2.7.2~118 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=2089240a2e07a3e9654d0bcd786187dd9beed2f1;p=thirdparty%2Fkea.git [#3477] Checkpoint: did libconfig --- diff --git a/src/lib/config/config_messages.cc b/src/lib/config/config_messages.cc index f2754a1f1f..24fd3e96f5 100644 --- a/src/lib/config/config_messages.cc +++ b/src/lib/config/config_messages.cc @@ -34,6 +34,9 @@ extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL = "COMMAND_SOCKET_WRI extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR = "COMMAND_WATCH_SOCKET_CLEAR_ERROR"; extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR = "COMMAND_WATCH_SOCKET_CLOSE_ERROR"; extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR = "COMMAND_WATCH_SOCKET_MARK_READY_ERROR"; +extern const isc::log::MessageID HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES = "HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES"; +extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STARTED = "HTTP_COMMAND_MGR_SERVICE_STARTED"; +extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING = "HTTP_COMMAND_MGR_SERVICE_STOPPING"; } // namespace config } // namespace isc @@ -68,6 +71,9 @@ const char* values[] = { "COMMAND_WATCH_SOCKET_CLEAR_ERROR", "watch socket failed to clear: %1", "COMMAND_WATCH_SOCKET_CLOSE_ERROR", "watch socket failed to close: %1", "COMMAND_WATCH_SOCKET_MARK_READY_ERROR", "watch socket failed to mark ready: %1", + "HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES", "ignore a change in TLS setup of the http control socket", + "HTTP_COMMAND_MGR_SERVICE_STARTED", "started %1 service bound to address %2 port %3", + "HTTP_COMMAND_MGR_SERVICE_STOPPING", "stopping %1 service%2", NULL }; diff --git a/src/lib/config/config_messages.h b/src/lib/config/config_messages.h index 3aac871c75..1a8982ea5b 100644 --- a/src/lib/config/config_messages.h +++ b/src/lib/config/config_messages.h @@ -35,6 +35,9 @@ extern const isc::log::MessageID COMMAND_SOCKET_WRITE_FAIL; extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLEAR_ERROR; extern const isc::log::MessageID COMMAND_WATCH_SOCKET_CLOSE_ERROR; extern const isc::log::MessageID COMMAND_WATCH_SOCKET_MARK_READY_ERROR; +extern const isc::log::MessageID HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES; +extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STARTED; +extern const isc::log::MessageID HTTP_COMMAND_MGR_SERVICE_STOPPING; } // namespace config } // namespace isc diff --git a/src/lib/config/config_messages.mes b/src/lib/config/config_messages.mes index 21a92b1a83..4e798873e1 100644 --- a/src/lib/config/config_messages.mes +++ b/src/lib/config/config_messages.mes @@ -152,3 +152,18 @@ This error message is issued when the command manager was unable to set ready status after scheduling asynchronous send. This is programmatic error that should be reported. The command manager may or may not continue to operate correctly. + +% HTTP_COMMAND_MGR_IGNORED_TLS_SETUP_CHANGES ignore a change in TLS setup of the http control socket +The warning message is issued when the HTTP/HTTPS control socket was +reconfigured with a different TLS setup but keeping the address and port. +As these changes can't be applied without opening a new socket which +will conflicts with the existing one they are ignored. + +% HTTP_COMMAND_MGR_SERVICE_STARTED started %1 service bound to address %2 port %3 +This informational message indicates that the server has started +HTTP/HTTPS service on the specified address and port for receiving +control commands. + +% HTTP_COMMAND_MGR_SERVICE_STOPPING stopping %1 service%2 +This informational message indicates that the server has stopped +HTTP/HTTPS service. When known the address and port are displayed. diff --git a/src/lib/config/http_command_mgr.cc b/src/lib/config/http_command_mgr.cc index 9f280bd647..bc9ad2a27b 100644 --- a/src/lib/config/http_command_mgr.cc +++ b/src/lib/config/http_command_mgr.cc @@ -7,23 +7,232 @@ #include #include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include #include +#include +#include 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 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 diff --git a/src/lib/config/http_command_mgr.h b/src/lib/config/http_command_mgr.h index 80f8b2f92d..10f1d8b370 100644 --- a/src/lib/config/http_command_mgr.h +++ b/src/lib/config/http_command_mgr.h @@ -8,15 +8,79 @@ #define HTTP_COMMAND_MGR_H #include -#include +#include #include -#include +#include #include -#include 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 impl_; +}; + } // end of isc::config namespace } // end of isc namespace #endif