return (isc::config::createAnswer(1, err.str()));
}
+ // Configure a callback to shut down the server when the bind socket
+ // attempts exceeded.
+ CfgIface::open_sockets_failed_callback_ =
+ std::bind(&ControlledDhcpv4Srv::openSocketsFailedCallback, srv, ph::_1);
+
// Configuration may change active interfaces. Therefore, we have to reopen
// sockets according to new configuration. It is possible that this
// operation will fail for some interfaces but the openSockets function
return (true);
}
+void
+ControlledDhcpv4Srv::openSocketsFailedCallback(
+ util::ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ // This should never happen
+ LOG_ERROR(dhcp4_logger, DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL);
+ return;
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_OPEN_SOCKETS_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ if (db_reconnect_ctl->exitOnFailure()) {
+ shutdownServer(EXIT_FAILURE);
+ }
+}
+
void
ControlledDhcpv4Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
boost::shared_ptr<unsigned> failure_count) {
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/command_interpreter.h>
-#include <database/database_connection.h>
+#include <util/reconnect_ctl.h>
#include <dhcpsrv/timer_mgr.h>
#include <dhcp4/dhcp4_srv.h>
/// configured reconnect parameters
///
/// @return false if reconnect is not configured, true otherwise
- bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+ bool dbLostCallback(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback DB backends should invoke upon restoration of
/// connectivity
/// recovered.
///
/// @return false if reconnect is not configured, true otherwise
- bool dbRecoveredCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+ bool dbRecoveredCallback(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback DB backends should invoke upon failing to restore
/// connectivity
/// connectivity. It stops the server.
///
/// @return false if reconnect is not configured, true otherwise
- bool dbFailedCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+ bool dbFailedCallback(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief This callback should be invoked upon failing to bind sockets.
+ ///
+ /// This function is invoked during the configuration of the interfaces
+ /// when they fail to bind the service sockets. It may stop the server.
+ void openSocketsFailedCallback(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback invoked periodically to fetch configuration updates
/// from the Config Backends.
extern const isc::log::MessageID DHCP4_NO_SOCKETS_OPEN = "DHCP4_NO_SOCKETS_OPEN";
extern const isc::log::MessageID DHCP4_OPEN_CONFIG_DB = "DHCP4_OPEN_CONFIG_DB";
extern const isc::log::MessageID DHCP4_OPEN_SOCKET = "DHCP4_OPEN_SOCKET";
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_FAILED = "DHCP4_OPEN_SOCKETS_FAILED";
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL = "DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL";
extern const isc::log::MessageID DHCP4_OPEN_SOCKET_FAIL = "DHCP4_OPEN_SOCKET_FAIL";
extern const isc::log::MessageID DHCP4_PACKET_DROP_0001 = "DHCP4_PACKET_DROP_0001";
extern const isc::log::MessageID DHCP4_PACKET_DROP_0002 = "DHCP4_PACKET_DROP_0002";
"DHCP4_NO_SOCKETS_OPEN", "no interface configured to listen to DHCP traffic",
"DHCP4_OPEN_CONFIG_DB", "Opening configuration database: %1",
"DHCP4_OPEN_SOCKET", "opening service sockets on port %1",
+ "DHCP4_OPEN_SOCKETS_FAILED", "maximum number of open service sockets attempts: %1, has been exhausted without success",
+ "DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL", "unexpected error in bind service sockets.",
"DHCP4_OPEN_SOCKET_FAIL", "failed to open socket: %1",
"DHCP4_PACKET_DROP_0001", "failed to parse packet from %1 to %2, received over interface %3, reason: %4",
"DHCP4_PACKET_DROP_0002", "%1, from interface %2: no suitable subnet configured for a direct client",
extern const isc::log::MessageID DHCP4_NO_SOCKETS_OPEN;
extern const isc::log::MessageID DHCP4_OPEN_CONFIG_DB;
extern const isc::log::MessageID DHCP4_OPEN_SOCKET;
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_FAILED;
+extern const isc::log::MessageID DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL;
extern const isc::log::MessageID DHCP4_OPEN_SOCKET_FAIL;
extern const isc::log::MessageID DHCP4_PACKET_DROP_0001;
extern const isc::log::MessageID DHCP4_PACKET_DROP_0002;
This info message indicates that the connection has been recovered and the dhcp
service has been restored.
+% DHCP4_OPEN_SOCKETS_NO_RECONNECT_CTL unexpected error in bind service sockets.
+This is an error message indicating a programmatic error that should not
+occur. It prohibits the server from attempting to bind to its
+service sockets if they are unavailable, and the server exits. This error
+should be reported.
+
+% DHCP4_OPEN_SOCKETS_FAILED maximum number of open service sockets attempts: %1, has been exhausted without success
+This error indicates that the server failed to bind service sockets after making
+the maximum configured number of reconnect attempts. This might cause the server
+to shut down as specified in the configuration.
+
% DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to kea-dhcp-ddns, error: %1, ncr: %2
This error message indicates that DHCP4 server attempted to send a DDNS
update request to the DHCP-DDNS server. This is most likely a configuration or
return (isc::config::createAnswer(1, err.str()));
}
+ // Configure a callback to shut down the server when the bind socket
+ // attempts exceeded.
+ CfgIface::open_sockets_failed_callback_ =
+ std::bind(&ControlledDhcpv6Srv::openSocketsFailedCallback, srv, ph::_1);
+
// Configuration may change active interfaces. Therefore, we have to reopen
// sockets according to new configuration. It is possible that this
// operation will fail for some interfaces but the openSockets function
return (true);
}
+void
+ControlledDhcpv6Srv::openSocketsFailedCallback(
+ util::ReconnectCtlPtr db_reconnect_ctl) {
+ if (!db_reconnect_ctl) {
+ // This should never happen
+ LOG_ERROR(dhcp6_logger, DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL);
+ return;
+ }
+
+ LOG_INFO(dhcp6_logger, DHCP6_OPEN_SOCKETS_FAILED)
+ .arg(db_reconnect_ctl->maxRetries());
+
+ if (db_reconnect_ctl->exitOnFailure()) {
+ shutdownServer(EXIT_FAILURE);
+ }
+}
+
void
ControlledDhcpv6Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
boost::shared_ptr<unsigned> failure_count) {
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/command_interpreter.h>
-#include <database/database_connection.h>
+#include <util/reconnect_ctl.h>
#include <dhcpsrv/timer_mgr.h>
#include <dhcp6/dhcp6_srv.h>
/// configured reconnect parameters
///
/// @return false if reconnect is not configured, true otherwise
- bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+ bool dbLostCallback(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback DB backends should invoke upon restoration of
/// connectivity
/// recovered.
///
/// @return false if reconnect is not configured, true otherwise
- bool dbRecoveredCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+ bool dbRecoveredCallback(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback DB backends should invoke upon failing to restore
/// connectivity
/// connectivity. It stops the server.
///
/// @return false if reconnect is not configured, true otherwise
- bool dbFailedCallback(db::ReconnectCtlPtr db_reconnect_ctl);
+ bool dbFailedCallback(util::ReconnectCtlPtr db_reconnect_ctl);
+
+ /// @brief This callback should be invoked upon failing to bind sockets.
+ ///
+ /// This function is invoked during the configuration of the interfaces
+ /// when they fail to bind the service sockets. It may stop the server.
+ void openSocketsFailedCallback(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback invoked periodically to fetch configuration updates
/// from the Config Backends.
extern const isc::log::MessageID DHCP6_NO_INTERFACES = "DHCP6_NO_INTERFACES";
extern const isc::log::MessageID DHCP6_NO_SOCKETS_OPEN = "DHCP6_NO_SOCKETS_OPEN";
extern const isc::log::MessageID DHCP6_OPEN_SOCKET = "DHCP6_OPEN_SOCKET";
+extern const isc::log::MessageID DHCP6_OPEN_SOCKETS_FAILED = "DHCP6_OPEN_SOCKETS_FAILED";
+extern const isc::log::MessageID DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL = "DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL";
extern const isc::log::MessageID DHCP6_OPEN_SOCKET_FAIL = "DHCP6_OPEN_SOCKET_FAIL";
extern const isc::log::MessageID DHCP6_PACKET_DROP_DHCP_DISABLED = "DHCP6_PACKET_DROP_DHCP_DISABLED";
extern const isc::log::MessageID DHCP6_PACKET_DROP_DROP_CLASS = "DHCP6_PACKET_DROP_DROP_CLASS";
"DHCP6_NO_INTERFACES", "failed to detect any network interfaces",
"DHCP6_NO_SOCKETS_OPEN", "no interface configured to listen to DHCP traffic",
"DHCP6_OPEN_SOCKET", "opening service sockets on port %1",
+ "DHCP6_OPEN_SOCKETS_FAILED", "maximum number of open service sockets attempts: %1, has been exhausted without success",
+ "DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL", "unexpected error in bind service sockets.",
"DHCP6_OPEN_SOCKET_FAIL", "failed to open socket: %1",
"DHCP6_PACKET_DROP_DHCP_DISABLED", "%1: DHCP service is globally disabled",
"DHCP6_PACKET_DROP_DROP_CLASS", "dropped as member of the special class 'DROP': %1",
extern const isc::log::MessageID DHCP6_NO_INTERFACES;
extern const isc::log::MessageID DHCP6_NO_SOCKETS_OPEN;
extern const isc::log::MessageID DHCP6_OPEN_SOCKET;
+extern const isc::log::MessageID DHCP6_OPEN_SOCKETS_FAILED;
+extern const isc::log::MessageID DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL;
extern const isc::log::MessageID DHCP6_OPEN_SOCKET_FAIL;
extern const isc::log::MessageID DHCP6_PACKET_DROP_DHCP_DISABLED;
extern const isc::log::MessageID DHCP6_PACKET_DROP_DROP_CLASS;
This info message indicates that the connection has been recovered and the dhcp
service has been restored.
+% DHCP6_OPEN_SOCKETS_NO_RECONNECT_CTL unexpected error in bind service sockets.
+This is an error message indicating a programmatic error that should not
+occur. It prohibits the server from attempting to bind to its
+service sockets if they are unavailable, and the server exits. This error
+should be reported.
+
+% DHCP6_OPEN_SOCKETS_FAILED maximum number of open service sockets attempts: %1, has been exhausted without success
+This error indicates that the server failed to bind service sockets after making
+the maximum configured number of reconnect attempts. This might cause the server
+to shut down as specified in the configuration.
+
% DHCP6_DDNS_CREATE_ADD_NAME_CHANGE_REQUEST created name change request: %1
This debug message is logged when the new NameChangeRequest has been created
to perform the DNS Update, which adds new RRs.
// Wasn't specified so we'll use default of 0;
}
- OnFailAction action = OnFailAction::STOP_RETRY_EXIT;
+ util::OnFailAction action = util::OnFailAction::STOP_RETRY_EXIT;
try {
parm_str = getParameter("on-fail");
- action = ReconnectCtl::onFailActionFromText(parm_str);
+ action = util::ReconnectCtl::onFailActionFromText(parm_str);
} catch (...) {
// Wasn't specified so we'll use default of "stop-retry-exit";
}
- reconnect_ctl_ = boost::make_shared<ReconnectCtl>(type, timer_name, retries,
- interval, action);
+ reconnect_ctl_ = boost::make_shared<util::ReconnectCtl>(type, timer_name, retries,
+ interval, action);
}
bool
-DatabaseConnection::invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+DatabaseConnection::invokeDbLostCallback(const util::ReconnectCtlPtr& db_reconnect_ctl) {
if (DatabaseConnection::db_lost_callback_) {
return (DatabaseConnection::db_lost_callback_(db_reconnect_ctl));
}
}
bool
-DatabaseConnection::invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+DatabaseConnection::invokeDbRecoveredCallback(const util::ReconnectCtlPtr& db_reconnect_ctl) {
if (DatabaseConnection::db_recovered_callback_) {
return (DatabaseConnection::db_recovered_callback_(db_reconnect_ctl));
}
}
bool
-DatabaseConnection::invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl) {
+DatabaseConnection::invokeDbFailedCallback(const util::ReconnectCtlPtr& db_reconnect_ctl) {
if (DatabaseConnection::db_failed_callback_) {
return (DatabaseConnection::db_failed_callback_(db_reconnect_ctl));
}
return (toElement(params));
}
-std::string
-ReconnectCtl::onFailActionToText(OnFailAction action) {
- switch (action) {
- case OnFailAction::STOP_RETRY_EXIT:
- return ("stop-retry-exit");
- case OnFailAction::SERVE_RETRY_EXIT:
- return ("serve-retry-exit");
- case OnFailAction::SERVE_RETRY_CONTINUE:
- return ("serve-retry-continue");
- }
- return ("invalid-action-type");
-}
-
-OnFailAction
-ReconnectCtl::onFailActionFromText(const std::string& text) {
- if (text == "stop-retry-exit") {
- return (OnFailAction::STOP_RETRY_EXIT);
- } else if (text == "serve-retry-exit") {
- return (OnFailAction::SERVE_RETRY_EXIT);
- } else if (text == "serve-retry-continue") {
- return (OnFailAction::SERVE_RETRY_CONTINUE);
- } else {
- isc_throw(BadValue, "Invalid action on connection loss: " << text);
- }
-}
-
DbCallback DatabaseConnection::db_lost_callback_ = 0;
DbCallback DatabaseConnection::db_recovered_callback_ = 0;
DbCallback DatabaseConnection::db_failed_callback_ = 0;
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <exceptions/exceptions.h>
+#include <util/reconnect_ctl.h>
#include <functional>
#include <map>
#include <string>
isc::Exception(file, line, what) {}
};
-/// @brief Type of action to take on connection loss.
-enum class OnFailAction {
- STOP_RETRY_EXIT,
- SERVE_RETRY_EXIT,
- SERVE_RETRY_CONTINUE
-};
-
-/// @brief Warehouses DB reconnect control values
-///
-/// When a DatabaseConnection loses connectivity to its backend, it
-/// creates an instance of this class based on its configuration parameters and
-/// passes the instance into connection's DB lost callback. This allows
-/// the layer(s) above the connection to know how to proceed.
-///
-class ReconnectCtl {
-public:
- /// @brief Constructor.
- ///
- /// @param backend_type type of the caller backend.
- /// @param timer_name timer associated to this object.
- /// @param max_retries maximum number of reconnect attempts to make.
- /// @param retry_interval amount of time to between reconnect attempts.
- /// @param action which should be taken on connection loss.
- ReconnectCtl(const std::string& backend_type, const std::string& timer_name,
- unsigned int max_retries, unsigned int retry_interval,
- OnFailAction action) :
- backend_type_(backend_type), timer_name_(timer_name),
- max_retries_(max_retries), retries_left_(max_retries),
- retry_interval_(retry_interval), action_(action) {}
-
- /// @brief Returns the type of the caller backend.
- std::string backendType() const {
- return (backend_type_);
- }
-
- /// @brief Returns the associated timer name.
- ///
- /// @return the associated timer.
- std::string timerName() const {
- return (timer_name_);
- }
-
- /// @brief Decrements the number of retries remaining
- ///
- /// Each call decrements the number of retries by one until zero is reached.
- /// @return true the number of retries remaining is greater than zero.
- bool checkRetries() {
- return (retries_left_ ? --retries_left_ : false);
- }
-
- /// @brief Returns the maximum number of retries allowed.
- unsigned int maxRetries() {
- return (max_retries_);
- }
-
- /// @brief Returns the number for retries remaining.
- unsigned int retriesLeft() {
- return (retries_left_);
- }
-
- /// @brief Returns the amount of time to wait between reconnect attempts.
- unsigned int retryInterval() {
- return (retry_interval_);
- }
-
- /// @brief Resets the retries count.
- void resetRetries() {
- retries_left_ = max_retries_;
- }
-
- /// @brief Return true if the connection loss should affect the service,
- /// false otherwise
- bool alterServiceState() {
- return (action_ == OnFailAction::STOP_RETRY_EXIT);
- }
-
- /// @brief Return true if the connection recovery mechanism should shut down
- /// the server on failure, false otherwise.
- bool exitOnFailure() {
- return ((action_ == OnFailAction::STOP_RETRY_EXIT) ||
- (action_ == OnFailAction::SERVE_RETRY_EXIT));
- }
-
- /// @brief Convert action to string.
- ///
- /// @param action The action type to be converted to text.
- /// @return The text representation of the action type.
- static std::string onFailActionToText(OnFailAction action);
-
- /// @brief Convert string to action.
- ///
- /// @param text The text to be converted to action type.
- /// @return The action type corresponding to the text representation.
- static OnFailAction onFailActionFromText(const std::string& text);
-
-private:
-
- /// @brief Caller backend type.
- const std::string backend_type_;
-
- /// @brief Timer associated to this object.
- std::string timer_name_;
-
- /// @brief Maximum number of retry attempts to make.
- unsigned int max_retries_;
-
- /// @brief Number of attempts remaining.
- unsigned int retries_left_;
-
- /// @brief The amount of time to wait between reconnect attempts.
- unsigned int retry_interval_;
-
- /// @brief Action to take on connection loss.
- OnFailAction action_;
-};
-
-/// @brief Pointer to an instance of ReconnectCtl
-typedef boost::shared_ptr<ReconnectCtl> ReconnectCtlPtr;
-
/// @brief Defines a callback prototype for propagating events upward
-typedef std::function<bool (ReconnectCtlPtr db_reconnect_ctl)> DbCallback;
+typedef std::function<bool (util::ReconnectCtlPtr db_reconnect_ctl)> DbCallback;
/// @brief Function which returns the IOService that can be used to recover the
/// connection.
/// @brief The reconnect settings.
///
/// @return The reconnect settings.
- ReconnectCtlPtr reconnectCtl() {
+ util::ReconnectCtlPtr reconnectCtl() {
return (reconnect_ctl_);
}
///
/// @return Returns the result of the callback or false if there is no
/// callback.
- static bool invokeDbLostCallback(const ReconnectCtlPtr& db_reconnect_ctl);
+ static bool invokeDbLostCallback(const util::ReconnectCtlPtr& db_reconnect_ctl);
/// @brief Invokes the connection's restored connectivity callback
///
/// @return Returns the result of the callback or false if there is no
/// callback.
- static bool invokeDbRecoveredCallback(const ReconnectCtlPtr& db_reconnect_ctl);
+ static bool invokeDbRecoveredCallback(const util::ReconnectCtlPtr& db_reconnect_ctl);
/// @brief Invokes the connection's restore failed connectivity callback
///
/// @return Returns the result of the callback or false if there is no
/// callback.
- static bool invokeDbFailedCallback(const ReconnectCtlPtr& db_reconnect_ctl);
+ static bool invokeDbFailedCallback(const util::ReconnectCtlPtr& db_reconnect_ctl);
/// @brief Unparse a parameter map
///
bool unusable_;
/// @brief Reconnect settings.
- ReconnectCtlPtr reconnect_ctl_;
+ util::ReconnectCtlPtr reconnect_ctl_;
};
} // namespace db
///
/// @param db_reconnect_ctl ReconnectCtl containing reconnect
/// parameters
- bool dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ bool dbLostCallback(isc::util::ReconnectCtlPtr db_reconnect_ctl) {
if (!db_reconnect_ctl) {
isc_throw(isc::BadValue, "db_reconnect_ctl should not be null");
}
///
/// @param db_reconnect_ctl ReconnectCtl containing reconnect
/// parameters
- bool dbRecoveredCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ bool dbRecoveredCallback(isc::util::ReconnectCtlPtr db_reconnect_ctl) {
if (!db_reconnect_ctl) {
isc_throw(isc::BadValue, "db_reconnect_ctl should not be null");
}
///
/// @param db_reconnect_ctl ReconnectCtl containing reconnect
/// parameters
- bool dbFailedCallback(ReconnectCtlPtr db_reconnect_ctl) {
+ bool dbFailedCallback(isc::util::ReconnectCtlPtr db_reconnect_ctl) {
if (!db_reconnect_ctl) {
isc_throw(isc::BadValue, "db_reconnect_ctl should not be null");
}
}
/// @brief Retainer for the control passed into the callback
- ReconnectCtlPtr db_reconnect_ctl_;
+ isc::util::ReconnectCtlPtr db_reconnect_ctl_;
};
/// @brief getParameter test
#include <dhcp/dhcp6.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/iface_mgr_error_handler.h>
-#include <dhcp/iface_mgr_retry_callback.h>
#include <dhcp/pkt_filter_inet.h>
#include <dhcp/pkt_filter_inet6.h>
#include <exceptions/exceptions.h>
bool
IfaceMgr::openSockets4(const uint16_t port, const bool use_bcast,
IfaceMgrErrorMsgCallback error_handler,
- IfaceMgrRetryCallback retry_callback) {
+ const bool skip_opened) {
int count = 0;
int bcast_num = 0;
if (iface->inactive4_) {
continue;
- } else {
- // If the interface has been specified in the configuration that
- // it should be used to listen the DHCP traffic we have to check
- // that the interface configuration is valid and that the interface
- // is not a loopback interface. In both cases, we want to report
- // that the socket will not be opened.
- // Relax the check when the loopback interface was explicitly
- // allowed
- if (iface->flag_loopback_ && !allow_loopback_) {
- IFACEMGR_ERROR(SocketConfigError, error_handler,
- "must not open socket on the loopback"
- " interface " << iface->getName());
- continue;
+ }
- }
+ // If the interface has been specified in the configuration that
+ // it should be used to listen the DHCP traffic we have to check
+ // that the interface configuration is valid and that the interface
+ // is not a loopback interface. In both cases, we want to report
+ // that the socket will not be opened.
+ // Relax the check when the loopback interface was explicitly
+ // allowed
+ if (iface->flag_loopback_ && !allow_loopback_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "must not open socket on the loopback"
+ " interface " << iface->getName());
+ continue;
- if (!iface->flag_up_) {
- IFACEMGR_ERROR(SocketConfigError, error_handler,
- "the interface " << iface->getName()
- << " is down");
- continue;
- }
+ }
- if (!iface->flag_running_) {
- IFACEMGR_ERROR(SocketConfigError, error_handler,
- "the interface " << iface->getName()
- << " is not running");
- continue;
- }
+ if (!iface->flag_up_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "the interface " << iface->getName()
+ << " is down");
+ continue;
+ }
- IOAddress out_address("0.0.0.0");
- if (!iface->getAddress4(out_address)) {
- IFACEMGR_ERROR(SocketConfigError, error_handler,
- "the interface " << iface->getName()
- << " has no usable IPv4 addresses configured");
- continue;
- }
+ if (!iface->flag_running_) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "the interface " << iface->getName()
+ << " is not running");
+ continue;
+ }
+
+ IOAddress out_address("0.0.0.0");
+ if (!iface->getAddress4(out_address)) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "the interface " << iface->getName()
+ << " has no usable IPv4 addresses configured");
+ continue;
}
+
for (Iface::Address addr : iface->getAddresses()) {
// Skip non-IPv4 addresses and those that weren't selected..
" on remaining interfaces");
continue;
}
-
- std::stringstream msg_stream("failed to open socket on interface ");
- msg_stream << iface->getName();
-
- try {
- // We haven't open any broadcast sockets yet, so we can
- // open at least one more or
- // not broadcast capable, do not set broadcast flags.
- callWithRetry<int>(
- std::bind(&IfaceMgr::openSocket, this,
+
+ // Skip the address that already has a bound socket. It allows
+ // for preventing bind errors or re-opening sockets.
+ if (!skip_opened || !IfaceMgr::hasOpenSocket(addr.get())) {
+ try {
+ // We haven't open any broadcast sockets yet, so we can
+ // open at least one more or
+ // not broadcast capable, do not set broadcast flags.
+ IfaceMgr::openSocket(
iface->getName(), addr.get(), port,
is_open_as_broadcast, is_open_as_broadcast
- ),
- msg_stream.str(), retry_callback
- );
- } catch (const Exception& ex) {
- msg_stream << ", reason: "
- << ex.what();
- IFACEMGR_ERROR(SocketConfigError, error_handler, msg_stream.str());
- continue;
+ );
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "failed to open socket on interface "
+ << iface->getName()
+ << ", reason: "
+ << ex.what());
+ continue;
+ }
}
if (is_open_as_broadcast) {
bool
IfaceMgr::openSockets6(const uint16_t port,
IfaceMgrErrorMsgCallback error_handler,
- IfaceMgrRetryCallback retry_callback) {
+ const bool skip_open) {
int count = 0;
for (IfacePtr iface : ifaces_) {
// Open unicast sockets if there are any unicast addresses defined
for (Iface::Address addr : iface->getUnicasts()) {
- std::stringstream msg_stream("failed to open unicast socket on interface ");
- msg_stream << iface->getName();
-
- try {
- callWithRetry<int>(
- std::bind(&IfaceMgr::openSocket, this,
+ // Skip the address that already has a bound socket. It allows
+ // for preventing bind errors or re-opening sockets.
+ if (!skip_open || !IfaceMgr::hasOpenSocket(addr)) {
+ try {
+ IfaceMgr::openSocket(
iface->getName(), addr, port, false, false
- ),
- msg_stream.str(), retry_callback
- );
- } catch (const Exception& ex) {
- msg_stream << ", reason: "
- << ex.what();
- IFACEMGR_ERROR(SocketConfigError, error_handler, msg_stream.str());
- continue;
+ );
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "failed to open unicast socket on interface "
+ << iface->getName()
+ << ", reason: " << ex.what());
+ continue;
+ }
}
count++;
// Run OS-specific function to open a socket capable of receiving
// packets sent to All_DHCP_Relay_Agents_and_Servers multicast
// address.
- std::stringstream msg_stream("failed to open multicast socket on interface ");
- msg_stream << iface->getName();
- try {
- callWithRetry<bool>(
- std::bind(&IfaceMgr::openMulticastSocket, this,
+ // Skip the address that already has a bound socket. It allows
+ // for preventing bind errors or re-opening sockets.
+ if (!skip_open || !IfaceMgr::hasOpenSocket(addr)) {
+ try {
+ IfaceMgr::openMulticastSocket(
// Pass a null pointer as an error handler to avoid
// suppressing an exception in a system-specific function.
- std::ref(*iface), addr, port, nullptr),
- msg_stream.str(), retry_callback
- );
- ++count;
- } catch (const Exception& ex) {
- msg_stream << ", reason: "
- << ex.what();
- IFACEMGR_ERROR(SocketConfigError, error_handler, msg_stream.str());
+ *iface, addr, port, nullptr
+ );
+ } catch (const Exception& ex) {
+ IFACEMGR_ERROR(SocketConfigError, error_handler,
+ "failed to open multicast socket on interface "
+ << iface->getName() << ", reason: " << ex.what());
+ continue;
+ }
}
+
+ ++count;
}
}
typedef
std::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback;
-/// @brief This type describes the callback function invoked when an opening of
-/// a socket fails and can be retried.
-///
-/// @param retries A number of an opening retries.
-/// @return true if an opening should be retried, false otherwise, and a wait time
-/// from the last attempt.
-typedef
-std::function<std::pair<bool, uint64_t>(uint32_t retries, const std::string& msg)> IfaceMgrRetryCallback;
-
/// @brief Handles network interfaces, transmission and reception.
///
/// IfaceMgr is an interface manager class that detects available network
/// @param error_handler A pointer to an error handler function which is
/// called by the openSockets6 when it fails to open a socket. This
/// parameter can be null to indicate that the callback should not be used.
- /// @param retry_callback A pointer to a retry callback function which is
- /// called by the openSockets4 when it fails to open a socket.
- /// The responsibility of the callback is to decide if the opening should be
- /// retried and after which time. This parameter can be null to indicate that
- /// the callback should not be used.
+ /// @param skip_opened skip the addresses that already have the opened port
///
/// @throw SocketOpenFailure if tried and failed to open socket.
/// @return true if any sockets were open
bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT,
IfaceMgrErrorMsgCallback error_handler = 0,
- IfaceMgrRetryCallback retry_callback = 0);
+ const bool skip_opened = false);
/// @brief Opens IPv4 sockets on detected interfaces.
///
///
/// @param port specifies port number (usually DHCP4_SERVER_PORT)
/// @param use_bcast configure sockets to support broadcast messages.
- /// @param error_handler A pointer to an error handler function which is
+ /// @param error_handler a pointer to an error handler function which is
/// called by the openSockets4 when it fails to open a socket. This
/// parameter can be null to indicate that the callback should not be used.
- /// @param retry_callback A pointer to a retry callback function which is
- /// called by the openSockets4 when it fails to open a socket.
- /// The responsibility of the callback is to decide if the opening should be
- /// retried and after which time. This parameter can be null to indicate that
- /// the callback should not be used.
+ /// @param skip_opened skip the addresses that already have the opened port
///
/// @throw SocketOpenFailure if tried and failed to open socket and callback
/// function hasn't been specified.
bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT,
const bool use_bcast = true,
IfaceMgrErrorMsgCallback error_handler = 0,
- IfaceMgrRetryCallback retry_callback = 0);
+ const bool skip_opened = false);
/// @brief Closes all open sockets.
///
+++ /dev/null
-// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC")
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-#ifndef IFACE_MGR_RETRY_CALLBACK_H
-#define IFACE_MGR_RETRY_CALLBACK_H
-
-#include <functional>
-#include <dhcp/iface_mgr.h>
-
-namespace isc {
-namespace dhcp {
-
-/// @brief An helper to call a function with retry.
-///
-/// There are certain cases when IfaceMgr may hit an error caused by
-/// temporary extarnal factors. A typical case is the function which opens
-/// sockets on available interfaces for a DHCP server. If this function
-/// fails to open a socket on a specific interface (for example, there is
-/// another socket already open on this interface and bound to the same address
-/// and port), it may be helpful to repeat an opening procedure.
-/// It is allowed that the error handler function is not installed (is NULL).
-/// In these cases it is expected that the function is just called without retrying.
-///
-/// @param f A function to call; template type T is an output type of this function.
-/// @param msg A message intended to log with a failed attempt.
-/// @param retry_callback A retry callback that decides to continue retries and wait
-/// time before next try. It should also log the info/warning message.
-template <typename T>
-T callWithRetry(std::function<T()> f,
- const std::string& msg,
- IfaceMgrRetryCallback retry_callback) {
-
- // If the retry callback is NULL, just call the function and return.
- if (retry_callback == nullptr) {
- return f();
- }
-
- // Counter of the retries.
- uint64_t retries = 0;
-
- // Leave the loop on success (return statement)
- // or stop retrying (throw statement).
- while (true) {
- try {
- return f();
- } catch (const Exception& ex) {
- std::stringstream message(msg);
- message << ", reason: " << ex.what();
- auto retry_msg = message.str();
-
- // Callback produces a log message
- const std::pair<bool, uint64_t>& result = retry_callback(retries++, retry_msg);
-
- bool should_retry = result.first;
- uint64_t wait_time = result.second;
-
- if (!should_retry) {
- throw;
- } else {
- // Wait before next attempt. The initialization cannot end before
- // opening a socket so we can wait in the foreground.
- std::this_thread::sleep_for(std::chrono::milliseconds(wait_time));
- }
- }
- }
-}
-
-}
-}
-
-#endif // IFACE_MGR_RETRY_CALLBACK_H
EXPECT_EQ(2, errors_count_);
}
-// Test that the external retry callback is called when trying to bind a new
-// socket to the address and port being in use. The opening should be repeated.
-TEST_F(IfaceMgrTest, openSocket4RetryCallback) {
+// Test that no exception is thown when a port is already bound but skip open
+// flag is provided.
+TEST_F(IfaceMgrTest, openSockets4SkipOpen) {
NakedIfaceMgr ifacemgr;
// Remove all real interfaces and create a set of dummy interfaces.
ASSERT_TRUE(custom_packet_filter);
ASSERT_NO_THROW(ifacemgr.setPacketFilter(custom_packet_filter));
- // Open socket on eth0.
- ASSERT_NO_THROW(ifacemgr.openSocket("eth0", IOAddress("10.0.0.1"),
+ // Open socket on eth1. The openSockets4 should detect that this
+ // socket has been already open and an attempt to open another socket
+ // and bind to this address and port should fail.
+ ASSERT_NO_THROW(ifacemgr.openSocket("eth1", IOAddress("192.0.2.3"),
DHCP4_SERVER_PORT));
- // Install an retry callback before trying to open sockets. This handler
- // should be called when the IfaceMgr fails to open socket on eth0.
- // The callback counts the retry attempts. The retry indices must be sequential.
- // The caller must wait specific time between calls.
- uint16_t total_attempts = 0;
- auto last_call_time = std::chrono::system_clock::time_point::min();
- isc::dhcp::IfaceMgrRetryCallback retry_callback =
- [&total_attempts, &last_call_time](uint16_t current_attempt, const std::string& msg){
- // An attempt index must be sequential.
- EXPECT_EQ(total_attempts, current_attempt);
- total_attempts++;
-
- // A waiting time isn't too short.
- auto now = std::chrono::system_clock::now();
- if (current_attempt != 0) {
- auto interval = now - last_call_time;
- EXPECT_GE(interval, std::chrono::milliseconds(10));
- }
- last_call_time = now;
-
- // Message has content.
- EXPECT_FALSE(msg.empty());
-
- // Test for 5 retries with 10 milliseconds waiting time.
- return std::make_pair(current_attempt < 4, 10);
- };
-
- // The openSockets4 should detect that there is another socket already
- // open and bound to the same address and port. An attempt to open
- // another socket and bind to this address and port should fail and be repeated
- // a few times. The exception is thrown because the error handler is NULL.
- EXPECT_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, nullptr, retry_callback),
- isc::dhcp::SocketConfigError);
+ // The function doesn't throw an exception when it tries to open a socket
+ // and bind it to the address in use but the skip open flag is provided.
+ EXPECT_NO_THROW(ifacemgr.openSockets4(DHCP4_SERVER_PORT, true, 0, true));
- // The callback should notice 5 attempts to open a port - 1 initial and 4 retries.
- EXPECT_EQ(5, total_attempts);
+ // Check that the other port is bound.
+ EXPECT_TRUE(ifacemgr.hasOpenSocket(IOAddress("10.0.0.1")));
}
// This test verifies that the function correctly checks that the v4 socket is
// Test that the external error handler is called when trying to bind a new
// socket to the address and port being in use. The sockets on the other
// interfaces should open just fine.
-TEST_F(IfaceMgrTest, openSocket6ErrorHandler) {
+TEST_F(IfaceMgrTest, openSockets6ErrorHandler) {
NakedIfaceMgr ifacemgr;
// Remove all real interfaces and create a set of dummy interfaces.
EXPECT_EQ(2, errors_count_);
}
-// Test that the external retry callback is called when trying to bind a new
-// multicast socket to the address and port being in use. The opening should
-// be repeated.
-TEST_F(IfaceMgrTest, openMulticastSocket6RetryCallback) {
+// Test that no exception is thown when a port is already bound but skip open
+// flag is provided.
+TEST_F(IfaceMgrTest, openSockets6SkipOpen) {
NakedIfaceMgr ifacemgr;
// Remove all real interfaces and create a set of dummy interfaces.
ASSERT_TRUE(filter);
ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
- // Open multicast socket on eth0.
+ // Open socket on eth0. The openSockets6 should detect that this
+ // socket has been already open and an attempt to open another socket
+ // and bind to this address and port should fail.
ASSERT_NO_THROW(ifacemgr.openSocket("eth0",
IOAddress("fe80::3a60:77ff:fed5:cdef"),
DHCP6_SERVER_PORT, true));
- // Install an retry callback before trying to open sockets. This handler
- // should be called when the IfaceMgr fails to open socket on eth0.
- // The callback counts the retry attempts. The retry indices must be sequential.
- // The caller must wait specific time between calls.
- uint16_t total_attempts = 0;
- auto last_call_time = std::chrono::system_clock::time_point::min();
- isc::dhcp::IfaceMgrRetryCallback retry_callback =
- [&total_attempts, &last_call_time](uint16_t current_attempt, const std::string& msg){
- // An attempt index must be sequential.
- EXPECT_EQ(total_attempts, current_attempt);
- total_attempts++;
-
- // A waiting time isn't too short.
- auto now = std::chrono::system_clock::now();
- if (current_attempt != 0) {
- auto interval = now - last_call_time;
- EXPECT_GE(interval, std::chrono::milliseconds(10));
- }
- last_call_time = now;
-
- // Message has content.
- EXPECT_FALSE(msg.empty());
+ // The function doesn't throw an exception when it tries to open a socket
+ // and bind it to the address in use but the skip open flag is provided.
+ EXPECT_NO_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, 0, true));
- // Test for 5 retries with 10 milliseconds waiting time.
- return std::make_pair(current_attempt < 4, 10);
- };
-
- // The openSockets6 should detect that there is another socket already
- // open and bound to the same address and port. An attempt to open
- // another socket and bind to this address and port should fail and be repeated
- // a few times. The exception is thrown because the error handler is NULL.
- EXPECT_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, nullptr, retry_callback),
- isc::dhcp::SocketConfigError);
-
- // The callback should notice 5 attempts to open a port - 1 initial and 4 retries.
- EXPECT_EQ(5, total_attempts);
-}
-
-// Test that the external retry callback is called when trying to bind a new
-// unicast socket to the address and port being in use. The opening should be
-// repeated.
-TEST_F(IfaceMgrTest, openUnicastSocket6RetryCallback) {
- NakedIfaceMgr ifacemgr;
-
- // Remove all real interfaces and create a set of dummy interfaces.
- ifacemgr.createIfaces();
- // Add an unicast.
- ifacemgr.getIface("eth1")->addUnicast(IOAddress("2001:db8:1::2"));
-
- boost::shared_ptr<PktFilter6Stub> filter(new PktFilter6Stub());
- ASSERT_TRUE(filter);
- ASSERT_NO_THROW(ifacemgr.setPacketFilter(filter));
-
- // Open unicast socket on eth1.
- ASSERT_NO_THROW(ifacemgr.openSocket("eth1",
- IOAddress("2001:db8:1::2"),
- DHCP6_SERVER_PORT, false));
-
- // Install an retry callback before trying to open sockets. This handler
- // should be called when the IfaceMgr fails to open socket on eth0.
- // The callback counts the retry attempts. The retry indices must be sequential.
- // The caller must wait specific time between calls.
- uint16_t total_attempts = 0;
- auto last_call_time = std::chrono::system_clock::time_point::min();
- isc::dhcp::IfaceMgrRetryCallback retry_callback =
- [&total_attempts, &last_call_time](uint16_t current_attempt, const std::string& msg){
- // An attempt index must be sequential.
- EXPECT_EQ(total_attempts, current_attempt);
- total_attempts++;
-
- // A waiting time isn't too short.
- auto now = std::chrono::system_clock::now();
- if (current_attempt != 0) {
- auto interval = now - last_call_time;
- EXPECT_GE(interval, std::chrono::milliseconds(10));
- }
- last_call_time = now;
-
- // Message has content.
- EXPECT_FALSE(msg.empty());
-
- // Test for 5 retries with 10 milliseconds waiting time.
- return std::make_pair(current_attempt < 4, 10);
- };
-
- // The openSockets6 should detect that there is another socket already
- // open and bound to the same address and port. An attempt to open
- // another socket and bind to this address and port should fail and be repeated
- // a few times. The exception is thrown because the error handler is NULL.
- EXPECT_THROW(ifacemgr.openSockets6(DHCP6_SERVER_PORT, nullptr, retry_callback),
- isc::dhcp::SocketConfigError);
-
- // The callback should notice 5 attempts to open a port - 1 initial and 4 retries.
- EXPECT_EQ(5, total_attempts);
+ // Check that the other port is bound.
+ EXPECT_TRUE(ifacemgr.isBound("eth1", "fe80::3a60:77ff:fed5:abcd"));
}
// This test verifies that the function correctly checks that the v6 socket is
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/cfg_iface.h>
+#include <dhcpsrv/timer_mgr.h>
+#include <util/reconnect_ctl.h>
+#include <util/multi_threading_mgr.h>
#include <util/strutil.h>
#include <algorithm>
#include <functional>
}
bool
-CfgIface::multipleAddressesPerInterfaceActive() const {
+CfgIface::multipleAddressesPerInterfaceActive() {
for (IfacePtr iface : IfaceMgr::instance().getIfaces()) {
if (iface->countActive4() > 1) {
return (true);
}
}
- // Set the callbacks which are called when the socket fails to open
- // for some specific interface.
-
- // If the config requires the binding of all sockets, then the error
- // callback is null - an exception is thrown on failure.
- IfaceMgrErrorMsgCallback error_callback = nullptr;
- if (!CfgIface::getServiceSocketsRequireAll()) {
- // This callback will simply log a warning message.
- error_callback = std::bind(&CfgIface::socketOpenErrorHandler, ph::_1);
- }
-
- IfaceMgrRetryCallback retry_callback =
- std::bind(&CfgIface::socketOpenRetryHandler, this, ph::_1, ph::_2);
+ // Use broadcast only if we're using raw sockets. For the UDP sockets,
+ // we only handle the relayed (unicast) traffic.
+ const bool can_use_bcast = use_bcast && (socket_type_ == SOCKET_RAW);
- bool sopen;
- if (family == AF_INET) {
- // Use broadcast only if we're using raw sockets. For the UDP sockets,
- // we only handle the relayed (unicast) traffic.
- const bool can_use_bcast = use_bcast && (socket_type_ == SOCKET_RAW);
- // Opening multiple raw sockets handling brodcast traffic on the single
- // interface may lead to processing the same message multiple times.
- // We don't prohibit such configuration because raw sockets can as well
- // handle the relayed traffic. We have to issue a warning, however, to
- // draw administrator's attention.
+ // Opening multiple raw sockets handling brodcast traffic on the single
+ // interface may lead to processing the same message multiple times.
+ // We don't prohibit such configuration because raw sockets can as well
+ // handle the relayed traffic. We have to issue a warning, however, to
+ // draw administrator's attention.
+ if (family == AF_INET && can_use_bcast && multipleAddressesPerInterfaceActive()) {
if (can_use_bcast && multipleAddressesPerInterfaceActive()) {
LOG_WARN(dhcpsrv_logger, DHCPSRV_MULTIPLE_RAW_SOCKETS_PER_IFACE);
}
- sopen = IfaceMgr::instance().openSockets4(port, can_use_bcast, error_callback,
- retry_callback);
- } else {
- // use_bcast is ignored for V6.
- sopen = IfaceMgr::instance().openSockets6(port, error_callback, retry_callback);
}
+ auto reconnect_ctl = makeReconnectCtl(family);
+ auto sopen = openSocketsWithRetry(reconnect_ctl, family, port, can_use_bcast);
+
if (!sopen) {
// If no socket were opened, log a warning because the server will
// not respond to any queries.
}
}
+std::pair<bool, bool>
+CfgIface::openSocketsForFamily(const uint16_t family, const uint16_t port,
+ const bool can_use_bcast, const bool skip_opened) {
+ bool no_errors = true;
+
+ // Set the callbacks which are called when the socket fails to open
+ // for some specific interface.
+ auto error_callback = [&no_errors](const std::string& errmsg){
+ socketOpenErrorHandler(errmsg);
+ no_errors = false;
+ };
+
+ bool sopen = false;
+ if (family == AF_INET) {
+ sopen = IfaceMgr::instance().openSockets4(
+ port, can_use_bcast, error_callback, skip_opened
+ );
+ } else {
+ // use_bcast is ignored for V6.
+ sopen = IfaceMgr::instance().openSockets6(
+ port, error_callback, skip_opened
+ );
+ }
+
+ return std::make_pair(sopen, no_errors);
+}
+
+util::ReconnectCtlPtr CfgIface::makeReconnectCtl(const uint16_t family) const {
+ // Create unique timer name per instance.
+ std::string timer_name = "ConfigInterface[";
+ timer_name += boost::lexical_cast<std::string>(reinterpret_cast<uint64_t>(this));
+ timer_name += "]SocketReopenFamily";
+ timer_name += boost::lexical_cast<std::string>(family);
+ timer_name += "Timer";
+
+ auto on_fail_action = util::OnFailAction::SERVE_RETRY_CONTINUE;
+ if (CfgIface::getServiceSocketsRequireAll()) {
+ on_fail_action = util::OnFailAction::STOP_RETRY_EXIT;
+ }
+
+ auto reconnect_ctl = boost::make_shared<util::ReconnectCtl>("open-sockets", timer_name,
+ // Add one attempt for an initial call.
+ CfgIface::getServiceSocketsMaxRetries(),
+ CfgIface::getServiceSocketsRetryWaitTime(),
+ on_fail_action);
+
+ return reconnect_ctl;
+}
+
+bool
+CfgIface::openSocketsWithRetry(util::ReconnectCtlPtr reconnect_ctl,
+ const uint16_t family, const uint16_t port, const bool can_use_bcast) {
+ util::MultiThreadingCriticalSection cs;
+
+ // Skip opened sockets in the retry calls.
+ bool skip_opened = reconnect_ctl->retriesLeft() != reconnect_ctl->maxRetries();
+ auto result_pair = openSocketsForFamily(family, port, can_use_bcast, skip_opened);
+ bool sopen = result_pair.first;
+ bool has_errors = !result_pair.second;
+
+ auto timer_name = reconnect_ctl->timerName();
+
+ // Has errors and can retry
+ if (has_errors && reconnect_ctl->retriesLeft() > 0) {
+ // Initial call is excluded from retries counter.
+ reconnect_ctl->checkRetries();
+ // Start the timer.
+ if (!TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->registerTimer(timer_name,
+ std::bind(&CfgIface::openSocketsWithRetry, reconnect_ctl,
+ family, port, can_use_bcast
+ ),
+ reconnect_ctl->retryInterval(),
+ asiolink::IntervalTimer::ONE_SHOT);
+ }
+ TimerMgr::instance()->setup(timer_name);
+ // Has errors but retries exceed
+ } else if (has_errors) {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+
+ if (open_sockets_failed_callback_ != nullptr) {
+ open_sockets_failed_callback_(reconnect_ctl);
+ }
+ // Has no errors
+ } else {
+ // Cancel the timer.
+ if (TimerMgr::instance()->isTimerRegistered(timer_name)) {
+ TimerMgr::instance()->unregisterTimer(timer_name);
+ }
+ }
+
+ return sopen;
+}
+
void
CfgIface::reset() {
wildcard_used_ = false;
LOG_WARN(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(errmsg);
}
-std::pair<bool, uint64_t>
-CfgIface::socketOpenRetryHandler(uint32_t retries, const std::string& msg) const {
- bool can_retry = retries < service_sockets_max_retries_;
- if (can_retry) {
- std::stringstream msg_stream;
- msg_stream << msg << "; retries left: " << service_sockets_max_retries_ - retries;
- LOG_INFO(dhcpsrv_logger, DHCPSRV_OPEN_SOCKET_FAIL).arg(msg_stream.str());
- }
-
- return std::make_pair(
- can_retry,
- service_sockets_retry_wait_time_
- );
-}
-
std::string
CfgIface::socketTypeToText() const {
switch (socket_type_) {
return (result);
}
+CfgIface::OpenSocketsFailedCallback CfgIface::open_sockets_failed_callback_ = 0;
+
} // end of isc::dhcp namespace
} // end of isc namespace
#include <asiolink/io_address.h>
#include <dhcp/iface_mgr.h>
+#include <util/reconnect_ctl.h>
#include <cc/cfg_to_element.h>
#include <cc/user_context.h>
#include <boost/shared_ptr.hpp>
/// @brief Set the socket service binding retry interval between attempts.
///
/// @param interval Milliseconds between attempts.
- void setServiceSocketsRetryWaitTime(uint64_t interval) {
+ void setServiceSocketsRetryWaitTime(unsigned int interval) {
service_sockets_retry_wait_time_ = interval;
}
/// @brief Indicates the socket service binding retry interval between attempts.
///
/// @return Milliseconds between attempts.
- uint64_t getServiceSocketsRetryWaitTime() {
+ unsigned int getServiceSocketsRetryWaitTime() const {
return (service_sockets_retry_wait_time_);
}
/// @brief Set a maximum number of service sockets bind attempts.
///
/// @param max_retries Number of attempts. The value 0 disables retries.
- void setServiceSocketsMaxRetries(uint32_t max_retries) {
+ void setServiceSocketsMaxRetries(unsigned int max_retries) {
service_sockets_max_retries_ = max_retries;
}
/// @brief Indicates the maximum number of service sockets bind attempts.
///
/// @return Number of attempts.
- uint32_t getServiceSocketsMaxRetries() {
+ unsigned int getServiceSocketsMaxRetries() const {
return (service_sockets_max_retries_);
}
+ /// @brief Represents a callback invoked if all retries of the
+ /// opening sockets fail.
+ typedef std::function<void(util::ReconnectCtlPtr)> OpenSocketsFailedCallback;
+
+ /// @brief Optional callback function to invoke if all retries of the
+ /// opening sockets fail.
+ static OpenSocketsFailedCallback open_sockets_failed_callback_;
+
private:
/// @brief Checks if multiple IPv4 addresses has been activated on any
///
/// @return true if multiple addresses are activated on any interface,
/// false otherwise.
- bool multipleAddressesPerInterfaceActive() const;
+ static bool multipleAddressesPerInterfaceActive();
/// @brief Selects or deselects interfaces.
///
/// @param errmsg Error message being logged by the function.
static void socketOpenErrorHandler(const std::string& errmsg);
+ /// @brief Calls a family-specific function to open sockets.
+ ///
+ /// It is a static function for a safe call from a CfgIface instance or a
+ /// timer handler.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param port Port number to be used to bind sockets to.
+ /// @param can_use_bcast A boolean flag which indicates if the broadcast
+ /// traffic should be received through the socket and the raw sockets are
+ /// used. For the UDP sockets, we only handle the relayed (unicast)
+ /// traffic. This parameter is ignored for IPv6.
+ /// @param skip_opened Omits the already opened sockets (doesn't try to
+ /// re-bind).
+ /// @return Pair of boolean flags. The first boolean is true if at least
+ /// one socket is successfully opened, and the second is true if no errors
+ /// occur.
+ static std::pair<bool, bool> openSocketsForFamily(const uint16_t family,
+ const uint16_t port, const bool can_use_bcast, const bool skip_opened);
+
+ /// @brief Creates a ReconnectCtl based on the configuration's
+ /// retry parameters.
+ ///
+ /// @param family IP family
+ /// @return The reconnect control created using the configuration
+ /// parameters.
+ util::ReconnectCtlPtr makeReconnectCtl(const uint16_t family) const;
+
+ /// Calls the @c CfgIface::openSocketsForFamily function and retry it if
+ /// socket opening fails.
+ ///
+ /// @param family Address family (AF_INET or AF_INET6).
+ /// @param port Port number to be used to bind sockets to.
+ /// @param can_use_bcast A boolean flag which indicates if the broadcast
+ /// traffic should be received through the socket and the raw sockets are
+ /// used. For the UDP sockets, we only handle the relayed (unicast)
+ /// traffic. This parameter is ignored for IPv6.
+ /// @return True if at least one socket opened successfully.
+ static bool openSocketsWithRetry(
+ util::ReconnectCtlPtr reconnect_ctl,
+ const uint16_t family, const uint16_t port, const bool can_use_bcast);
+
/// @brief Retry handler for executed when opening a socket fail.
///
/// A pointer to this function is passed to the @c IfaceMgr::openSockets4
/// or @c IfaceMgr::openSockets6. These functions call this handler when
- /// they fail to open a socket. The handler decides if the opening should be
- /// retried and logs info passed in the parameter. It also returns a time to
- /// wait from the last attempt. It allows extending the waiting time dynamically
- /// with the next tries.
+ /// they fail to open a socket. The handler decides if the opening should
+ /// be retried and logs info passed in the parameter. It also returns a
+ /// time to wait from the last attempt. It allows extending the waiting
+ /// time dynamically with the next tries.
///
/// @param retries An index of opening retries
/// @param msg Message being logged by the function.
- /// @return true if the opening should be retried and milliseconds to wait from last attempt.
- std::pair<bool, uint64_t> socketOpenRetryHandler(uint32_t retries, const std::string& msg) const;
+ /// @return true if the opening should be retried and milliseconds to wait
+ /// from last attempt.
+ std::pair<bool, uint64_t> socketOpenRetryHandler(uint32_t retries,
+ const std::string& msg) const;
/// @brief Represents a set of interface names.
typedef std::set<std::string> IfaceSet;
/// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
/// configured reconnect parameters.
/// @return true if connection has been recovered, false otherwise.
- static bool dbReconnect(db::ReconnectCtlPtr db_reconnect_ctl);
+ static bool dbReconnect(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Local version of getDBVersion() class method
static std::string getDBVersion();
/// @param db_reconnect_ctl pointer to the ReconnectCtl containing the
/// configured reconnect parameters.
/// @return true if connection has been recovered, false otherwise.
- static bool dbReconnect(db::ReconnectCtlPtr db_reconnect_ctl);
+ static bool dbReconnect(util::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Local version of getDBVersion() class method
static std::string getDBVersion();
#include <dhcp/tests/pkt_filter_test_stub.h>
#include <dhcp/tests/pkt_filter6_test_stub.h>
#include <dhcpsrv/cfg_iface.h>
+#include <asiolink/io_service.h>
+#include <asiolink/asio_wrapper.h>
+#include <asiolink/interval_timer.h>
+#include <dhcpsrv/timer_mgr.h>
#include <testutils/test_to_element.h>
#include <gtest/gtest.h>
/// @param iface_name Interface name.
bool unicastOpen(const std::string& iface_name) const;
+ /// @brief Wait for specific timeout.
+ ///
+ /// @param timeout Wait timeout in milliseconds.
+ void doWait(const long timeout);
+
/// @brief Holds a fake configuration of the interfaces.
IfaceMgrTestConfig iface_mgr_test_config_;
+ /// @brief Pointer to IO service used by the tests.
+ asiolink::IOServicePtr io_service_;
+
+private:
+
+ /// @brief Prepares the class for a test.
+ virtual void SetUp();
+
+ /// @brief Cleans up after the test.
+ virtual void TearDown();
+
};
+void
+CfgIfaceTest::SetUp() {
+ io_service_.reset(new asiolink::IOService());
+ auto timer_mgr_ = TimerMgr::instance();
+ timer_mgr_->setIOService(io_service_);
+}
+
+void
+CfgIfaceTest::TearDown() {
+ // Remove all timers.
+ TimerMgr::instance()->unregisterTimers();
+}
+
bool
CfgIfaceTest::socketOpen(const std::string& iface_name,
const int family) const {
return (iface_mgr_test_config_.unicastOpen(iface_name));
}
+void
+CfgIfaceTest::doWait(const long timeout) {
+ asiolink::IntervalTimer timer(*io_service_);
+ timer.setup([this]() {
+ io_service_->stop();
+ }, timeout, asiolink::IntervalTimer::ONE_SHOT);
+ io_service_->run();
+ io_service_->get_io_service().reset();
+}
+
// This test checks that the interface names can be explicitly selected
// by their names and IPv4 sockets are opened on these interfaces.
TEST_F(CfgIfaceTest, explicitNamesV4) {
ASSERT_NO_THROW(cfg.use(AF_INET, "eth0"));
ASSERT_NO_THROW(cfg.use(AF_INET, "eth1"));
- // Open sockets on specified interfaces.
+ // Open sockets on spsetecified interfaces.
cfg.openSockets(AF_INET, DHCP4_SERVER_PORT);
// Sockets should be now open on eth0 and eth1, but not on loopback.
TEST_F(CfgIfaceTest, requireOpenAllServiceSockets) {
CfgIface cfg4;
CfgIface cfg6;
+
+ // Configure a fail callback
+ uint16_t fail_calls = 0;
+ CfgIface::OpenSocketsFailedCallback on_fail_callback =
+ [&fail_calls](util::ReconnectCtlPtr reconnect_ctl){
+ EXPECT_TRUE(reconnect_ctl != nullptr);
+ EXPECT_TRUE(reconnect_ctl->exitOnFailure());
+ fail_calls++;
+ };
+
+ CfgIface::open_sockets_failed_callback_ = on_fail_callback;
+
ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
ASSERT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
// Require all sockets bind successfully
cfg4.setServiceSocketsRequireAll(true);
+ cfg4.setServiceSocketsMaxRetries(0);
cfg6.setServiceSocketsRequireAll(true);
+ cfg6.setServiceSocketsMaxRetries(0);
- // Open an available port
+ // Open the available ports
ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
cfg4.closeSockets();
ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter6));
// Open an unavailable port
- EXPECT_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT), isc::dhcp::SocketConfigError);
- EXPECT_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT), isc::dhcp::SocketConfigError);
+ EXPECT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+ EXPECT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
+
+ // Both instances should call the fail callback.
+ EXPECT_EQ(fail_calls, 2);
+
+ // Reset global handlers
+ CfgIface::open_sockets_failed_callback_ = nullptr;
}
-// This test verifies that if any socket fails to bind, then the opening will retry.
-TEST_F(CfgIfaceTest, retryOpenServiceSockets) {
+// This test verifies that if any IPv4 socket fails to bind,
+// the opening will retry.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets4) {
CfgIface cfg4;
- CfgIface cfg6;
ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
ASSERT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
- ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
- ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+ // Parameters
const uint16_t RETRIES = 5;
const uint16_t WAIT_TIME = 10; // miliseconds
+ // The number of sockets opened in a single retry attempt.
+ const uint16_t CALLS_PER_RETRY = 2;
// Require retry socket binding
cfg4.setServiceSocketsMaxRetries(RETRIES);
cfg4.setServiceSocketsRetryWaitTime(WAIT_TIME);
+
+ // Set the callback to count calls and check wait time
+ size_t total_calls = 0;
+ auto last_call_time = std::chrono::system_clock::time_point::min();
+ auto open_callback = [&total_calls, &last_call_time, RETRIES, WAIT_TIME,
+ CALLS_PER_RETRY](){
+ auto now = std::chrono::system_clock::now();
+
+ // Check waiting time only for the first call in a retry attempt.
+ if (total_calls % CALLS_PER_RETRY == 0) {
+ // Don't check the waiting time for initial call.
+ if (total_calls != 0) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
+ }
+
+ total_calls++;
+
+ // Fail to open a socket
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(
+ new isc::dhcp::test::PktFilterTestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // Open an unavailable port
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For each interface perform 1 init open and a few retries.
+ EXPECT_EQ(CALLS_PER_RETRY * (RETRIES + 1), total_calls);
+}
+
+// This test verifies that if any IPv4 socket fails to bind, the opening will
+// retry, but the opened sockets will not be re-bound.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets4OmitBound) {
+ CfgIface cfg4;
+
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth0"));
+ ASSERT_NO_THROW(cfg4.use(AF_INET, "eth1/192.0.2.3"));
+
+ // Parameters
+ const uint16_t RETRIES = 5;
+ const uint16_t WAIT_TIME = 10; // miliseconds
+
+ // Require retry socket binding
+ cfg4.setServiceSocketsMaxRetries(RETRIES);
+ cfg4.setServiceSocketsRetryWaitTime(WAIT_TIME);
+
+ // Set the callback to count calls and check wait time
+ size_t total_calls = 0;
+ auto last_call_time = std::chrono::system_clock::time_point::min();
+ auto open_callback = [&total_calls, &last_call_time, RETRIES, WAIT_TIME](){
+ auto now = std::chrono::system_clock::now();
+ bool is_eth1 = total_calls == 1;
+
+ // Skip the wait time check for the socket when two sockets are
+ // binding in a single attempt.
+ if (!is_eth1) {
+ // Don't check the waiting time for initial call.
+ if (total_calls != 0) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
+ }
+
+ total_calls++;
+
+ // Fail to open a socket on eth0, success for eth1
+ if (!is_eth1) {
+ isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
+ }
+ };
+
+ boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(
+ new isc::dhcp::test::PktFilterTestStub()
+ );
+
+ filter->setOpenSocketCallback(open_callback);
+
+ ASSERT_TRUE(filter);
+ ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
+
+ // Open an unavailable port
+ ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
+
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For eth0 interface perform 1 init open and a few retries,
+ // for eth1 interface perform only init open.
+ EXPECT_EQ((RETRIES + 1) + 1, total_calls);
+}
+
+// This test verifies that if any IPv6 socket fails to bind,
+// the opening will retry.
+TEST_F(CfgIfaceTest, retryOpenServiceSockets6) {
+ CfgIface cfg6;
+
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth0/2001:db8:1::1"));
+ ASSERT_NO_THROW(cfg6.use(AF_INET6, "eth1"));
+
+ // Parameters
+ const uint16_t RETRIES = 5;
+ const uint16_t WAIT_TIME = 10; // miliseconds
+ // The number of sockets opened in a single retry attempt.
+ // 2 multicast sockets and 1 unicast.
+ const uint16_t CALLS_PER_RETRY = 3;
+
+ // Require retry socket binding
cfg6.setServiceSocketsMaxRetries(RETRIES);
cfg6.setServiceSocketsRetryWaitTime(WAIT_TIME);
// Set the callback to count calls and check wait time
size_t total_calls = 0;
auto last_call_time = std::chrono::system_clock::time_point::min();
- auto open_callback = [&total_calls, &last_call_time, RETRIES, WAIT_TIME](){
+ auto open_callback = [&total_calls, &last_call_time, RETRIES, WAIT_TIME,
+ CALLS_PER_RETRY](){
auto now = std::chrono::system_clock::now();
- // Don't check the waiting time for initial calls as they
- // can be done immediately after the last call for the previous socket.
- if (total_calls % (RETRIES + 1) != 0) {
- auto interval = now - last_call_time;
- EXPECT_GE(interval, std::chrono::milliseconds(WAIT_TIME));
+ // Check waiting time only for the first call in a retry attempt.
+ if (total_calls % CALLS_PER_RETRY == 0) {
+ // Don't check the waiting time for initial call.
+ if (total_calls != 0) {
+ auto interval = now - last_call_time;
+ auto interval_ms =
+ std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval
+ ).count();
+
+ EXPECT_GE(interval_ms, WAIT_TIME);
+ }
+
+ last_call_time = now;
}
- last_call_time = now;
total_calls++;
// Fail to open a socket
isc_throw(Unexpected, "CfgIfaceTest: cannot open a port");
};
- boost::shared_ptr<isc::dhcp::test::PktFilterTestStub> filter(new isc::dhcp::test::PktFilterTestStub());
- boost::shared_ptr<isc::dhcp::test::PktFilter6TestStub> filter6(new isc::dhcp::test::PktFilter6TestStub());
+
+ boost::shared_ptr<isc::dhcp::test::PktFilter6TestStub> filter(
+ new isc::dhcp::test::PktFilter6TestStub()
+ );
filter->setOpenSocketCallback(open_callback);
- filter6->setOpenSocketCallback(open_callback);
ASSERT_TRUE(filter);
- ASSERT_TRUE(filter6);
ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter));
- ASSERT_NO_THROW(IfaceMgr::instance().setPacketFilter(filter6));
// Open an unavailable port
- ASSERT_NO_THROW(cfg4.openSockets(AF_INET, DHCP4_SERVER_PORT));
ASSERT_NO_THROW(cfg6.openSockets(AF_INET6, DHCP6_SERVER_PORT));
- // For IPv4 bind to: eth0 and eth1 (2).
- // For IPv6 bind to: unicast for eth0 and multicast for eth0 and eth1 (3).
- // For each interface perform 1 init open and 5 retries (6).
- // Perform 30 open calls ((2+3) * 6).
- EXPECT_EQ(30, total_calls);
+ // Wait for a finish sockets binding (with a safe margin).
+ doWait(RETRIES * WAIT_TIME * 2);
+
+ // For each interface perform 1 init open and a few retries.
+ EXPECT_EQ(CALLS_PER_RETRY * (RETRIES + 1), total_calls);
}
// This test verifies that it is possible to specify the socket
void testDbLostAndFailedAfterTimeoutCallback();
/// @brief Callback function registered with the lease manager
- bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_lost_callback_called_);
}
uint32_t db_lost_callback_called_;
/// @brief Callback function registered with the lease manager
- bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_recovered_callback_called_);
}
uint32_t db_recovered_callback_called_;
/// @brief Callback function registered with the lease manager
- bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_failed_callback_called_);
}
#ifndef GENERIC_CONFIG_BACKEND_RECOVERY_H
#define GENERIC_CONFIG_BACKEND_RECOVERY_H
-#include <database/database_connection.h>
+#include <util/reconnect_ctl.h>
#include <database/server_collection.h>
#include <dhcpsrv/config_backend_dhcp4_mgr.h>
#include <dhcpsrv/testutils/generic_backend_unittest.h>
void testDbLostAndFailedAfterTimeoutCallback();
/// @brief Callback function registered with the CB manager
- bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_lost_callback_called_);
}
uint32_t db_lost_callback_called_;
/// @brief Callback function registered with the CB manager
- bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_recovered_callback_called_);
}
uint32_t db_recovered_callback_called_;
/// @brief Callback function registered with the CB manager
- bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_failed_callback_called_);
}
#define GENERIC_HOST_DATA_SOURCE_UNITTEST_H
#include <asiolink/io_address.h>
-#include <database/database_connection.h>
+#include <util/reconnect_ctl.h>
#include <dhcpsrv/base_host_data_source.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/host.h>
void testDbLostAndFailedAfterTimeoutCallback();
/// @brief Callback function registered with the host manager
- bool db_lost_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_lost_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_lost_callback_called_);
}
uint32_t db_lost_callback_called_;
/// @brief Callback function registered with the host manager
- bool db_recovered_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_recovered_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_recovered_callback_called_);
}
uint32_t db_recovered_callback_called_;
/// @brief Callback function registered with the host manager
- bool db_failed_callback(db::ReconnectCtlPtr /* not_used */) {
+ bool db_failed_callback(util::ReconnectCtlPtr /* not_used */) {
return (++db_failed_callback_called_);
}
libkea_util_la_SOURCES += pointer_util.h
libkea_util_la_SOURCES += range_utilities.h
libkea_util_la_SOURCES += readwrite_mutex.h
+libkea_util_la_SOURCES += reconnect_ctl.h reconnect_ctl.cc
libkea_util_la_SOURCES += staged_value.h
libkea_util_la_SOURCES += state_model.cc state_model.h
libkea_util_la_SOURCES += stopwatch.cc stopwatch.h
pointer_util.h \
range_utilities.h \
readwrite_mutex.h \
+ reconnect_ctl.h \
staged_value.h \
state_model.h \
stopwatch.h \
--- /dev/null
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+#include <util/reconnect_ctl.h>
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace util {
+
+std::string
+ReconnectCtl::onFailActionToText(OnFailAction action) {
+ switch (action) {
+ case OnFailAction::STOP_RETRY_EXIT:
+ return ("stop-retry-exit");
+ case OnFailAction::SERVE_RETRY_EXIT:
+ return ("serve-retry-exit");
+ case OnFailAction::SERVE_RETRY_CONTINUE:
+ return ("serve-retry-continue");
+ }
+ return ("invalid-action-type");
+}
+
+OnFailAction
+ReconnectCtl::onFailActionFromText(const std::string& text) {
+ if (text == "stop-retry-exit") {
+ return (OnFailAction::STOP_RETRY_EXIT);
+ } else if (text == "serve-retry-exit") {
+ return (OnFailAction::SERVE_RETRY_EXIT);
+ } else if (text == "serve-retry-continue") {
+ return (OnFailAction::SERVE_RETRY_CONTINUE);
+ } else {
+ isc_throw(BadValue, "Invalid action on connection loss: " << text);
+ }
+}
+
+}
+}
--- /dev/null
+// Copyright (C) 2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef RECONNECT_CTL_H
+#define RECONNECT_CTL_H
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace util {
+
+/// @brief Type of action to take on connection loss.
+enum class OnFailAction {
+ STOP_RETRY_EXIT,
+ SERVE_RETRY_EXIT,
+ SERVE_RETRY_CONTINUE
+};
+
+/// @brief Warehouses reconnect control values
+///
+/// When any connection loses connectivity to its backend, it
+/// creates an instance of this class based on its configuration parameters and
+/// passes the instance into connection's lost callback. This allows
+/// the layer(s) above the connection to know how to proceed.
+///
+class ReconnectCtl {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param backend_type type of the caller backend.
+ /// @param timer_name timer associated to this object.
+ /// @param max_retries maximum number of reconnect attempts to make.
+ /// @param retry_interval amount of time to between reconnect attempts.
+ /// @param action which should be taken on connection loss.
+ ReconnectCtl(const std::string& backend_type, const std::string& timer_name,
+ unsigned int max_retries, unsigned int retry_interval,
+ OnFailAction action) :
+ backend_type_(backend_type), timer_name_(timer_name),
+ max_retries_(max_retries), retries_left_(max_retries),
+ retry_interval_(retry_interval), action_(action) {}
+
+ /// @brief Returns the type of the caller backend.
+ std::string backendType() const {
+ return (backend_type_);
+ }
+
+ /// @brief Returns the associated timer name.
+ ///
+ /// @return the associated timer.
+ std::string timerName() const {
+ return (timer_name_);
+ }
+
+ /// @brief Decrements the number of retries remaining
+ ///
+ /// Each call decrements the number of retries by one until zero is reached.
+ /// @return true the number of retries remaining is greater than zero.
+ bool checkRetries() {
+ return (retries_left_ ? --retries_left_ : false);
+ }
+
+ /// @brief Returns the maximum number of retries allowed.
+ unsigned int maxRetries() const {
+ return (max_retries_);
+ }
+
+ /// @brief Returns the number for retries remaining.
+ unsigned int retriesLeft() const {
+ return (retries_left_);
+ }
+
+ /// @brief Returns an index of current retry.
+ unsigned int retryIndex() {
+ return (max_retries_ - retries_left_);
+ }
+
+ /// @brief Returns the amount of time to wait between reconnect attempts.
+ unsigned int retryInterval() const {
+ return (retry_interval_);
+ }
+
+ /// @brief Resets the retries count.
+ void resetRetries() {
+ retries_left_ = max_retries_;
+ }
+
+ /// @brief Return true if the connection loss should affect the service,
+ /// false otherwise
+ bool alterServiceState() const {
+ return (action_ == OnFailAction::STOP_RETRY_EXIT);
+ }
+
+ /// @brief Return true if the connection recovery mechanism should shut down
+ /// the server on failure, false otherwise.
+ bool exitOnFailure() const {
+ return ((action_ == OnFailAction::STOP_RETRY_EXIT) ||
+ (action_ == OnFailAction::SERVE_RETRY_EXIT));
+ }
+
+ /// @brief Convert action to string.
+ ///
+ /// @param action The action type to be converted to text.
+ /// @return The text representation of the action type.
+ static std::string onFailActionToText(OnFailAction action);
+
+ /// @brief Convert string to action.
+ ///
+ /// @param text The text to be converted to action type.
+ /// @return The action type corresponding to the text representation.
+ static OnFailAction onFailActionFromText(const std::string& text);
+
+private:
+
+ /// @brief Caller backend type.
+ const std::string backend_type_;
+
+ /// @brief Timer associated to this object.
+ std::string timer_name_;
+
+ /// @brief Maximum number of retry attempts to make.
+ unsigned int max_retries_;
+
+ /// @brief Number of attempts remaining.
+ unsigned int retries_left_;
+
+ /// @brief The amount of time to wait between reconnect attempts.
+ unsigned int retry_interval_;
+
+ /// @brief Action to take on connection loss.
+ OnFailAction action_;
+};
+
+/// @brief Pointer to an instance of ReconnectCtl
+typedef boost::shared_ptr<ReconnectCtl> ReconnectCtlPtr;
+
+}
+}
+
+#endif // RECONNECT_CTL_H
\ No newline at end of file