#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>
continue;
}
- bool is_opened = false;
- bool should_retry = false;
- uint16_t attempt = 0;
- do
- {
- 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.
- openSocket(iface->getName(), addr.get(), port, is_open_as_broadcast, is_open_as_broadcast);
- is_opened = true;
- } catch (const Exception& ex) {
- auto err_msg = (std::stringstream("failed to open socket on interface ")
- << iface->getName() << ", reason: "
- << ex.what()
- ).str();
-
- uint16_t wait_time = 0;
- if (retry_callback != nullptr) {
- // Callback produces a log message
- const auto& pair = retry_callback(attempt++, err_msg);
- should_retry = pair.first;
- wait_time = pair.second;
- }
- if (!should_retry) {
- IFACEMGR_ERROR(SocketConfigError, error_handler, err_msg);
- } 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));
- }
- }
- }
- while(!is_opened && should_retry);
-
- if (!is_opened) {
- continue;
+ auto msg_stream = std::stringstream("failed to open socket on interface ")
+ << 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,
+ 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;
}
if (is_open_as_broadcast) {
// Open unicast sockets if there are any unicast addresses defined
for (Iface::Address addr : iface->getUnicasts()) {
- bool is_opened = false;
- bool should_retry = false;
- uint16_t attempt = 0;
-
- do {
- try {
- openSocket(iface->getName(), addr, port);
- } catch (const Exception& ex) {
- auto err_msg = (std::stringstream("failed to open unicast socket on interface ")
- << iface->getName() << ", reason: "
- << ex.what()).str();
-
- uint16_t wait_time = 0;
- if (retry_callback != nullptr) {
- // Callback produces a log message
- const auto& pair = retry_callback(attempt++, err_msg);
- should_retry = pair.first;
- wait_time = pair.second;
- }
-
- if (!should_retry) {
- IFACEMGR_ERROR(SocketConfigError, error_handler, err_msg);
- } 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));
- }
- }
- }
- while (!is_opened && should_retry);
-
- if (!is_opened) {
+ auto msg_stream = std::stringstream("failed to open unicast socket on interface ")
+ << iface->getName();
+
+ try {
+ callWithRetry<int>(
+ std::bind(&IfaceMgr::openSocket, this,
+ 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;
}
// Run OS-specific function to open a socket capable of receiving
// packets sent to All_DHCP_Relay_Agents_and_Servers multicast
// address.
- if (openMulticastSocket(*iface, addr, port, error_handler)) {
+ auto msg_stream = std::stringstream("failed to open multicast socket on interface ")
+ << iface->getName();
+
+ try {
+ callWithRetry<bool>(
+ std::bind(&IfaceMgr::openMulticastSocket, this,
+ // 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());
}
-
}
}
--- /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.
+ uint16_t retries = 0;
+
+ // Leave the loop on success (return statement)
+ // or stop retrying (throw statement).
+ while(true)
+ {
+ try {
+ return f();
+ } catch (const Exception& ex) {
+ auto retry_msg = (std::stringstream(msg)
+ << ", reason: "
+ << ex.what()).str();
+ // Callback produces a log message
+ const auto [should_retry, wait_time] = retry_callback(retries++, retry_msg);
+
+ 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
\ No newline at end of file