diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fe5ee16..84431742 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ option(ENABLE_LEGACY_PLUGINS "Enable Legacy Plugins" ON) option(USE_RDK_LOGGER "Enable RDK Logger for logging" OFF ) option(ENABLE_UNIT_TESTING "Enable unit tests" OFF) option(USE_TELEMETRY "Enable Telemetry T2 support" OFF) +option(USE_CONNECTIVITYCHECKMGR "Enable ConnectivityCheckMgr delegation (compiles the delegation client and reads the TR-181 RFC feature flag via rfcapi)" OFF) option(ENABLE_ETHERNET_CONNECTION_HANDLING "Enable pre-sleep Ethernet deactivation" OFF) @@ -77,6 +78,16 @@ if (USE_TELEMETRY) message("Telemetry support enabled") endif(USE_TELEMETRY) +# Optional ConnectivityCheckMgr delegation. Compiles the delegation client and pulls +# in the rfcapi library used to read the TR-181 RFC feature flag at runtime, e.g. +# Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.ConnectivityCheckMgr.Enable. +if (USE_CONNECTIVITYCHECKMGR) + find_library(RFCAPI_LIBRARY rfcapi REQUIRED) + find_path(RFCAPI_INCLUDE_DIR rfcapi.h) + add_compile_definitions(USE_CONNECTIVITYCHECKMGR=1) + message(STATUS "ConnectivityCheckMgr delegation enabled (rfcapi lib=${RFCAPI_LIBRARY}, include=${RFCAPI_INCLUDE_DIR})") +endif(USE_CONNECTIVITYCHECKMGR) + add_subdirectory(interface) add_subdirectory(definition) add_subdirectory(plugin) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 84facd1e..922fdf07 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -1437,6 +1437,36 @@ ] } }, + "onRouteChange":{ + "summary": "Triggered when the default route changes and a new gateway/DNS becomes available for an interface.", + "params": { + "type": "object", + "properties": { + "interface":{ + "$ref": "#/definitions/interface" + }, + "ipversion": { + "$ref": "#/definitions/ipversion" + }, + "ipaddress": { + "$ref": "#/definitions/ipaddress" + }, + "gateway": { + "$ref": "#/definitions/gateway" + }, + "primarydns": { + "$ref": "#/definitions/primarydns" + } + }, + "required": [ + "interface", + "ipversion", + "ipaddress", + "gateway", + "primarydns" + ] + } + }, "onActiveInterfaceChange":{ "summary": "Triggered when the primary/active interface changes", "params": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 978dd2e8..6d3a27b4 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -1787,6 +1787,7 @@ NetworkManager interface events: | :-------- | :-------- | | [onInterfaceStateChange](#event.onInterfaceStateChange) | Triggered when an interface state is changed | | [onAddressChange](#event.onAddressChange) | Triggered when an IP Address is assigned or lost | +| [onRouteChange](#event.onRouteChange) | Triggered when the default route changes and a new gateway/DNS becomes available for an interface | | [onActiveInterfaceChange](#event.onActiveInterfaceChange) | Triggered when the primary/active interface changes | | [onInternetStatusChange](#event.onInternetStatusChange) | Triggered when internet connection state changed | | [onAvailableSSIDs](#event.onAvailableSSIDs) | Triggered when scan completes or when scan cancelled | @@ -1858,6 +1859,38 @@ Triggered when an IP Address is assigned or lost. } ``` + +## *onRouteChange [event](#head.Notifications)* + +Triggered when the default route changes and a new gateway/DNS becomes available for an interface. + +### Parameters + +| Name | Type | Description | +| :-------- | :-------- | :-------- | +| params | object | | +| params.interface | string | An interface, such as `eth0` or `wlan0`, depending upon availability of the given interface | +| params.ipversion | string | Either IPv4 or IPv6 | +| params.ipaddress | string | The IP address | +| params.gateway | string | The gateway address | +| params.primarydns | string | The primary DNS address | + +### Example + +```json +{ + "jsonrpc": "2.0", + "method": "client.events.1.onRouteChange", + "params": { + "interface": "wlan0", + "ipversion": "IPv4", + "ipaddress": "192.168.1.101", + "gateway": "192.168.1.1", + "primarydns": "192.168.1.1" + } +} +``` + ## *onActiveInterfaceChange [event](#head.Notifications)* diff --git a/interface/INetworkManager.h b/interface/INetworkManager.h index ce8cb509..6d8f399c 100644 --- a/interface/INetworkManager.h +++ b/interface/INetworkManager.h @@ -282,6 +282,7 @@ namespace WPEFramework virtual void onInterfaceStateChange(const InterfaceState state /* @in */, const string interface /* @in */){}; virtual void onActiveInterfaceChange(const string prevActiveInterface /* @in */, const string currentActiveInterface /* @in */){}; virtual void onIPAddressChange(const string interface /* @in */, const string ipversion /* @in */, const string ipaddress /* @in */, const IPStatus status /* @in */){}; + virtual void onRouteChange(const string interface /* @in */, const string ipversion /* @in */, const string ipaddress /* @in */, const string gateway /* @in */, const string primarydns /* @in */){}; virtual void onInternetStatusChange(const InternetStatus prevState /* @in */, const InternetStatus currState /* @in */, const string interface /* @in */){}; // WiFi Notifications that other processes can subscribe to diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 32d065dc..6e592856 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -51,6 +51,7 @@ endif () set(PLUGIN_NETWORKMANAGER_LOGLEVEL "3" CACHE STRING "To configure default loglevel NetworkManager plugin") set(PLUGIN_NETWORKMANAGER_STARTUPORDER "25" CACHE STRING "To configure startup order of Unified NetworkManager plugin") +set(PLUGIN_NETWORKMANAGER_USE_CONNECTIVITYCHECKMGR "false" CACHE STRING "Config fallback to delegate connectivity to ConnectivityCheckMgr when the RFC flag is unset") set(PLUGIN_NETWORKMANAGER_AUTOSTART "false" CACHE STRING "Set the default AutoStart of NetworkManager Plugin") set(PLUGIN_BUILD_REFERENCE ${PROJECT_VERSION} CACHE STRING "To Set the Hash for the plugin") @@ -79,12 +80,31 @@ set_target_properties(${MODULE_NAME} PROPERTIES add_library(${MODULE_IMPL_NAME} SHARED NetworkManagerImplementation.cpp - NetworkManagerConnectivity.cpp NetworkManagerStunClient.cpp NetworkManagerLogger.cpp NetworkManagerPowerClient.cpp Module.cpp) +# The built-in ConnectivityMonitor is always compiled. The ConnectivityCheckMgr +# delegation client (NetworkManagerConnectivityClient.cpp) is compiled only when +# USE_CONNECTIVITYCHECKMGR is enabled; runtime selection between the two backends +# is done in resolveConnectivityCheckMgrEnabled(). STUN is unaffected either way. +target_sources(${MODULE_IMPL_NAME} PRIVATE + NetworkManagerConnectivity.cpp) + +# Optional ConnectivityCheckMgr delegation. The USE_CONNECTIVITYCHECKMGR option, +# rfcapi library discovery, and the USE_CONNECTIVITYCHECKMGR compile definition are +# declared in the top-level CMakeLists.txt (mirroring USE_TELEMETRY). When enabled we +# compile the delegation client and link the rfcapi library used to read the TR-181 +# feature flag. +if(USE_CONNECTIVITYCHECKMGR) + target_sources(${MODULE_IMPL_NAME} PRIVATE NetworkManagerConnectivityClient.cpp) + target_link_libraries(${MODULE_IMPL_NAME} PRIVATE ${RFCAPI_LIBRARY}) + if(RFCAPI_INCLUDE_DIR) + target_include_directories(${MODULE_IMPL_NAME} PRIVATE ${RFCAPI_INCLUDE_DIR}) + endif() +endif() + if(ENABLE_GNOME_NETWORKMANAGER) if(ENABLE_GNOME_GDBUS) message("networkmanager building with gdbus") diff --git a/plugin/NetworkManager.conf.in b/plugin/NetworkManager.conf.in index ac3127cc..6ecde006 100644 --- a/plugin/NetworkManager.conf.in +++ b/plugin/NetworkManager.conf.in @@ -24,4 +24,5 @@ configuration.add("root", process) configuration.add("connectivity", connectivity) configuration.add("stun", stun) configuration.add("loglevel", "@PLUGIN_NETWORKMANAGER_LOGLEVEL@") +configuration.add("useConnectivityCheckMgr", "@PLUGIN_NETWORKMANAGER_USE_CONNECTIVITYCHECKMGR@") diff --git a/plugin/NetworkManager.h b/plugin/NetworkManager.h index 6a101bdf..d6317f28 100644 --- a/plugin/NetworkManager.h +++ b/plugin/NetworkManager.h @@ -79,6 +79,11 @@ namespace WPEFramework _parent.onIPAddressChange(interface, ipversion, ipaddress, status); } + void onRouteChange(const string interface, const string ipversion, const string ipaddress, const string gateway, const string primarydns) override + { + _parent.onRouteChange(interface, ipversion, ipaddress, gateway, primarydns); + } + void onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) override { _parent.onInternetStatusChange(prevState, currState, interface); @@ -260,6 +265,7 @@ namespace WPEFramework void onInterfaceStateChange(const Exchange::INetworkManager::InterfaceState state, const string interface); void onActiveInterfaceChange(const string prevActiveInterface, const string currentActiveinterface); void onIPAddressChange(const string interface, const string ipversion, const string ipaddress, const Exchange::INetworkManager::IPStatus status); + void onRouteChange(const string interface, const string ipversion, const string ipaddress, const string gateway, const string primarydns); void onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface); void onAvailableSSIDs(const string jsonOfScanResults); void onWiFiStateChange(const Exchange::INetworkManager::WiFiState state); diff --git a/plugin/NetworkManagerConnectivityClient.cpp b/plugin/NetworkManagerConnectivityClient.cpp new file mode 100644 index 00000000..7725b55f --- /dev/null +++ b/plugin/NetworkManagerConnectivityClient.cpp @@ -0,0 +1,123 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2026 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#include "NetworkManagerConnectivityClient.h" +#include "NetworkManagerLogger.h" +#include + +using namespace WPEFramework; +using namespace WPEFramework::Exchange; +using namespace WPEFramework::Plugin; + +// --------------------------------------------------------------------------- +// NetworkManagerConnectivityClient +// --------------------------------------------------------------------------- + +NetworkManagerConnectivityClient::NetworkManagerConnectivityClient() +{ + NMLOG_INFO("connecting to ConnectivityCheckMgr"); + if (auto r = Open(RPC::CommunicationTimeOut, Connector(), "org.rdk.ConnectivityCheckMgr"); r == Core::ERROR_NONE) { + // Connected; Operational() will be called by the framework when the proxy is ready. + } else { + NMLOG_ERROR("failed to open link to ConnectivityCheckMgr (error %u)", r); + } +} + +NetworkManagerConnectivityClient::~NetworkManagerConnectivityClient() +{ + NMLOG_INFO("shutting down"); + { + std::lock_guard lock(mLock); + if (mConnectivity != nullptr) { + mConnectivity->Release(); + mConnectivity = nullptr; + } + } + Close(Core::infinite); +} + +bool NetworkManagerConnectivityClient::IsValid() const +{ + LOG_ENTRY_FUNCTION(); + std::lock_guard lock(mLock); + return mConnectivity != nullptr; +} + +void NetworkManagerConnectivityClient::Operational(bool upAndRunning) +{ + NMLOG_DEBUG("Operational(%s)", upAndRunning ? "true" : "false"); + std::lock_guard lock(mLock); + if (upAndRunning) { + if (mConnectivity == nullptr) { + mConnectivity = Interface(); + } + } else { + if (mConnectivity != nullptr) { + mConnectivity->Release(); + mConnectivity = nullptr; + } + } +} + +NetworkManagerConnectivityClient::NmInternetStatus +NetworkManagerConnectivityClient::mapStatus(Exchange::IConnectivityCheck::InternetStatus status) +{ + switch (status) { + case Exchange::IConnectivityCheck::NO_INTERNET: return Exchange::INetworkManager::INTERNET_NOT_AVAILABLE; + case Exchange::IConnectivityCheck::LIMITED_INTERNET: return Exchange::INetworkManager::INTERNET_LIMITED; + case Exchange::IConnectivityCheck::CAPTIVE_PORTAL: return Exchange::INetworkManager::INTERNET_CAPTIVE_PORTAL; + case Exchange::IConnectivityCheck::FULLY_CONNECTED: return Exchange::INetworkManager::INTERNET_FULLY_CONNECTED; + case Exchange::IConnectivityCheck::UNKNOWN: + default: return Exchange::INetworkManager::INTERNET_UNKNOWN; + } +} + +NetworkManagerConnectivityClient::NmInternetStatus +NetworkManagerConnectivityClient::getInternetState() +{ + LOG_ENTRY_FUNCTION(); + std::lock_guard lock(mLock); + if (mConnectivity == nullptr) { + NMLOG_WARNING("ConnectivityCheckMgr not available; returning INTERNET_UNKNOWN"); + return Exchange::INetworkManager::INTERNET_UNKNOWN; + } + + Exchange::IConnectivityCheck::StatusInfo info{}; + if (auto r = mConnectivity->GetInternetStatus(info); r != Core::ERROR_NONE) { + NMLOG_ERROR("ConnectivityCheckMgr GetInternetStatus failed (%u)", r); + return Exchange::INetworkManager::INTERNET_UNKNOWN; + } + return mapStatus(info.status); +} + +std::string NetworkManagerConnectivityClient::getCaptivePortalURI() +{ + LOG_ENTRY_FUNCTION(); + std::lock_guard lock(mLock); + std::string uri; + if (mConnectivity == nullptr) { + NMLOG_WARNING("ConnectivityCheckMgr not available; returning empty captive-portal URI"); + return uri; + } + if (auto r = mConnectivity->GetCaptivePortalURI(uri); r != Core::ERROR_NONE) { + NMLOG_ERROR("ConnectivityCheckMgr GetCaptivePortalURI failed (%u)", r); + uri.clear(); + } + return uri; +} diff --git a/plugin/NetworkManagerConnectivityClient.h b/plugin/NetworkManagerConnectivityClient.h new file mode 100644 index 00000000..59e859bb --- /dev/null +++ b/plugin/NetworkManagerConnectivityClient.h @@ -0,0 +1,84 @@ +/** +* If not stated otherwise in this file or this component's LICENSE +* file the following copyright and licenses apply: +* +* Copyright 2026 RDK Management +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +**/ + +#pragma once + +#include "Module.h" +#include "INetworkManager.h" +#include +#include +#include + +namespace WPEFramework { +namespace Plugin { + +/** + * COM-RPC client that delegates internet-connectivity queries to the + * ConnectivityCheckMgr plugin (org.rdk.ConnectivityCheckMgr). + * + * Because the delegation lives in the out-of-process implementation, both the + * JSON-RPC surface (shell -> implementation) and direct COM-RPC callers of + * INetworkManager::IsConnectedToInternet / GetCaptivePortalURI are served + * consistently. + * + * Mirrors NetworkManagerPowerClient: + * - Inherits SmartInterfaceType for automatic + * connect / reconnect and Operational() lifecycle callbacks. + * + * Lifecycle: + * Construction -> Open() connects to ConnectivityCheckMgr (async). + * Operational(true) -> acquires the proxy; IsValid() returns true. + * Operational(false) -> releases the proxy. + * Destruction -> Close(). + * + * All queries fall back to INTERNET_UNKNOWN / empty when the plugin is not + * available, so callers never crash on a boot-order race or NM/CCM restart. + */ +class NetworkManagerConnectivityClient : protected RPC::SmartInterfaceType { +public: + using NmInternetStatus = Exchange::INetworkManager::InternetStatus; + + NetworkManagerConnectivityClient(); + ~NetworkManagerConnectivityClient() override; + + NetworkManagerConnectivityClient(const NetworkManagerConnectivityClient&) = delete; + NetworkManagerConnectivityClient& operator=(const NetworkManagerConnectivityClient&) = delete; + + /** Returns true when the ConnectivityCheckMgr COMRPC proxy is available. */ + bool IsValid() const; + + /** Delegated internet status, mapped to NetworkManager's InternetStatus. */ + NmInternetStatus getInternetState(); + + /** Delegated captive-portal URI (empty when unavailable / not captive). */ + std::string getCaptivePortalURI(); + +private: + // SmartInterfaceType lifecycle callback. + void Operational(bool upAndRunning) override; + + // 1:1 mapping ConnectivityCheckMgr InternetStatus -> NetworkManager InternetStatus. + static NmInternetStatus mapStatus(Exchange::IConnectivityCheck::InternetStatus status); + + mutable std::mutex mLock; + Exchange::IConnectivityCheck* mConnectivity{nullptr}; +}; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 1ba0509a..373d5831 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -24,6 +24,11 @@ #include #include "NetworkManagerImplementation.h" +#ifdef USE_CONNECTIVITYCHECKMGR +#include +#include "rfcapi.h" +#endif + #if USE_TELEMETRY #include "NetworkManagerJsonEnum.h" #include @@ -60,6 +65,11 @@ namespace WPEFramework m_ethDisconnectedForSleep.store(false); m_wlanDisconnectedForSleep.store(false); + /* Default connectivity backend is the built-in monitor. Configure() + * may switch to ConnectivityCheckMgr delegation based on the RFC flag + * (see resolveConnectivityCheckMgrEnabled). */ + connectivityMonitor.reset(new ConnectivityMonitor()); + /* Set NetworkManager Out-Process name to be NWMgrPlugin */ Core::ProcessInfo().Name("NWMgrPlugin"); @@ -83,7 +93,10 @@ namespace WPEFramework { NMLOG_INFO("NetworkManager Out-Of-Process Shutdown/Cleanup"); m_powerClient.reset(); - connectivityMonitor.stopConnectivityMonitor(); + if(!m_useConnectivityCheckMgr && connectivityMonitor) + { + connectivityMonitor->stopConnectivityMonitor(); + } _instance = nullptr; platform_deinit(); if(m_registrationThread.joinable()) @@ -171,6 +184,28 @@ namespace WPEFramework NetworkManagerLogger::SetLevel(static_cast (config.loglevel.Value())); NMLOG_DEBUG("loglevel %d", config.loglevel.Value()); + /* Resolve the connectivity backend at runtime (replaces the old + * USE_CONNECTIVITY_CHECK_MGR compile-time macro). */ + m_useConnectivityCheckMgr = resolveConnectivityCheckMgrEnabled(config); +#ifdef USE_CONNECTIVITYCHECKMGR + if(m_useConnectivityCheckMgr) + { + /* Stop/destroy the built-in monitor (constructed by default) so it + * does not run alongside the delegation client. */ + if(connectivityMonitor) + connectivityMonitor.reset(); + if(!connectivityClient) + connectivityClient.reset(new NetworkManagerConnectivityClient()); + NMLOG_INFO("Connectivity delegated to ConnectivityCheckMgr (runtime selection)"); + } + else +#endif + { + if(!connectivityMonitor) + connectivityMonitor.reset(new ConnectivityMonitor()); + NMLOG_INFO("Using built-in ConnectivityMonitor (runtime selection)"); + } + /* STUN configuration copy */ m_stunEndpoint = config.stun.stunEndpoint.Value(); m_stunPort = config.stun.port.Value(); @@ -204,18 +239,18 @@ namespace WPEFramework connectEndpts.push_back(config.connectivityConf.endpoint_5.Value().c_str()); } - /* check whether the endpoint is already loaded from Cache; if Yes, do not use the one from configuration */ - if (connectivityMonitor.getConnectivityMonitorEndpoints().size() < 1) - { - NMLOG_INFO("Use the connectivity endpoint from config"); - connectivityMonitor.setConnectivityMonitorEndpoints(connectEndpts); - } - else if (connectEndpts.size() < 1) + if (connectEndpts.size() < 1) { std::vector backup; NMLOG_INFO("Connectivity endpoints are empty in config; use the default"); backup.push_back("http://clients3.google.com/generate_204"); - connectivityMonitor.setConnectivityMonitorEndpoints(backup); + if(!m_useConnectivityCheckMgr && connectivityMonitor) + connectivityMonitor->setConnectivityMonitorEndpoints(backup); + } + else if (!m_useConnectivityCheckMgr && connectivityMonitor && connectivityMonitor->getConnectivityMonitorEndpoints().size() < 1) + { + NMLOG_INFO("Use the connectivity endpoint from config"); + connectivityMonitor->setConnectivityMonitorEndpoints(connectEndpts); } /* As all the configuration is set, lets instantiate platform */ @@ -226,6 +261,37 @@ namespace WPEFramework return(Core::ERROR_NONE); } + /* @brief Resolve whether internet-connectivity queries are delegated to the + * ConnectivityCheckMgr plugin. Precedence: RFC feature flag (when the + * RFC API is compiled in) -> config-line fallback -> default false. */ + bool NetworkManagerImplementation::resolveConnectivityCheckMgrEnabled(const Configuration& config) const + { + LOG_ENTRY_FUNCTION(); +#ifdef USE_CONNECTIVITYCHECKMGR + RFC_ParamData_t rfcParam = {0}; + WDMP_STATUS wdmpStatus = getRFCParameter(const_cast("NetworkManager"), + "Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.ConnectivityCheckMgr.Enable", + &rfcParam); + if (wdmpStatus == WDMP_SUCCESS || wdmpStatus == WDMP_ERR_DEFAULT_VALUE) + { + bool enabled = (0 == strcasecmp(rfcParam.value, "true")); + NMLOG_INFO("RFC ConnectivityCheckMgr.Enable = '%s' -> %s", rfcParam.value, + enabled ? "delegate" : "internal monitor"); + return enabled; + } + NMLOG_WARNING("getRFCParameter(ConnectivityCheckMgr.Enable) failed (status=%d); using config fallback", + wdmpStatus); + bool enabled = config.useConnectivityCheckMgr.Value(); + NMLOG_INFO("ConnectivityCheckMgr delegation (config fallback) = %s", + enabled ? "enabled" : "disabled"); + return enabled; +#else + (void)config; + NMLOG_INFO("ConnectivityCheckMgr delegation not compiled in; using built-in monitor"); + return false; +#endif + } + /* @brief Get STUN Endpoint to be used for identifying Public IP */ uint32_t NetworkManagerImplementation::GetStunEndpoint (string &endpoint /* @out */, uint32_t& port /* @out */, uint32_t& bindTimeout /* @out */, uint32_t& cacheTimeout /* @out */) const { @@ -273,7 +339,9 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::GetConnectivityTestEndpoints(IStringIterator*& endpoints/* @out */) const { LOG_ENTRY_FUNCTION(); - std::vector tmpEndpoints = connectivityMonitor.getConnectivityMonitorEndpoints(); + std::vector tmpEndpoints; + if(!m_useConnectivityCheckMgr && connectivityMonitor) + tmpEndpoints = connectivityMonitor->getConnectivityMonitorEndpoints(); endpoints = (Core::Service::Create(tmpEndpoints)); if(endpoints == nullptr) { return Core::ERROR_GENERAL; @@ -299,7 +367,8 @@ namespace WPEFramework tmpEndpoints.push_back(endpoint); } } - connectivityMonitor.setConnectivityMonitorEndpoints(tmpEndpoints); + if(!m_useConnectivityCheckMgr && connectivityMonitor) + connectivityMonitor->setConnectivityMonitorEndpoints(tmpEndpoints); } return Core::ERROR_NONE; } @@ -332,7 +401,19 @@ namespace WPEFramework return Core::ERROR_BAD_REQUEST; } - result = connectivityMonitor.getInternetState(interface, curlIPversion, ipVersionNotSpecified); +#ifdef USE_CONNECTIVITYCHECKMGR + if(m_useConnectivityCheckMgr) + { + (void)ipVersionNotSpecified; + result = connectivityClient ? connectivityClient->getInternetState() + : Exchange::INetworkManager::INTERNET_UNKNOWN; + } + else +#endif + { + result = connectivityMonitor ? connectivityMonitor->getInternetState(interface, curlIPversion, ipVersionNotSpecified) + : Exchange::INetworkManager::INTERNET_UNKNOWN; + } if (Exchange::INetworkManager::IP_ADDRESS_V6 == curlIPversion) ipversion = "IPv6"; else @@ -348,7 +429,12 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::GetCaptivePortalURI(string &uri /* @out */) const { LOG_ENTRY_FUNCTION(); - uri = connectivityMonitor.getCaptivePortalURI(); +#ifdef USE_CONNECTIVITYCHECKMGR + if(m_useConnectivityCheckMgr) + uri = connectivityClient ? connectivityClient->getCaptivePortalURI() : std::string(); + else +#endif + uri = connectivityMonitor ? connectivityMonitor->getCaptivePortalURI() : std::string(); return Core::ERROR_NONE; } @@ -857,7 +943,8 @@ namespace WPEFramework m_ethConnected.store(false); setDefaultInterface("wlan0"); // If WiFi is connected, make it the default interface // As default interface is changed to wlan0, switch connectivity monitor to initial check - connectivityMonitor.switchToInitialCheck(); + if(!m_useConnectivityCheckMgr && connectivityMonitor) + connectivityMonitor->switchToInitialCheck(); } else if(interface == "wlan0") { @@ -874,7 +961,8 @@ namespace WPEFramework { // When WiFi is disconnected while Ethernet is connected, we don't need to trigger connectivity monitor. // For WiFi-only state and WiFi disconnected, we should trigger connectivity monitor. - connectivityMonitor.switchToInitialCheck(); + if(!m_useConnectivityCheckMgr && connectivityMonitor) + connectivityMonitor->switchToInitialCheck(); } } } @@ -965,7 +1053,8 @@ namespace WPEFramework if(isDefaultIface) { // As default interface is connected, switch connectivity monitor to initial check any way - connectivityMonitor.switchToInitialCheck(); + if(!m_useConnectivityCheckMgr && connectivityMonitor) + connectivityMonitor->switchToInitialCheck(); } else NMLOG_DEBUG("No need to trigger connectivity monitor interface is %s", interface.c_str()); @@ -979,6 +1068,33 @@ namespace WPEFramework } } + void NetworkManagerImplementation::ReportRouteChange(const string& interface, const string& ipversion) + { + string iface = interface; + Exchange::INetworkManager::IPAddress settings{}; + if (GetIPSettings(iface, ipversion, settings) != Core::ERROR_NONE) { + return; + } + ReportRouteChange(interface, ipversion, settings); + } + + void NetworkManagerImplementation::ReportRouteChange(const string& interface, const string& ipversion, const Exchange::INetworkManager::IPAddress& settings) + { + if (settings.ipaddress.empty() || settings.gateway.empty() || settings.primarydns.empty()) { + return; + } + + _notificationLock.Lock(); + NMLOG_INFO("Posting onRouteChange %s %s ip=%s gw=%s dns=%s", + interface.c_str(), ipversion.c_str(), settings.ipaddress.c_str(), + settings.gateway.c_str(), settings.primarydns.c_str()); + for (const auto callback : _notificationCallbacks) { + callback->onRouteChange(interface, settings.ipversion, settings.ipaddress, + settings.gateway, settings.primarydns); + } + _notificationLock.Unlock(); + } + void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) { LOG_ENTRY_FUNCTION(); diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index bafe337f..f618b8d2 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -38,6 +38,9 @@ using namespace std; #include "INetworkManager.h" #include "NetworkManagerLogger.h" #include "NetworkManagerConnectivity.h" +#ifdef USE_CONNECTIVITYCHECKMGR +#include "NetworkManagerConnectivityClient.h" +#endif #include "NetworkManagerStunClient.h" #include "NetworkManagerPowerClient.h" @@ -202,6 +205,7 @@ namespace WPEFramework Add(_T("connectivity"), &connectivityConf); Add(_T("stun"), &stun); Add(_T("loglevel"), &loglevel); + Add(_T("useConnectivityCheckMgr"), &useConnectivityCheckMgr); } ~Configuration() override = default; @@ -209,6 +213,7 @@ namespace WPEFramework ConnectivityConf connectivityConf; Stun stun; Core::JSON::DecUInt32 loglevel; + Core::JSON::Boolean useConnectivityCheckMgr; }; enum NMPublishEvents { @@ -370,6 +375,8 @@ namespace WPEFramework void ReportInterfaceStateChange(const Exchange::INetworkManager::InterfaceState state, const string interface); void ReportActiveInterfaceChange(const string prevActiveInterface, const string currentActiveinterface); void ReportIPAddressChange(const string interface, const string ipversion, const string ipaddress, const Exchange::INetworkManager::IPStatus status); + void ReportRouteChange(const string& interface, const string& ipversion); + void ReportRouteChange(const string& interface, const string& ipversion, const Exchange::INetworkManager::IPAddress& settings); void ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface); void ReportAvailableSSIDs(const JsonArray &arrayofWiFiScanResults); void ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state); @@ -387,6 +394,11 @@ namespace WPEFramework void platform_init(void); void platform_deinit(void); void platform_logging(const NetworkManagerLogger::LogLevel& level); + /* Resolve whether connectivity is delegated to ConnectivityCheckMgr: + * RFC flag Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.ConnectivityCheckMgr.Enable + * (when USE_CONNECTIVITYCHECKMGR is built in) takes precedence, then the config-line + * fallback key, then default false (built-in monitor). */ + bool resolveConnectivityCheckMgrEnabled(const Configuration& config) const; void getInitialConnectionState(void); void executeExternally(NetworkEvents event, const string commandToExecute, string& response); void threadEventRegistration(bool iarmInit, bool iarmConnect); @@ -452,7 +464,16 @@ namespace WPEFramework std::atomic m_wlanDisconnectedForSleep; std::string m_lastConnectedSSID; GMainContext *m_nmContext{nullptr}; /* isolated context for per-call NMClient creation */ - mutable ConnectivityMonitor connectivityMonitor; + /* Runtime connectivity backend selection (replaces the old + * USE_CONNECTIVITY_CHECK_MGR compile-time macro). When + * m_useConnectivityCheckMgr is true, connectivity queries are + * delegated to ConnectivityCheckMgr via connectivityClient; + * otherwise the built-in connectivityMonitor is used. */ + bool m_useConnectivityCheckMgr {false}; + mutable std::unique_ptr connectivityMonitor; +#ifdef USE_CONNECTIVITYCHECKMGR + mutable std::unique_ptr connectivityClient; +#endif string getDefaultInterface() const { diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index d49e6c5d..fb92f425 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -1072,6 +1072,19 @@ namespace WPEFramework Notify(_T("onIPAddressChange"), parameters); } + void NetworkManager::onRouteChange(const string interface, const string ipversion, const string ipaddress, const string gateway, const string primarydns) + { + JsonObject parameters; + parameters["interface"] = interface; + parameters["ipversion"] = ipversion; + parameters["ipaddress"] = ipaddress; + parameters["gateway"] = gateway; + parameters["primarydns"] = primarydns; + + LOG_INPARAM(); + Notify(_T("onRouteChange"), parameters); + } + void NetworkManager::onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) { JsonObject parameters; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 7a87cc20..3bd4830a 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -197,6 +197,23 @@ namespace WPEFramework _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); } } + + /* Coalesced "route ready" event: emit once address + gateway + primary DNS + are all populated for this family. The notify::addresses / + notify::gateway / notify::nameservers subscriptions on NMIPConfig all + funnel here, so a single emission per snapshot covers all three. + Each family emits independently — dual-stack consumers will see one + event per family. */ + if (newCache.valid + && !newCache.globalAddresses.empty() + && !newCache.gateway.empty() + && !newCache.primarydns.empty()) { + /* Values are already in hand from the snapshot we just built, so emit + them directly instead of having ReportRouteChange re-query the cache. */ + Exchange::INetworkManager::IPAddress settings = newCache.toIPAddress(); + settings.ipversion = family; + _instance->ReportRouteChange(ifname, family, settings); + } } static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) @@ -842,6 +859,15 @@ namespace WPEFramework _instance->ReportActiveInterfaceChange(oldIface, newIface); NMLOG_INFO("old interface - %s new interface - %s", oldIface.c_str(), newIface.c_str()); oldIface = newIface; + + /* Default-route owner changed (e.g. eth0↔wlan0 failover). The new primary + already has its IP/gateway/DNS in the cache from prior refreshIpFamilyCache, + so emit a coalesced route-ready event for both families — ReportRouteChange + is a no-op for whichever family isn't fully populated. */ + if (_instance != nullptr && !newIface.empty() && newIface != "Unknown") { + _instance->ReportRouteChange(newIface, "IPv4"); + _instance->ReportRouteChange(newIface, "IPv6"); + } } } diff --git a/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp b/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp index ebae2073..2248ab74 100644 --- a/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp +++ b/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp @@ -158,7 +158,7 @@ TEST_F(NetworkManagerImplTest, GetConnectivityTestEndpoints) { // Set up mock endpoints in the ConnectivityMonitor std::vector mockEndpoints = {"http://clients3.google.com/generate_204", "http://example.com"}; - NetworkManagerImplementation->connectivityMonitor.setConnectivityMonitorEndpoints(mockEndpoints); + NetworkManagerImplementation->connectivityMonitor->setConnectivityMonitorEndpoints(mockEndpoints); // Call GetConnectivityTestEndpoints RPC::IIteratorType* endpoints = nullptr; @@ -193,7 +193,7 @@ TEST_F(NetworkManagerImplTest, SetConnectivityTestEndpoints_EmptyEndpoints) { EXPECT_EQ(result, Core::ERROR_NONE); // Verify the endpoints were not set - std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor.getConnectivityMonitorEndpoints(); + std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor->getConnectivityMonitorEndpoints(); printf("Retrieved Endpoints Size: %zu %s\n", retrievedEndpoints.size(), retrievedEndpoints[0].c_str()); EXPECT_TRUE(retrievedEndpoints.empty() != true && retrievedEndpoints[0] == "http://clients3.google.com/generate_204"); // default endpoint should remain @@ -213,7 +213,7 @@ TEST_F(NetworkManagerImplTest, SetConnectivityTestEndpoints_ValidEndpoints) { EXPECT_EQ(result, Core::ERROR_NONE); // Verify the endpoints were set correctly - std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor.getConnectivityMonitorEndpoints(); + std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor->getConnectivityMonitorEndpoints(); EXPECT_EQ(retrievedEndpoints, validEndpoints); // Clean up @@ -233,7 +233,7 @@ TEST_F(NetworkManagerImplTest, SetConnectivityTestEndpoints_InvalidEndpoints) { EXPECT_EQ(result, Core::ERROR_NONE); // Verify the endpoints were not set - std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor.getConnectivityMonitorEndpoints(); + std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor->getConnectivityMonitorEndpoints(); EXPECT_TRUE(retrievedEndpoints.size() == 1 && retrievedEndpoints[0] == "1234567890"); // Clean up @@ -254,7 +254,7 @@ TEST_F(NetworkManagerImplTest, SetConnectivityTestEndpoints_TooManyEndpoints) { // Verify the result EXPECT_EQ(result, Core::ERROR_NONE); - std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor.getConnectivityMonitorEndpoints(); + std::vector retrievedEndpoints = NetworkManagerImplementation->connectivityMonitor->getConnectivityMonitorEndpoints(); EXPECT_EQ((int)retrievedEndpoints.size(), 12); // Clean up