From f3c063649b4f98b186873f7ddf6c2c7577d7a9d4 Mon Sep 17 00:00:00 2001 From: Matthew Russell Date: Wed, 22 Jan 2025 09:53:37 -0500 Subject: [PATCH] Modify network detection on QNX to delay SD Implemented a "simple_connector" to mimic the netlink_connector available on Linux. This connector, along with vsomeip3.5.4 introduction of the RS_DELAYED_RESUME routing state, allows us to delay SD until the network is available. The simple_connector works by using QNX's waitfor to wait for a file whose creation indicates network availability. This file is created externally. The simple_connector does not continue to monitor the network. It simply notifies the routing_manager the first time the network is available. That said, the interface does allow for easy extension in the future This behaviour is controlled with the env vars VSOMEIP_USE_ASYNCHRONOUS_SD and VSOMEIP_WAIT_FOR_INTERFACE. An alternative implementation would be to use PPS, but that isn't available by default on our deployment of QNX 7. This implementation has a reduced foot print from earlier implementations and also does not modify any interfaces. Notes: - There is no timeout on the waitfor. The original implementation had a configurable timeout, however because timing out left us in an error state anyways, this timeout was removed (raised to numeric_limits::max() = ~45 days, give or take.) --- .../configuration/include/internal.hpp.in | 21 +++ .../endpoints/include/simple_connector.hpp | 59 +++++++++ .../endpoints/src/netlink_connector.cpp | 2 +- .../endpoints/src/simple_connector.cpp | 122 ++++++++++++++++++ .../routing/include/routing_manager_impl.hpp | 6 + .../routing/src/routing_manager_impl.cpp | 13 +- 6 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 implementation/endpoints/include/simple_connector.hpp create mode 100644 implementation/endpoints/src/simple_connector.cpp diff --git a/implementation/configuration/include/internal.hpp.in b/implementation/configuration/include/internal.hpp.in index a2b5cb93c..1fa907bf0 100644 --- a/implementation/configuration/include/internal.hpp.in +++ b/implementation/configuration/include/internal.hpp.in @@ -33,6 +33,27 @@ #define VSOMEIP_ROUTING_HOST_PORT_DEFAULT 31490 +// +// Defines related to using the "simple_connector" for network monitoring as +// opposed to the netlink connector available on Linux systems. This is not +// available upstream. The original implementation allowed SD to start +// asynchronously and hence the variables below were named to support that. The +// implementation has moved on to the simple_connector, but the environment +// variable names remain. + +// Env var that if exists will allow the simple_connector to launch a thread to +// wait for the network to be available and notify the routing manager. If not +// set, the simple_connector will assume the network is available and notify the +// routing manager as such. +#define VSOMEIP_ENV_USE_ASYNCHRONOUS_SD "VSOMEIP_USE_ASYNCHRONOUS_SD" + +// The current waiting mechanism is to block until a file (specified by this +// define) +#define VSOMEIP_ENV_NETWORK_INT_READY_FILE "@VSOMEIP_NETWORK_INT_READY_FILE@" + +// /end of async change +// + #ifdef _WIN32 #define VSOMEIP_CFG_LIBRARY "vsomeip3-cfg.dll" #else diff --git a/implementation/endpoints/include/simple_connector.hpp b/implementation/endpoints/include/simple_connector.hpp new file mode 100644 index 000000000..839e3f9f5 --- /dev/null +++ b/implementation/endpoints/include/simple_connector.hpp @@ -0,0 +1,59 @@ +#ifndef VSOMEIP_V3_SIMPLE_CONNECTOR_HPP_ +#define VSOMEIP_V3_SIMPLE_CONNECTOR_HPP_ + +#include +#include +#include +#include +#include + +#include + +#ifdef ANDROID +#include "../../configuration/include/internal_android.hpp" +#else +#include "../../configuration/include/internal.hpp" +#endif + +namespace vsomeip_v3 { + +using simple_net_ready_handler_t = + std::function; + +/** \brief simple network connector, designed for use on QNX. + * + * This class is a simple network connector that is designed to be used on QNX + * to detect when the network interface is available. This is done by waiting + * for (`waitfor`) the presence of VSOMEIP_NETWORK_INT_READY_FILE which is + * created externally of vsomeip. An alternative implementation would be to use + * a service like PPS. + */ +class simple_connector : public std::enable_shared_from_this { +public: + simple_connector(); + ~simple_connector(); + + void + register_net_if_changes_handler(const simple_net_ready_handler_t &_handler); + void unregister_net_if_changes_handler(); + + void start(); + void stop(); + +private: + bool wait_for_interface(); + + static constexpr std::string_view if_name_ = "emac0"; + std::atomic network_ready_; + + std::thread wait_for_network_thread_; + + simple_net_ready_handler_t handler_{nullptr}; +}; + +} // namespace vsomeip_v3 + +#endif // VSOMEIP_V3_SIMPLE_CONNECTOR_HPP_ diff --git a/implementation/endpoints/src/netlink_connector.cpp b/implementation/endpoints/src/netlink_connector.cpp index f81e0abaf..1ad1bb0e1 100644 --- a/implementation/endpoints/src/netlink_connector.cpp +++ b/implementation/endpoints/src/netlink_connector.cpp @@ -9,7 +9,7 @@ #include #include -#include +#include #include diff --git a/implementation/endpoints/src/simple_connector.cpp b/implementation/endpoints/src/simple_connector.cpp new file mode 100644 index 000000000..b1b067fc4 --- /dev/null +++ b/implementation/endpoints/src/simple_connector.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +#ifdef __QNX__ +#include +#endif + +#include + +#include "../include/simple_connector.hpp" + +#ifndef VSOMEIP_ENV_USE_ASYNCHRONOUS_SD +#define VSOMEIP_ENV_USE_ASYNCHRONOUS_SD "VSOMEIP_USE_ASYNCHRONOUS_SD" +#endif + +namespace vsomeip_v3 { + +simple_connector::simple_connector() + : network_ready_( +#ifdef __QNX__ + false +#else + true +#endif + ) { +} + +simple_connector::~simple_connector() { + if (wait_for_network_thread_.joinable()) { + wait_for_network_thread_.join(); + } +} + +void simple_connector::register_net_if_changes_handler( + const simple_net_ready_handler_t &_handler) { + handler_ = _handler; +} + +void simple_connector::unregister_net_if_changes_handler() { + handler_ = nullptr; +} + +void simple_connector::stop() { return; } + +bool simple_connector::wait_for_interface() { +#ifdef __QNX__ + namespace cr = std::chrono; + static std::string_view constexpr path = VSOMEIP_ENV_NETWORK_INT_READY_FILE; + if (path.empty()) { + VSOMEIP_ERROR << "No network interface signal path defined, service " + "discovery will effectively be disabled."; + return false; + } + // Indefinite delay. If the condition we're waiting for doesn't occur then + // we are in an error state and thus should not continue. + static auto constexpr delay_ms = std::numeric_limits::max(); + static int constexpr poll_ms = 50; + + auto const start = cr::steady_clock::now(); + VSOMEIP_DEBUG + << "Waiting (blocking) indefinitely on network interface (signal=" << path + << ")"; + auto const r = waitfor(path.data(), delay_ms, poll_ms); + auto const end = cr::steady_clock::now(); + auto diff = end - start; + if (0 == r) { + VSOMEIP_DEBUG << "Waited (blocked) for network interface (signal=" << path + << ") for " + << cr::duration_cast(diff).count() + << " ms."; + return true; + } else { + VSOMEIP_ERROR << "Timedout waiting for network interface (signal=" << path + << ") after " + << cr::duration_cast(diff).count() + << " ms: errno=" << errno << ", msg=" << strerror(errno); + return false; + } +#else + // Omitting a non-QNX implementation for now. The QNX implementation might be + // modified to use pps in the future, similar to how the Linux implementation + // uses Netlink.. A Linux implementation could also mirror what is above + // using ionotify, but it would only be useful for testing - in practice it + // would simple be a worse implementation than using netlink + return true; +#endif +} + +void simple_connector::start() { + auto *const use_async_sd = getenv(VSOMEIP_ENV_USE_ASYNCHRONOUS_SD); + if (!use_async_sd) { + // If VSOMEIP_ENV_USE_ASYNCHRONOUS_SD is not set, so we will assume the + // network is "ready" and proceed normally. + network_ready_ = true; + handler_(true, if_name_.data(), true); + handler_(false, if_name_.data(), true); + + return; + } + + wait_for_network_thread_ = std::thread([this]() { +#if defined(__linux__) || defined(ANDROID) || defined(__QNX__) + { + auto err = pthread_setname_np(wait_for_network_thread_.native_handle(), + "wait_network"); + if (err) { + VSOMEIP_ERROR << "Could not rename SD thread: " << errno << ":" + << std::strerror(errno); + } + } +#endif + network_ready_ = wait_for_interface(); + + handler_(true, if_name_.data(), network_ready_); + handler_(false, if_name_.data(), network_ready_); + }); + + return; +} + +} // namespace vsomeip_v3 diff --git a/implementation/routing/include/routing_manager_impl.hpp b/implementation/routing/include/routing_manager_impl.hpp index 598b2e21f..05435bc23 100644 --- a/implementation/routing/include/routing_manager_impl.hpp +++ b/implementation/routing/include/routing_manager_impl.hpp @@ -23,7 +23,11 @@ #include "routing_manager_stub_host.hpp" #include "types.hpp" +#if defined(__linux__) || defined(ANDROID) #include "../../endpoints/include/netlink_connector.hpp" +#elif defined(__QNX__) +#include "../../endpoints/include/simple_connector.hpp" +#endif #include "../../service_discovery/include/service_discovery_host.hpp" #include "../../endpoints/include/endpoint_manager_impl.hpp" @@ -509,6 +513,8 @@ class routing_manager_impl: public routing_manager_base, std::vector> pending_sd_offers_; #if defined(__linux__) || defined(ANDROID) std::shared_ptr netlink_connector_; +#elif defined(__QNX__) + std::shared_ptr simple_connector_; #endif std::mutex pending_offers_mutex_; diff --git a/implementation/routing/src/routing_manager_impl.cpp b/implementation/routing/src/routing_manager_impl.cpp index 0a6c95f70..e54b68186 100644 --- a/implementation/routing/src/routing_manager_impl.cpp +++ b/implementation/routing/src/routing_manager_impl.cpp @@ -235,7 +235,13 @@ void routing_manager_impl::start() { this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); netlink_connector_->start(); #else - start_ip_routing(); + simple_connector_ = std::make_shared(); + simple_connector_->register_net_if_changes_handler( + [this](bool is_interface, std::string interface_name, bool available) { + on_net_interface_or_route_state_changed(is_interface, interface_name, + available); + }); + simple_connector_->start(); #endif if (stub_) @@ -4028,7 +4034,7 @@ void routing_manager_impl::on_net_interface_or_route_state_changed( } void routing_manager_impl::start_ip_routing() { -#if defined(_WIN32) || defined(__QNX__) +#if defined(_WIN32) if_state_running_ = true; #endif @@ -4052,12 +4058,13 @@ void routing_manager_impl::start_ip_routing() { pending_sd_offers_.clear(); VSOMEIP_INFO << "rmi::" << __func__ << ": clear pending_sd_offers_"; } - + routing_running_ = true; VSOMEIP_INFO << VSOMEIP_ROUTING_READY_MESSAGE; } inline bool routing_manager_impl::is_external_routing_ready() const { + return if_state_running_ && (!configuration_->is_sd_enabled() || (configuration_->is_sd_enabled() && sd_route_set_));