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