From 50a46dbf20b5a7eada0780723e552eea61dc01a4 Mon Sep 17 00:00:00 2001 From: Marek Blaha Date: Wed, 1 Nov 2023 13:21:12 +0100 Subject: [PATCH] automatic: Implement waiting for network Automatic plugin will wait up to `timeout` seconds for network to be on-line. Timeout is taken from automatic configuration file. --- dnf5-plugins/automatic_plugin/automatic.cpp | 136 +++++++++++++++++++- dnf5-plugins/automatic_plugin/automatic.hpp | 2 + 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/dnf5-plugins/automatic_plugin/automatic.cpp b/dnf5-plugins/automatic_plugin/automatic.cpp index 4c314b49b1..700511c5d0 100644 --- a/dnf5-plugins/automatic_plugin/automatic.cpp +++ b/dnf5-plugins/automatic_plugin/automatic.cpp @@ -23,19 +23,23 @@ along with libdnf. If not, see . #include "emitters.hpp" #include "transaction_callbacks_simple.hpp" +#include #include #include #include #include #include #include +#include #include +#include #include #include #include #include #include +#include namespace { @@ -59,11 +63,139 @@ static bool reboot_needed(const libdnf5::base::Transaction & transaction) { return false; } +static bool server_available(std::string_view host, std::string_view service) { + // Resolve host name/service to IP address/port + struct addrinfo * server_info; + if (getaddrinfo(host.data(), service.data(), nullptr, &server_info) != 0) { + return false; + } + + // Create a socket + int sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + freeaddrinfo(server_info); + return false; + } + + // Attempt to connect to the server + bool retval = true; + struct timeval timeout; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + if (connect(sockfd, server_info->ai_addr, server_info->ai_addrlen) < 0) { + retval = false; + } + + close(sockfd); + freeaddrinfo(server_info); + return retval; +} + } // namespace namespace dnf5 { +void AutomaticCommand::wait_for_network() { + auto timeout = config_automatic.config_commands.network_online_timeout.get_value(); + if (timeout <= 0) { + return; + } + + auto & context = get_context(); + auto & base = context.base; + auto & logger = *base.get_logger(); + logger.debug("Waiting for internet connection..."); + + const auto time_0 = std::chrono::system_clock::now(); + const auto time_end = time_0 + std::chrono::seconds(timeout); + + // collect repository addresses to check network availability on + libdnf5::repo::RepoQuery repos(base); + repos.filter_enabled(true); + std::vector urls{}; + for (const auto & repo : repos) { + std::string proxy{}; + try { + proxy = repo->get_config().get_proxy_option().get_value(); + if (!proxy.empty()) { + urls.emplace_back(proxy); + continue; + } + } catch (const libdnf5::OptionValueNotSetError & e) { + } + try { + auto mirrorlist = repo->get_config().get_mirrorlist_option().get_value(); + if (!mirrorlist.empty()) { + urls.emplace_back(std::move(mirrorlist)); + } + } catch (const libdnf5::OptionValueNotSetError & e) { + } + try { + auto metalink = repo->get_config().get_metalink_option().get_value(); + if (!metalink.empty()) { + urls.emplace_back(std::move(metalink)); + } + } catch (const libdnf5::OptionValueNotSetError & e) { + } + try { + for (auto u : repo->get_config().get_baseurl_option().get_value()) { + if (!u.empty()) { + urls.emplace_back(std::move(u)); + } + } + } catch (const libdnf5::OptionValueNotSetError & e) { + } + } + + // parse url to [(host, service(port number or scheme))] using libcurl + std::vector> addresses; + CURLUcode rc; + CURLU * url = curl_url(); + unsigned int set_flags = CURLU_NON_SUPPORT_SCHEME; + unsigned int get_flags = 0; + for (auto & u : urls) { + rc = curl_url_set(url, CURLUPART_URL, u.c_str(), set_flags); + if (!rc) { + std::string host{}; + std::string port{}; + char * val = nullptr; + rc = curl_url_get(url, CURLUPART_HOST, &val, get_flags); + if (rc != CURLUE_OK) { + continue; + } + host = val; + curl_free(val); + // service is port or (if not given) scheme + rc = curl_url_get(url, CURLUPART_PORT, &val, get_flags); + if (rc == CURLUE_OK) { + port = val; + } else { + rc = curl_url_get(url, CURLUPART_SCHEME, &val, get_flags); + if (rc != CURLUE_OK) { + continue; + } + port = val; + } + curl_free(val); + addresses.emplace_back(std::move(host), std::move(port)); + } + } + curl_url_cleanup(url); + + while (std::chrono::system_clock::now() < time_end) { + for (auto const & [host, service] : addresses) { + if (server_available(host, service)) { + return; + } + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + logger.warning("No network connection detected."); +} + void AutomaticCommand::set_parent_command() { auto * arg_parser_parent_cmd = get_session().get_argument_parser().get_root_command(); auto * arg_parser_this_cmd = get_argument_parser_command(); @@ -158,8 +290,6 @@ void AutomaticCommand::pre_configure() { auto & context = get_context(); auto & base = context.base; - // TODO wait for network - auto random_sleep = config_automatic.config_commands.random_sleep.get_value(); if (timer->get_value() && random_sleep > 0) { random_wait(random_sleep); @@ -199,6 +329,8 @@ void AutomaticCommand::configure() { context.update_repo_metadata_from_advisory_options( {}, config_automatic.config_commands.upgrade_type.get_value() == "security", false, false, false, {}, {}, {}); context.set_load_available_repos(Context::LoadAvailableRepos::ENABLED); + + wait_for_network(); } void AutomaticCommand::run() { diff --git a/dnf5-plugins/automatic_plugin/automatic.hpp b/dnf5-plugins/automatic_plugin/automatic.hpp index a0b0bfe35d..ad487b9398 100644 --- a/dnf5-plugins/automatic_plugin/automatic.hpp +++ b/dnf5-plugins/automatic_plugin/automatic.hpp @@ -47,6 +47,8 @@ class AutomaticCommand : public Command { void run() override; private: + void wait_for_network(); + std::unique_ptr timer{nullptr}; ConfigAutomatic config_automatic; bool download_callbacks_set{false};