From c746091bed2e122496bde0d7057754866648f5be Mon Sep 17 00:00:00 2001 From: jincysam87 <167995204+jincysam87@users.noreply.github.com> Date: Thu, 28 May 2026 10:37:47 -0400 Subject: [PATCH 01/32] RDK-61444 : Network Manager Plugin to support Scan Specific SSID (#306) Reason for change: Support to scan multiple SSIDs Test Procedure: Test wifi scan API with multiple SSIDs Risks: Low Signed-off-by: [jincysaramma_sam@comcast.com](mailto:jincysaramma_sam@comcast.com) --- .github/workflows/validate_pr_desc.yml | 2 +- definition/NetworkManager.json | 26 +++++++---- docs/NetworkManagerPlugin.md | 17 ++++--- interface/INetworkManager.h | 4 +- legacy/LegacyWiFiManagerAPIs.cpp | 28 ++++++++++-- plugin/NetworkManagerImplementation.cpp | 31 +++++++++---- plugin/NetworkManagerImplementation.h | 4 +- plugin/NetworkManagerJsonEnum.h | 7 +++ plugin/NetworkManagerJsonRpc.cpp | 44 ++++++++++++++---- plugin/gnome/NetworkManagerGnomeProxy.cpp | 45 +++++++++++++++---- plugin/gnome/NetworkManagerGnomeWIFI.cpp | 28 ++++++------ plugin/gnome/NetworkManagerGnomeWIFI.h | 3 +- .../gnome/gdbus/NetworkManagerGdbusProxy.cpp | 2 +- plugin/rdk/NetworkManagerRDKProxy.cpp | 15 ++++--- tests/l2Test/libnm/l2_test_libnmproxyWifi.cpp | 2 +- tests/l2Test/rdk/l2_test_rdkproxy.cpp | 4 +- tests/mocks/INetworkManagerMock.h | 2 +- tools/plugincli/NetworkManagerLibnmTest.cpp | 3 +- 18 files changed, 192 insertions(+), 75 deletions(-) diff --git a/.github/workflows/validate_pr_desc.yml b/.github/workflows/validate_pr_desc.yml index b6249ff7..3264db93 100644 --- a/.github/workflows/validate_pr_desc.yml +++ b/.github/workflows/validate_pr_desc.yml @@ -15,7 +15,7 @@ jobs: PR_TITLE: ${{ github.event.pull_request.title }} run: | # Define valid ticket IDs - VALID_TICKET_IDS=("RDKEMW") + VALID_TICKET_IDS=("RDKEMW" "RDK") # Function to validate ticket format and ID validate_ticket() { diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 65fb6e6c..8ada9b76 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -846,17 +846,27 @@ } }, "StartWiFiScan":{ - "summary": "Initiates WiFi scaning. This method supports scanning for specific range of frequency like 2.4GHz only or 5GHz only or 6GHz only or ALL. When no input passed about the frequency to be scanned, it scans for all. When list of SSIDs to be scanned specifically, it can be passed as input. It publishes 'onAvailableSSIDs' event upon completion.", + "summary": "Initiates WiFi scanning. This method supports scanning specific frequency bands (2.4GHz, 5GHz, 6GHz). When no input is passed for frequency, it scans all supported frequencies. When list of SSIDs to be scanned specifically, it can be passed as input. It publishes 'onAvailableSSIDs' event upon completion.", "events": { "onAvailableSSIDs" : "Triggered when list of SSIDs is available after the scan completes." }, "params": { "type": "object", "properties": { - "frequency": { - "summary": "The frequency to scan. An empty or `null` value scans all frequencies.", - "type": "string", - "example": "5" + "frequencies": { + "summary": "Frequency bands to scan. Omit this field or pass \"ALL\" to scan all frequencies.", + "type": "array", + "items": { + "summary": "The frequency to scan.", + "type": "string", + "enum": [ + "ALL", + "2.4", + "5", + "6" + ], + "example": "2.4" + } }, "ssids": { "summary": "The list of SSIDs to be scanned.", @@ -1233,7 +1243,7 @@ } }, "GetWiFiSignalQuality":{ - "summary": "Get WiFi signal quality of currently connected SSID. The signal quality is identifed based on the Signal to Noise ratio which is calculated as SNR = rssi - noise. The possible states are\n* 'Excellent' : More than 40 dBm\n* 'Good' : 40 dBm to 25 dBm\n* 'Fair' : 25 dBm to 18 dBm\n* 'Weak' : 18 dBm to 0 dBm\n* 'Disconnected' : 0 dBm\n", + "summary": "Get WiFi signal quality of currently connected SSID. The signal quality is identified based on the Signal to Noise ratio which is calculated as SNR = rssi - noise. The possible states are\n* 'Excellent' : More than 40 dBm\n* 'Good' : 40 dBm to 25 dBm\n* 'Fair' : 25 dBm to 18 dBm\n* 'Weak' : 18 dBm to 0 dBm\n* 'Disconnected' : 0 dBm\n", "events":{ "onWiFiSignalQualityChange" : "Triggered when Wifi signal strength switches between Excellent, Good, Fair, Weak." }, @@ -1294,7 +1304,7 @@ "example": 2 }, "EAP": { - "summary": "Supports security mode WPA enterpise", + "summary": "Supports security mode WPA enterprise", "type": "integer", "example": 3 } @@ -1455,7 +1465,7 @@ "type": "object", "properties": { "prevState":{ - "summary": "The privious internet connection state", + "summary": "The previous internet connection state", "type": "integer", "example": 1 }, diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 2c31542f..bba3aa46 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -89,7 +89,7 @@ NetworkManager interface methods: | [GetPublicIP](#method.GetPublicIP) | Gets the internet/public IP Address of the device | | [Ping](#method.Ping) | Pings the specified endpoint with the specified number of packets | | [Trace](#method.Trace) | Traces the specified endpoint with the specified number of packets using `traceroute` | -| [StartWiFiScan](#method.StartWiFiScan) | Initiates WiFi scaning | +| [StartWiFiScan](#method.StartWiFiScan) | Initiates WiFi scanning | | [StopWiFiScan](#method.StopWiFiScan) | Stops WiFi scanning | | [GetKnownSSIDs](#method.GetKnownSSIDs) | Gets list of saved SSIDs | | [AddToKnownSSIDs](#method.AddToKnownSSIDs) | Saves the SSID, passphrase, and security mode for upcoming and future sessions | @@ -1006,7 +1006,7 @@ Traces the specified endpoint with the specified number of packets using `tracer ## *StartWiFiScan [method](#head.Methods)* -Initiates WiFi scaning. This method supports scanning for specific range of frequency like 2.4GHz only or 5GHz only or 6GHz only or ALL. When no input passed about the frequency to be scanned, it scans for all. When list of SSIDs to be scanned specifically, it can be passed as input. It publishes 'onAvailableSSIDs' event upon completion. +Initiates WiFi scanning. This method supports scanning specific frequency bands (2.4GHz, 5GHz, 6GHz). When no input is passed for frequency, it scans all supported frequencies. When list of SSIDs to be scanned specifically, it can be passed as input. It publishes 'onAvailableSSIDs' event upon completion. Also see: [onAvailableSSIDs](#event.onAvailableSSIDs) @@ -1015,7 +1015,8 @@ Also see: [onAvailableSSIDs](#event.onAvailableSSIDs) | Name | Type | Description | | :-------- | :-------- | :-------- | | params | object | | -| params?.frequency | string | *(optional)* The frequency to scan. An empty or `null` value scans all frequencies | +| params?.frequencies | array | *(optional)* Frequency bands to scan. Omit this field or pass "ALL" to scan all frequencies | +| params?.frequencies[#] | string | *(optional)* The frequency to scan | | params?.ssids | array | *(optional)* The list of SSIDs to be scanned | | params?.ssids[#] | string | *(optional)* The SSID to scan | @@ -1036,7 +1037,9 @@ Also see: [onAvailableSSIDs](#event.onAvailableSSIDs) "id": 42, "method": "org.rdk.NetworkManager.1.StartWiFiScan", "params": { - "frequency": "5", + "frequencies": [ + "2.4" + ], "ssids": [ "Xfinity Mobile" ] @@ -1558,7 +1561,7 @@ This method takes no parameters. ## *GetWiFiSignalQuality [method](#head.Methods)* -Get WiFi signal quality of currently connected SSID. The signal quality is identifed based on the Signal to Noise ratio which is calculated as SNR = rssi - noise. The possible states are +Get WiFi signal quality of currently connected SSID. The signal quality is identified based on the Signal to Noise ratio which is calculated as SNR = rssi - noise. The possible states are * 'Excellent' : More than 40 dBm * 'Good' : 40 dBm to 25 dBm * 'Fair' : 25 dBm to 18 dBm @@ -1631,7 +1634,7 @@ This method takes no parameters. | result.security.NONE | integer | Security mode for open network | | result.security.WPA_PSK | integer | Supports security mode WPA,WPA-PSK,WPA2-PSK, WPA3-Personal-Transition | | result.security.SAE | integer | Supports security mode WPA3-Personal | -| result.security.EAP | integer | Supports security mode WPA enterpise | +| result.security.EAP | integer | Supports security mode WPA enterprise | | result.success | boolean | Whether the request succeeded | ### Example @@ -1891,7 +1894,7 @@ Triggered when internet connection state changed.The possible internet connectio | Name | Type | Description | | :-------- | :-------- | :-------- | | params | object | | -| params.prevState | integer | The privious internet connection state | +| params.prevState | integer | The previous internet connection state | | params.prevStatus | string | The previous internet connection status | | params.state | integer | The internet connection state | | params.status | string | The internet connection status | diff --git a/interface/INetworkManager.h b/interface/INetworkManager.h index 15c739cf..ce8cb509 100644 --- a/interface/INetworkManager.h +++ b/interface/INetworkManager.h @@ -105,7 +105,7 @@ namespace WPEFramework enum WIFIFrequency : uint8_t { - WIFI_FREQUENCY_NONE /* @text: NONE */, + WIFI_FREQUENCY_ALL /* @text: ALL */, WIFI_FREQUENCY_2_4_GHZ /* @text: 2.4GHz */, WIFI_FREQUENCY_5_GHZ /* @text: 5GHz */, WIFI_FREQUENCY_6_GHZ /* @text: 6GHz */, @@ -248,7 +248,7 @@ namespace WPEFramework // WiFi Specific Methods /* @brief Initiate a WIFI Scan; This is Async method and returns the scan results as Event */ - virtual uint32_t StartWiFiScan(const string& frequency /* @in */, IStringIterator* const ssids/* @in */) = 0; + virtual uint32_t StartWiFiScan(IStringIterator* const frequencies /* @in */, IStringIterator* const ssids/* @in */) = 0; virtual uint32_t StopWiFiScan(void) = 0; virtual uint32_t GetKnownSSIDs(IStringIterator*& ssids /* @out */) = 0; diff --git a/legacy/LegacyWiFiManagerAPIs.cpp b/legacy/LegacyWiFiManagerAPIs.cpp index 9132ed09..9a6573ff 100644 --- a/legacy/LegacyWiFiManagerAPIs.cpp +++ b/legacy/LegacyWiFiManagerAPIs.cpp @@ -634,12 +634,27 @@ namespace WPEFramework { LOG_INPARAM(); uint32_t rc = Core::ERROR_GENERAL; - string frequency{}; + Exchange::INetworkManager::IStringIterator* frequencies = nullptr; Exchange::INetworkManager::IStringIterator* ssids = NULL; - if (parameters.HasLabel("frequency")) - frequency = parameters["frequency"].String(); + { + std::vector frequencyList; + if (Core::JSON::Variant::type::STRING == parameters["frequency"].Content()) + { + frequencyList.push_back(parameters["frequency"].String()); + } + else + { + NMLOG_ERROR("Unexpected variant type in frequency parameter."); + returnJson(rc); + } + + frequencies = (Core::Service::Create(frequencyList)); + if (frequencies == nullptr) { + returnJson(rc); + } + } if (parameters.HasLabel("ssid")) { @@ -654,6 +669,8 @@ namespace WPEFramework ssids = (Core::Service::Create(inputSSIDlist)); if (ssids == nullptr) { + if (frequencies) + frequencies->Release(); returnJson(rc); } } @@ -661,12 +678,15 @@ namespace WPEFramework auto _nwmgr = m_service->QueryInterfaceByCallsign(NETWORK_MANAGER_CALLSIGN); if (_nwmgr) { - rc = _nwmgr->StartWiFiScan(frequency, ssids); + rc = _nwmgr->StartWiFiScan(frequencies, ssids); _nwmgr->Release(); } else rc = Core::ERROR_UNAVAILABLE; + if (frequencies) + frequencies->Release(); + if (ssids) ssids->Release(); diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index b4b5bca6..c1fe15f8 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -590,7 +590,6 @@ namespace WPEFramework return; } - void NetworkManagerImplementation::filterScanResults(JsonArray &ssids) { JsonArray result; @@ -598,27 +597,41 @@ namespace WPEFramework std::unordered_set scanForSsidsSet(m_filterSsidslist.begin(), m_filterSsidslist.end()); // If neither SSID list nor frequency is provided, exit - if (m_filterSsidslist.empty() && m_filterfrequency.empty()) + if (m_filterSsidslist.empty() && m_filterFrequencies.empty()) { NMLOG_DEBUG("Neither SSID nor Frequency is provided. Exiting function."); return; } - if (!m_filterfrequency.empty()) - { - filterFreq = std::stod(m_filterfrequency); - } - for (int i = 0; i < ssids.Length(); i++) { JsonObject object = ssids[i].Object(); string ssid = object["ssid"].String(); string frequency = object["frequency"].String(); - double frequencyValue = std::stod(frequency); bool ssidMatches = scanForSsidsSet.empty() || scanForSsidsSet.find(ssid) != scanForSsidsSet.end(); - bool freqMatches = m_filterfrequency.empty() || (filterFreq == frequencyValue); + bool freqMatches = m_filterFrequencies.empty(); + if (!freqMatches) + { + for (const auto& selectedFrequency : m_filterFrequencies) + { + if (selectedFrequency == "ALL") + { + freqMatches = true; + break; + } + else + { + filterFreq = std::stod(selectedFrequency); + if (filterFreq == frequencyValue) + { + freqMatches = true; + break; + } + } + } + } if (ssidMatches && freqMatches) result.Add(object); diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index f5bd49b1..a1563787 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -216,7 +216,7 @@ namespace WPEFramework // WiFi Specific Methods /* @brief Initiate a WIFI Scan; This is Async method and returns the scan results as Event */ - uint32_t StartWiFiScan(const string& frequency /* @in */, IStringIterator* const ssids/* @in */) override; + uint32_t StartWiFiScan(IStringIterator* const frequencies /* @in */, IStringIterator* const ssids/* @in */) override; uint32_t StopWiFiScan(void) override; uint32_t GetKnownSSIDs(IStringIterator*& ssids /* @out */) override; @@ -301,7 +301,7 @@ namespace WPEFramework uint16_t m_stunBindTimeout; uint16_t m_stunCacheTimeout; std::thread m_registrationThread; - string m_filterfrequency; + std::vector m_filterFrequencies; std::vector m_filterSsidslist; std::thread m_monitorThread; diff --git a/plugin/NetworkManagerJsonEnum.h b/plugin/NetworkManagerJsonEnum.h index 07b9318a..d47768e1 100644 --- a/plugin/NetworkManagerJsonEnum.h +++ b/plugin/NetworkManagerJsonEnum.h @@ -96,4 +96,11 @@ ENUM_CONVERSION_BEGIN(Exchange::INetworkManager::IPStatus) { Exchange::INetworkManager::IPStatus::IP_ACQUIRED, _TXT("ACQUIRED") }, ENUM_CONVERSION_END(Exchange::INetworkManager::IPStatus) +ENUM_CONVERSION_BEGIN(Exchange::INetworkManager::WIFIFrequency) + { Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_ALL, _TXT("ALL") }, + { Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_2_4_GHZ, _TXT("2.4") }, + { Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_5_GHZ, _TXT("5") }, + { Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_6_GHZ, _TXT("6") }, +ENUM_CONVERSION_END(Exchange::INetworkManager::WIFIFrequency) + } diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index 5ed0b60f..d49e6c5d 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -642,19 +642,40 @@ namespace WPEFramework { LOG_INPARAM(); uint32_t rc = Core::ERROR_GENERAL; - string frequency{}; - Exchange::INetworkManager::IStringIterator* ssids = NULL; + Exchange::INetworkManager::IStringIterator* frequencies = nullptr; + Exchange::INetworkManager::IStringIterator* ssids = NULL; - if (parameters.HasLabel("frequency")) - frequency = parameters["frequency"].String(); + if (parameters.HasLabel("frequencies")) + { + JsonArray array = parameters["frequencies"].Array(); + std::vector frequencyList; + JsonArray::Iterator index(array.Elements()); - if (parameters.HasLabel("ssids")) + while (index.Next() == true) + { + if (Core::JSON::Variant::type::STRING == index.Current().Content()) + { + frequencyList.push_back(index.Current().String()); + } + else + { + NMLOG_ERROR("Unexpected variant type in frequency array."); + returnJson(rc); + } + } + frequencies = Core::Service::Create(frequencyList); + if (frequencies == nullptr) { + returnJson(rc); + } + } + + if (parameters.HasLabel("ssids")) { JsonArray array = parameters["ssids"].Array(); std::vector ssidslist; - JsonArray::Iterator index(array.Elements()); + JsonArray::Iterator index(array.Elements()); - while (index.Next() == true) + while (index.Next() == true) { if (Core::JSON::Variant::type::STRING == index.Current().Content()) { @@ -663,20 +684,27 @@ namespace WPEFramework else { NMLOG_ERROR("Unexpected variant type in SSID array."); + if (frequencies) + frequencies->Release(); returnJson(rc); } } ssids = (Core::Service::Create(ssidslist)); if(ssids == nullptr){ + if (frequencies) + frequencies->Release(); returnJson(rc); } } if (_networkManager) - rc = _networkManager->StartWiFiScan(frequency, ssids); + rc = _networkManager->StartWiFiScan(frequencies, ssids); else rc = Core::ERROR_UNAVAILABLE; + if (frequencies) + frequencies->Release(); + if (ssids) ssids->Release(); diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index b661a8de..b2010fe9 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -986,32 +986,61 @@ namespace WPEFramework return rc; } - uint32_t NetworkManagerImplementation::StartWiFiScan(const string& frequency /* @in */, IStringIterator* const ssids/* @in */) + uint32_t NetworkManagerImplementation::StartWiFiScan(IStringIterator* const frequencies /* @in */, IStringIterator* const ssids/* @in */) { uint32_t rc = Core::ERROR_RPC_CALL_FAILED; //Cleared the Existing Store filterred SSID list m_filterSsidslist.clear(); - m_filterfrequency.clear(); + m_filterFrequencies.clear(); if(ssids) { string tmpssidlist{}; while (ssids->Next(tmpssidlist) == true) { - m_filterSsidslist.push_back(tmpssidlist.c_str()); - NMLOG_DEBUG("%s added to SSID filtering", tmpssidlist.c_str()); + if (!tmpssidlist.empty()) + { + m_filterSsidslist.push_back(tmpssidlist.c_str()); + NMLOG_DEBUG("%s added to SSID filtering", tmpssidlist.c_str()); + } + else + { + NMLOG_DEBUG("Empty SSID encountered in input list; skipping."); + } } } - if (!frequency.empty()) + if (frequencies) { - m_filterfrequency = frequency; - NMLOG_DEBUG("Scan SSIDs of frequency %s", m_filterfrequency.c_str()); + string frequency{}; + while (frequencies->Next(frequency) == true) + { + if (!frequency.empty()) + { + Core::JSON::EnumType parsedFrequency; + parsedFrequency.FromString(frequency); + const string normalizedFrequency = parsedFrequency.Data(); + if ((!normalizedFrequency.empty()) && (normalizedFrequency == frequency)) + { + m_filterFrequencies.push_back(normalizedFrequency); + NMLOG_DEBUG("Frequency %s added to scan filtering", normalizedFrequency.c_str()); + } + else + { + NMLOG_ERROR("Invalid frequency value: %s", frequency.c_str()); + return Core::ERROR_BAD_REQUEST; + } + } + else + { + NMLOG_DEBUG("Empty frequency encountered in input list; skipping."); + } + } } nmEvent->setwifiScanOptions(true); - if(wifi->wifiScanRequest(m_filterSsidslist.size() == 1 ? m_filterSsidslist[0] : "")) + if(wifi->wifiScanRequest(m_filterSsidslist)) rc = Core::ERROR_NONE; return rc; } diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 2348124d..7fb4bb5d 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -755,7 +755,7 @@ namespace WPEFramework g_object_set(sWireless, NM_SETTING_WIRELESS_BSSID, ssidinfo.bssid.c_str(), NULL); } - if(ssidinfo.frequency != Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_NONE) + if(ssidinfo.frequency != Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_ALL) { if(ssidinfo.frequency == Exchange::INetworkManager::WIFIFrequency::WIFI_FREQUENCY_2_4_GHZ) { @@ -1685,7 +1685,7 @@ namespace WPEFramework g_main_loop_quit(_wifiManager->m_loop); } - bool wifiManager::wifiScanRequest(std::string ssidReq) + bool wifiManager::wifiScanRequest(const std::vector& ssidsToFilter) { if(!createClientNewConnection()) return false; @@ -1696,23 +1696,25 @@ namespace WPEFramework return false; } m_isSuccess = false; - if(!ssidReq.empty()) + if(!ssidsToFilter.empty()) { - NMLOG_INFO("starting wifi scanning .. %s", ssidReq.c_str()); - GVariantBuilder builder, array_builder; + NMLOG_INFO("Starting wifi scanning for %d SSIDs:",static_cast(ssidsToFilter.size())); + GVariantBuilder nm_variant, nm_array_variant; GVariant *options; - g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT); - g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aay")); - g_variant_builder_add(&array_builder, "@ay", - g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (const guint8 *) ssidReq.c_str(), ssidReq.length(), 1) - ); - g_variant_builder_add(&builder, "{sv}", "ssids", g_variant_builder_end(&array_builder)); - options = g_variant_builder_end(&builder); + g_variant_builder_init(&nm_variant, G_VARIANT_TYPE_VARDICT); + g_variant_builder_init(&nm_array_variant, G_VARIANT_TYPE("aay")); + for (const auto& ssid : ssidsToFilter) { + g_variant_builder_add(&nm_array_variant, "@ay", + g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (const guint8 *) ssid.c_str(), ssid.length(), 1) + ); + } + g_variant_builder_add(&nm_variant, "{sv}", "ssids", g_variant_builder_end(&nm_array_variant)); + options = g_variant_builder_end(&nm_variant); nm_device_wifi_request_scan_options_async(wifiDevice, options, m_cancellable, wifiScanCb, this); g_variant_unref(options); // Unreference the GVariant after passing it to the async function } else { - NMLOG_INFO("staring normal wifi scanning .."); + NMLOG_INFO("Starting normal wifi scanning .."); nm_device_wifi_request_scan_async(wifiDevice, m_cancellable, wifiScanCb, this); } wait(m_loop); diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.h b/plugin/gnome/NetworkManagerGnomeWIFI.h index 51164aa5..4d0fa086 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.h +++ b/plugin/gnome/NetworkManagerGnomeWIFI.h @@ -31,6 +31,7 @@ #include #include #include +#include #define WPS_RETRY_WAIT_IN_MS 10 // 10 sec #define WPS_RETRY_COUNT 10 @@ -54,7 +55,7 @@ namespace WPEFramework bool activateKnownConnection(std::string iface, std::string knowConnectionID=""); bool wifiConnectedSSIDInfo(Exchange::INetworkManager::WiFiSSIDInfo &ssidinfo); bool wifiConnect(const Exchange::INetworkManager::WiFiConnectTo &ssidInfo); - bool wifiScanRequest(std::string ssidReq = ""); + bool wifiScanRequest(const std::vector& ssidsToFilter = {}); bool isWifiScannedRecently(int timelimitInSec = 5); // default 5 sec as shotest scanning interval bool getKnownSSIDs(std::list& ssids); bool addToKnownSSIDs(const Exchange::INetworkManager::WiFiConnectTo &ssidinfo); diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp index 73aa49d9..3cbb977e 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp @@ -216,7 +216,7 @@ namespace WPEFramework return rc; } - uint32_t NetworkManagerImplementation::StartWiFiScan(const string& frequency /* @in */, IStringIterator* const ssids/* @in */) + uint32_t NetworkManagerImplementation::StartWiFiScan(IStringIterator* const frequencies /* @in */, IStringIterator* const ssids/* @in */) { uint32_t rc = Core::ERROR_GENERAL; _nmGdbusEvents->setwifiScanOptions(true); /* Enable event posting */ diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index f218d61c..0bbde194 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -965,7 +965,7 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { return rc; } - uint32_t NetworkManagerImplementation::StartWiFiScan(const string& frequency /* @in */, IStringIterator* const ssids/* @in */) + uint32_t NetworkManagerImplementation::StartWiFiScan(IStringIterator* const frequencies /* @in */, IStringIterator* const ssids/* @in */) { LOG_ENTRY_FUNCTION(); uint32_t rc = Core::ERROR_RPC_CALL_FAILED; @@ -974,8 +974,7 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { //Cleared the Existing Store filterred SSID list m_filterSsidslist.clear(); - m_filterfrequency.clear(); - + m_filterFrequencies.clear(); if(ssids) { string ssidlist{}; @@ -986,10 +985,14 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { } } - if (!frequency.empty()) + if (frequencies) { - m_filterfrequency = frequency; - NMLOG_DEBUG("Scan SSIDs of frequency %s", m_filterfrequency.c_str()); + string frequencyList{}; + while (frequencies->Next(frequencyList) == true) + { + m_filterFrequencies.push_back(frequencyList.c_str()); + NMLOG_DEBUG("%s added to Frequency filtering", frequencyList.c_str()); + } } memset(¶m, 0, sizeof(param)); diff --git a/tests/l2Test/libnm/l2_test_libnmproxyWifi.cpp b/tests/l2Test/libnm/l2_test_libnmproxyWifi.cpp index d32f4615..c0086a00 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyWifi.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyWifi.cpp @@ -768,7 +768,7 @@ TEST_F(NetworkManagerWifiTest, StartWiFiScan_with_Frequency) EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("StartWiFiScan"), _T("{\"frequency\":\"5\", \"ssids\":[\"Testssid_1\", \"Testssid_2\"]}"), response)); + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("StartWiFiScan"), _T("{\"frequencies\":[\"2.4\",\"5\"], \"ssids\":[\"Testssid_1\", \"Testssid_2\"]}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); g_object_unref(deviceDummy); diff --git a/tests/l2Test/rdk/l2_test_rdkproxy.cpp b/tests/l2Test/rdk/l2_test_rdkproxy.cpp index 5bcdc3d5..0d13cace 100644 --- a/tests/l2Test/rdk/l2_test_rdkproxy.cpp +++ b/tests/l2Test/rdk/l2_test_rdkproxy.cpp @@ -661,7 +661,7 @@ TEST_F(NetworkManagerTest, StartWiFiScan_Success) )); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("StartWiFiScan"), - _T("{\"frequency\":\"2.4GHz\"}"), response)); + _T("{\"frequency\":[\"2.4\"]}"), response)); EXPECT_EQ(response, _T("{\"success\":true}")); } @@ -673,7 +673,7 @@ TEST_F(NetworkManagerTest, StartWiFiScan_Failed) .WillOnce(::testing::Return(IARM_RESULT_IPCCORE_FAIL)); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("StartWiFiScan"), - _T("{\"frequency\":\"2.4GHz\"}"), response)); + _T("{\"frequency\":[\"2.4\"]}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); } diff --git a/tests/mocks/INetworkManagerMock.h b/tests/mocks/INetworkManagerMock.h index 27a8b287..d9a21719 100644 --- a/tests/mocks/INetworkManagerMock.h +++ b/tests/mocks/INetworkManagerMock.h @@ -36,7 +36,7 @@ class MockINetworkManager : public WPEFramework::Exchange::INetworkManager { MOCK_METHOD(uint32_t, GetPublicIP, (string& interface, string& ipversion, string& ipaddress), (override)); MOCK_METHOD(uint32_t, Ping, (const string ipversion, const string endpoint, const uint32_t count, const uint16_t timeout, const string guid, string& response), (override)); MOCK_METHOD(uint32_t, Trace, (const string ipversion, const string endpoint, const uint32_t nqueries, const string guid, string& response), (override)); - MOCK_METHOD(uint32_t, StartWiFiScan, (const string& frequency, IStringIterator* const ssids), (override)); + MOCK_METHOD(uint32_t, StartWiFiScan, (IStringIterator* const frequencies, IStringIterator* const ssids), (override)); MOCK_METHOD(uint32_t, StopWiFiScan, (), (override)); MOCK_METHOD(uint32_t, GetKnownSSIDs, (IStringIterator*& ssids), (override)); MOCK_METHOD(uint32_t, AddToKnownSSIDs, (const WiFiConnectTo& ssid), (override)); diff --git a/tools/plugincli/NetworkManagerLibnmTest.cpp b/tools/plugincli/NetworkManagerLibnmTest.cpp index 650a1ba1..3a358b56 100644 --- a/tools/plugincli/NetworkManagerLibnmTest.cpp +++ b/tools/plugincli/NetworkManagerLibnmTest.cpp @@ -220,7 +220,8 @@ int main() NMLOG_INFO("Sending WiFi scan request%s", ssid.empty() ? " (all SSIDs)" : (" for SSID: " + ssid).c_str()); - if (wifiMgr->wifiScanRequest(ssid)) { + bool scanRequestSent = ssid.empty() ? wifiMgr->wifiScanRequest() : wifiMgr->wifiScanRequest({ssid}); + if (scanRequestSent) { NMLOG_INFO("WiFi scan request sent successfully."); } else { NMLOG_ERROR("Failed to send WiFi scan request."); From 927fbfb02f4a3f8af2de90cb732d19d870f1e570 Mon Sep 17 00:00:00 2001 From: RAFI <103924677+cmuhammedrafi@users.noreply.github.com> Date: Thu, 28 May 2026 23:04:08 +0530 Subject: [PATCH 02/32] RDKEMW-18247 : Panel is listing as a PLATCO device in Router client list in WiFi mode (#313) * RDKEMW-18247 Panel is listing as a PLATCO device in Router client list in WiFi mode Reason for change: Set the NetworkManager dhcp-hostname in the connection profile to use the default hostname instead of the device name. --- .github/workflows/libnm_proxy_L1_test.yml | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 +-- plugin/gnome/NetworkManagerGnomeProxy.cpp | 9 +++++- plugin/gnome/NetworkManagerGnomeUtils.cpp | 10 +++---- plugin/gnome/NetworkManagerGnomeWIFI.cpp | 36 ++++++++++++++++------- 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/.github/workflows/libnm_proxy_L1_test.yml b/.github/workflows/libnm_proxy_L1_test.yml index 80d945d5..d947d643 100644 --- a/.github/workflows/libnm_proxy_L1_test.yml +++ b/.github/workflows/libnm_proxy_L1_test.yml @@ -111,7 +111,7 @@ jobs: run: | sudo bash -c 'echo "ETHERNET_INTERFACE=eth0 WIFI_INTERFACE=wlan0 - DEVICE_NAME=rdk_test_device " > /etc/device.properties' + DEFAULT_HOSTNAME=rdk_test_device " > /etc/device.properties' - name: Generate IARM headers run: | diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 8ada9b76..70cf138d 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -1351,7 +1351,7 @@ } }, "SetHostname": { - "summary": "To configure a custom DHCP hostname instead of the default (which is typically the device name).\n\nSetting host name will take effect upon reconnect; like, device reboot, wake-up from deepsleep, while connecting to new Wi-Fi connection, WiFi On/Off, or renewal of the DHCP lease.", + "summary": "To configure a custom DHCP hostname instead of the default (which is typically the default hostname).\n\nSetting host name will take effect upon reconnect; like, device reboot, wake-up from deepsleep, while connecting to new Wi-Fi connection, WiFi On/Off, or renewal of the DHCP lease.", "params": { "type": "object", "properties": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index bba3aa46..a1aac7cc 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -103,7 +103,7 @@ NetworkManager interface methods: | [GetWiFiSignalQuality](#method.GetWiFiSignalQuality) | Get WiFi signal quality of currently connected SSID | | [GetSupportedSecurityModes](#method.GetSupportedSecurityModes) | Returns the Wifi security modes that the device supports | | [GetWifiState](#method.GetWifiState) | Returns the current Wifi State | -| [SetHostname](#method.SetHostname) | To configure a custom DHCP hostname instead of the default (which is typically the device name) | +| [SetHostname](#method.SetHostname) | To configure a custom DHCP hostname instead of the default (which is typically the default hostname) | ## *SetLogLevel [method](#head.Methods)* @@ -1729,7 +1729,7 @@ This method takes no parameters. ## *SetHostname [method](#head.Methods)* -To configure a custom DHCP hostname instead of the default (which is typically the device name). +To configure a custom DHCP hostname instead of the default (which is typically the default hostname). Setting host name will take effect upon reconnect; like, device reboot, wake-up from deepsleep, while connecting to new Wi-Fi connection, WiFi On/Off, or renewal of the DHCP lease. diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index b2010fe9..81b57dd0 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -150,7 +150,14 @@ namespace WPEFramework // read persistent hostname if exist if(!nmUtils::readPersistentHostname(hostname)) { - hostname = nmUtils::deviceHostname(); // default hostname as device name + hostname = nmUtils::deviceHostname(); // default hostname as default hostname + } + + // Validate hostname is non-empty regardless of source (persistent or default) + if(hostname.empty()) + { + NMLOG_WARNING("Hostname is empty. No modification will be made to NM connections."); + return false; } connections = nm_client_get_connections(client); diff --git a/plugin/gnome/NetworkManagerGnomeUtils.cpp b/plugin/gnome/NetworkManagerGnomeUtils.cpp index 7d97e5e8..fa2b2869 100644 --- a/plugin/gnome/NetworkManagerGnomeUtils.cpp +++ b/plugin/gnome/NetworkManagerGnomeUtils.cpp @@ -40,7 +40,7 @@ namespace WPEFramework { static std::string m_ethifname = "eth0"; static std::string m_wlanifname = "wlan0"; - static std::string m_deviceHostname = "rdk-device"; // Device name can be empty if not set in /etc/device.properties + static std::string m_deviceHostname = "rdk-device"; // default hostname can be empty if not set in /etc/device.properties const char* nmUtils::wlanIface() {return m_wlanifname.c_str();} const char* nmUtils::ethIface() {return m_ethifname.c_str();} @@ -261,14 +261,14 @@ namespace WPEFramework } } - if (line.find("DEVICE_NAME=") != std::string::npos) { + if (line.find("DEFAULT_HOSTNAME=") != std::string::npos) { deviceHostname = line.substr(line.find('=') + 1); deviceHostname.erase(deviceHostname.find_last_not_of("\r\n\t") + 1); deviceHostname.erase(0, deviceHostname.find_first_not_of("\r\n\t")); if(deviceHostname.empty()) { - NMLOG_WARNING("DEVICE_NAME is empty in /etc/device.properties"); - deviceHostname = ""; // set empty device name + NMLOG_WARNING("DEFAULT_HOSTNAME is empty in /etc/device.properties"); + deviceHostname = ""; // set empty default hostname } } } @@ -277,7 +277,7 @@ namespace WPEFramework m_wlanifname = wifiIfname; m_ethifname = ethIfname; m_deviceHostname = deviceHostname; - NMLOG_INFO("/etc/device.properties eth: %s, wlan: %s, device name: %s", m_ethifname.c_str(), m_wlanifname.c_str(), m_deviceHostname.c_str()); + NMLOG_INFO("/etc/device.properties eth: %s, wlan: %s, default hostname: %s", m_ethifname.c_str(), m_wlanifname.c_str(), m_deviceHostname.c_str()); return true; } diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 7fb4bb5d..4e52fc11 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -637,18 +637,27 @@ namespace WPEFramework NMLOG_DEBUG("No persistent hostname found, using device hostname"); } + if(hostname.empty()) + NMLOG_WARNING("dhcp hostname: "); + else + NMLOG_INFO("dhcp hostname: %s", hostname.c_str()); + // IPv4 settings with DHCP NMSettingIP4Config *sIpv4 = (NMSettingIP4Config *)nm_setting_ip4_config_new(); g_object_set(G_OBJECT(sIpv4), NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); - g_object_set(G_OBJECT(sIpv4), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); - g_object_set(G_OBJECT(sIpv4), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); + if(!hostname.empty()) { + g_object_set(G_OBJECT(sIpv4), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); + g_object_set(G_OBJECT(sIpv4), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); + } nm_connection_add_setting(connection, NM_SETTING(sIpv4)); // IPv6 settings with DHCP NMSettingIP6Config *sIpv6 = (NMSettingIP6Config *)nm_setting_ip6_config_new(); g_object_set(G_OBJECT(sIpv6), NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); - g_object_set(G_OBJECT(sIpv6), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); - g_object_set(G_OBJECT(sIpv6), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); + if(!hostname.empty()) { + g_object_set(G_OBJECT(sIpv6), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); + g_object_set(G_OBJECT(sIpv6), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); + } nm_connection_add_setting(connection, NM_SETTING(sIpv6)); NMLOG_DEBUG("Created minimal ethernet connection with autoconnect=true"); @@ -894,23 +903,30 @@ namespace WPEFramework if(!nmUtils::readPersistentHostname(hostname)) { hostname = nmUtils::deviceHostname(); - NMLOG_DEBUG("no persistent hostname found taking device name as hostname !"); + NMLOG_DEBUG("No persistent hostname found, using device hostname"); } - NMLOG_INFO("dhcp hostname: %s", hostname.c_str()); + if(hostname.empty()) + NMLOG_WARNING("dhcp hostname: "); + else + NMLOG_INFO("dhcp hostname: %s", hostname.c_str()); /* Build up the 'IPv4' Setting */ NMSettingIP4Config *sIpv4Conf = (NMSettingIP4Config *) nm_setting_ip4_config_new(); g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL); // autoconf = true - g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); - g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); // hostname send enabled + if(!hostname.empty()) { + g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); + g_object_set(G_OBJECT(sIpv4Conf), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); // hostname send enabled + } nm_connection_add_setting(m_connection, NM_SETTING(sIpv4Conf)); /* Build up the 'IPv6' Setting */ NMSettingIP6Config *sIpv6Conf = (NMSettingIP6Config *) nm_setting_ip6_config_new(); g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL); // autoconf = true - g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); - g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); // hostname send enabled + if(!hostname.empty()) { + g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, hostname.c_str(), NULL); + g_object_set(G_OBJECT(sIpv6Conf), NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, TRUE, NULL); // hostname send enabled + } nm_connection_add_setting(m_connection, NM_SETTING(sIpv6Conf)); return true; } From 21e2ad7d2005055dc7ad27fe400f26f483f97769 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Thu, 28 May 2026 15:19:41 -0400 Subject: [PATCH 03/32] Release of 3.0.0 Release of 3.0.0 --- CHANGELOG.md | 5 +++++ CMakeLists.txt | 4 ++-- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07c24818..c1726e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.0.0] - 2026-05-28 +### Changed +- The device hostname header that used to retrive has changed as "DEFAULT_HOSTNAME" +- Updated the WiFiStartScan to scan for specific SSID and also updated to take array of freq band as input instead of single freq + ## [2.3.0] - 2026-05-21 ### Fixed - Fixed the issue which leads Enabling and Disabling of interfaces are taking longer diff --git a/CMakeLists.txt b/CMakeLists.txt index a04ad16b..fbc2ec83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,8 +36,8 @@ if (NOT WPEFramework_FOUND AND NOT Thunder_FOUND) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") -set(VERSION_MAJOR 2) -set(VERSION_MINOR 3) +set(VERSION_MAJOR 3) +set(VERSION_MINOR 0) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 70cf138d..5e95f935 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "2.3.0" + "version": "3.0.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index a1aac7cc..a15fc6a4 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 2.3.0** +**Version: 3.0.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 2.3.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.0.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 88c9b22f0232d211b2b8bae4d4ee11046ff1247c Mon Sep 17 00:00:00 2001 From: Anand73-n Date: Thu, 4 Jun 2026 18:17:44 +0530 Subject: [PATCH 04/32] RDK-61440: Implementation of handling PowerMode Change in NM plugin (#308) * RDK-61440: Implementation of handling PowerMode Change in NM plugin Reason for change: Ctrl ntwrk state based on PowerMode transitions Test procedure: Change the PowerMode state and verify the behavior Risks: low Priority: P1 Signed-off-by: Anand N Co-authored-by: Karunakaran A --- .github/workflows/gdbus_proxy_L1_test.yml | 5 + .github/workflows/legacy_L1_L2_test.yml | 5 + .github/workflows/libnm_proxy_L1_test.yml | 10 +- .github/workflows/rdk_proxy_L1_L2_test.yml | 5 + CMakeLists.txt | 7 + plugin/CMakeLists.txt | 1 + plugin/NetworkManagerImplementation.cpp | 123 +++++++++ plugin/NetworkManagerImplementation.h | 15 + plugin/NetworkManagerPowerClient.cpp | 303 +++++++++++++++++++++ plugin/NetworkManagerPowerClient.h | 175 ++++++++++++ plugin/gnome/NetworkManagerGnomeProxy.cpp | 16 ++ plugin/gnome/NetworkManagerGnomeWIFI.cpp | 189 ++++++++++++- plugin/gnome/NetworkManagerGnomeWIFI.h | 13 +- plugin/rdk/NetworkManagerRDKProxy.cpp | 14 + tests/l2Test/libnm/CMakeLists.txt | 1 + tests/l2Test/rdk/CMakeLists.txt | 1 + tests/mocks/thunder/IPowerManager.h | 85 ++++++ 17 files changed, 958 insertions(+), 10 deletions(-) create mode 100644 plugin/NetworkManagerPowerClient.cpp create mode 100644 plugin/NetworkManagerPowerClient.h create mode 100644 tests/mocks/thunder/IPowerManager.h diff --git a/.github/workflows/gdbus_proxy_L1_test.yml b/.github/workflows/gdbus_proxy_L1_test.yml index 242ca6bd..16edc038 100644 --- a/.github/workflows/gdbus_proxy_L1_test.yml +++ b/.github/workflows/gdbus_proxy_L1_test.yml @@ -107,6 +107,11 @@ jobs: && cmake --build build/ThunderInterfaces --target install -j8 + - name: Install IPowerManager header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Build networkmanager with Gnome GDBUS Proxy run: > cmake diff --git a/.github/workflows/legacy_L1_L2_test.yml b/.github/workflows/legacy_L1_L2_test.yml index b3645cce..03538a0a 100644 --- a/.github/workflows/legacy_L1_L2_test.yml +++ b/.github/workflows/legacy_L1_L2_test.yml @@ -110,6 +110,11 @@ jobs: && cmake --build build/ThunderInterfaces --target install -j8 + - name: Install IPowerManager header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Generate IARM headers run: | touch install/usr/lib/libIARMBus.so diff --git a/.github/workflows/libnm_proxy_L1_test.yml b/.github/workflows/libnm_proxy_L1_test.yml index d947d643..e5f64def 100644 --- a/.github/workflows/libnm_proxy_L1_test.yml +++ b/.github/workflows/libnm_proxy_L1_test.yml @@ -12,7 +12,8 @@ env: jobs: L1-tests: name: Build and run unit tests - runs-on: ubuntu-22.04 + # Note: Ubuntu 24.04 is required for libnm 1.46 which is needed for NetworkManagerPowerClient support + runs-on: ubuntu-24.04 steps: # Set up Thunder cache @@ -107,6 +108,11 @@ jobs: && cmake --build build/ThunderInterfaces --target install -j8 + - name: Install IPowerManager header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Generate dependency files run: | sudo bash -c 'echo "ETHERNET_INTERFACE=eth0 @@ -160,7 +166,7 @@ jobs: - name: Generate coverage run: | - lcov -c -o coverage.info -d build/networkmanager_libnm/ + lcov --rc geninfo_unexecuted_blocks=1 -c -o coverage.info -d build/networkmanager_libnm/ --ignore-errors mismatch lcov -e coverage.info '*/networkmanager/plugin/gnome/*' -o filtered_coverage.info - name: Generate the html report diff --git a/.github/workflows/rdk_proxy_L1_L2_test.yml b/.github/workflows/rdk_proxy_L1_L2_test.yml index 4fb464fe..fc89bc38 100644 --- a/.github/workflows/rdk_proxy_L1_L2_test.yml +++ b/.github/workflows/rdk_proxy_L1_L2_test.yml @@ -107,6 +107,11 @@ jobs: && cmake --build build/ThunderInterfaces --target install -j8 + - name: Install IPowerManager header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Generate IARM headers run: | touch install/usr/lib/libIARMBus.so diff --git a/CMakeLists.txt b/CMakeLists.txt index fbc2ec83..a5d13178 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,13 @@ 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(ENABLE_ETHERNET_CONNECTION_HANDLING + "Enable pre-sleep Ethernet deactivation" OFF) + +if(ENABLE_ETHERNET_CONNECTION_HANDLING) + add_definitions(-DENABLE_ETHERNET_CONNECTION_HANDLING) + message(STATUS "Ethernet connection handling: enabled") +endif() if (USE_TELEMETRY) find_package(T2 REQUIRED) diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index a5b12d94..6090b8f1 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -81,6 +81,7 @@ add_library(${MODULE_IMPL_NAME} SHARED NetworkManagerConnectivity.cpp NetworkManagerStunClient.cpp NetworkManagerLogger.cpp + NetworkManagerPowerClient.cpp Module.cpp) if(ENABLE_GNOME_NETWORKMANAGER) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index c1fe15f8..35d6a6f4 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -54,6 +54,8 @@ namespace WPEFramework m_wlanConnected.store(false); m_ethEnabled.store(false); m_wlanEnabled.store(false); + m_ethDisconnectedForSleep.store(false); + m_wlanDisconnectedForSleep.store(false); /* Set NetworkManager Out-Process name to be NWMgrPlugin */ Core::ProcessInfo().Name("NWMgrPlugin"); @@ -71,6 +73,7 @@ namespace WPEFramework NetworkManagerImplementation::~NetworkManagerImplementation() { NMLOG_INFO("NetworkManager Out-Of-Process Shutdown/Cleanup"); + m_powerClient.reset(); connectivityMonitor.stopConnectivityMonitor(); _instance = nullptr; platform_deinit(); @@ -199,6 +202,7 @@ namespace WPEFramework NetworkManagerImplementation::platform_init(); /* change gnome networkmanager or netsrvmgr logg level */ NetworkManagerImplementation::platform_logging(static_cast (config.loglevel.Value())); + m_powerClient.reset(new NetworkManagerPowerClient(*this)); return(Core::ERROR_NONE); } @@ -1197,5 +1201,124 @@ namespace WPEFramework } #endif } + + void NetworkManagerImplementation::OnPowerModePreChange( + const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState, + std::function sendAck) + { + // Called from NetworkManagerPowerClient's power thread. + NMLOG_DEBUG("OnPowerModePreChange: current=%d new=%d", + static_cast(currentState), static_cast(newState)); + + using PowerState = Exchange::IPowerManager::PowerState; + + if (newState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP) + { + if (m_wlanEnabled.load() && m_wlanConnected.load()) + { + NMLOG_INFO("OnPowerModePreChange: going to DeepSleep — disconnecting WiFi"); + + uint32_t rcWifiDown = WiFiDisconnect(); + if (rcWifiDown == Core::ERROR_NONE) + { + m_wlanDisconnectedForSleep.store(true); + } + else + { + NMLOG_ERROR("OnPowerModePreChange: WiFiDisconnect failed (rc=%u), will not reconnect on wakeup", rcWifiDown); + } + } + else + { + NMLOG_DEBUG("OnPowerModePreChange: going to DeepSleep — WiFi not connected, skipping disconnect"); + } +#ifdef ENABLE_ETHERNET_CONNECTION_HANDLING + if (m_ethEnabled.load() && m_ethConnected.load()) + { + NMLOG_INFO("OnPowerModePreChange: going to DeepSleep — deactivating Ethernet"); + + uint32_t rcEthDown = EthernetDeactivate(); + if (rcEthDown == Core::ERROR_NONE) + { + m_ethDisconnectedForSleep.store(true); + } + else + { + NMLOG_ERROR("OnPowerModePreChange: EthernetDeactivate failed (rc=%u), will not activate on wakeup", rcEthDown); + } + } + else + { + NMLOG_DEBUG("OnPowerModePreChange: going to DeepSleep — Ethernet not activated, skipping deactivate"); + } +#endif + } + else if (currentState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP) + { + if (m_wlanDisconnectedForSleep.load()) + { + if (!m_lastConnectedSSID.empty()) + { + NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — reconnecting to '%s'", + m_lastConnectedSSID.c_str()); + uint32_t rcWifiUp = ConnectToKnownSSID(m_lastConnectedSSID); + if (rcWifiUp == Core::ERROR_NONE) + { + m_wlanDisconnectedForSleep.store(false); + } + else + { + NMLOG_ERROR("OnPowerModePreChange: ConnectToKnownSSID failed (rc=%u)", rcWifiUp); + } + } + else + { + NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — no last SSID, skipping reconnect"); + } + } + else + { + NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — WiFi was not connected or was already down before sleep, skipping reconnect"); + } + } + sendAck(); + } + + void NetworkManagerImplementation::OnPowerModeChanged( + const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState) + { + NMLOG_INFO("OnPowerModeChanged: current=%d new=%d", + static_cast(currentState), static_cast(newState)); + if (currentState == Exchange::IPowerManager::PowerState::POWER_STATE_STANDBY_DEEP_SLEEP) { + + if (m_wlanEnabled.load() && m_wlanConnected.load()) + { + // Waking from DeepSleep with Network Standby ON: the AP may have + // changed channel while the device slept (802.11 CSA). Trigger an + // active scan so the driver discovers the AP on its new channel. + NMLOG_INFO("OnPowerModeChanged: waking from DeepSleep, triggering active WiFi scan"); + if (StartWiFiScan(nullptr, nullptr) != Core::ERROR_NONE) + { + NMLOG_ERROR("OnPowerModeChanged: StartWiFiScan failed"); + } + + NMLOG_INFO("OnPowerModeChanged: waking from DeepSleep, requesting DHCP lease on wlan0"); + if (ReacquireDHCPLease("wlan0") != Core::ERROR_NONE) + { + NMLOG_ERROR("OnPowerModeChanged: ReacquireDHCPLease(wlan0) failed"); + } + } + if (m_ethEnabled.load() && m_ethConnected.load()) + { + NMLOG_INFO("OnPowerModeChanged: waking from DeepSleep, requesting DHCP lease on eth0"); + if (ReacquireDHCPLease("eth0") != Core::ERROR_NONE) + { + NMLOG_ERROR("OnPowerModeChanged: ReacquireDHCPLease(eth0) failed"); + } + } + } + } } } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index a1563787..bff75ba7 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -27,6 +27,7 @@ #include #include #include +#include using namespace std; @@ -34,6 +35,7 @@ using namespace std; #include "NetworkManagerLogger.h" #include "NetworkManagerConnectivity.h" #include "NetworkManagerStunClient.h" +#include "NetworkManagerPowerClient.h" /* Forward declarations to avoid pulling GLib/libnm headers into this header */ typedef struct _NMClient NMClient; @@ -63,6 +65,7 @@ namespace WPEFramework namespace Plugin { class NetworkManagerImplementation : public Exchange::INetworkManager + , public INetworkPowerCallback { enum NetworkEvents { @@ -226,6 +229,8 @@ namespace WPEFramework uint32_t WiFiConnect(const WiFiConnectTo& ssid /* @in */) override; uint32_t WiFiDisconnect(void) override; + uint32_t EthernetDeactivate(void); + uint32_t ReacquireDHCPLease(const string& iface); uint32_t GetConnectedSSID(WiFiSSIDInfo& ssidInfo /* @out */) override; uint32_t StartWPS(const WiFiWPS& method /* @in */, const string& wps_pin /* @in */) override; @@ -277,6 +282,13 @@ namespace WPEFramework void ReportWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality); void logTelemetry(const std::string& eventName, const std::string& message); + // INetworkPowerCallback overrides + void OnPowerModePreChange(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState, + std::function sendAck) override; + void OnPowerModeChanged(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState) override; + private: void platform_init(void); void platform_deinit(void); @@ -314,6 +326,7 @@ namespace WPEFramework std::atomic m_stopThread{false}; std::mutex m_condVariableMutex; std::condition_variable m_condVariable; + std::unique_ptr m_powerClient; public: IPAddress m_ethIPv4Address; IPAddress m_wlanIPv4Address; @@ -323,6 +336,8 @@ namespace WPEFramework std::atomic m_wlanConnected; std::atomic m_ethEnabled; std::atomic m_wlanEnabled; + std::atomic m_ethDisconnectedForSleep; + std::atomic m_wlanDisconnectedForSleep; std::string m_lastConnectedSSID; NMClient *m_nmClient{nullptr}; /* proxy NMClient — bound to m_nmContext */ GMainContext *m_nmContext{nullptr}; /* isolated context, not the global default */ diff --git a/plugin/NetworkManagerPowerClient.cpp b/plugin/NetworkManagerPowerClient.cpp new file mode 100644 index 00000000..3d6d2ed9 --- /dev/null +++ b/plugin/NetworkManagerPowerClient.cpp @@ -0,0 +1,303 @@ +/** +* 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 "NetworkManagerPowerClient.h" +#include "NetworkManagerLogger.h" +#include + +using namespace WPEFramework; +using namespace WPEFramework::Exchange; +using namespace WPEFramework::Plugin; + +// --------------------------------------------------------------------------- +// NetworkManagerPowerClient +// --------------------------------------------------------------------------- + +NetworkManagerPowerClient::NetworkManagerPowerClient(INetworkPowerCallback& callback) + : mCallback(callback) + , mPreChangeNotification(*this) + , mChangedNotification(*this) +{ + NMLOG_INFO("connecting to PowerManager"); + if (auto r = Open(RPC::CommunicationTimeOut, Connector(), "org.rdk.PowerManager"); 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 PowerManager (error %u)", r); + } +} + +NetworkManagerPowerClient::~NetworkManagerPowerClient() +{ + NMLOG_INFO("shutting down"); + // Stop the power-event thread first so any in-flight work completes + // before we release the COM-RPC proxy. + mStopThread = true; + mQueueCv.notify_one(); + if (mPowerThread.joinable()) { + mPowerThread.join(); + } + unregisterEvents(); + Close(Core::infinite); +} + +bool NetworkManagerPowerClient::IsValid() const +{ + LOG_ENTRY_FUNCTION(); + + return mPowerManager != nullptr; +} + +bool NetworkManagerPowerClient::getNetworkStandbyMode() const +{ + LOG_ENTRY_FUNCTION(); + + bool standbyMode = false; + if (IsValid()) { + if (auto r = mPowerManager->GetNetworkStandbyMode(standbyMode); r != Core::ERROR_NONE) { + NMLOG_ERROR("GetNetworkStandbyMode failed (%u)", r); + } + } + return standbyMode; +} + +void NetworkManagerPowerClient::sendPowerModePreChangeComplete(int transactionId) +{ + LOG_ENTRY_FUNCTION(); + + if (IsValid()) { + if (mClientId == 0) { + NMLOG_ERROR("sendPowerModePreChangeComplete called with invalid clientId=0, skipping"); + return; + } + NMLOG_DEBUG("sending PowerModePreChangeComplete for transactionId=%d, mClientId=%u", transactionId, mClientId); + mPowerManager->PowerModePreChangeComplete(mClientId, transactionId); + } +} + +void NetworkManagerPowerClient::sendDelayPowerModeChange(int transactionId, int seconds) +{ + LOG_ENTRY_FUNCTION(); + + if (IsValid()) { + if (mClientId == 0) { + NMLOG_ERROR("sendDelayPowerModeChange called with invalid clientId=0, skipping"); + return; + } + if (auto r = mPowerManager->DelayPowerModeChangeBy(mClientId, transactionId, seconds); r != Core::ERROR_NONE) { + NMLOG_ERROR("DelayPowerModeChangeBy failed (%u)", r); + } + } +} + +void NetworkManagerPowerClient::Operational(bool upAndRunning) +{ + NMLOG_DEBUG("Operational(%s)", upAndRunning ? "true" : "false"); + if (upAndRunning) { + if (!IsValid()) { + mPowerManager = Interface(); + registerEvents(); + // Start the dedicated power-event thread after registration so it + // is ready to handle events as soon as they can arrive. + mStopThread = false; + mPowerThread = std::thread(&NetworkManagerPowerClient::powerThreadLoop, this); + } + } else { + // Stop the power-event thread before unregistering so any in-flight + // event that was already enqueued is drained. + mStopThread = true; + mQueueCv.notify_one(); + if (mPowerThread.joinable()) { + mPowerThread.join(); + } + unregisterEvents(); + } +} + +void NetworkManagerPowerClient::registerEvents() +{ + NMLOG_DEBUG("registering events"); + if (!IsValid()) { + NMLOG_ERROR("not in valid state, skipping event registration"); + return; + } + if (auto r = mPowerManager->AddPowerModePreChangeClient("org.rdk.NetworkManager", mClientId); r != Core::ERROR_NONE) { + NMLOG_ERROR("AddPowerModePreChangeClient failed (%u) — skipping pre-change sink", r); + // mClientId stays 0; do NOT register mPreChangeNotification + } else { + NMLOG_INFO("registered as pre-change client, mClientId=%u", mClientId); + if (auto r2 = mPowerManager->Register(&mPreChangeNotification); r2 != Core::ERROR_NONE) { + NMLOG_ERROR("register(preChange) failed (%u)", r2); + } + } + if (auto r = mPowerManager->Register(&mChangedNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("register(changed) failed (%u)", r); + } +} + +void NetworkManagerPowerClient::unregisterEvents() +{ + NMLOG_DEBUG("unregistering events"); + if (!IsValid()) { + NMLOG_ERROR("not in valid state, skipping event unregistration"); + return; + } + // NOTE: RemovePowerModePreChangeClient MUST be called before Unregister + if (mClientId != 0) { + if (auto r = mPowerManager->RemovePowerModePreChangeClient(mClientId); r != Core::ERROR_NONE) { + NMLOG_ERROR("removePowerModePreChangeClient failed (%u)", r); + } + if (auto r = mPowerManager->Unregister(&mPreChangeNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("unregister(preChange) failed (%u)", r); + } + mClientId = 0; + } + if (auto r = mPowerManager->Unregister(&mChangedNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("unregister(changed) failed (%u)", r); + } + + mPowerManager->Release(); + mPowerManager = nullptr; +} + +// --------------------------------------------------------------------------- +// Power event thread +// --------------------------------------------------------------------------- + +void NetworkManagerPowerClient::powerThreadLoop() +{ + NMLOG_DEBUG("power event thread started"); + while (true) { + PowerEvent event{}; + { + std::unique_lock lock(mQueueMutex); + mQueueCv.wait(lock, [this]{ return !mEventQueue.empty() || mStopThread.load(); }); + + if (mStopThread) { + // Drain remaining events with fast acks before exiting so + // PowerManager is never left waiting on a stale transaction. + // CHANGED events have no ack protocol — skip them. + std::vector pending; + while (!mEventQueue.empty()) { + pending.push_back(mEventQueue.front()); + mEventQueue.pop(); + } + lock.unlock(); + for (const auto& e : pending) { + if (e.type == PowerEvent::EventType::PRE_CHANGE) { + sendPowerModePreChangeComplete(e.transactionId); + } + } + break; + } + + event = mEventQueue.front(); + mEventQueue.pop(); + } + // Lock released — process event on this thread (blocking is fine here) + + const bool toDeepSleep = (event.newState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP); + const bool fromDeepSleep = (event.currentState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP); + + if (event.type == PowerEvent::EventType::CHANGED) { + // Wakeup notification — no ack required. + if (fromDeepSleep && event.standbyMode) { + NMLOG_INFO("power thread — wakeup from DeepSleep standby ON"); + mCallback.OnPowerModeChanged(event.currentState, event.newState); + } else { + NMLOG_DEBUG("power thread — CHANGED event, no action (fromDeepSleep=%d networkStandbyMode=%d)", + fromDeepSleep, event.standbyMode); + } + continue; + } + + // PRE_CHANGE event processing below + auto sendAck = [transactionId = event.transactionId, this]() { + sendPowerModePreChangeComplete(transactionId); + }; + + if ((toDeepSleep || fromDeepSleep) && !event.standbyMode) { + // Deep-sleep transition with Network Standby OFF: delegate to + // NetworkManagerImplementation (WiFiDisconnect / reconnect) which + // will call sendAck() when done. + NMLOG_INFO("power thread — %s DeepSleep standby OFF", + toDeepSleep ? "to" : "from"); + mCallback.OnPowerModePreChange(event.currentState, event.newState, sendAck); + } else { + // standby ON or non-DeepSleep: no WiFi action needed, ack immediately. + NMLOG_DEBUG("power thread ack (standbyMode=%d toDeepSleep=%d fromDeepSleep=%d)", + event.standbyMode, toDeepSleep, fromDeepSleep); + sendAck(); + } + } + NMLOG_INFO("power event thread stopped"); +} + +// --------------------------------------------------------------------------- +// PreChangeNotification +// --------------------------------------------------------------------------- + +void NetworkManagerPowerClient::PreChangeNotification::OnPowerModePreChange( + const PowerState currentState, const PowerState newState, + const int transactionId, const int stateChangeAfter) +{ + NMLOG_DEBUG("OnPowerModePreChange current=%d new=%d txId=%d after=%ds", + static_cast(currentState), static_cast(newState), transactionId, stateChangeAfter); + + // Query standby mode + const bool standbyMode = mClient.getNetworkStandbyMode(); + + // Cache for use by ChangedNotification + mClient.mLastChangeStandbyMode = standbyMode; + + // Reserve a delay window now (before returning) so PowerManager knows to + // wait at least 5 s. + if (newState == PowerState::POWER_STATE_STANDBY_DEEP_SLEEP && !standbyMode) { + mClient.sendDelayPowerModeChange(transactionId, 5); + } + + // Enqueue and return immediately so the COM-RPC dispatcher thread is freed. + { + std::lock_guard lock(mClient.mQueueMutex); + mClient.mEventQueue.push(PowerEvent{PowerEvent::EventType::PRE_CHANGE, + currentState, newState, standbyMode, transactionId}); + } + mClient.mQueueCv.notify_one(); +} + +// --------------------------------------------------------------------------- +// ChangedNotification +// --------------------------------------------------------------------------- + +void NetworkManagerPowerClient::ChangedNotification::OnPowerModeChanged( + const PowerState currentState, const PowerState newState) +{ + NMLOG_DEBUG("OnPowerModeChanged current=%d new=%d", + static_cast(currentState), static_cast(newState)); + + // Use the cached standby mode + const bool standbyMode = mClient.mLastChangeStandbyMode; + + // Enqueue and return immediately so the COM-RPC dispatcher thread is freed. + { + std::lock_guard lock(mClient.mQueueMutex); + mClient.mEventQueue.push(PowerEvent{PowerEvent::EventType::CHANGED, + currentState, newState, standbyMode, 0}); + } + mClient.mQueueCv.notify_one(); +} diff --git a/plugin/NetworkManagerPowerClient.h b/plugin/NetworkManagerPowerClient.h new file mode 100644 index 00000000..73949e0e --- /dev/null +++ b/plugin/NetworkManagerPowerClient.h @@ -0,0 +1,175 @@ +/** +* 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 +#include +#include +#include +#include +#include +#include +#include + +namespace WPEFramework { +namespace Plugin { + +/** + * Callback interface that decouples NetworkManagerPowerClient from + * NetworkManagerImplementation. The implementation receives power state + * transitions and must call sendAck() exactly once per OnPowerModePreChange. + */ +struct INetworkPowerCallback { + virtual ~INetworkPowerCallback() = default; + + /** + * Called when a power mode pre-change event arrives that involves + * POWER_STATE_STANDBY_DEEP_SLEEP (either as the new state or the current + * state). The implementation MUST call sendAck() exactly once. + */ + virtual void OnPowerModePreChange(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState, + std::function sendAck) = 0; + + /** + * Called when a power mode changed event arrives (informational only; + * no ack required). + */ + virtual void OnPowerModeChanged(const Exchange::IPowerManager::PowerState currentState, + const Exchange::IPowerManager::PowerState newState) = 0; +}; + +/** + * - Inherits SmartInterfaceType for automatic + * reconnect / Operational() lifecycle callbacks. + * - Registers as an AddPowerModePreChangeClient so it participates + * in the pre-change ack protocol. + * - Delegates DeepSleep transitions to INetworkPowerCallback. + * - Sends a fast-path PowerModePreChangeComplete for all other transitions. + * + * Lifecycle: + * Construction → Open() connects to PowerManager (async). + * Operational(true) → registers events; IsValid() returns true. + * Operational(false) → unregisters events and releases proxy. + * Destruction → unregisterEvents() then Close() . + */ +class NetworkManagerPowerClient : protected RPC::SmartInterfaceType { +public: + using PowerState = Exchange::IPowerManager::PowerState; + + explicit NetworkManagerPowerClient(INetworkPowerCallback& callback); + ~NetworkManagerPowerClient() override; + + NetworkManagerPowerClient(const NetworkManagerPowerClient&) = delete; + NetworkManagerPowerClient& operator=(const NetworkManagerPowerClient&) = delete; + + /** Returns true when the PowerManager COMRPC proxy is available. */ + bool IsValid() const; + + /** Queries the current Network Standby mode from PowerManager. */ + bool getNetworkStandbyMode() const; + + /** Sends PowerModePreChangeComplete to PowerManager. */ + void sendPowerModePreChangeComplete(int transactionId); + + /** Requests a delay window extension via DelayPowerModeChangeBy. */ + void sendDelayPowerModeChange(int transactionId, int seconds); + +private: + // ----------------------------------------------------------------------- + // IModePreChangeNotification sink + // ----------------------------------------------------------------------- + class PreChangeNotification : public Exchange::IPowerManager::IModePreChangeNotification { + public: + explicit PreChangeNotification(NetworkManagerPowerClient& client) + : mClient(client) {} + + void OnPowerModePreChange(const PowerState currentState, const PowerState newState, + const int transactionId, const int stateChangeAfter) override; + + BEGIN_INTERFACE_MAP(PreChangeNotification) + INTERFACE_ENTRY(Exchange::IPowerManager::IModePreChangeNotification) + END_INTERFACE_MAP + + private: + NetworkManagerPowerClient& mClient; + }; + + // ----------------------------------------------------------------------- + // IModeChangedNotification sink + // ----------------------------------------------------------------------- + class ChangedNotification : public Exchange::IPowerManager::IModeChangedNotification { + public: + explicit ChangedNotification(NetworkManagerPowerClient& client) : mClient(client) {} + + void OnPowerModeChanged(const PowerState currentState, const PowerState newState) override; + + BEGIN_INTERFACE_MAP(ChangedNotification) + INTERFACE_ENTRY(Exchange::IPowerManager::IModeChangedNotification) + END_INTERFACE_MAP + + private: + NetworkManagerPowerClient& mClient; + }; + + // ----------------------------------------------------------------------- + // SmartInterfaceType lifecycle callback + // ----------------------------------------------------------------------- + void Operational(bool upAndRunning) override; + + void registerEvents(); + void unregisterEvents(); + void powerThreadLoop(); + + + // ----------------------------------------------------------------------- + // Members + // ----------------------------------------------------------------------- + INetworkPowerCallback& mCallback; + Exchange::IPowerManager* mPowerManager{nullptr}; + Core::Sink mPreChangeNotification; + Core::Sink mChangedNotification; + uint32_t mClientId{0}; + + // Power-event thread: receives events enqueued by the COM-RPC dispatcher + // thread and processes them (WiFiDisconnect etc.) without blocking the + // dispatcher. + struct PowerEvent { + enum class EventType { PRE_CHANGE, CHANGED }; + + EventType type; + PowerState currentState; + PowerState newState; + bool standbyMode; + int transactionId; + }; + + std::thread mPowerThread; + std::queue mEventQueue; + std::mutex mQueueMutex; + std::condition_variable mQueueCv; + std::atomic mStopThread{false}; + // Cached standby mode from the last PRE_CHANGE event. + std::atomic mLastChangeStandbyMode{false}; +}; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 81b57dd0..81d1e933 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -1154,6 +1154,22 @@ namespace WPEFramework return rc; } + uint32_t NetworkManagerImplementation::EthernetDeactivate(void) + { + uint32_t rc = Core::ERROR_GENERAL; + if(wifi->ethernetDeactivate()) + rc = Core::ERROR_NONE; + return rc; + } + + uint32_t NetworkManagerImplementation::ReacquireDHCPLease(const string& iface) + { + uint32_t rc = Core::ERROR_GENERAL; + if(wifi->reacquireDhcpLease(iface)) + rc = Core::ERROR_NONE; + return rc; + } + uint32_t NetworkManagerImplementation::GetConnectedSSID(WiFiSSIDInfo& ssidInfo /* @out */) { uint32_t rc = Core::ERROR_RPC_CALL_FAILED; diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 4e52fc11..58872d48 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -45,7 +45,11 @@ namespace WPEFramework wifiManager::wifiManager() : m_client(nullptr), m_loop(nullptr), m_createNewConnection(false), m_objectPath(nullptr), m_wifidevice(nullptr), m_source(nullptr), m_cancellable(nullptr){ NMLOG_INFO("wifiManager"); m_nmContext = g_main_context_new(); - g_main_context_push_thread_default(m_nmContext); + // g_main_context_push_thread_default(m_nmContext); + // Do NOT push m_nmContext here. Pushing here permanently locks ownership + // to the constructor thread (owner_count stays at 1, never released). + // All callers — must push/pop around + // each createClientNewConnection()/deleteClientConnection() pair instead. m_loop = g_main_loop_new(m_nmContext, FALSE); } @@ -53,12 +57,21 @@ namespace WPEFramework { GError *error = NULL; + // Serialize concurrent wifi operations from different threads + m_opMutex.lock(); + + g_main_context_push_thread_default(m_nmContext); + m_client = nm_client_new(NULL, &error); if (!m_client || !m_loop) { if (error) { NMLOG_ERROR("Could not connect to NetworkManager: %s.", error->message); g_error_free(error); } + g_clear_object(&m_client); + m_client = nullptr; + g_main_context_pop_thread_default(m_nmContext); + m_opMutex.unlock(); return false; } @@ -79,7 +92,7 @@ namespace WPEFramework NMLOG_DEBUG("Cancelling pending async operations"); g_cancellable_cancel(m_cancellable); g_clear_object(&m_cancellable); - m_cancellable = NULL; + m_cancellable = nullptr; } } @@ -93,15 +106,21 @@ namespace WPEFramework g_main_context_iteration(context, TRUE); } g_main_context_unref(context); - m_client = NULL; + m_client = nullptr; } if(m_objectPath) { NMLOG_DEBUG("Freeing object path"); g_free(m_objectPath); - m_objectPath = NULL; + m_objectPath = nullptr; } + + // Pop the context pushed in createClientNewConnection() + if (m_nmContext) + g_main_context_pop_thread_default(m_nmContext); + // Release operation lock acquired in createClientNewConnection() + m_opMutex.unlock(); } bool wifiManager::quit(NMDevice *wifiNMDevice) @@ -407,6 +426,168 @@ namespace WPEFramework return m_isSuccess; } + static void ethernetDeactivateCb(GObject *object, GAsyncResult *result, gpointer user_data) + { + NMClient *client = NM_CLIENT(object); + GError *error = NULL; + wifiManager *_wifiManager = static_cast(user_data); + + NMLOG_DEBUG("ethernet connection deactivating..."); + _wifiManager->m_isSuccess = true; + if (!nm_client_deactivate_connection_finish(client, result, &error)) + { + NMLOG_ERROR("ethernet connection deactivate failed !"); + if(error != NULL) + { + if(g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + NMLOG_DEBUG("Deactivate operation was cancelled"); + } + else + { + NMLOG_ERROR("Deactivate error: %s", error->message); + } + g_error_free(error); + } + _wifiManager->m_isSuccess = false; + } + _wifiManager->quit(NULL); + } + + bool wifiManager::ethernetDeactivate() + { + NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; + if(!createClientNewConnection()) + return false; + + NMDevice *ethDevice = nm_client_get_device_by_iface(m_client, nmUtils::ethIface()); + if(ethDevice == NULL) { + NMLOG_WARNING("ethernet device not found !"); + deleteClientConnection(); + return false; + } + + deviceState = nm_device_get_state(ethDevice); + NMLOG_DEBUG("ethernet device current state is %d !", deviceState); + if (deviceState <= NM_DEVICE_STATE_DISCONNECTED || deviceState == NM_DEVICE_STATE_FAILED || deviceState == NM_DEVICE_STATE_DEACTIVATING) + { + NMLOG_WARNING("ethernet already disconnected !"); + deleteClientConnection(); + return true; + } + + NMActiveConnection *activeConn = nm_device_get_active_connection(ethDevice); + if(activeConn == NULL) { + NMLOG_WARNING("ethernet has no active connection, nothing to deactivate !"); + deleteClientConnection(); + return true; + } + + nm_client_deactivate_connection_async(m_client, activeConn, m_cancellable, ethernetDeactivateCb, this); + wait(m_loop); + deleteClientConnection(); + return m_isSuccess; + } + + static void appliedConnCb(GObject *src, GAsyncResult *res, gpointer user_data) + { + wifiManager *_wifiManager = static_cast(user_data); + GError *error = NULL; + guint64 versionId = 0; + NMConnection *conn = nm_device_get_applied_connection_finish( + NM_DEVICE(src), res, &versionId, &error); + if (error) { + NMLOG_ERROR("reacquireDhcpLease: get_applied_connection failed: %s", error->message); + g_error_free(error); + _wifiManager->m_appliedConn = nullptr; + _wifiManager->m_isSuccess = false; + } else { + _wifiManager->m_appliedConn = conn; + _wifiManager->m_versionId = versionId; + _wifiManager->m_isSuccess = true; + } + if (_wifiManager->m_loop) + g_main_loop_quit(_wifiManager->m_loop); + } + + static void reappliedCb(GObject *src, GAsyncResult *res, gpointer user_data) + { + wifiManager *_wifiManager = static_cast(user_data); + GError *error = NULL; + nm_device_reapply_finish(NM_DEVICE(src), res, &error); + if (error) { + NMLOG_ERROR("reacquireDhcpLease: reapply failed: %s", error->message); + g_error_free(error); + _wifiManager->m_isSuccess = false; + } else { + _wifiManager->m_isSuccess = true; + } + if (_wifiManager->m_loop) + g_main_loop_quit(_wifiManager->m_loop); + NMLOG_DEBUG("reacquireDhcpLease: reapply completed for '%s'", nm_device_get_iface(NM_DEVICE(src))); + } + + bool wifiManager::reacquireDhcpLease(const std::string& iface) + { + /* No direct libnm API to trigger a DHCP renew, hence by toggling ipv4.auto-route-ext-gw on the + * APPLIED connection (not the stored profile) and calling reapply(). + */ + if(!createClientNewConnection()) + return false; + + NMDevice *device = nm_client_get_device_by_iface(m_client, iface.c_str()); + if (device == NULL) { + NMLOG_ERROR("reacquireDhcpLease: device '%s' not found", iface.c_str()); + deleteClientConnection(); + return false; + } + + /* Round 1: fetch what NM actually has applied in memory */ + m_isSuccess = false; + m_appliedConn = nullptr; + nm_device_get_applied_connection_async(device, 0, m_cancellable, appliedConnCb, this); + wait(m_loop); + + if (!m_isSuccess || m_appliedConn == nullptr) { + NMLOG_ERROR("reacquireDhcpLease: could not get applied connection for '%s'", iface.c_str()); + deleteClientConnection(); + return false; + } + + NMSettingIPConfig *s_ip4 = NM_SETTING_IP_CONFIG( + nm_connection_get_setting(m_appliedConn, NM_TYPE_SETTING_IP4_CONFIG)); + if (s_ip4 == NULL) { + NMLOG_ERROR("reacquireDhcpLease: no IPv4 settings on '%s'", iface.c_str()); + g_object_unref(m_appliedConn); + m_appliedConn = nullptr; + deleteClientConnection(); + return false; + } + + NMTernary currentVal = NM_TERNARY_DEFAULT; + g_object_get(s_ip4, NM_SETTING_IP_CONFIG_AUTO_ROUTE_EXT_GW, ¤tVal, NULL); + NMTernary newVal = (currentVal == NM_TERNARY_DEFAULT) ? NM_TERNARY_TRUE : NM_TERNARY_DEFAULT; + NMLOG_DEBUG("reacquireDhcpLease: '%s' auto-route-ext-gw %d -> %d (in-memory only)", + iface.c_str(), static_cast(currentVal), static_cast(newVal)); + g_object_set(s_ip4, NM_SETTING_IP_CONFIG_AUTO_ROUTE_EXT_GW, newVal, NULL); + + /* Round 2: reapply with version_id for race safety — no disk write */ + m_isSuccess = false; + nm_device_reapply_async(device, m_appliedConn, m_versionId, 0, m_cancellable, reappliedCb, this); + wait(m_loop); + + if (!m_isSuccess) { + NMLOG_ERROR("reacquireDhcpLease: reapply failed for '%s'", iface.c_str()); + } else { + NMLOG_INFO("reacquireDhcpLease: reapply successful on '%s'", iface.c_str()); + } + + g_object_unref(m_appliedConn); + m_appliedConn = nullptr; + deleteClientConnection(); + return m_isSuccess; + } + static NMAccessPoint* findMatchingSSID(const GPtrArray* ApList, Exchange::INetworkManager::WiFiConnectTo& ssidInfo) { NMAccessPoint *AccessPoint = nullptr; diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.h b/plugin/gnome/NetworkManagerGnomeWIFI.h index 4d0fa086..cfc741fb 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.h +++ b/plugin/gnome/NetworkManagerGnomeWIFI.h @@ -52,6 +52,8 @@ namespace WPEFramework bool getWifiState(Exchange::INetworkManager::WiFiState& state); bool wifiDisconnect(); + bool ethernetDeactivate(); + bool reacquireDhcpLease(const std::string& iface); bool activateKnownConnection(std::string iface, std::string knowConnectionID=""); bool wifiConnectedSSIDInfo(Exchange::INetworkManager::WiFiSSIDInfo &ssidinfo); bool wifiConnect(const Exchange::INetworkManager::WiFiConnectTo &ssidInfo); @@ -76,14 +78,14 @@ namespace WPEFramework wifiManager(); ~wifiManager() { NMLOG_INFO("~wifiManager"); + if(m_client != NULL) { + deleteClientConnection(); // handles pop + } if (m_nmContext) { - g_main_context_pop_thread_default(m_nmContext); + // Do NOT pop here — deleteClientConnection already popped g_main_context_unref(m_nmContext); m_nmContext = NULL; } - if(m_client != NULL) { - deleteClientConnection(); - } if(m_loop != NULL) { g_main_loop_unref(m_loop); m_loop = NULL; @@ -107,7 +109,10 @@ namespace WPEFramework GSource *m_source; GCancellable *m_cancellable; std::mutex m_cancellableMutex; + std::mutex m_opMutex; // serializes concurrent wifi operations from different threads bool m_isSuccess = false; + NMConnection *m_appliedConn = nullptr; + guint64 m_versionId = 0; SecretAgent m_secretAgent; }; } // Plugin diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index 0bbde194..b163ae3c 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -1180,6 +1180,20 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { return rc; } + uint32_t NetworkManagerImplementation::EthernetDeactivate(void) + { + /* No-op on RDK platform */ + NMLOG_INFO("EthernetDeactivate: no-op on RDK platform"); + return Core::ERROR_UNAVAILABLE; + } + + uint32_t NetworkManagerImplementation::ReacquireDHCPLease(const string& iface) + { + /* No-op on RDK platform */ + NMLOG_INFO("ReacquireDHCPLease: no-op on RDK platform (iface=%s)", iface.c_str()); + return Core::ERROR_UNAVAILABLE; + } + uint32_t NetworkManagerImplementation::GetConnectedSSID(WiFiSSIDInfo& ssidInfo /* @out */) { LOG_ENTRY_FUNCTION(); diff --git a/tests/l2Test/libnm/CMakeLists.txt b/tests/l2Test/libnm/CMakeLists.txt index 092cc1d3..9b1a44d0 100644 --- a/tests/l2Test/libnm/CMakeLists.txt +++ b/tests/l2Test/libnm/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable(${NM_LIBNM_PROXY_L2_TEST} ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerImplementation.cpp ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerConnectivity.cpp ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerStunClient.cpp + ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerPowerClient.cpp ${CMAKE_SOURCE_DIR}/plugin/gnome/NetworkManagerGnomeProxy.cpp ${CMAKE_SOURCE_DIR}/plugin/gnome/NetworkManagerGnomeWIFI.cpp ${CMAKE_SOURCE_DIR}/plugin/gnome/NetworkManagerGnomeEvents.cpp diff --git a/tests/l2Test/rdk/CMakeLists.txt b/tests/l2Test/rdk/CMakeLists.txt index ed5d4fab..3a94c63d 100644 --- a/tests/l2Test/rdk/CMakeLists.txt +++ b/tests/l2Test/rdk/CMakeLists.txt @@ -38,6 +38,7 @@ add_executable(${NM_RDK_PROXY_L2_TEST} ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerImplementation.cpp ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerConnectivity.cpp ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerStunClient.cpp + ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerPowerClient.cpp ${CMAKE_SOURCE_DIR}/plugin/rdk/NetworkManagerRDKProxy.cpp ${PROXY_STUB_SOURCES} ) diff --git a/tests/mocks/thunder/IPowerManager.h b/tests/mocks/thunder/IPowerManager.h new file mode 100644 index 00000000..30c1d4d7 --- /dev/null +++ b/tests/mocks/thunder/IPowerManager.h @@ -0,0 +1,85 @@ +/** +* 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 + +/** + * Minimal CI stub for IPowerManager — compatible with Thunder R4.4.3. + * + * This file is used only during CI builds (gdbus/libnm L1 proxy tests) where + * the full entservices-apis stack is not available. It defines exactly the + * surface consumed by NetworkManagerPowerClient and nothing more. + * + * DO NOT use this file outside of test/CI contexts. + */ + +#include + +namespace WPEFramework { +namespace Exchange { + + struct EXTERNAL IPowerManager : virtual public Core::IUnknown { + + // Stub ID — not used for COM lookup in L1 unit tests. + enum { ID = 0x8180 }; + + enum PowerState : uint8_t { + POWER_STATE_UNKNOWN = 0, + POWER_STATE_OFF = 1, + POWER_STATE_STANDBY = 2, + POWER_STATE_ON = 3, + POWER_STATE_STANDBY_LIGHT_SLEEP = 4, + POWER_STATE_STANDBY_DEEP_SLEEP = 5, + }; + + // @event + struct EXTERNAL IModePreChangeNotification : virtual public Core::IUnknown { + enum { ID = 0x8182 }; + virtual void OnPowerModePreChange(const PowerState currentState, + const PowerState newState, + const int transactionId, + const int stateChangeAfter) {} + }; + + // @event + struct EXTERNAL IModeChangedNotification : virtual public Core::IUnknown { + enum { ID = 0x8183 }; + virtual void OnPowerModeChanged(const PowerState currentState, + const PowerState newState) {} + }; + + virtual Core::hresult Register(IModePreChangeNotification* notification) {}; + virtual Core::hresult Unregister(const IModePreChangeNotification* notification) {}; + + virtual Core::hresult Register(IModeChangedNotification* notification) {}; + virtual Core::hresult Unregister(const IModeChangedNotification* notification) {}; + + virtual Core::hresult AddPowerModePreChangeClient(const string& clientName, + uint32_t& clientId) {}; + virtual Core::hresult RemovePowerModePreChangeClient(const uint32_t clientId) {}; + virtual Core::hresult PowerModePreChangeComplete(const uint32_t clientId, + const int transactionId) {}; + virtual Core::hresult DelayPowerModeChangeBy(const uint32_t clientId, + const int transactionId, + const int delayPeriod) {}; + virtual Core::hresult GetNetworkStandbyMode(bool& standbyMode) {}; + }; + +} // namespace Exchange +} // namespace WPEFramework From 225a47a72359b82d22b39afcef709d4d4fc28ada Mon Sep 17 00:00:00 2001 From: tukken-comcast Date: Mon, 8 Jun 2026 21:56:44 +0530 Subject: [PATCH 05/32] RDK-61247: IP caching and refresh logic (#310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * AI agent's initial code modifications as per approved plan * fix(gnome-events): address three IP cache gaps from initial implementation - Subscribe to notify::options on NMDhcpConfig in all three wiring sites (device-added, startup walk, ip4/ip6ConfigChangedCb) so dhcpserver stays current across mid-lease renewals; remove stale TODO comment - Replace IpFamilyCache::globalAddresses (std::set) + prefix (uint32_t) with std::map so toIPAddress() always projects ipaddress and prefix from the same entry - Remove dead GnomeNetworkManagerEvents::onAddressChangeCb() definition and declaration, superseded by the cache-diff path in refreshIpFamilyCache * fix: replace narrow fe80: string check with correct IPv6 fe80::/10 detection Extract isIPv6LinkLocal() helper into NetworkManagerImplementation.h and use it at all four call sites. Also remove now-unused #include . * fix: fix build errors after IpFamilyCache::globalAddresses type change Update cache insert calls in gnome and rdk proxy fallback paths to use the map API (insert({addr, prefix})) and remove the now-absent top-level c->prefix field assignments. * fix(ip-cache): read IP config from device instead of active connection refreshIpFamilyCache() was reading addresses from nm_active_connection_get_ip4/6_config(), which returns NULL on platforms like xione-uk where NetworkManager does not manage the IP configuration directly. The signal handlers (ip4ChangedCb, ip6ChangedCb) are connected to the device-level NMIPConfig objects, so the cache must also read from nm_device_get_ip4/6_config() to see the same addresses that triggered the notification. This mismatch caused the cache to remain empty and no IP_ACQUIRED events were ever emitted despite signals firing correctly. * fix(ip-cache): guard DNS array access to prevent out-of-bounds read nm_ip_config_get_nameservers() returns a NULL-terminated strv. In newer libnm versions this is guaranteed non-NULL even when empty (returns a pointer to {NULL}). The previous code accessed dnsArr[1] unconditionally after checking dnsArr non-NULL, which reads past the single-element allocation when there are zero DNS servers configured. On the xione-uk platform, the device-level IP config has addresses from the kernel but no DNS servers via NetworkManager, so the returned strv is empty. Reading dnsArr[1] past the allocation boundary causes SIGSEGV on this embedded platform. Fix: only access dnsArr[1] when dnsArr[0] is confirmed non-NULL. Also use the correct const type to avoid the C-style cast. * fix(ip-cache): emit IP_LOST events on interface disconnect Three changes fix the missing IP_LOST events when disconnecting: 1. Call refreshIpFamilyCache in deviceStateChangeCb before reporting INTERFACE_LINK_DOWN or INTERFACE_REMOVED. When NM disconnects a device, it batches State and Ip4Config/Ip6Config property changes into a single D-Bus PropertiesChanged signal. libnm updates all properties atomically then emits notify signals in arbitrary order. If notify::state fires before notify::ip4-config, the cache was being cleared (by ReportInterfaceStateChange) before refreshIpFamilyCache could diff against it. By explicitly calling refreshIpFamilyCache from the state callback, the diff runs while the cache still has the old addresses, producing IP_LOST events through the canonical path with proper logging. 2. ReportInterfaceStateChange now emits IP_LOST for every cached address before clearing the cache. This is a safety net: if refreshIpFamilyCache already handled the transition (because notify::ip4-config fired first), the cache will be empty and this emits nothing. If it didn't, this catches any remaining addresses. 3. Move the nm_device_get_ip4/6_config() read outside the if(conn) gate in refreshIpFamilyCache. The device-level IP config does not require an active connection, so the cache can detect address presence or absence during teardown when the active connection is already gone. * fix(ip-cache): emit IP_LOST events reliably on interface disconnect - refreshIpFamilyCache now checks device state: when devState <= NM_DEVICE_STATE_DISCONNECTED, it skips the libnm read so newCache is empty and the diff emits IP_LOST for all cached addresses. This eliminates the signal-ordering dead zone where addresses were still present in libnm but about to be cleared. - Remove IP_LOST emission from ReportInterfaceStateChange (was duplicating the cache-clear + emit logic). That function now only manages interface state (connected flags, default interface, connectivity monitor). - Move the authoritative 'IP acquired:'/'IP lost:' log line into ReportIPAddressChange, which is the single guaranteed call site for every IP event regardless of origin. - Remove the now-redundant 'IP acquired:'/'IP lost:' prints from refreshIpFamilyCache's diff section. Verified: disconnect produces exactly one IP_LOST per cached address, no spurious IP_ACQUIRED, and IP_LOST events precede LINK_DOWN. * refactor: replace named IpFamilyCache fields with map-based storage Replace four hardcoded cache fields (m_ethIPv4Cache, m_wlanIPv4Cache, m_ethIPv6Cache, m_wlanIPv6Cache) with a single std::map keyed by (interface, ipFamily) pair. Add getIpCache() convenience accessor. Change toIPAddress(bool isIPv6) to toIPAddress() with auto-detection via inet_pton, since the cache is already partitioned by IP family. This removes ~60 lines of repetitive if/else-if interface selection boilerplate across all backends (GnomeProxy, GdbusClient, RDKProxy) and eliminates hardcoded interface-name assumptions from the storage layer. * fix(ip-cache): populate IPAddress.ula with actual ULA, not link-local The IPAddress.ula field was incorrectly being populated with IPv6 link-local addresses (fe80::/10). ULA (Unique Local Address, fc00::/7) is a different address class — analogous to RFC 1918 private IPv4. Changes: - Add isIPv6ULA() helper using the same bitmask as NetworkManager's nm_ip6_addr_is_ula(): (s6_addr32[0] & 0xfe000000) == 0xfc000000 - Add ulaAddress field to IpFamilyCache, separate from linkLocalAddress - Map IpFamilyCache::ulaAddress to IPAddress::ula in toIPAddress() - Add three-way IPv6 classification (link-local / ULA / global) in GnomeEvents, GnomeProxy, GdbusClient, GdbusEvent, and RDK proxy - ULA addresses are NOT reported as global: they do not go into globalAddresses and do not trigger IP_ACQUIRED/IP_LOST events - Fix pre-existing gdbus bug: globalAddresses.insert() now uses the correct {address, prefix} pair instead of bare string - Fix NetworkManager.json example to use valid ULA (fd00::/8 prefix) - Fix docs example: IPv4 result should have empty ula field - Replace IN_IS_ADDR_LINKLOCAL macro with isIPv4LinkLocal() inline helper for consistency with the IPv6 helpers; remove now-unnecessary arpa/inet.h includes from GnomeProxy and GnomeEvents * feat(ip-cache): filter MAC-based EUI-64 global IPv6 from GetIPSettings Store interface HW address in IpFamilyCache.macAddress (populated from nm_device_get_hw_address). toIPAddress() iterates globalAddresses preferring non-MAC-based globals; falls back to MAC-based if all are EUI-64 derived. Direct-read (fallback) path in GnomeProxy also filters at selection time. Adds isIPv6MacBased() helper to detect EUI-64 addresses derived from the interface MAC. * refactor(GetIPSettings): remove fallback path, serve exclusively from event-driven cache The event thread seeds the IP cache synchronously during startup (via refreshIpFamilyCache) before entering g_main_loop_run(), so the cache is always populated before any RPC can arrive. The fallback path that read directly from the RPC thread's m_nmClient was effectively dead code. Remove the ~250-line fallback (m_nmClient reads, connection lookups, IPv4/IPv6 address iteration, and fallback cache writes). On cache miss, return Core::ERROR_GENERAL with a warning log. Also remove the now-unused isAutoConnectEnabled() helper and its 10 local variable declarations that only served the fallback. * refactor(ip-cache): split address containers and move helpers out of header - Split IpFamilyCache into three containers: globalAddresses (map, event-diffable), linkLocalAddresses (set), uniqueLocalAddresses (set) - Move isIPv4LinkLocal, isIPv6LinkLocal, isIPv6ULA, toIPAddress from inline header definitions to NetworkManagerImplementation.cpp - Add explicit constructors to GlobalAddressInfo for C++11 compatibility - Simplify event diffing in refreshIpFamilyCache to operate on globalAddresses keys directly without type filtering * fix: address PR review feedback on IP cache helpers and proxy - isIPv6ULA: replace non-portable s6_addr32 with s6_addr[0] byte check - isIPv6MacBased: rewrite with binary EUI-64 comparison via inet_pton and memcmp, fixing broken string matching from inet_ntop zero suppression - Add parseMac() helper accepting both colon-separated and bare hex MACs - Add #include and for memcmp/sscanf - cleanupSignalHandlers: explicitly disconnect DHCP option callbacks - Remove stale 'GetIPSettings fallback path' comment - GetIPSettings: return ERROR_NONE with empty result when cache has no entry for the requested interface+family, since absence of an address is not an error - GetIPSettings: reset result to IPAddress{} with correct ipversion before cache lookup, and preserve requested family after toIPAddress() - Remove onAddressChangeCb L2 test: the old callback was replaced by event-driven cache diffs in refreshIpFamilyCache(); the test had no assertions and exercised only dead compatibility code - deviceAddedCB: seed IP cache after attaching signal handlers so GetIPSettings returns data immediately for hotplugged devices - Encapsulate m_ipCacheMap/m_ipCacheMutex as private; expose lookupIpCache() and swapIpCache() locked accessors to prevent unsynchronised access from outside the class - lookupIpCache returns IPAddress directly (via toIPAddress() under the lock) instead of copying the entire IpFamilyCache by value - refreshIpFamilyCache: make this an internal helper function - deviceRemovedCB: disconnect all IP-config, DHCP, and device-level signal handlers symmetrically with deviceAddedCB, and clear the IP cache (emitting IP_LOST events) to prevent stale entries and handler accumulation on device removal/re-addition * fix(gnome): use device-level DHCP config API to fix empty dhcpserver in GetIPSettings The ActiveConnection's Dhcp4Config property is not populated until the connection reaches ACTIVATED state, but ip4ChangedCb fires earlier (when addresses appear on the IP config object). At that point, nm_active_connection_get_dhcp4_config() returns NULL, causing refreshIpFamilyCache() to store an empty dhcpserver in the cache. Switch to nm_device_get_dhcp4_config() / nm_device_get_dhcp6_config() which read from the Device object's Dhcp4Config property. This property is set when the device enters IP_CONFIG state and its options are populated before the IP address appears — ensuring dhcpserver is available when the cache is built. Apply the same fix to DHCP signal handler connections in ip4ConfigChangedCb, ip6ConfigChangedCb, deviceAddedCB, networkMangerEventMonitor, deviceRemovedCB, and cleanupSignalHandlers. * build(backends): gate legacy IP members and export backend macros * test(libnm): update L1 tests for cache-based GetIPSettings and refreshIpFamilyCache - Remove 5 GetIPSettings error-path tests that exercised libnm API call sequences no longer present in the cache-based implementation - Replace 5 GetIPSettings data tests with cache-based equivalents that populate IpFamilyCache via swapIpCache() and verify the JSON-RPC response - Fix 5 event tests (disconnected/unmanaged/unknown for wlan0 and eth0) by changing nm_device_get_iface and nm_device_get_state from WillOnce to WillRepeatedly to accommodate additional calls from refreshIpFamilyCache() - Fix platformInit test: GetIPSettings now returns success:true with empty data for valid interfaces when the cache is empty * tests: fix segfault in cache tests by using _instance global The Instantiate mock on COMLink was never invoked because RootConfig parses ConfigLine looking for 'root' at the top level, but the test's JSON nests it inside 'configuration'. This caused Root() to take the in-process path via ServiceAdministrator, bypassing COMLink entirely. As a result, the NetworkManagerImpl ProxyType member was never populated (null _realObject). The new cache tests that call NetworkManagerImpl->swapIpCache() dereferenced null, crashing at mutex offset 0x3d0. Fix by using the Plugin::_instance global pointer which is set during platform_init() in the in-process instantiation path. * tests: add 9 new GetIPSettings test scenarios for IP cache behavior Add test coverage for IP cache and address selection logic: - IPv6 MAC-based fallback when all globals are MAC-derived - IPv6 prefer non-MAC-based global address selection - IPv6 only-cached request for IPv4 returns empty - Cache invalidation returns success:true with empty fields - IP version case insensitivity (ipv4/IPV6/etc) - IPv6 ULA-only cache (no global -> address fields omitted) - swapIpCache returns old global address keys - Separate IPv4/IPv6 caches per interface - Cache clearing via invalid cache swap * tests: add utility function tests for IP address classification Add 14 tests for pure utility functions introduced with IP cache: - isIPv4LinkLocal: link-local (169.254/16), non-link-local, invalid input - isIPv6LinkLocal: link-local (fe80::/10), non-link-local, invalid input - isIPv6ULA: ULA (fc00::/7 including fc and fd), non-ULA, invalid input - isIPv6MacBased: EUI-64 match with colon and plain hex MAC formats, non-matching, privacy extension, and invalid inputs (also exercises parseMac indirectly) * tests: add disconnect cache-clearing event tests Add 3 behavioral tests verifying that device disconnect events clear the IP cache: - disconnect_clears_ipv4_cache_eth0 - disconnect_clears_ipv6_cache_wlan0 - disconnect_clears_both_ip_family_caches Tests use public APIs (swapIpCache, deviceStateChangeCb, GetIPSettings) and assert observable behavior rather than internal NM API call sequences. * fix: restore IP address cache clearing on disconnect for RDK/GDBUS backends The m_eth*Address and m_wlan*Address fields were no longer being cleared in ReportInterfaceStateChange on link-down/remove, causing stale IP settings to be returned by GetIPSettings in the RDK and GDBUS backends. Restore the clearing, guarded by preprocessor checks for NM_BACKEND_GDBUS and NM_BACKEND_RDK since these fields are not defined for the libnm backend. Also fix lookupIpCache to set out.ipversion from the requested ipFamily rather than relying on toIPAddress() inference, which could default to "IPv4" when the cache is valid but contains no addresses. This makes lookupIpCache self-consistent for any future callers. --- CMakeLists.txt | 8 + definition/NetworkManager.json | 4 +- docs/NetworkManagerPlugin.md | 2 +- plugin/NetworkManagerImplementation.cpp | 159 ++++- plugin/NetworkManagerImplementation.h | 53 ++ plugin/gnome/NetworkManagerGnomeEvents.cpp | 437 +++++++----- plugin/gnome/NetworkManagerGnomeEvents.h | 1 - plugin/gnome/NetworkManagerGnomeProxy.cpp | 301 +-------- tests/l2Test/libnm/l2_test_libnmproxy.cpp | 639 ++++++++++-------- .../l2Test/libnm/l2_test_libnmproxyEvent.cpp | 146 +++- tests/l2Test/libnm/l2_test_libnmproxyInit.cpp | 2 +- 11 files changed, 975 insertions(+), 777 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a5d13178..c10e4331 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,14 @@ if(ENABLE_ETHERNET_CONNECTION_HANDLING) message(STATUS "Ethernet connection handling: enabled") endif() +# Backend identity macros are consumed by shared headers; define them globally +# so all targets (plugin, tests, tools) compile against the same API surface. +if(ENABLE_GNOME_NETWORKMANAGER AND ENABLE_GNOME_GDBUS) + add_compile_definitions(NM_BACKEND_GDBUS=1) +elseif(NOT ENABLE_GNOME_NETWORKMANAGER) + add_compile_definitions(NM_BACKEND_RDK=1) +endif() + if (USE_TELEMETRY) find_package(T2 REQUIRED) add_compile_definitions(USE_TELEMETRY=1) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 5e95f935..01f2517a 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -47,9 +47,9 @@ "example": 24 }, "ula": { - "summary": "The IPv6 Unified Local Address", + "summary": "The IPv6 Unique Local Address", "type": "string", - "example": "d00:410:2016::" + "example": "fd00:410:2016::" }, "gateway": { "summary": "The gateway address", diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index a15fc6a4..d095446b 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -453,7 +453,7 @@ Gets the IP setting for the given interface. "ipaddress": "192.168.1.101", "prefix": 24, "gateway": "192.168.1.1", - "ula": "d00:410:2016::", + "ula": "", "primarydns": "192.168.1.1", "secondarydns": "192.168.1.2", "success": true diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 35d6a6f4..b10b375b 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -19,6 +19,9 @@ #include #include +#include +#include +#include #include "NetworkManagerImplementation.h" #if USE_TELEMETRY @@ -671,8 +674,10 @@ namespace WPEFramework { if(interface == "eth0") { +#if defined(NM_BACKEND_GDBUS) || defined(NM_BACKEND_RDK) m_ethIPv4Address = {}; m_ethIPv6Address = {}; +#endif 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 @@ -680,8 +685,10 @@ namespace WPEFramework } else if(interface == "wlan0") { +#if defined(NM_BACKEND_GDBUS) || defined(NM_BACKEND_RDK) m_wlanIPv4Address = {}; m_wlanIPv6Address = {}; +#endif m_wlanConnected.store(false); bool triggerConnectivityCheck; if(m_ethConnected.load()) @@ -792,7 +799,9 @@ namespace WPEFramework } _notificationLock.Lock(); - NMLOG_INFO("Posting onIPAddressChange %s - %s", ipaddress.c_str(), interface.c_str()); + NMLOG_INFO("Posting onIPAddressChange %s: %s %s %s", + (Exchange::INetworkManager::IP_ACQUIRED == status) ? "IP acquired" : "IP lost", + interface.c_str(), ipversion.c_str(), ipaddress.c_str()); for (const auto callback : _notificationCallbacks) { callback->onIPAddressChange(interface, ipversion, ipaddress, status); } @@ -1320,5 +1329,153 @@ namespace WPEFramework } } } + + bool isIPv4LinkLocal(const std::string& addr) + { + struct in_addr sa{}; + return inet_pton(AF_INET, addr.c_str(), &sa) == 1 && + (ntohl(sa.s_addr) & 0xffff0000u) == 0xa9fe0000u; + } + + bool isIPv6LinkLocal(const std::string& addr) + { + struct in6_addr sa6{}; + return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && + sa6.s6_addr[0] == 0xfe && (sa6.s6_addr[1] & 0xc0) == 0x80; + } + + bool isIPv6ULA(const std::string& addr) + { + struct in6_addr sa6{}; + return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && + (sa6.s6_addr[0] & 0xfe) == 0xfc; + } + + /* Parse a MAC string into 6 bytes. Accepts both "AA:BB:CC:DD:EE:FF" and "aabbccddeeff". */ + static bool parseMac(const std::string& mac, uint8_t out[6]) + { + unsigned int b[6]; + if (mac.size() >= 17 && + sscanf(mac.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", + &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]) == 6) + { + for (int i = 0; i < 6; ++i) out[i] = static_cast(b[i]); + return true; + } + if (mac.size() >= 12 && + sscanf(mac.c_str(), "%02x%02x%02x%02x%02x%02x", + &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]) == 6) + { + for (int i = 0; i < 6; ++i) out[i] = static_cast(b[i]); + return true; + } + return false; + } + + bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr) + { + struct in6_addr sa6{}; + uint8_t mac[6]; + if (inet_pton(AF_INET6, ipv6Addr.c_str(), &sa6) != 1 || !parseMac(macAddr, mac)) + return false; + + /* Build 8-byte EUI-64 identifier from 6-byte MAC: + mac[0..2] | ff:fe | mac[3..5], then flip the U/L bit (bit 1) of byte 0. */ + uint8_t eui64[8]; + eui64[0] = mac[0] ^ 0x02; // flip Universal/Local bit + eui64[1] = mac[1]; + eui64[2] = mac[2]; + eui64[3] = 0xff; + eui64[4] = 0xfe; + eui64[5] = mac[3]; + eui64[6] = mac[4]; + eui64[7] = mac[5]; + + /* Compare against the interface-ID (last 8 bytes) of the IPv6 address. */ + if (memcmp(&sa6.s6_addr[8], eui64, 8) == 0) + { + NMLOG_DEBUG("MAC %s based global v6 address %s", macAddr.c_str(), ipv6Addr.c_str()); + return true; + } + return false; + } + + bool NetworkManagerImplementation::lookupIpCache( + const std::string& iface, const std::string& ipFamily, + Exchange::INetworkManager::IPAddress& out) const + { + std::lock_guard lock(m_ipCacheMutex); + auto it = m_ipCacheMap.find({iface, ipFamily}); + if (it != m_ipCacheMap.end() && it->second.valid) { + out = it->second.toIPAddress(); + out.ipversion = ipFamily; + return true; + } + return false; + } + + std::set NetworkManagerImplementation::swapIpCache( + const std::string& iface, const std::string& ipFamily, + IpFamilyCache newCache) + { + std::set oldKeys; + std::lock_guard lock(m_ipCacheMutex); + IpFamilyCache& cache = m_ipCacheMap[{iface, ipFamily}]; + for (const auto& kv : cache.globalAddresses) + oldKeys.insert(kv.first); + cache = std::move(newCache); + return oldKeys; + } + + Exchange::INetworkManager::IPAddress IpFamilyCache::toIPAddress() const + { + Exchange::INetworkManager::IPAddress addr{}; + /* Detect IP version from any available address. */ + bool isIPv6 = false; + { + const std::string* sample = nullptr; + if (!globalAddresses.empty()) + sample = &globalAddresses.begin()->first; + else if (!uniqueLocalAddresses.empty()) + sample = &(*uniqueLocalAddresses.begin()); + else if (!linkLocalAddresses.empty()) + sample = &(*linkLocalAddresses.begin()); + if (sample) { + struct in6_addr sa6{}; + isIPv6 = (inet_pton(AF_INET6, sample->c_str(), &sa6) == 1); + } + } + addr.ipversion = isIPv6 ? "IPv6" : "IPv4"; + addr.autoconfig = autoconfig; + addr.dhcpserver = dhcpserver; + addr.ula = uniqueLocalAddresses.empty() ? "" : *uniqueLocalAddresses.begin(); + addr.gateway = gateway; + addr.primarydns = primarydns; + addr.secondarydns = secondarydns; + /* Prefer non-MAC-based global; fall back to MAC-based if all are MAC-based. */ + const std::string* bestGlobal = nullptr; + uint32_t bestPrefix = 0; + const std::string* fallbackMac = nullptr; + uint32_t fallbackMacPrefix = 0; + for (const auto& kv : globalAddresses) { + if (kv.second.type == ADDR_GLOBAL) { + bestGlobal = &kv.first; + bestPrefix = kv.second.prefix; + break; + } + if (!fallbackMac) { + fallbackMac = &kv.first; + fallbackMacPrefix = kv.second.prefix; + } + } + if (bestGlobal) { + addr.ipaddress = *bestGlobal; + addr.prefix = bestPrefix; + } else if (fallbackMac) { + addr.ipaddress = *fallbackMac; + addr.prefix = fallbackMacPrefix; + } + return addr; + } } } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index bff75ba7..6f4ab5bd 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -26,8 +26,10 @@ #include #include #include +#include #include #include +#include using namespace std; @@ -64,6 +66,47 @@ namespace WPEFramework { namespace Plugin { + /* Returns true if the given string is an IPv4 link-local address (169.254.0.0/16). */ + bool isIPv4LinkLocal(const std::string& addr); + + /* Returns true if the given string is an IPv6 link-local address (fe80::/10). */ + bool isIPv6LinkLocal(const std::string& addr); + + /* Returns true if the given string is an IPv6 Unique Local Address (ULA, fc00::/7). */ + bool isIPv6ULA(const std::string& addr); + + /* Returns true if the given global IPv6 address is derived (EUI-64) from the MAC. */ + bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr); + + /* Sub-classification of global-scope addresses in the IP cache. */ + enum GlobalAddressType : uint8_t { + ADDR_GLOBAL, // non-MAC-based global (preferred by GetIPSettings) + ADDR_GLOBAL_MAC_BASED, // EUI-64 global derived from interface MAC (fallback) + }; + + struct GlobalAddressInfo { + uint32_t prefix; + GlobalAddressType type; + GlobalAddressInfo() : prefix(0), type(ADDR_GLOBAL) {} + GlobalAddressInfo(uint32_t p, GlobalAddressType t) : prefix(p), type(t) {} + }; + + /* Per-interface, per-address-family cache populated by libnm events. */ + struct IpFamilyCache { + bool valid = false; + std::map globalAddresses; // event-diffable global addresses + std::set linkLocalAddresses; // fe80::/10 or 169.254.x.x — not diffed for events + std::set uniqueLocalAddresses; // fc00::/7 (IPv6 only) — not diffed for events + std::string gateway; + std::string primarydns; + std::string secondarydns; + std::string dhcpserver; + bool autoconfig = false; + + Exchange::INetworkManager::IPAddress toIPAddress() const; + void clear() { *this = IpFamilyCache{}; } + }; + class NetworkManagerImplementation : public Exchange::INetworkManager , public INetworkPowerCallback { @@ -328,10 +371,18 @@ namespace WPEFramework std::condition_variable m_condVariable; std::unique_ptr m_powerClient; public: +#if defined(NM_BACKEND_GDBUS) || defined(NM_BACKEND_RDK) IPAddress m_ethIPv4Address; IPAddress m_wlanIPv4Address; IPAddress m_ethIPv6Address; IPAddress m_wlanIPv6Address; +#endif + bool lookupIpCache(const std::string& iface, const std::string& ipFamily, + Exchange::INetworkManager::IPAddress& out) const; + std::set swapIpCache(const std::string& iface, + const std::string& ipFamily, + IpFamilyCache newCache); + std::atomic m_ethConnected; std::atomic m_wlanConnected; std::atomic m_ethEnabled; @@ -358,6 +409,8 @@ namespace WPEFramework private: string m_defaultInterface; mutable std::mutex m_defaultInterfaceMutex; + std::map, IpFamilyCache> m_ipCacheMap; + mutable std::mutex m_ipCacheMutex; }; } } diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 761c39fb..7a87cc20 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -30,6 +30,8 @@ #include "NetworkManagerGnomeUtils.h" #include "NetworkManagerImplementation.h" #include "INetworkManager.h" +#include + #ifdef ENABLE_MIGRATION_MFRMGR_SUPPORT #include "NetworkManagerGnomeMfrMgr.h" #endif @@ -74,6 +76,198 @@ namespace WPEFramework } } + /* Refresh the per-interface/per-family IP cache from current libnm state and + emit acquired/lost events for address-set differences. + + Build a fresh IpFamilyCache from current libnm state for one device/family, + swap it into _instance under the cache mutex, then emit acquired/lost events + for any address-set differences outside the lock. */ + static void refreshIpFamilyCache(NMDevice* device, bool isIPv6) + { + if (!device || !NM_IS_DEVICE(device) || !_instance) + return; + + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; + + bool isEth = (ifname == nmUtils::ethIface()); + bool isWlan = (ifname == nmUtils::wlanIface()); + if (!isEth && !isWlan) return; + + /* Build the new snapshot locally (no locks held during NM calls). + * Skip the NM read when the device is in a disconnected/down state + * so that newCache stays empty and the diff emits IP_LOST for every + * address still in the cache. This also prevents spurious + * "IP acquired" events from intermediate NM signals (nameserver, + * gateway clearing) that fire after the cache has been emptied + * but before NM clears addresses on the config object. */ + NMDeviceState devState = nm_device_get_state(device); + bool skipRead = (devState <= NM_DEVICE_STATE_DISCONNECTED); + IpFamilyCache newCache; + NMActiveConnection* conn = skipRead ? nullptr : nm_device_get_active_connection(device); + if (conn) { + /* autoconfig: method "auto" or "dhcp" → true */ + NMConnection* nmConn = NM_CONNECTION(nm_active_connection_get_connection(conn)); + if (nmConn) { + NMSettingIPConfig* ipSetting = isIPv6 + ? NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(nmConn)) + : NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(nmConn)); + if (ipSetting) { + const char* method = nm_setting_ip_config_get_method(ipSetting); + newCache.autoconfig = method && + (g_strcmp0(method, "auto") == 0 || g_strcmp0(method, "dhcp") == 0); + } + } + } + + /* IP config read is device-level and does not require an active connection. */ + NMIPConfig* ipConfig = skipRead ? nullptr + : (isIPv6 ? nm_device_get_ip6_config(device) + : nm_device_get_ip4_config(device)); + + if (ipConfig) { + GPtrArray* ipAddresses = nm_ip_config_get_addresses(ipConfig); + std::string macAddr; + if (isIPv6) { + const char* hw = nm_device_get_hw_address(device); + if (hw) macAddr = hw; + } + if (ipAddresses) { + for (guint i = 0; i < ipAddresses->len; i++) { + NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(ipAddresses, i); + if (!addr) continue; + const char* addrStr = nm_ip_address_get_address(addr); + if (!addrStr) continue; + std::string addrString = addrStr; + uint32_t prefix = nm_ip_address_get_prefix(addr); + if (isIPv6) { + if (isIPv6LinkLocal(addrString)) { + newCache.linkLocalAddresses.insert(addrString); + } else if (isIPv6ULA(addrString)) { + newCache.uniqueLocalAddresses.insert(addrString); + } else { + GlobalAddressType type = (!macAddr.empty() && isIPv6MacBased(addrString, macAddr)) + ? ADDR_GLOBAL_MAC_BASED : ADDR_GLOBAL; + newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, type}); + } + } else { + if (isIPv4LinkLocal(addrString)) { + newCache.linkLocalAddresses.insert(addrString); + } else { + newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, ADDR_GLOBAL}); + } + } + } + } + + const char* gw = nm_ip_config_get_gateway(ipConfig); + if (gw) newCache.gateway = gw; + + const char* const* dnsArr = nm_ip_config_get_nameservers(ipConfig); + if (dnsArr && dnsArr[0]) { + newCache.primarydns = dnsArr[0]; + if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; + } + + NMDhcpConfig* dhcpConfig = isIPv6 + ? nm_device_get_dhcp6_config(device) + : nm_device_get_dhcp4_config(device); + if (dhcpConfig) { + const char* server = nm_dhcp_config_get_one_option(dhcpConfig, "dhcp_server_identifier"); + if (server) newCache.dhcpserver = server; + } + + newCache.valid = true; + } + + /* Swap new snapshot into instance cache; collect old global address keys for diff. */ + std::set oldKeys = _instance->swapIpCache( + ifname, isIPv6 ? "IPv6" : "IPv4", newCache); + + /* Emit address acquired/lost events from global-address key diff (outside the lock). */ + std::string family = isIPv6 ? "IPv6" : "IPv4"; + for (const auto& kv : newCache.globalAddresses) { + if (oldKeys.find(kv.first) == oldKeys.end()) { + _instance->ReportIPAddressChange(ifname, family, kv.first, Exchange::INetworkManager::IP_ACQUIRED); + } + } + for (const auto& key : oldKeys) { + if (newCache.globalAddresses.find(key) == newCache.globalAddresses.end()) { + _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); + } + } + } + + static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, false); + } + + static void ip6ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, true); + } + + /* Called when DHCP options change mid-lease (e.g. renewed with different server/options). */ + static void dhcp4OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, false); + } + + static void dhcp6OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, true); + } + + /* Called when the ip4-config or ip6-config object on a device is replaced + (e.g. after reconnect). Re-attaches notify handlers to the new object. */ + static void ip4ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) + { + if (!device || !NM_IS_DEVICE(device)) return; + NMIPConfig* ipv4Config = nm_device_get_ip4_config(device); + if (ipv4Config) { + g_signal_handlers_disconnect_by_func(ipv4Config, (gpointer)ip4ChangedCb, device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); + } + /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ + NMDhcpConfig* dhcp4 = nm_device_get_dhcp4_config(device); + if (dhcp4) { + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + g_signal_connect(dhcp4, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + } + refreshIpFamilyCache(device, false); + } + + static void ip6ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) + { + if (!device || !NM_IS_DEVICE(device)) return; + NMIPConfig* ipv6Config = nm_device_get_ip6_config(device); + if (ipv6Config) { + g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); + } + /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ + NMDhcpConfig* dhcp6 = nm_device_get_dhcp6_config(device); + if (dhcp6) { + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); + g_signal_connect(dhcp6, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); + } + refreshIpFamilyCache(device, true); + } + void GnomeNetworkManagerEvents::deviceStateChangeCb(NMDevice *device, GParamSpec *pspec, NMEvents *nmEvents) { static bool isEthDisabled = false; @@ -125,11 +319,15 @@ namespace WPEFramework case NM_DEVICE_STATE_UNKNOWN: wifiState = "WIFI_STATE_UNINSTALLED"; GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_UNINSTALLED); + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); break; case NM_DEVICE_STATE_UNMANAGED: wifiState = "WIFI_STATE_DISABLED"; GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISABLED); + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); isWlanDisabled = true; break; @@ -137,6 +335,8 @@ namespace WPEFramework case NM_DEVICE_STATE_DISCONNECTED: wifiState = "WIFI_STATE_DISCONNECTED"; GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISCONNECTED); + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_LINK_DOWN, nmUtils::wlanIface()); break; case NM_DEVICE_STATE_PREPARE: @@ -212,11 +412,15 @@ namespace WPEFramework { case NM_DEVICE_STATE_UNKNOWN: case NM_DEVICE_STATE_UNMANAGED: + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::ethIface()); isEthDisabled = true; break; case NM_DEVICE_STATE_UNAVAILABLE: case NM_DEVICE_STATE_DISCONNECTED: + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_LINK_DOWN, nmUtils::ethIface()); break; case NM_DEVICE_STATE_PREPARE: @@ -261,101 +465,6 @@ namespace WPEFramework } } - static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) - { - if (!ipConfig) { - NMLOG_ERROR("IP config is null"); - return; - } - - NMDevice *device = (NMDevice*)userData; - if((device == NULL) || (!NM_IS_DEVICE(device))) - return; - - const char* iface = nm_device_get_iface(device); - if(iface == NULL) - return; - std::string ifname = iface; - - GPtrArray *addresses = nm_ip_config_get_addresses(ipConfig); - if (!addresses) { - NMLOG_ERROR("No addresses found"); - return; - } - else { - if(addresses->len == 0) { - GnomeNetworkManagerEvents::onAddressChangeCb(ifname, "", false, false); - return; - } - } - - for (guint i = 0; i < addresses->len; ++i) { - NMIPAddress *address = (NMIPAddress *)g_ptr_array_index(addresses, i); - if(address == NULL) - { - NMLOG_WARNING("IPv4 address is null"); - continue; - } - if (nm_ip_address_get_family(address) == AF_INET) { - const char *ipAddress = nm_ip_address_get_address(address); - if(ipAddress != NULL) { - GnomeNetworkManagerEvents::onAddressChangeCb(iface, ipAddress, true, false); - } - } - } - } - - static void ip6ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) - { - if (!ipConfig) { - NMLOG_ERROR("ip config is null"); - return; - } - - NMDevice *device = (NMDevice*)userData; - if( ((device != NULL) && NM_IS_DEVICE(device)) ) - { - const char* iface = nm_device_get_iface(device); - if(iface == NULL) - return; - std::string ifname = iface; - GPtrArray *addresses = nm_ip_config_get_addresses(ipConfig); - if (!addresses) { - NMLOG_ERROR("No addresses found"); - return; - } - else { - if(addresses->len == 0) { - GnomeNetworkManagerEvents::onAddressChangeCb(ifname, "", false, true); - return; - } - } - - for (guint i = 0; i < addresses->len; ++i) { - NMIPAddress *address = (NMIPAddress *)g_ptr_array_index(addresses, i); - if(address == NULL) - { - NMLOG_WARNING("IPv6 address is null"); - continue; - } - if (nm_ip_address_get_family(address) == AF_INET6) { - const char *ipaddr = nm_ip_address_get_address(address); - //int prefix = nm_ip_address_get_prefix(address); - if(ipaddr != NULL) { - std::string ipAddress = ipaddr; - if (ipAddress.compare(0, 5, "fe80:") == 0 || - ipAddress.compare(0, 6, "fe80::") == 0) { - NMLOG_DEBUG("%s It's link-local ip", ipAddress.c_str()); - continue; // It's link-local so skiping - } - GnomeNetworkManagerEvents::onAddressChangeCb(iface, ipAddress, true, true); - break; // SLAAC protocol may include multip ipv6 address posting only one Global address - } - } - } - } - } - static void deviceAddedCB(NMClient *client, NMDevice *device, NMEvents *nmEvents) { if( ((device != NULL) && NM_IS_DEVICE(device)) ) @@ -374,17 +483,35 @@ namespace WPEFramework if(ifname == nmUtils::ethIface() || ifname == nmUtils::wlanIface()) { g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); - // TODO call notify::" NM_DEVICE_ACTIVE_CONNECTION if needed + g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); + g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); if (ipv4Config) { - g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); } if (ipv6Config) { - g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); } + /* Subscribe to DHCP option changes so dhcpserver stays current mid-lease. */ + NMDhcpConfig* dhcp4Added = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6Added = nm_device_get_dhcp6_config(device); + if (dhcp4Added) + g_signal_connect(dhcp4Added, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + if (dhcp6Added) + g_signal_connect(dhcp6Added, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); + + /* Seed the IP cache so GetIPSettings works immediately if the + device already has an address (e.g. hotplug in activated state). */ + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); + if (NM_IS_DEVICE_WIFI(device)) { // Register signal handler for WiFi scanning events to detect when scan operations complete @@ -405,14 +532,45 @@ namespace WPEFramework std::string ifname = nm_device_get_iface(device); if(ifname == nmUtils::wlanIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); - g_signal_handlers_disconnect_by_func(device, (gpointer)GnomeNetworkManagerEvents::deviceStateChangeCb, nmEvents); NMLOG_INFO("WIFI device removed: %s", ifname.c_str()); } else if(ifname == nmUtils::ethIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::ethIface()); - g_signal_handlers_disconnect_by_func(device, (gpointer)GnomeNetworkManagerEvents::deviceStateChangeCb, nmEvents); NMLOG_INFO("ETHERNET device removed: %s", ifname.c_str()); } + else { + return; // not a tracked interface + } + + /* Disconnect all device-level signals (state, ip4/ip6-config changes). */ + g_signal_handlers_disconnect_by_data(device, nmEvents); + + /* Disconnect IP config property signals (addresses, gateway, nameservers). */ + NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); + NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); + if (ipv4Config) + g_signal_handlers_disconnect_by_func(ipv4Config, (gpointer)ip4ChangedCb, device); + if (ipv6Config) + g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); + + /* Disconnect DHCP option signals. */ + NMDhcpConfig* dhcp4 = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6 = nm_device_get_dhcp6_config(device); + if (dhcp4) + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + if (dhcp6) + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); + + /* Clear IP cache for the removed device (emits IP_LOST for any cached addresses). */ + if (_instance) { + for (const char* family : {"IPv4", "IPv6"}) { + IpFamilyCache empty; + std::set oldKeys = _instance->swapIpCache(ifname, family, empty); + for (const auto& key : oldKeys) { + _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); + } + } + } } // guint disconnected_count = g_signal_handlers_disconnect_matched( _nmEventInstance->activeConn, @@ -492,6 +650,8 @@ namespace WPEFramework /* Register device state change event */ g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); + g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); + g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); if(NM_IS_DEVICE_WIFI(device)) { nmEvents->wifiDevice = NM_DEVICE_WIFI(device); g_signal_connect(nmEvents->wifiDevice, "notify::" NM_DEVICE_WIFI_LAST_SCAN, G_CALLBACK(GnomeNetworkManagerEvents::onAvailableSSIDsCb), nmEvents); @@ -500,18 +660,32 @@ namespace WPEFramework NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); if (ipv4Config) { - ip4ChangedCb(ipv4Config, NULL, device); // posting event if interface already connected - g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); } else NMLOG_WARNING("IPv4 config is null for device: %s, No IPv4 monitor", ifname.c_str()); if (ipv6Config) { - ip6ChangedCb(ipv6Config, NULL, device); - g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); } else NMLOG_WARNING("IPv6 config is null for device: %s, No IPv6 monitor", ifname.c_str()); + + /* Subscribe to DHCP option changes so dhcpserver stays current mid-lease. */ + NMDhcpConfig* dhcp4Init = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6Init = nm_device_get_dhcp6_config(device); + if (dhcp4Init) + g_signal_connect(dhcp4Init, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + if (dhcp6Init) + g_signal_connect(dhcp6Init, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); + + /* Seed the IP cache from current state for already-connected devices. */ + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); } else NMLOG_DEBUG("device type not eth/wifi %s", ifname.c_str()); @@ -593,6 +767,14 @@ namespace WPEFramework if (ipv6Config) { g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); } + + // Clean up DHCP option signals + NMDhcpConfig* dhcp4 = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6 = nm_device_get_dhcp6_config(device); + if (dhcp4) + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + if (dhcp6) + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); } } } @@ -714,53 +896,6 @@ namespace WPEFramework } } - void GnomeNetworkManagerEvents::onAddressChangeCb(std::string iface, std::string ipAddress, bool acquired, bool isIPv6) - { - /* - * notify::addresses g signal only send ipaddress when accuired time only. - * we need to post ip address when ipaddress lost case also so we caching the ip address per interface - */ - static std::map ipv6Map; - static std::map ipv4Map; - - if(acquired) - { - if (isIPv6) - { - if (ipv6Map[iface].find(ipAddress) == std::string::npos) { // same ip comes multiple time so avoding that - ipv6Map[iface] = ipAddress; - } - else // same ip not posting - return; - } - else - { - ipv4Map[iface] = ipAddress; - } - } - else - { - if (isIPv6) - { - ipAddress = ipv6Map[iface]; - ipv6Map[iface].clear(); - } - else - { - ipAddress = ipv4Map[iface]; - ipv4Map[iface].clear(); - } - if(ipAddress.empty()) - return; // empty ip address not posting event - } - Exchange::INetworkManager::IPStatus ipStatus{}; - if (acquired) - ipStatus = Exchange::INetworkManager::IP_ACQUIRED; - if(_instance != nullptr) - _instance->ReportIPAddressChange(iface, isIPv6?"IPv6":"IPv4", ipAddress, ipStatus); - NMLOG_INFO("iface:%s - ipaddress:%s - %s - %s", iface.c_str(), ipAddress.c_str(), acquired?"acquired":"lost", isIPv6?"isIPv6":"isIPv4"); - } - bool GnomeNetworkManagerEvents::apToJsonObject(NMAccessPoint *ap, JsonObject& ssidObj) { GBytes *ssid = NULL; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index ee29ce6d..bf282de7 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -44,7 +44,6 @@ namespace WPEFramework public: static void onInterfaceStateChangeCb(uint8_t newState, std::string iface); // ReportInterfaceStateChange - static void onAddressChangeCb(std::string iface, std::string ipAddress, bool acqired, bool isIPv6); // ReportIPAddressChange static void onActiveInterfaceChangeCb(std::string newInterface); // ReportActiveInterfaceChange static void onAvailableSSIDsCb(NMDeviceWifi *wifiDevice, GParamSpec *pspec, gpointer userData); // ReportAvailableSSIDs static void onWIFIStateChanged(uint8_t state); // ReportWiFiStateChange diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 81d1e933..234a8df9 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -22,9 +22,6 @@ #include "NetworkManagerGnomeUtils.h" #include #include -#include - -#define IN_IS_ADDR_LINKLOCAL(a) ((((uint32_t)ntohl(a)) & 0xffff0000U) == 0xa9fe0000U) using namespace WPEFramework; using namespace WPEFramework::Plugin; using namespace std; @@ -656,40 +653,9 @@ namespace WPEFramework return Core::ERROR_GENERAL; } - bool static isAutoConnectEnabled(NMActiveConnection* activeConn) - { - NMConnection *connection = NM_CONNECTION(nm_active_connection_get_connection(activeConn)); - if(connection == NULL) - return false; - - NMSettingIPConfig *ipConfig = nm_connection_get_setting_ip4_config(connection); - if(ipConfig) - { - const char* ipConfMethod = nm_setting_ip_config_get_method (ipConfig); - if(ipConfMethod != NULL && g_strcmp0(ipConfMethod, "auto") == 0) - return true; - else - NMLOG_WARNING("ip configuration: %s", ipConfMethod != NULL? ipConfMethod: "null"); - } - - return false; - } - /* @brief Get IP Address Of the Interface */ uint32_t NetworkManagerImplementation::GetIPSettings(string& interface /* @inout */, const string &ipversion /* @in */, IPAddress& result /* @out */) { - NMActiveConnection *conn = NULL; - NMIPConfig *ip4_config = NULL; - NMIPConfig *ip6_config = NULL; - const gchar *gateway = NULL; - char **dnsArr = NULL; - NMDhcpConfig *dhcp4_config = NULL; - NMDhcpConfig *dhcp6_config = NULL; - const char* dhcpserver; - NMSettingConnection *settings = NULL; - NMDevice *device = NULL; - string ipversionStr; - std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); if(interface.empty()) @@ -712,271 +678,22 @@ namespace WPEFramework return Core::ERROR_GENERAL; } - if(ipversion.empty()) - { - ipversionStr = "IPV4"; - } - else - { - ipversionStr = ipversion; - } - - // Add caching optimization similar to RDK proxy - if (wifiname == interface) - { - if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && !m_wlanIPv4Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv4 address from cache", wifiname.c_str()); - result = m_wlanIPv4Address; - return Core::ERROR_NONE; - } - else if(nmUtils::caseInsensitiveCompare(ipversion, "IPV6") && !m_wlanIPv6Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv6 address from cache", wifiname.c_str()); - result = m_wlanIPv6Address; - return Core::ERROR_NONE; - } - } - else if (ethname == interface) - { - if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && !m_ethIPv4Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv4 address from cache", ethname.c_str()); - result = m_ethIPv4Address; - return Core::ERROR_NONE; - } - else if(nmUtils::caseInsensitiveCompare(ipversion, "IPV6") && !m_ethIPv6Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv6 address from cache", ethname.c_str()); - result = m_ethIPv6Address; - return Core::ERROR_NONE; - } - } - - if(m_nmClient == nullptr) - { - NMLOG_WARNING("NMClient is null"); - return Core::ERROR_RPC_CALL_FAILED; - } - - /* Drain any pending D-Bus property-change events queued on m_nmContext - * before reading libnm GObject state. Because m_nmContext is isolated - * from the event thread, nobody else can run it — so this loop is - * single-threaded and safe. It ensures m_nmClient reflects the latest state - * from NetworkManager before we start iterating connections/addresses. */ - if (m_nmContext) { - for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ - // Intentional empty body: just flushing the event queue - } - } - - device = nm_client_get_device_by_iface(m_nmClient, interface.c_str()); - if (device == NULL) - { - NMLOG_FATAL("libnm doesn't have device corresponding to %s", interface.c_str()); - return Core::ERROR_GENERAL; - } - - NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; - deviceState = nm_device_get_state(device); - if(deviceState < NM_DEVICE_STATE_DISCONNECTED) - { - NMLOG_WARNING("%s state is not a valid state: (%d)", interface.c_str(), deviceState); - return Core::ERROR_GENERAL; - } + string ipversionStr = ipversion.empty() ? "IPv4" : ipversion; + std::string family = nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6") ? "IPv6" : "IPv4"; - // if(ipversion.empty()) - // NMLOG_DEBUG("ipversion is empty default value IPv4"); + result = IPAddress{}; - const GPtrArray *connections = nm_client_get_active_connections(m_nmClient); - if(connections == NULL) + // Serve from event-driven cache + if (lookupIpCache(interface, family, result)) { - NMLOG_WARNING("no active connection; ip is not assigned to interface"); - return Core::ERROR_GENERAL; + NMLOG_DEBUG("%s %s address from cache", interface.c_str(), family.c_str()); } - - for (guint i = 0; i < connections->len; i++) - { - if(connections->pdata[i] == NULL) - continue; - - NMActiveConnection *connection = NM_ACTIVE_CONNECTION(connections->pdata[i]); - if (connection == nullptr) - continue; - - NMRemoteConnection* retConn = nm_active_connection_get_connection(connection); - if(retConn == NULL) - { - NMLOG_INFO("remote connection is null"); - continue; - } - - settings = nm_connection_get_setting_connection(NM_CONNECTION(retConn)); - if(settings == NULL) - continue; - if (g_strcmp0(nm_setting_connection_get_interface_name(settings), interface.c_str()) == 0) - { - conn = connection; - break; - } - } - - if (conn == NULL) + else { - NMLOG_WARNING("no active connection on %s interface", interface.c_str()); - return Core::ERROR_GENERAL; + NMLOG_DEBUG("no %s address on %s", family.c_str(), interface.c_str()); } + result.ipversion = family; - result.autoconfig = isAutoConnectEnabled(conn); - - if(ipversion.empty() || nmUtils::caseInsensitiveCompare(ipversion, "IPV4")) - { - const GPtrArray *ipByte = nullptr; - result.ipversion = "IPv4"; - ip4_config = nm_active_connection_get_ip4_config(conn); - NMIPAddress *ipAddr = NULL; - std::string ipStr; - struct sockaddr_in sa; - if (ip4_config) - ipByte = nm_ip_config_get_addresses(ip4_config); - else - NMLOG_WARNING("no IPv4 configurtion on %s", interface.c_str()); - if(ipByte) - { - for (guint i = 0; i < ipByte->len; i++) - { - ipStr.clear(); - ipAddr = static_cast(ipByte->pdata[i]); - if(ipAddr) - { - const char* addr = nm_ip_address_get_address(ipAddr); - if(addr) - ipStr = addr; - } - if(!ipStr.empty()) - { - // Skip link-local IPv4 addresses (169.254.x.x) - inet_pton(AF_INET, ipStr.c_str(), &(sa.sin_addr)); - if(IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr)) - { - NMLOG_DEBUG("Skipping link-local IPv4 address: %s", ipStr.c_str()); - continue; - } - result.ipaddress = ipStr; - result.prefix = nm_ip_address_get_prefix(ipAddr); - NMLOG_DEBUG("IPv4 addr: %s/%d", result.ipaddress.c_str(), result.prefix); - } - } - gateway = nm_ip_config_get_gateway(ip4_config); - if(gateway) - result.gateway = gateway; - dnsArr = (char **)nm_ip_config_get_nameservers(ip4_config); - if(dnsArr) - { - if(dnsArr[0]) - result.primarydns = std::string(dnsArr[0]); - if(dnsArr[1]) - result.secondarydns = std::string(dnsArr[1]); - } - dhcp4_config = nm_active_connection_get_dhcp4_config(conn); - if(dhcp4_config) - { - dhcpserver = nm_dhcp_config_get_one_option (dhcp4_config, "dhcp_server_identifier"); - if(dhcpserver) - result.dhcpserver = dhcpserver; - } - result.ula = ""; - - // Check if only link-local IPv4 is available (no valid global address found) - if(result.ipaddress.empty()) - { - NMLOG_WARNING("Only link-local IPv4 available on %s, not returning it", interface.c_str()); - // Clear cache for link-local only - if(ethname == interface) - m_ethIPv4Address = IPAddress(); - else if(wifiname == interface) - m_wlanIPv4Address = IPAddress(); - return Core::ERROR_GENERAL; - } - - // Cache the IPv4 address - if(ethname == interface) - m_ethIPv4Address = result; - else if(wifiname == interface) - m_wlanIPv4Address = result; - } - } - if((result.ipaddress.empty() && !(nmUtils::caseInsensitiveCompare(ipversion, "IPV4"))) || nmUtils::caseInsensitiveCompare(ipversion, "IPV6")) - { - std::string ipStr; - const GPtrArray *ipArray = nullptr; - result.ipversion = "IPv6"; - NMIPAddress *ipAddr = nullptr; - ip6_config = nm_active_connection_get_ip6_config(conn); - if(ip6_config) - ipArray = nm_ip_config_get_addresses(ip6_config); - else - NMLOG_WARNING("no IPv6 configurtion on %s", interface.c_str()); - if(ipArray) - { - for (guint i = 0; i < ipArray->len; i++) - { - ipStr.clear(); - ipAddr = static_cast(ipArray->pdata[i]); - if(ipAddr) - { - const char* addr = nm_ip_address_get_address(ipAddr); - if(addr) - ipStr = addr; - } - if(!ipStr.empty()) - { - if (ipStr.compare(0, 5, "fe80:") == 0 || ipStr.compare(0, 6, "fe80::") == 0) - { - result.ula = ipStr; - NMLOG_DEBUG("link-local ip: %s", result.ula.c_str()); - } - else - { - result.prefix = nm_ip_address_get_prefix(ipAddr); - if(result.ipaddress.empty()) // SLAAC mutiple ip not added - result.ipaddress = ipStr; - NMLOG_DEBUG("global ip %s/%d", ipStr.c_str(), result.prefix); - } - } - } - - gateway = nm_ip_config_get_gateway(ip6_config); - if(gateway) - result.gateway= gateway; - dnsArr = (char **)nm_ip_config_get_nameservers(ip6_config); - if(dnsArr) - { - if(dnsArr[0]) - result.primarydns = std::string(dnsArr[0]); - if(dnsArr[1]) - result.secondarydns = std::string(dnsArr[1]); - } - dhcp6_config = nm_active_connection_get_dhcp6_config(conn); - if(dhcp6_config) - { - dhcpserver = nm_dhcp_config_get_one_option (dhcp6_config, "dhcp_server_identifier"); - if(dhcpserver) - result.dhcpserver = dhcpserver; - } - // Cache the IPv6 address - if(ethname == interface) - m_ethIPv6Address = result; - else if(wifiname == interface) - m_wlanIPv6Address = result; - } - } - if(result.ipaddress.empty()) - { - result.autoconfig = true; - if(ipversion.empty()) - result.ipversion = "IPv4"; - } return Core::ERROR_NONE; } diff --git a/tests/l2Test/libnm/l2_test_libnmproxy.cpp b/tests/l2Test/libnm/l2_test_libnmproxy.cpp index e4f9b1ec..0a3dad69 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxy.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxy.cpp @@ -40,6 +40,8 @@ using namespace WPEFramework; using ::testing::NiceMock; +namespace WPEFramework { namespace Plugin { extern NetworkManagerImplementation* _instance; } } + class NetworkManagerTest : public ::testing::Test { protected: Core::ProxyType plugin; @@ -515,24 +517,15 @@ TEST_F(NetworkManagerTest, GetIPSettings_unknown_iface) EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_invalidDevice) -{ - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); -} - -TEST_F(NetworkManagerTest, GetIPSettings_invalid_state) +TEST_F(NetworkManagerTest, GetIPSettings_emptyCache) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); - + /* With no cache populated, GetIPSettings should still succeed but return no IP data */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv4\"") != std::string::npos); + /* No ipaddress key when cache is empty */ + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); } TEST_F(NetworkManagerTest, GetIPSettings_interface_Empty) @@ -548,348 +541,408 @@ TEST_F(NetworkManagerTest, GetIPSettings_GetPrimary_failed) EXPECT_EQ(Core::ERROR_GENERAL, NetworkManagerImpl2->GetIPSettings(interface, ipversion, address)); } -TEST_F(NetworkManagerTest, GetIPSettings_Invalid_ActiveConnection) +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_fromCache) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate IPv4 cache for eth0, then verify GetIPSettings returns cached data */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.globalAddresses["192.168.1.2"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.1"; + cache.primarydns = "8.8.8.8"; + cache.secondarydns = "8.8.4.4"; + cache.dhcpserver = "192.168.1.11"; + cache.autoconfig = true; + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv4\"") != std::string::npos); + EXPECT_TRUE(response.find("\"autoconfig\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.2\"") != std::string::npos); + EXPECT_TRUE(response.find("\"prefix\":24") != std::string::npos); + EXPECT_TRUE(response.find("\"gateway\":\"192.168.1.1\"") != std::string::npos); + EXPECT_TRUE(response.find("\"primarydns\":\"8.8.8.8\"") != std::string::npos); + EXPECT_TRUE(response.find("\"secondarydns\":\"8.8.4.4\"") != std::string::npos); + EXPECT_TRUE(response.find("\"dhcpserver\":\"192.168.1.11\"") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_Invalid_Connection) +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_autoconfig) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *nullConnection = static_cast(NULL); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, nullConnection); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate IPv4 cache with autoconfig=true but no IP address */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + std::string expectedResponse = + _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); + EXPECT_EQ(response, expectedResponse); } -TEST_F(NetworkManagerTest, GetIPSettings_valid_ConnectionSettingsEmpty) +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_staticConfig) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate IPv4 cache with autoconfig=false (static config) */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = false; + cache.globalAddresses["192.168.1.100"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.1"; + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"autoconfig\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.100\"") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config) +TEST_F(NetworkManagerTest, GetIPSettings_wlan0_fromCache) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); + /* Populate IPv4 cache for wlan0 */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["10.0.0.5"] = Plugin::GlobalAddressInfo(8, Plugin::ADDR_GLOBAL); + cache.gateway = "10.0.0.1"; + cache.primarydns = "1.1.1.1"; + Plugin::_instance->swapIpCache("wlan0", "IPv4", cache); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"wlan0\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"wlan0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"10.0.0.5\"") != std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config_valid) +{ + /* Populate full IPv4 cache for eth0 and verify all fields */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["192.168.1.2"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.0"; + cache.primarydns = "8.8.8.8"; + cache.secondarydns = "8.8.4.4"; + cache.dhcpserver = "192.168.1.11"; + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - std::string expectedResponse = - _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); - EXPECT_EQ(response, expectedResponse); - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"secondarydns\":\"8.8.4.4\"") != std::string::npos); + EXPECT_TRUE(response.find("\"primarydns\":\"8.8.8.8\"") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.2\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ula\":\"\"") != std::string::npos); + EXPECT_TRUE(response.find("\"dhcpserver\":\"192.168.1.11\"") != std::string::npos); + EXPECT_TRUE(response.find("\"gateway\":\"192.168.1.0\"") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_configAutoConftrue) +TEST_F(NetworkManagerTest, GetIPSettings_ipv6_config_valid) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); + /* Populate full IPv6 cache for eth0 and verify all fields */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["2001:db8:1:2:3:4:5:6"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + cache.uniqueLocalAddresses.insert("fd12::1234:5678:abcd:ef01"); + cache.gateway = "2001:4860:4860::1"; + cache.primarydns = "2001:4860:4860::8888"; + cache.secondarydns = "2001:4860:4860::8844"; + cache.dhcpserver = "2001:db8::1"; + Plugin::_instance->swapIpCache("eth0", "IPv6", cache); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv6\"") != std::string::npos); + EXPECT_TRUE(response.find("\"secondarydns\":\"2001:4860:4860::8844\"") != std::string::npos); + EXPECT_TRUE(response.find("\"primarydns\":\"2001:4860:4860::8888\"") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8:1:2:3:4:5:6\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ula\":\"fd12::1234:5678:abcd:ef01\"") != std::string::npos); + EXPECT_TRUE(response.find("\"prefix\":64") != std::string::npos); + EXPECT_TRUE(response.find("\"dhcpserver\":\"2001:db8::1\"") != std::string::npos); + EXPECT_TRUE(response.find("\"gateway\":\"2001:4860:4860::1\"") != std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_ip_config_get_method(::testing::_)) - .WillOnce(::testing::Return("auto")); +TEST_F(NetworkManagerTest, GetIPSettings_ipv6_mac_based_fallback) +{ + /* When all global addresses are MAC-based, toIPAddress should use the MAC-based one */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["2001:db8::aabb:ccff:fedd:eeff"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL_MAC_BASED); + cache.gateway = "fe80::1"; + Plugin::_instance->swapIpCache("eth0", "IPv6", cache); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8::aabb:ccff:fedd:eeff\"") != std::string::npos); + EXPECT_TRUE(response.find("\"prefix\":64") != std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(retConn))); +TEST_F(NetworkManagerTest, GetIPSettings_ipv6_prefer_non_mac_global) +{ + /* ADDR_GLOBAL should be preferred over ADDR_GLOBAL_MAC_BASED */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + /* Insert MAC-based first to ensure it's not selected by insertion order */ + cache.globalAddresses["2001:db8::aabb:ccff:fedd:eeff"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL_MAC_BASED); + cache.globalAddresses["2001:db8::1234:5678"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv6", cache); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8::1234:5678\"") != std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); +TEST_F(NetworkManagerTest, GetIPSettings_ipv6_only_cached_request_ipv4) +{ + /* Only IPv6 cached, but IPv4 requested — should return success with no IP */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["2001:db8::1"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv6", cache); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv4\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv4\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); +TEST_F(NetworkManagerTest, GetIPSettings_cache_invalidated) +{ + /* Cache exists but valid=false — should return success with no IP data */ + Plugin::IpFamilyCache cache; + cache.valid = false; + cache.globalAddresses["192.168.1.5"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - std::string expectedResponse = - _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); - EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_configAutoConfNull) +TEST_F(NetworkManagerTest, GetIPSettings_ipversion_case_insensitive) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_ip_config_get_method(::testing::_)) - .WillOnce(::testing::Return("not auto")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(retConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); + /* ipversion "ipv6" (lowercase) should be treated as IPv6 */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["2001:db8::99"] = Plugin::GlobalAddressInfo(128, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv6", cache); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"ipv6\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8::99\"") != std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); +TEST_F(NetworkManagerTest, GetIPSettings_ipv6_ula_only) +{ + /* Cache has only ULA addresses, no global — ipaddress is empty so the + JSON-RPC layer (NetworkManagerJsonRpc.cpp) skips all address fields + including ula, gateway, etc. Only interface/ipversion/autoconfig/success + are returned. */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.uniqueLocalAddresses.insert("fd00::1234:abcd"); + cache.gateway = "fe80::1"; + Plugin::_instance->swapIpCache("eth0", "IPv6", cache); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + /* No global address → ipaddress empty → JSON-RPC omits address detail fields */ + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); + EXPECT_TRUE(response.find("\"ula\"") == std::string::npos); + EXPECT_TRUE(response.find("\"gateway\"") == std::string::npos); + EXPECT_TRUE(response.find("\"autoconfig\":true") != std::string::npos); +} +TEST_F(NetworkManagerTest, GetIPSettings_swapIpCache_returns_old_keys) +{ + /* Verify swapIpCache returns the old global address keys */ + Plugin::IpFamilyCache cache1; + cache1.valid = true; + cache1.globalAddresses["192.168.1.10"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache1.globalAddresses["192.168.1.20"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv4", cache1); + + /* Now swap with a new cache and check old keys are returned */ + Plugin::IpFamilyCache cache2; + cache2.valid = true; + cache2.globalAddresses["10.0.0.1"] = Plugin::GlobalAddressInfo(8, Plugin::ADDR_GLOBAL); + std::set oldKeys = Plugin::_instance->swapIpCache("eth0", "IPv4", cache2); + + EXPECT_EQ(oldKeys.size(), 2u); + EXPECT_TRUE(oldKeys.count("192.168.1.10") == 1); + EXPECT_TRUE(oldKeys.count("192.168.1.20") == 1); + + /* Verify the new cache is now active */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - std::string expectedResponse = - _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); - EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + EXPECT_TRUE(response.find("\"ipaddress\":\"10.0.0.1\"") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config_valid) +TEST_F(NetworkManagerTest, GetIPSettings_separate_ipv4_ipv6_caches) { - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - NMIPAddress* ipv4Addr = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - - GPtrArray* dummyActiveConn = g_ptr_array_new(); - GPtrArray* ipvAddr = g_ptr_array_new(); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - g_ptr_array_add(ipvAddr, ipv4Addr); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_dhcp_config_get_one_option(::testing::_, ::testing::_)) - .WillOnce(::testing::Return("192.168.1.11")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_dhcp4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100170))); - - const char* fakeDnsServers[] = {"8.8.8.8", "8.8.4.4", nullptr}; - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_nameservers(::testing::_)) - .WillOnce(::testing::Return(fakeDnsServers)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_gateway(::testing::_)) - .WillOnce(::testing::Return("192.168.1.0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_address(::testing::_)) - .WillOnce(::testing::Return("192.168.1.2")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_addresses(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(ipvAddr))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100171))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); + /* IPv4 and IPv6 caches for the same interface should be independent */ + Plugin::IpFamilyCache cache4; + cache4.valid = true; + cache4.autoconfig = true; + cache4.globalAddresses["192.168.1.50"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv4", cache4); + + Plugin::IpFamilyCache cache6; + cache6.valid = true; + cache6.autoconfig = true; + cache6.globalAddresses["2001:db8::50"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + cache6.uniqueLocalAddresses.insert("fd12::50"); + Plugin::_instance->swapIpCache("eth0", "IPv6", cache6); + + /* Query IPv4 */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv4\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.50\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv4\"") != std::string::npos); + + /* Query IPv6 */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8::50\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv6\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ula\":\"fd12::50\"") != std::string::npos); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); +TEST_F(NetworkManagerTest, GetIPSettings_cache_cleared) +{ + /* Populate cache, then swap with empty/invalid cache — simulates disconnect */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.globalAddresses["192.168.1.99"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); + /* Verify it's there */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.99\"") != std::string::npos); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Clear cache by swapping with default (valid=false) */ + Plugin::IpFamilyCache empty; + Plugin::_instance->swapIpCache("eth0", "IPv4", empty); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); - EXPECT_TRUE(response.find("\"secondarydns\":\"8.8.4.4\"") != std::string::npos); - EXPECT_TRUE(response.find("\"primarydns\":\"8.8.8.8\"") != std::string::npos); - EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); - EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.2\"") != std::string::npos); - EXPECT_TRUE(response.find("\"ula\":\"\"") != std::string::npos); - EXPECT_TRUE(response.find("\"dhcpserver\":\"192.168.1.11\"") != std::string::npos); - EXPECT_TRUE(response.find("\"gateway\":\"192.168.1.0\"") != std::string::npos); - - g_object_unref(ethActiveConn); - g_object_unref(retConn); - g_object_unref(ipv4Addr); - g_ptr_array_free(dummyActiveConn, TRUE); - g_ptr_array_free(ipvAddr, TRUE); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv6_config_valid) -{ - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - NMIPAddress* ipv6Addr = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - - GPtrArray* dummyActiveConn = g_ptr_array_new(); - GPtrArray* ipvAddr = g_ptr_array_new(); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - g_ptr_array_add(ipvAddr, ipv6Addr); - g_ptr_array_add(ipvAddr, reinterpret_cast(0x100176)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_dhcp_config_get_one_option(::testing::_, ::testing::_)) - .WillOnce(::testing::Return("2001:db8::1")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_dhcp6_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100170))); - - const char* fakeDnsServers[] = {"2001:4860:4860::8888", "2001:4860:4860::8844", nullptr}; - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_nameservers(::testing::_)) - .WillOnce(::testing::Return(fakeDnsServers)); +/* ──────────────────────────────────────────────────────────────────────────── + * Utility function tests — isIPv4LinkLocal, isIPv6LinkLocal, isIPv6ULA, + * isIPv6MacBased (which exercises parseMac internally). + * ──────────────────────────────────────────────────────────────────────────── */ - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_gateway(::testing::_)) - .WillOnce(::testing::Return("2001:4860:4860::1")); +TEST_F(NetworkManagerTest, isIPv4LinkLocal_true_for_169_254) +{ + EXPECT_TRUE(Plugin::isIPv4LinkLocal("169.254.0.1")); + EXPECT_TRUE(Plugin::isIPv4LinkLocal("169.254.255.255")); + EXPECT_TRUE(Plugin::isIPv4LinkLocal("169.254.100.50")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_prefix(::testing::_)) - .WillOnce(::testing::Return(64)); +TEST_F(NetworkManagerTest, isIPv4LinkLocal_false_for_non_link_local) +{ + EXPECT_FALSE(Plugin::isIPv4LinkLocal("192.168.1.1")); + EXPECT_FALSE(Plugin::isIPv4LinkLocal("10.0.0.1")); + EXPECT_FALSE(Plugin::isIPv4LinkLocal("169.253.255.255")); + EXPECT_FALSE(Plugin::isIPv4LinkLocal("169.255.0.1")); + EXPECT_FALSE(Plugin::isIPv4LinkLocal("0.0.0.0")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_address(::testing::_)) - .WillOnce(::testing::Return("2001:db8:1:2:3:4:5:6")) - .WillOnce(::testing::Return("fe80::1234:5678:abcd:ef01")); +TEST_F(NetworkManagerTest, isIPv4LinkLocal_false_for_invalid_input) +{ + EXPECT_FALSE(Plugin::isIPv4LinkLocal("")); + EXPECT_FALSE(Plugin::isIPv4LinkLocal("not_an_ip")); + EXPECT_FALSE(Plugin::isIPv4LinkLocal("fe80::1")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_addresses(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(ipvAddr))); +TEST_F(NetworkManagerTest, isIPv6LinkLocal_true_for_fe80) +{ + EXPECT_TRUE(Plugin::isIPv6LinkLocal("fe80::1")); + EXPECT_TRUE(Plugin::isIPv6LinkLocal("fe80::abcd:1234:5678:9abc")); + EXPECT_TRUE(Plugin::isIPv6LinkLocal("fe80::ffff:ffff:ffff:ffff")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip6_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100171))); +TEST_F(NetworkManagerTest, isIPv6LinkLocal_false_for_non_link_local) +{ + EXPECT_FALSE(Plugin::isIPv6LinkLocal("2001:db8::1")); + EXPECT_FALSE(Plugin::isIPv6LinkLocal("fd00::1")); + EXPECT_FALSE(Plugin::isIPv6LinkLocal("::1")); + EXPECT_FALSE(Plugin::isIPv6LinkLocal("fc00::1")); + EXPECT_FALSE(Plugin::isIPv6LinkLocal("fec0::1")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); +TEST_F(NetworkManagerTest, isIPv6LinkLocal_false_for_invalid_input) +{ + EXPECT_FALSE(Plugin::isIPv6LinkLocal("")); + EXPECT_FALSE(Plugin::isIPv6LinkLocal("not_an_ip")); + EXPECT_FALSE(Plugin::isIPv6LinkLocal("169.254.1.1")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); +TEST_F(NetworkManagerTest, isIPv6ULA_true_for_fc_fd) +{ + EXPECT_TRUE(Plugin::isIPv6ULA("fc00::1")); + EXPECT_TRUE(Plugin::isIPv6ULA("fd00::1")); + EXPECT_TRUE(Plugin::isIPv6ULA("fd12:3456:789a::1")); + EXPECT_TRUE(Plugin::isIPv6ULA("fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); +TEST_F(NetworkManagerTest, isIPv6ULA_false_for_non_ula) +{ + EXPECT_FALSE(Plugin::isIPv6ULA("2001:db8::1")); + EXPECT_FALSE(Plugin::isIPv6ULA("fe80::1")); + EXPECT_FALSE(Plugin::isIPv6ULA("::1")); + EXPECT_FALSE(Plugin::isIPv6ULA("fb00::1")); + EXPECT_FALSE(Plugin::isIPv6ULA("fe00::1")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); +TEST_F(NetworkManagerTest, isIPv6ULA_false_for_invalid_input) +{ + EXPECT_FALSE(Plugin::isIPv6ULA("")); + EXPECT_FALSE(Plugin::isIPv6ULA("garbage")); + EXPECT_FALSE(Plugin::isIPv6ULA("192.168.1.1")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); +TEST_F(NetworkManagerTest, isIPv6MacBased_true_for_eui64_colon_mac) +{ + /* MAC AA:BB:CC:DD:EE:FF → EUI-64: A8:BB:CC:FF:FE:DD:EE:FF + (byte 0: 0xAA ^ 0x02 = 0xA8, insert FF:FE in middle) */ + EXPECT_TRUE(Plugin::isIPv6MacBased("2001:db8::a8bb:ccff:fedd:eeff", "AA:BB:CC:DD:EE:FF")); +} - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); +TEST_F(NetworkManagerTest, isIPv6MacBased_true_for_eui64_plain_mac) +{ + /* Same MAC in plain hex format */ + EXPECT_TRUE(Plugin::isIPv6MacBased("2001:db8::a8bb:ccff:fedd:eeff", "aabbccddeeff")); +} - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); +TEST_F(NetworkManagerTest, isIPv6MacBased_false_for_non_eui64) +{ + /* Address that doesn't match the MAC's EUI-64 */ + EXPECT_FALSE(Plugin::isIPv6MacBased("2001:db8::1234:5678:9abc:def0", "AA:BB:CC:DD:EE:FF")); +} - EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); - EXPECT_TRUE(response.find("\"ipversion\":\"IPv6\"") != std::string::npos); - EXPECT_TRUE(response.find("\"secondarydns\":\"2001:4860:4860::8844\"") != std::string::npos); - EXPECT_TRUE(response.find("\"primarydns\":\"2001:4860:4860::8888\"") != std::string::npos); - EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); - EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8:1:2:3:4:5:6\"") != std::string::npos); - EXPECT_TRUE(response.find("\"ula\":\"fe80::1234:5678:abcd:ef01\"") != std::string::npos); - EXPECT_TRUE(response.find("\"prefix\":64") != std::string::npos); - EXPECT_TRUE(response.find("\"dhcpserver\":\"2001:db8::1\"") != std::string::npos); - EXPECT_TRUE(response.find("\"gateway\":\"2001:4860:4860::1\"") != std::string::npos); +TEST_F(NetworkManagerTest, isIPv6MacBased_false_for_privacy_address) +{ + /* Privacy extension address — random interface ID, not MAC-based */ + EXPECT_FALSE(Plugin::isIPv6MacBased("2001:db8::4f2a:8c91:e3d7:b560", "00:11:22:33:44:55")); +} - g_object_unref(ethActiveConn); - g_object_unref(retConn); - g_object_unref(ipv6Addr); - g_ptr_array_free(dummyActiveConn, TRUE); - g_ptr_array_free(ipvAddr, TRUE); +TEST_F(NetworkManagerTest, isIPv6MacBased_false_for_invalid_inputs) +{ + EXPECT_FALSE(Plugin::isIPv6MacBased("", "AA:BB:CC:DD:EE:FF")); + EXPECT_FALSE(Plugin::isIPv6MacBased("2001:db8::1", "")); + EXPECT_FALSE(Plugin::isIPv6MacBased("not_ipv6", "AA:BB:CC:DD:EE:FF")); + EXPECT_FALSE(Plugin::isIPv6MacBased("2001:db8::1", "not_a_mac")); + EXPECT_FALSE(Plugin::isIPv6MacBased("192.168.1.1", "AA:BB:CC:DD:EE:FF")); } TEST_F(NetworkManagerTest, SetInterfaceState_deviceFailed_wlan0) diff --git a/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp b/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp index c587688a..dfecf5fd 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp @@ -42,6 +42,8 @@ using namespace WPEFramework; using ::testing::NiceMock; +namespace WPEFramework { namespace Plugin { extern NetworkManagerImplementation* _instance; } } + class NetworkManagerEventTest : public ::testing::Test { protected: Core::ProxyType plugin; @@ -208,31 +210,6 @@ TEST_F(NetworkManagerEventTest, onInterfaceStateChangeCb) WPEFramework::Plugin::GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_DISABLED, "eth0"); } -TEST_F(NetworkManagerEventTest, onAddressChangeCb) -{ - // Test acquiring IPv4 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "192.168.1.100", true, false); - - // Test acquiring IPv6 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "2001:db8::1", true, true); - - // Test acquiring same IPv6 address again (should skip posting) - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "2001:db8::1", true, true); - - // Test acquiring different IPv6 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "2001:db8::2", true, true); - - // Test losing IPv4 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "", false, false); - - // Test losing IPv6 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "", false, true); - - // Test losing IP on interface with empty cache (should skip posting) - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth1", "", false, false); - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth1", "", false, true); -} - TEST_F(NetworkManagerEventTest, onAvailableSSIDsCb) { GPtrArray* fakeDevices = g_ptr_array_new(); @@ -372,9 +349,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_disconnected) { NMDevice *wifiDummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")); + .WillRepeatedly(::testing::Return("wlan0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(wifiDummyDevice), nullptr, nullptr); @@ -384,9 +361,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_unmanaged) { NMDevice *wifiDummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")); + .WillRepeatedly(::testing::Return("wlan0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(wifiDummyDevice), nullptr, nullptr); @@ -492,9 +469,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_unknown) { NMDevice *wifiDummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNKNOWN)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_UNKNOWN)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")); + .WillRepeatedly(::testing::Return("wlan0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(wifiDummyDevice), nullptr, nullptr); @@ -504,9 +481,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_eth0_unmanaged) { NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("eth0")); + .WillRepeatedly(::testing::Return("eth0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(DummyDevice), nullptr, nullptr); @@ -517,9 +494,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_eth0_disconnected) { NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("eth0")); + .WillRepeatedly(::testing::Return("eth0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(DummyDevice), nullptr, nullptr); @@ -564,3 +541,102 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_eth0_activated) WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(DummyDevice, nullptr, nullptr); g_object_unref(DummyDevice); } + +/* ──────────────────────────────────────────────────────────────────────────── + * Cache-clearing tests — verify that disconnect events clear the IP cache. + * These test observable behavior (cache state after disconnect) rather than + * internal NM API call sequences, so they remain stable across refactors. + * ──────────────────────────────────────────────────────────────────────────── */ + +TEST_F(NetworkManagerEventTest, disconnect_clears_ipv4_cache_eth0) +{ + /* Pre-populate the IPv4 cache for eth0 */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.globalAddresses["192.168.1.50"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.1"; + Plugin::_instance->swapIpCache("eth0", "IPv4", cache); + + /* Verify cache is populated */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.50\"") != std::string::npos); + + /* Trigger eth0 disconnect — refreshIpFamilyCache runs with skipRead=true, + swapping an empty cache and clearing the old addresses. */ + NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) + .WillRepeatedly(::testing::Return("eth0")); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) + .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); + WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(DummyDevice, nullptr, nullptr); + g_object_unref(DummyDevice); + + /* Cache should now be empty — GetIPSettings returns success but no address fields */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); +} + +TEST_F(NetworkManagerEventTest, disconnect_clears_ipv6_cache_wlan0) +{ + /* Pre-populate the IPv6 cache for wlan0 */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.globalAddresses["2001:db8::1"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + cache.gateway = "fe80::1"; + Plugin::_instance->swapIpCache("wlan0", "IPv6", cache); + + /* Verify cache is populated */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"wlan0\",\"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8::1\"") != std::string::npos); + + /* Trigger wlan0 disconnect */ + NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) + .WillRepeatedly(::testing::Return("wlan0")); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) + .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); + WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(DummyDevice), nullptr, nullptr); + g_object_unref(DummyDevice); + + /* Cache should now be empty */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"wlan0\",\"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); +} + +TEST_F(NetworkManagerEventTest, disconnect_clears_both_ip_family_caches) +{ + /* Pre-populate both IPv4 and IPv6 caches for eth0 */ + Plugin::IpFamilyCache cache4; + cache4.valid = true; + cache4.globalAddresses["192.168.1.50"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv4", cache4); + + Plugin::IpFamilyCache cache6; + cache6.valid = true; + cache6.globalAddresses["2001:db8::99"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + Plugin::_instance->swapIpCache("eth0", "IPv6", cache6); + + /* Trigger disconnect — refreshIpFamilyCache is called for BOTH families */ + NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) + .WillRepeatedly(::testing::Return("eth0")); + EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) + .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); + WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(DummyDevice, nullptr, nullptr); + g_object_unref(DummyDevice); + + /* Both families should be cleared */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv6\"}"), response)); + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); +} diff --git a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp index e47f24fb..b550b3d6 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp @@ -158,7 +158,7 @@ TEST_F(NetworkManagerInitTest, platformInit) EXPECT_EQ(response, _T("{\"success\":false}")); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"wlan0\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":false}")); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("SetHostname"), _T("{\"hostname\":\"test-host\"}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); From 94f4f43cf80f7fd604e9a2e0cced6a3520c53287 Mon Sep 17 00:00:00 2001 From: tukken-comcast Date: Wed, 10 Jun 2026 00:43:21 +0530 Subject: [PATCH 06/32] RDK-61247: Updated the documentation (#314) Reason for change: Updated the documentation Test Procedure: Verify docs/NetworkManagerPlugin.md reflects content in definition/NetworkManager.json Priority: P1 Risk: Low --- docs/NetworkManagerPlugin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index d095446b..5394a1b8 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -418,7 +418,7 @@ Gets the IP setting for the given interface. | result.ipaddress | string | The IP address | | result.prefix | integer | The prefix number | | result.gateway | string | The gateway address | -| result.ula | string | The IPv6 Unified Local Address | +| result.ula | string | The IPv6 Unique Local Address | | result.primarydns | string | The primary DNS address | | result.secondarydns | string | The secondary DNS address | | result.success | boolean | Whether the request succeeded | @@ -453,7 +453,7 @@ Gets the IP setting for the given interface. "ipaddress": "192.168.1.101", "prefix": 24, "gateway": "192.168.1.1", - "ula": "", + "ula": "fd00:410:2016::", "primarydns": "192.168.1.1", "secondarydns": "192.168.1.2", "success": true From fcaa5a29e9636594074db90f0629f46e03ab1432 Mon Sep 17 00:00:00 2001 From: gururaajar <83449026+gururaajar@users.noreply.github.com> Date: Fri, 12 Jun 2026 09:26:38 -0400 Subject: [PATCH 07/32] RDKEMW-19267: WPEFramework crash observed with VSS increase (#315) Reason for Change: The persistent m_nmClient created during platform_init() subscribed to all D-Bus PropertyChanged signals from the NetworkManager daemon. Its isolated m_nmContext was never continuously drained, causing every signal (device state changes, DHCP renewals, WiFi scan updates, DNS changes) to queue indefinitely as GSource objects. This produced memory leak that grew gradually over time. The fix adopts the same create-per-call pattern already used by wifiManager: each proxy API call creates a temporary NMClient, uses it for the synchronous libnm query, then destroys it via the contextBusyWatcher drain loop. Between calls no client exists, so no D-Bus subscription is active and no signals accumulate. No GMainLoop or background thread is required since all proxy operations are synchronous. Test Procedure: Longrun scenario and observe for memory leaks Priority: P1 Risks: Medium --- plugin/NetworkManagerImplementation.h | 4 +- plugin/gnome/NetworkManagerGnomeProxy.cpp | 218 +++++++++++++--------- 2 files changed, 134 insertions(+), 88 deletions(-) diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 6f4ab5bd..65baa0b1 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -40,7 +40,6 @@ using namespace std; #include "NetworkManagerPowerClient.h" /* Forward declarations to avoid pulling GLib/libnm headers into this header */ -typedef struct _NMClient NMClient; typedef struct _GMainContext GMainContext; /* @@ -390,8 +389,7 @@ namespace WPEFramework std::atomic m_ethDisconnectedForSleep; std::atomic m_wlanDisconnectedForSleep; std::string m_lastConnectedSSID; - NMClient *m_nmClient{nullptr}; /* proxy NMClient — bound to m_nmContext */ - GMainContext *m_nmContext{nullptr}; /* isolated context, not the global default */ + GMainContext *m_nmContext{nullptr}; /* isolated context for per-call NMClient creation */ mutable ConnectivityMonitor connectivityMonitor; string getDefaultInterface() const diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 234a8df9..932dd9a8 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -30,6 +30,40 @@ namespace WPEFramework { namespace Plugin { + /* + * Per-call NMClient helpers (same pattern as wifiManager). + * A fresh NMClient is created for each proxy API call and destroyed + * immediately after use, so no D-Bus signals accumulate between calls. + */ + static NMClient* createProxyClient(GMainContext *ctx) + { + GError *error = NULL; + g_main_context_push_thread_default(ctx); + NMClient *client = nm_client_new(NULL, &error); + g_main_context_pop_thread_default(ctx); + if (!client) { + if (error) { + NMLOG_ERROR("Failed to create NMClient: %s", error->message); + g_error_free(error); + } + } + return client; + } + + static void deleteProxyClient(NMClient *client) + { + if (!client) + return; + GMainContext *context = g_main_context_ref(nm_client_get_main_context(client)); + GObject *contextBusyWatcher = nm_client_get_context_busy_watcher(client); + g_object_add_weak_pointer(contextBusyWatcher, (gpointer *)&contextBusyWatcher); + g_clear_object(&client); + while (contextBusyWatcher) { + g_main_context_iteration(context, TRUE); + } + g_main_context_unref(context); + } + wifiManager *wifi = nullptr; GnomeNetworkManagerEvents *nmEvent = nullptr; NetworkManagerImplementation* _instance = nullptr; @@ -196,11 +230,8 @@ namespace WPEFramework /* @brief Set the dhcp hostname */ uint32_t NetworkManagerImplementation::SetHostname(const string& hostname /* @in */) { - const GPtrArray *connections = NULL; - NMConnection *connection = NULL; - - if (m_nmClient == nullptr) { - NMLOG_ERROR("NMClient is NULL"); + if (m_nmContext == nullptr) { + NMLOG_ERROR("NMContext is NULL"); return Core::ERROR_GENERAL; } @@ -210,10 +241,17 @@ namespace WPEFramework return Core::ERROR_BAD_REQUEST; } - connections = nm_client_get_connections(m_nmClient); + NMClient *client = createProxyClient(m_nmContext); + if (client == nullptr) { + NMLOG_ERROR("Failed to create NMClient for SetHostname"); + return Core::ERROR_GENERAL; + } + + const GPtrArray *connections = nm_client_get_connections(client); if (connections == NULL || connections->len == 0) { NMLOG_ERROR("Could not get nm connections"); + deleteProxyClient(client); return Core::ERROR_GENERAL; } @@ -221,7 +259,7 @@ namespace WPEFramework for (uint32_t i = 0; i < connections->len; i++) { - connection = NM_CONNECTION(connections->pdata[i]); + NMConnection *connection = NM_CONNECTION(connections->pdata[i]); if(connection != NULL) { const char *iface = nm_connection_get_interface_name(connection); @@ -240,6 +278,7 @@ namespace WPEFramework if(!setHostname(connection, hostname)) { NMLOG_ERROR("Failed to set hostname for connection at index %d", i); + deleteProxyClient(client); return Core::ERROR_GENERAL; } } @@ -247,6 +286,8 @@ namespace WPEFramework NMLOG_ERROR("Connection at index %d is NULL", i); } + deleteProxyClient(client); + // Write the hostname to persistent storage nmUtils::writePersistentHostname(hostname); @@ -255,7 +296,6 @@ namespace WPEFramework void NetworkManagerImplementation::platform_deinit() { - if(m_nmClient) { g_object_unref(m_nmClient); m_nmClient = nullptr; } if(m_nmContext) { g_main_context_unref(m_nmContext); m_nmContext = nullptr; } } @@ -270,38 +310,31 @@ namespace WPEFramework void NetworkManagerImplementation::platform_init() { ::_instance = this; - GError *error = NULL; - // initialize the NMClient object - // Create an isolated GMainContext so this m_nmClient's D-Bus socket is NOT a - // source on the global default context. The event thread runs the default - // context via g_main_loop_run(); without isolation it would own and mutate - // this m_nmClient's GObjects concurrently with the RPC thread. + // Create an isolated GMainContext for per-call NMClient creation. m_nmContext = g_main_context_new(); - g_main_context_push_thread_default(m_nmContext); - m_nmClient = nm_client_new(NULL, &error); - g_main_context_pop_thread_default(m_nmContext); - if (m_nmClient == NULL) { - if (error) { - NMLOG_FATAL("Error initializing NMClient: %s", error->message); - g_error_free(error); - } - if (m_nmContext) { - g_main_context_unref(m_nmContext); - m_nmContext = nullptr; - } + + // Create a temporary client for one-time init work + NMClient *initClient = createProxyClient(m_nmContext); + if (initClient == NULL) { + NMLOG_FATAL("Error initializing NMClient during platform_init"); + g_main_context_unref(m_nmContext); + m_nmContext = nullptr; return; } nmUtils::getDeviceProperties(); // get interface name form '/etc/device.proprties' - modifyDefaultConnConfig(m_nmClient); - NMDeviceState ethState = ifaceState(m_nmClient, nmUtils::ethIface()); + modifyDefaultConnConfig(initClient); + NMDeviceState ethState = ifaceState(initClient, nmUtils::ethIface()); if(ethState > NM_DEVICE_STATE_DISCONNECTED && ethState < NM_DEVICE_STATE_DEACTIVATING) setDefaultInterface(nmUtils::ethIface()); else setDefaultInterface(nmUtils::wlanIface()); NMLOG_INFO("default interface is %s", getDefaultInterface().c_str()); + + deleteProxyClient(initClient); + // getInitialConnectionState function not called here, as event monitor will report the initial state nmEvent = GnomeNetworkManagerEvents::getInstance(); nmEvent->startNetworkMangerEventMonitor(); @@ -314,20 +347,22 @@ namespace WPEFramework std::vector interfaceList; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); - if(m_nmClient == nullptr) { - NMLOG_FATAL("NMClient is null"); + if(m_nmContext == nullptr) { + NMLOG_FATAL("NMContext is null"); return Core::ERROR_GENERAL; } - if (m_nmContext) { - for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ - // Intentional empty body: just flushing the event queue - } + NMClient *client = createProxyClient(m_nmContext); + if (client == nullptr) { + NMLOG_FATAL("Failed to create NMClient for GetAvailableInterfaces"); + return Core::ERROR_GENERAL; } - GPtrArray *devices = const_cast(nm_client_get_devices(m_nmClient)); + + GPtrArray *devices = const_cast(nm_client_get_devices(client)); if (devices == NULL) { NMLOG_ERROR("Failed to get device list."); - return Core::ERROR_GENERAL; + deleteProxyClient(client); + return rc; } for (guint j = 0; j < devices->len; j++) @@ -373,6 +408,11 @@ namespace WPEFramework } } + deleteProxyClient(client); + + if (rc != Core::ERROR_NONE) + return rc; + using Implementation = RPC::IteratorType; interfacesItr = Core::Service::Create(interfaceList); if(interfacesItr == nullptr) { @@ -454,9 +494,9 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::SetInterfaceState(const string& interface/* @in */, const bool enabled /* @in */) { - if(m_nmClient == nullptr) + if(m_nmContext == nullptr) { - NMLOG_WARNING("NMClient is null"); + NMLOG_WARNING("NMContext is null"); return Core::ERROR_RPC_CALL_FAILED; } @@ -492,57 +532,62 @@ namespace WPEFramework { NMLOG_INFO("BOOT_MIGRATION detected, deleting all wired NM connections"); - // Bring down the ethernet interface before wiping its connections - // so NM doesn't immediately re-activate them during deletion. - NMDevice *ethDev = nm_client_get_device_by_iface(m_nmClient, interface.c_str()); - if(ethDev) + NMClient *client = createProxyClient(m_nmContext); + if (client != nullptr) { - GError *discError = nullptr; - if(!nm_device_disconnect(ethDev, nullptr, &discError)) + // Bring down the ethernet interface before wiping its connections + // so NM doesn't immediately re-activate them during deletion. + NMDevice *ethDev = nm_client_get_device_by_iface(client, interface.c_str()); + if(ethDev) { - NMLOG_WARNING("Failed to disconnect %s before migration cleanup: %s", - interface.c_str(), - discError ? discError->message : "unknown error"); - if(discError) g_error_free(discError); + GError *discError = nullptr; + if(!nm_device_disconnect(ethDev, nullptr, &discError)) + { + NMLOG_WARNING("Failed to disconnect %s before migration cleanup: %s", + interface.c_str(), + discError ? discError->message : "unknown error"); + if(discError) g_error_free(discError); + } } - } - const GPtrArray *connections = nm_client_get_connections(m_nmClient); - if(connections && connections->len > 0) - { - /* Snapshot the list before iterating: nm_client_get_connections() - * returns an internal array that can be mutated as connections - * are removed, so we must not iterate it while deleting. */ - GPtrArray *snapshot = g_ptr_array_new_full(connections->len, g_object_unref); - for(guint i = 0; i < connections->len; ++i) + const GPtrArray *connections = nm_client_get_connections(client); + if(connections && connections->len > 0) { - NMRemoteConnection *conn = NM_REMOTE_CONNECTION(connections->pdata[i]); - if(!conn) continue; - NMSettingConnection *sCon = nm_connection_get_setting_connection(NM_CONNECTION(conn)); - if(!sCon) continue; - const char *connType = nm_setting_connection_get_connection_type(sCon); - if(g_strcmp0(connType, NM_SETTING_WIRED_SETTING_NAME) != 0) + /* Snapshot the list before iterating: nm_client_get_connections() + * returns an internal array that can be mutated as connections + * are removed, so we must not iterate it while deleting. */ + GPtrArray *snapshot = g_ptr_array_new_full(connections->len, g_object_unref); + for(guint i = 0; i < connections->len; ++i) { - NMLOG_DEBUG("Skipping non-wired connection type: %s", connType ? connType : "null"); - continue; + NMRemoteConnection *conn = NM_REMOTE_CONNECTION(connections->pdata[i]); + if(!conn) continue; + NMSettingConnection *sCon = nm_connection_get_setting_connection(NM_CONNECTION(conn)); + if(!sCon) continue; + const char *connType = nm_setting_connection_get_connection_type(sCon); + if(g_strcmp0(connType, NM_SETTING_WIRED_SETTING_NAME) != 0) + { + NMLOG_DEBUG("Skipping non-wired connection type: %s", connType ? connType : "null"); + continue; + } + g_ptr_array_add(snapshot, g_object_ref(conn)); } - g_ptr_array_add(snapshot, g_object_ref(conn)); - } - for(guint i = 0; i < snapshot->len; ++i) - { - NMRemoteConnection *conn = NM_REMOTE_CONNECTION(snapshot->pdata[i]); - GError *error = nullptr; - if(!nm_remote_connection_delete(conn, nullptr, &error)) + for(guint i = 0; i < snapshot->len; ++i) { - const char *connId = nm_connection_get_id(NM_CONNECTION(conn)); - NMLOG_ERROR("Failed to delete connection %s: %s", - connId ? connId : "", - error ? error->message : "unknown error"); - if(error) g_error_free(error); + NMRemoteConnection *conn = NM_REMOTE_CONNECTION(snapshot->pdata[i]); + GError *error = nullptr; + if(!nm_remote_connection_delete(conn, nullptr, &error)) + { + const char *connId = nm_connection_get_id(NM_CONNECTION(conn)); + NMLOG_ERROR("Failed to delete connection %s: %s", + connId ? connId : "", + error ? error->message : "unknown error"); + if(error) g_error_free(error); + } } + g_ptr_array_unref(snapshot); } - g_ptr_array_unref(snapshot); + deleteProxyClient(client); } } } @@ -604,22 +649,23 @@ namespace WPEFramework return Core::ERROR_GENERAL; } - if(m_nmClient == nullptr) + if(m_nmContext == nullptr) { - NMLOG_WARNING("NMClient is null"); + NMLOG_WARNING("NMContext is null"); return Core::ERROR_RPC_CALL_FAILED; } - if (m_nmContext) { - for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ - // Intentional empty body: just flushing the event queue - } + NMClient *client = createProxyClient(m_nmContext); + if (client == nullptr) { + NMLOG_ERROR("Failed to create NMClient for GetInterfaceState"); + return Core::ERROR_RPC_CALL_FAILED; } - GPtrArray *devices = const_cast(nm_client_get_devices(m_nmClient)); + GPtrArray *devices = const_cast(nm_client_get_devices(client)); if (devices == NULL) { NMLOG_ERROR("Failed to get device list."); + deleteProxyClient(client); return Core::ERROR_GENERAL; } @@ -646,6 +692,8 @@ namespace WPEFramework } } + deleteProxyClient(client); + if(isIfaceFound) return Core::ERROR_NONE; else From 5a125cb1ed54d912633c1bb5dca2be253c968537 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Fri, 12 Jun 2026 11:57:38 -0400 Subject: [PATCH 08/32] Release of 3.3.0 Release of 3.3.0 --- CHANGELOG.md | 14 ++++++++++++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1726e44..d262c5e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,20 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.3.0] - 2026-06-12 +### Fixed +- Fixed the memory leak that caused by the persistent m_nmClient and m_nmContext. + +## [3.2.0] - 2026-06-09 +### Added +- Implemented IP Caching logic to avoid multiple hard fetches from Gnome & invalidating the cache on change. + +## [3.1.0] - 2026-06-08 +### Changed +- Handled Power State changes within NetworkManager as below, +- Transition to DeepSleep will disconnect when NSM is OFF +- Transition from DeepSleep will Renew IP when NSM is ON + ## [3.0.0] - 2026-05-28 ### Changed - The device hostname header that used to retrive has changed as "DEFAULT_HOSTNAME" diff --git a/CMakeLists.txt b/CMakeLists.txt index c10e4331..6d4cc1db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 3) -set(VERSION_MINOR 0) +set(VERSION_MINOR 3) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 01f2517a..f79bd812 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.0.0" + "version": "3.3.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 5394a1b8..3d109f0a 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.0.0** +**Version: 3.3.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.0.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.3.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 7decf26f4adfbb721ff3718ff90570c88ec34dcb Mon Sep 17 00:00:00 2001 From: jincysam87 <167995204+jincysam87@users.noreply.github.com> Date: Wed, 1 Jul 2026 11:27:04 -0400 Subject: [PATCH 09/32] RDKEMW-20574 : Fix the notification locks in networkmanager plugin (#319) Reason for change: Fix the notification locks in networkmanager plugin Priority: P1 Test Procedure: Check the test steps in RDKEMW-20574 Risks: medium Signed-off-by: jincysaramma_sam@comcast.com --- plugin/NetworkManagerImplementation.cpp | 39 +++++++++++++++-------- plugin/NetworkManagerImplementation.h | 3 +- plugin/NetworkManagerLogger.h | 2 +- plugin/gnome/NetworkManagerGnomeProxy.cpp | 19 +++++++---- plugin/rdk/NetworkManagerRDKProxy.cpp | 2 ++ 5 files changed, 43 insertions(+), 22 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index b10b375b..1beffab6 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -597,14 +597,16 @@ namespace WPEFramework return; } - void NetworkManagerImplementation::filterScanResults(JsonArray &ssids) + void NetworkManagerImplementation::filterScanResults(JsonArray &ssids, + const std::vector& filterSsidslist, + const std::vector& filterFrequencies) { JsonArray result; double filterFreq = 0.0; - std::unordered_set scanForSsidsSet(m_filterSsidslist.begin(), m_filterSsidslist.end()); + std::unordered_set scanForSsidsSet(filterSsidslist.begin(), filterSsidslist.end()); // If neither SSID list nor frequency is provided, exit - if (m_filterSsidslist.empty() && m_filterFrequencies.empty()) + if (filterSsidslist.empty() && filterFrequencies.empty()) { NMLOG_DEBUG("Neither SSID nor Frequency is provided. Exiting function."); return; @@ -618,10 +620,10 @@ namespace WPEFramework double frequencyValue = std::stod(frequency); bool ssidMatches = scanForSsidsSet.empty() || scanForSsidsSet.find(ssid) != scanForSsidsSet.end(); - bool freqMatches = m_filterFrequencies.empty(); + bool freqMatches = filterFrequencies.empty(); if (!freqMatches) { - for (const auto& selectedFrequency : m_filterFrequencies) + for (const auto& selectedFrequency : filterFrequencies) { if (selectedFrequency == "ALL") { @@ -738,7 +740,6 @@ namespace WPEFramework void NetworkManagerImplementation::ReportActiveInterfaceChange(const string prevActiveInterface, const string currentActiveinterface) { - _notificationLock.Lock(); NMLOG_INFO("Posting onActiveInterfaceChange %s", currentActiveinterface.c_str()); if(currentActiveinterface == "eth0") @@ -754,15 +755,15 @@ namespace WPEFramework // FIXME : This could be the place to define `m_defaultInterface` to incoming `currentActiveinterface`. // m_defaultInterface = currentActiveinterface; - + _notificationLock.Lock(); for (const auto callback : _notificationCallbacks) { callback->onActiveInterfaceChange(prevActiveInterface, currentActiveinterface); } + _notificationLock.Unlock(); #if USE_TELEMETRY NMLOG_INFO("NM_INTERFACE_STATUS = Interface changed to %s", currentActiveinterface.c_str()); logTelemetry("NM_INTERFACE_STATUS", "Interface changed to " + currentActiveinterface); -#endif - _notificationLock.Unlock(); +#endif } void NetworkManagerImplementation::ReportIPAddressChange(const string interface, const string ipversion, const string ipaddress, const Exchange::INetworkManager::IPStatus status) @@ -810,7 +811,6 @@ namespace WPEFramework void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) { - _notificationLock.Lock(); NMLOG_INFO("Posting onInternetStatusChange with current state as %u", (unsigned)currState); #if USE_TELEMETRY // Log error only when ethernet is up and there's no internet @@ -823,15 +823,16 @@ namespace WPEFramework logTelemetry("NM_ETHERNET_CONNECTIVITY", "Ethernet connectivity failed"); } #endif + _notificationLock.Lock(); for (const auto callback : _notificationCallbacks) { callback->onInternetStatusChange(prevState, currState, interface); } + _notificationLock.Unlock(); #if USE_TELEMETRY string stateStr = Core::EnumerateType(currState).Data(); NMLOG_INFO("NM_INTERNET_STATUS = %s", stateStr.c_str()); logTelemetry("NM_INTERNET_STATUS", stateStr); #endif - _notificationLock.Unlock(); } int32_t NetworkManagerImplementation::logSSIDs(Logging level, const JsonArray &ssids) @@ -855,7 +856,6 @@ namespace WPEFramework void NetworkManagerImplementation::ReportAvailableSSIDs(const JsonArray &arrayofWiFiScanResults) { - _notificationLock.Lock(); string jsonOfWiFiScanResults; string jsonOfFilterScanResults; JsonArray filterResult = arrayofWiFiScanResults; @@ -864,12 +864,23 @@ namespace WPEFramework NMLOG_DEBUG("Discovered %d SSIDs before filtering as,", filterResult.Length()); logSSIDs(LOG_LEVEL_DEBUG, filterResult); - filterScanResults(filterResult); + // Snapshot filter vectors under lock, then release before calling filterScanResults + // to ensure exception-safety (std::stod can throw). + std::vector ssidsSnapshot; + std::vector frequenciesSnapshot; + m_filterVectorsLock.Lock(); + ssidsSnapshot = m_filterSsidslist; + frequenciesSnapshot = m_filterFrequencies; + m_filterVectorsLock.Unlock(); + + // Call filterScanResults outside the lock with snapshots (exception-safe) + filterScanResults(filterResult, ssidsSnapshot, frequenciesSnapshot); filterResult.ToString(jsonOfFilterScanResults); NMLOG_INFO("Posting onAvailableSSIDs event with %d SSIDs as,", filterResult.Length()); logSSIDs(LOG_LEVEL_INFO, filterResult); + _notificationLock.Lock(); for (const auto callback : _notificationCallbacks) { callback->onAvailableSSIDs(jsonOfFilterScanResults); } @@ -1177,13 +1188,13 @@ namespace WPEFramework m_wlanConnected.store(false); /* Any other state is considered as WiFi not connected. */ } - _notificationLock.Lock(); NMLOG_INFO("Posting onWiFiStateChange (%d)", state); #if USE_TELEMETRY string stateStr = Core::EnumerateType(state).Data(); NMLOG_INFO("NM_WIFI_STATUS = %s", stateStr.c_str()); logTelemetry("NM_WIFI_STATUS", stateStr); #endif + _notificationLock.Lock(); for (const auto callback : _notificationCallbacks) { callback->onWiFiStateChange(state); } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 65baa0b1..b7bffe91 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -338,7 +338,7 @@ namespace WPEFramework void getInitialConnectionState(void); void executeExternally(NetworkEvents event, const string commandToExecute, string& response); void threadEventRegistration(bool iarmInit, bool iarmConnect); - void filterScanResults(JsonArray &ssids); + void filterScanResults(JsonArray &ssids, const std::vector& filterSsidslist, const std::vector& filterFrequencies); void startWiFiSignalQualityMonitor(int interval); void stopWiFiSignalQualityMonitor(); void monitorThreadFunction(int interval); @@ -348,6 +348,7 @@ namespace WPEFramework private: std::list _notificationCallbacks; Core::CriticalSection _notificationLock; + Core::CriticalSection m_filterVectorsLock; string m_publicIP; stun::client stunClient; string m_stunEndpoint; diff --git a/plugin/NetworkManagerLogger.h b/plugin/NetworkManagerLogger.h index 4c35f1ab..b5484c35 100644 --- a/plugin/NetworkManagerLogger.h +++ b/plugin/NetworkManagerLogger.h @@ -70,7 +70,7 @@ void logPrint(LogLevel level, const char* file, const char* func, int line, cons #define NMLOG_ERROR(FMT, ...) logPrint(NetworkManagerLogger::ERROR_LEVEL, __FILE__, __func__, __LINE__, FMT, ##__VA_ARGS__) #define NMLOG_FATAL(FMT, ...) logPrint(NetworkManagerLogger::FATAL_LEVEL, __FILE__,__func__, __LINE__, FMT, ##__VA_ARGS__) -#define LOG_ENTRY_FUNCTION() { NMLOG_DEBUG("Entering"); } +#define LOG_ENTRY_FUNCTION() { NMLOG_INFO("Entering %s", __func__); } } // namespace NetworkManagerLogger diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 932dd9a8..30d065e8 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -762,9 +762,8 @@ namespace WPEFramework { uint32_t rc = Core::ERROR_RPC_CALL_FAILED; - //Cleared the Existing Store filterred SSID list - m_filterSsidslist.clear(); - m_filterFrequencies.clear(); + std::vector filteredSsids; + std::vector filteredFrequencies; if(ssids) { @@ -773,7 +772,7 @@ namespace WPEFramework { if (!tmpssidlist.empty()) { - m_filterSsidslist.push_back(tmpssidlist.c_str()); + filteredSsids.push_back(tmpssidlist); NMLOG_DEBUG("%s added to SSID filtering", tmpssidlist.c_str()); } else @@ -795,7 +794,7 @@ namespace WPEFramework const string normalizedFrequency = parsedFrequency.Data(); if ((!normalizedFrequency.empty()) && (normalizedFrequency == frequency)) { - m_filterFrequencies.push_back(normalizedFrequency); + filteredFrequencies.push_back(normalizedFrequency); NMLOG_DEBUG("Frequency %s added to scan filtering", normalizedFrequency.c_str()); } else @@ -811,8 +810,16 @@ namespace WPEFramework } } + m_filterVectorsLock.Lock(); + // Replace existing stored filters only after successful parsing/validation. + m_filterSsidslist.clear(); + m_filterFrequencies.clear(); + m_filterSsidslist = filteredSsids; + m_filterFrequencies = filteredFrequencies; + m_filterVectorsLock.Unlock(); + nmEvent->setwifiScanOptions(true); - if(wifi->wifiScanRequest(m_filterSsidslist)) + if(wifi->wifiScanRequest(filteredSsids)) rc = Core::ERROR_NONE; return rc; } diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index b163ae3c..b6b70e1a 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -973,6 +973,7 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { IARM_Result_t retVal = IARM_RESULT_SUCCESS; //Cleared the Existing Store filterred SSID list + m_filterVectorsLock.Lock(); m_filterSsidslist.clear(); m_filterFrequencies.clear(); if(ssids) @@ -994,6 +995,7 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { NMLOG_DEBUG("%s added to Frequency filtering", frequencyList.c_str()); } } + m_filterVectorsLock.Unlock(); memset(¶m, 0, sizeof(param)); From 5227c350a2231bd79d7fd92e99d5cbecfbfe940b Mon Sep 17 00:00:00 2001 From: Anand73-n Date: Mon, 6 Jul 2026 19:37:44 +0530 Subject: [PATCH 10/32] RDKEMW-20973: reconnect failure on deepsleep wakeup (#321) Reason for change: fix deepsleep reconnect failure due to "wpa_cli status" returning empty BSSID, which cleared last SSID. Test procedure: WiFi should reconnect on DeepSleep wakeup Risks: low Priority: P1 Signed-off-by: Anand N --- plugin/NetworkManagerImplementation.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 1beffab6..c0120534 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -1151,7 +1151,8 @@ namespace WPEFramework GetWiFiSignalQuality(ssid, strength, noise, snr, newSignalQuality); - m_lastConnectedSSID = ssid; // last connected ssid used in wifiConnect + if (!ssid.empty()) + m_lastConnectedSSID = ssid; // last connected ssid used in wifiConnect if (oldSignalQuality != newSignalQuality) { oldSignalQuality = newSignalQuality; From a2ffa869020b6dee1e3305e07c55b516f43fccd9 Mon Sep 17 00:00:00 2001 From: gururaajar <83449026+gururaajar@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:06:21 -0400 Subject: [PATCH 11/32] RDKEMW-21453: LegacyNetworkAPIs logs are not printed under wpeframework logs (#324) * RDKEMW-21453: LegacyNetworkAPIs logs are not printed under wpeframework logs Reason for Change: Fixed the legacy prints not printing issue Test Procedure: Check whether legacy prints are getting printed Priority: P1 Risks: Medium Signed-off-by: Gururaaja ESR * Addressed compilation issue * Addressed the legacy prints missing issue * Addressed the legacy prints missing issue --------- Signed-off-by: Gururaaja ESR --- legacy/LegacyNetworkAPIs.cpp | 1 + legacy/LegacyWiFiManagerAPIs.cpp | 1 + plugin/NetworkManager.cpp | 11 +++++------ plugin/NetworkManagerImplementation.cpp | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/legacy/LegacyNetworkAPIs.cpp b/legacy/LegacyNetworkAPIs.cpp index 071466cd..4e31974c 100644 --- a/legacy/LegacyNetworkAPIs.cpp +++ b/legacy/LegacyNetworkAPIs.cpp @@ -96,6 +96,7 @@ namespace WPEFramework const string Network::Initialize(PluginHost::IShell* service ) { + NetworkManagerLogger::Init(); m_service = service; m_service->AddRef(); string message{}; diff --git a/legacy/LegacyWiFiManagerAPIs.cpp b/legacy/LegacyWiFiManagerAPIs.cpp index 9a6573ff..02c9da61 100644 --- a/legacy/LegacyWiFiManagerAPIs.cpp +++ b/legacy/LegacyWiFiManagerAPIs.cpp @@ -128,6 +128,7 @@ namespace WPEFramework const string WiFiManager::Initialize(PluginHost::IShell* service ) { + NetworkManagerLogger::Init(); m_service = service; string message{}; diff --git a/plugin/NetworkManager.cpp b/plugin/NetworkManager.cpp index 1049e503..97e55ee6 100644 --- a/plugin/NetworkManager.cpp +++ b/plugin/NetworkManager.cpp @@ -84,12 +84,6 @@ namespace WPEFramework // Still running inside the main WPEFramework process - the child process will have now been spawned and registered if necessary if (_networkManager != nullptr) { - - // Set the plugin log level - Exchange::INetworkManager::Logging _loglevel; - _networkManager->GetLogLevel(_loglevel); - NetworkManagerLogger::SetLevel(static_cast (_loglevel)); - // Register Notifications SYSLOG(Logging::Startup, (_T("Registering Notification to NetworkManager"))); _networkManager->Register(&_notification); @@ -103,6 +97,11 @@ namespace WPEFramework { SYSLOG(Logging::Startup, (_T("Configuring successful"))); } + + // Set the plugin log level + Exchange::INetworkManager::Logging _loglevel; + _networkManager->GetLogLevel(_loglevel); + NetworkManagerLogger::SetLevel(static_cast (_loglevel)); // Register all custom JSON-RPC methods SYSLOG(Logging::Startup, (_T("Registering JSONRPC Methods"))); RegisterAllMethods(); diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index c0120534..99c98b39 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -65,7 +65,7 @@ namespace WPEFramework /* Initialize Network Manager */ NetworkManagerLogger::Init(); - NMLOG_INFO((_T("NWMgrPlugin Out-Of-Process Instantiation; SHA: " _T(EXPAND_AND_QUOTE(PLUGIN_BUILD_REFERENCE))))); + SYSLOG(::WPEFramework::Logging::Startup, (_T("NWMgrPlugin Out-Of-Process Instantiation; SHA: ") _T(EXPAND_AND_QUOTE(PLUGIN_BUILD_REFERENCE)))); m_processMonThread = std::thread(&NetworkManagerImplementation::processMonitor, this, NM_PROCESS_MONITOR_INTERVAL_SEC); #if USE_TELEMETRY // Initialize Telemetry T2 for NwMgrPlugin @@ -142,12 +142,12 @@ namespace WPEFramework Configuration config; if(configLine.empty()) { - NMLOG_FATAL("config line : is empty !"); + SYSLOG(::WPEFramework::Logging::Shutdown, (_T("config line is empty"))); return Core::ERROR_GENERAL; } else { - NMLOG_INFO("Loading the incoming configuration : %s", configLine.c_str()); + SYSLOG(::WPEFramework::Logging::Startup, (_T("Loading incoming configuration"))); config.FromString(configLine); } From 9da3967185e77680d8bea986baad449809db6408 Mon Sep 17 00:00:00 2001 From: gururaajar <83449026+gururaajar@users.noreply.github.com> Date: Fri, 17 Jul 2026 10:08:26 -0400 Subject: [PATCH 12/32] RDKEMW-21513: Added telemetry T2 event posting for IPv6 public IP (#325) Reason for Change: Posting telemetry T2 event for IPv6 public IP Co-authored-by: Karunakaran A <48997923+karuna2git@users.noreply.github.com> --- plugin/NetworkManagerImplementation.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 99c98b39..1b387096 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -384,6 +384,11 @@ namespace WPEFramework NMLOG_INFO("NM_PUBLIC_IPV4 = %s", ipaddress.c_str()); logTelemetry("NM_PUBLIC_IPV4", ipaddress); } + else + { + NMLOG_INFO("NM_PUBLIC_IPV6 = %s", ipaddress.c_str()); + logTelemetry("NM_PUBLIC_IPV6", ipaddress); + } #endif return Core::ERROR_NONE; } From 4ca6e48d0f039931730f295c16f8e16aa3fcc75d Mon Sep 17 00:00:00 2001 From: Karunakaran A <48997923+karuna2git@users.noreply.github.com> Date: Fri, 17 Jul 2026 14:49:44 -0400 Subject: [PATCH 13/32] RDKEMW-20646 : Using dedicated Eventing thread to send the events (#322) Reason for change: All the NetworkManager Events are published from the calling thread context; somethings are from NMEvent, NMThread, somethings are from Connectivity thread and WiFiQualityMonitoring thread. When consumer is not responding properly, this will lead to extended lock & Sometimes Gnome NetworkManager Crash. To avoid this, all the events are sent thro dedicated eventing thread. NOTE: The Thread::WorkerPool that is default created by the Thunder is always using the main thread when available even though worker pool is available. So this is of no use. Test Procedure: Verify All the Networking Events Risks: Medium Signed-off-by: Karunakaran A GetLogLevel(_loglevel); NetworkManagerLogger::SetLevel(static_cast (_loglevel)); + // Register all custom JSON-RPC methods SYSLOG(Logging::Startup, (_T("Registering JSONRPC Methods"))); RegisterAllMethods(); diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 1b387096..1ba0509a 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -67,6 +67,12 @@ namespace WPEFramework NetworkManagerLogger::Init(); SYSLOG(::WPEFramework::Logging::Startup, (_T("NWMgrPlugin Out-Of-Process Instantiation; SHA: ") _T(EXPAND_AND_QUOTE(PLUGIN_BUILD_REFERENCE)))); m_processMonThread = std::thread(&NetworkManagerImplementation::processMonitor, this, NM_PROCESS_MONITOR_INTERVAL_SEC); + + /* Start dedicated event dispatch thread */ + m_eventThreadStop.store(false); + m_eventThread = std::thread(&NetworkManagerImplementation::eventThreadFunction, this); + NMLOG_INFO("Event dispatch thread started"); + #if USE_TELEMETRY // Initialize Telemetry T2 for NwMgrPlugin t2_init("NwMgrPlugin"); @@ -87,6 +93,17 @@ namespace WPEFramework /* Stop WiFi Signal Monitoring */ stopWiFiSignalQualityMonitor(); + /* Stop event dispatch thread */ + { + std::unique_lock lock(m_eventMutex); + m_eventThreadStop.store(true); + m_eventCondVar.notify_one(); + } + if (m_eventThread.joinable()) { + m_eventThread.join(); + NMLOG_INFO("Event dispatch thread stopped"); + } + { std::unique_lock lock(m_processMonMutex); m_processMonThreadStop.store(true); @@ -338,6 +355,7 @@ namespace WPEFramework /* @brief Get the active Interface used for external world communication */ uint32_t NetworkManagerImplementation::GetPrimaryInterface (string& interface /* @out */) { + LOG_ENTRY_FUNCTION(); if(m_ethEnabled.load() && m_ethConnected.load()) interface = "eth0"; else if(m_wlanEnabled.load() && m_wlanConnected.load()) @@ -402,6 +420,7 @@ namespace WPEFramework /* @brief Set the network manager plugin log level */ uint32_t NetworkManagerImplementation::SetLogLevel(const Logging& level /* @in */) { + LOG_ENTRY_FUNCTION(); NetworkManagerLogger::SetLevel(static_cast(level)); platform_logging(static_cast(level)); NMLOG_DEBUG("loglevel %d", level); @@ -411,6 +430,7 @@ namespace WPEFramework /* @brief Get the network manager plugin log level */ uint32_t NetworkManagerImplementation::GetLogLevel(Logging& level /* @out */) { + LOG_ENTRY_FUNCTION(); LogLevel inLevel; NetworkManagerLogger::GetLevel(inLevel); @@ -421,6 +441,7 @@ namespace WPEFramework /* @brief Request for ping and get the response in as event. The GUID used in the request will be returned in the event. */ uint32_t NetworkManagerImplementation::Ping (const string ipversion /* @in */, const string endpoint /* @in */, const uint32_t noOfRequest /* @in */, const uint16_t timeOutInSeconds /* @in */, const string guid /* @in */, string& response /* @out */) { + LOG_ENTRY_FUNCTION(); char cmd[100] = ""; string tempResult = ""; if (endpoint.empty() || (ipversion != "IPv4" && ipversion != "IPv6")) @@ -452,6 +473,7 @@ namespace WPEFramework /* @brief Request for trace get the response in as event. The GUID used in the request will be returned in the event. */ uint32_t NetworkManagerImplementation::Trace (const string ipversion /* @in */, const string endpoint /* @in */, const uint32_t noOfRequest /* @in */, const string guid /* @in */, string& response /* @out */) { + LOG_ENTRY_FUNCTION(); char cmd[256] = ""; string tempResult = ""; if (endpoint.empty() || (ipversion != "IPv4" && ipversion != "IPv6")) @@ -482,6 +504,7 @@ namespace WPEFramework void NetworkManagerImplementation::executeExternally(NetworkEvents event, const string commandToExecute, string& response) { + LOG_ENTRY_FUNCTION(); FILE *pipe = NULL; string output{}; char buffer[1024]; @@ -579,7 +602,7 @@ namespace WPEFramework } pingResult.ToString(response); - NMLOG_INFO("Response is, %s", response.c_str()); + NMLOG_DEBUG("Response is, %s", response.c_str()); } else if (NETMGR_TRACE == event) { @@ -597,15 +620,16 @@ namespace WPEFramework pclose(pipe); list.ToString(response); - NMLOG_INFO("Response is, %s", response.c_str()); + NMLOG_DEBUG("Response is, %s", response.c_str()); } return; } - void NetworkManagerImplementation::filterScanResults(JsonArray &ssids, - const std::vector& filterSsidslist, - const std::vector& filterFrequencies) + void NetworkManagerImplementation::filterScanResults(JsonArray &ssids, + const std::vector& filterSsidslist, + const std::vector& filterFrequencies) { + LOG_ENTRY_FUNCTION(); JsonArray result; double filterFreq = 0.0; std::unordered_set scanForSsidsSet(filterSsidslist.begin(), filterSsidslist.end()); @@ -674,6 +698,151 @@ namespace WPEFramework return Core::ERROR_NONE; } + + void NetworkManagerImplementation::enqueueEvent(NMPublishEvents event, EventDataVariant&& data) + { + LOG_ENTRY_FUNCTION(); + if (m_eventThreadStop.load(std::memory_order_relaxed)) + { + NMLOG_WARNING("Dropping event %d because event thread is stopping", event); + return; + } + { + std::lock_guard lock(m_eventMutex); + m_eventQueue.push({event, std::move(data)}); + NMLOG_DEBUG("Event %d queued, queue size: %zu", event, m_eventQueue.size()); + } + m_eventCondVar.notify_one(); + } + + void NetworkManagerImplementation::eventThreadFunction() + { + NMLOG_INFO("Event thread started"); + + while (!m_eventThreadStop.load()) { + std::unique_lock lock(m_eventMutex); + + // Wait for events or stop signal + m_eventCondVar.wait(lock, [this] { + return !m_eventQueue.empty() || m_eventThreadStop.load(); + }); + + // Process all queued events + while (!m_eventQueue.empty() && !m_eventThreadStop.load()) { + EventData eventData = std::move(m_eventQueue.front()); + m_eventQueue.pop(); + + // Unlock while processing to avoid blocking new events + lock.unlock(); + + NMLOG_DEBUG("Processing event %d from queue", eventData.event); + dispatchEvent(eventData.event, eventData.data); + + lock.lock(); + } + } + + NMLOG_INFO("Event thread exiting"); + } + + void NetworkManagerImplementation::dispatchEvent(NMPublishEvents event, const EventDataVariant& data) + { + LOG_ENTRY_FUNCTION(); + std::list callbacks; + /* + * Avoid holding _notificationLock while notifying subscribers: callbacks can be slow and may + * call back into NetworkManager, risking deadlock. Take a snapshot of callbacks with an + * extra reference, then release each reference after the invocation. + */ + _notificationLock.Lock(); + for (auto* tmpCB : _notificationCallbacks) { + tmpCB->AddRef(); + callbacks.push_back(tmpCB); + } + _notificationLock.Unlock(); + + switch(event) + { + case NM_ON_INTERFACESTATE_CHANGE: + { + NMLOG_INFO("Publishing onInterfaceStateChange Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onInterfaceStateChange(eventData.state, eventData.interface); + callback->Release(); + } + } + break; + case NM_ON_ACTIVEINTERFACE_CHANGE: + { + NMLOG_INFO("Publishing onActiveInterfaceChange Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onActiveInterfaceChange(eventData.prevActiveInterface, eventData.currentActiveInterface); + callback->Release(); + } + } + break; + case NM_ON_IPADDRESS_CHANGE: + { + NMLOG_INFO("Publishing onIPAddressChange Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onIPAddressChange(eventData.interface, eventData.ipversion, eventData.ipaddress, eventData.status); + callback->Release(); + } + } + break; + case NM_ON_INTERNETSTATUS_CHANGE: + { + NMLOG_INFO("Publishing onInternetStatusChange Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onInternetStatusChange(eventData.prevState, eventData.currState, eventData.interface); + callback->Release(); + } + } + break; + case NM_ON_AVAILABLESSIDS: + { + NMLOG_INFO("Publishing onAvailableSSIDs Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onAvailableSSIDs(eventData.jsonResult); + callback->Release(); + } + } + break; + case NM_ON_WIFISTATE_CHANGE: + { + NMLOG_INFO("Publishing onWiFiStateChange Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onWiFiStateChange(eventData.state); + callback->Release(); + } + } + break; + case NM_ON_WIFISIGNALQUALITY_CHANGE: + { + NMLOG_INFO("Publishing onWiFiSignalQualityChange Event"); + const auto& eventData = std::get(data); + for (const auto callback : callbacks) { + callback->onWiFiSignalQualityChange(eventData.ssid, eventData.strength, eventData.noise, eventData.snr, eventData.quality); + callback->Release(); + } + } + break; + default: + { + for (const auto callback : callbacks) { + callback->Release(); + } + } + break; + } + } + void NetworkManagerImplementation::ReportInterfaceStateChange(const Exchange::INetworkManager::InterfaceState state, const string interface) { LOG_ENTRY_FUNCTION(); @@ -735,17 +904,17 @@ namespace WPEFramework m_wlanEnabled.store(true); } - _notificationLock.Lock(); - NMLOG_INFO("Posting onInterfaceChange %s - %u", interface.c_str(), (unsigned)state); - for (const auto callback : _notificationCallbacks) { - callback->onInterfaceStateChange(state, interface); + { + InterfaceStateChangeData eventData{state, interface}; + NMLOG_INFO("Posting onInterfaceChange %s - %u", interface.c_str(), (unsigned)state); + enqueueEvent(NM_ON_INTERFACESTATE_CHANGE, std::move(eventData)); } - _notificationLock.Unlock(); + return; } void NetworkManagerImplementation::ReportActiveInterfaceChange(const string prevActiveInterface, const string currentActiveinterface) { - NMLOG_INFO("Posting onActiveInterfaceChange %s", currentActiveinterface.c_str()); + LOG_ENTRY_FUNCTION(); if(currentActiveinterface == "eth0") { @@ -758,13 +927,11 @@ namespace WPEFramework m_wlanEnabled.store(true); } - // FIXME : This could be the place to define `m_defaultInterface` to incoming `currentActiveinterface`. - // m_defaultInterface = currentActiveinterface; - _notificationLock.Lock(); - for (const auto callback : _notificationCallbacks) { - callback->onActiveInterfaceChange(prevActiveInterface, currentActiveinterface); + { + ActiveInterfaceChangeData eventData{prevActiveInterface, currentActiveinterface}; + NMLOG_INFO("Posting onActiveInterfaceChange %s", currentActiveinterface.c_str()); + enqueueEvent(NM_ON_ACTIVEINTERFACE_CHANGE, std::move(eventData)); } - _notificationLock.Unlock(); #if USE_TELEMETRY NMLOG_INFO("NM_INTERFACE_STATUS = Interface changed to %s", currentActiveinterface.c_str()); logTelemetry("NM_INTERFACE_STATUS", "Interface changed to " + currentActiveinterface); @@ -804,19 +971,17 @@ namespace WPEFramework NMLOG_DEBUG("No need to trigger connectivity monitor interface is %s", interface.c_str()); } - _notificationLock.Lock(); - NMLOG_INFO("Posting onIPAddressChange %s: %s %s %s", - (Exchange::INetworkManager::IP_ACQUIRED == status) ? "IP acquired" : "IP lost", - interface.c_str(), ipversion.c_str(), ipaddress.c_str()); - for (const auto callback : _notificationCallbacks) { - callback->onIPAddressChange(interface, ipversion, ipaddress, status); + { + IPAddressChangeData eventData{interface, ipversion, ipaddress, status}; + NMLOG_INFO("Posting onIPAddressChange %s: %s %s %s", (Exchange::INetworkManager::IP_ACQUIRED == status) ? "IP acquired" : "IP lost", + interface.c_str(), ipversion.c_str(), ipaddress.c_str()); + enqueueEvent(NM_ON_IPADDRESS_CHANGE, std::move(eventData)); } - _notificationLock.Unlock(); } void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) { - NMLOG_INFO("Posting onInternetStatusChange with current state as %u", (unsigned)currState); + LOG_ENTRY_FUNCTION(); #if USE_TELEMETRY // Log error only when ethernet is up and there's no internet if(currState == Exchange::INetworkManager::INTERNET_NOT_AVAILABLE && @@ -828,11 +993,12 @@ namespace WPEFramework logTelemetry("NM_ETHERNET_CONNECTIVITY", "Ethernet connectivity failed"); } #endif - _notificationLock.Lock(); - for (const auto callback : _notificationCallbacks) { - callback->onInternetStatusChange(prevState, currState, interface); + { + InternetStatusChangeData eventData{prevState, currState, interface}; + NMLOG_INFO("Posting onInternetStatusChange with current state as %u", (unsigned)currState); + enqueueEvent(NM_ON_INTERNETSTATUS_CHANGE, std::move(eventData)); } - _notificationLock.Unlock(); + #if USE_TELEMETRY string stateStr = Core::EnumerateType(currState).Data(); NMLOG_INFO("NM_INTERNET_STATUS = %s", stateStr.c_str()); @@ -842,6 +1008,7 @@ namespace WPEFramework int32_t NetworkManagerImplementation::logSSIDs(Logging level, const JsonArray &ssids) { + LOG_ENTRY_FUNCTION(); Logging inLevel; GetLogLevel(inLevel); if (level > inLevel) @@ -861,6 +1028,7 @@ namespace WPEFramework void NetworkManagerImplementation::ReportAvailableSSIDs(const JsonArray &arrayofWiFiScanResults) { + LOG_ENTRY_FUNCTION(); string jsonOfWiFiScanResults; string jsonOfFilterScanResults; JsonArray filterResult = arrayofWiFiScanResults; @@ -885,15 +1053,15 @@ namespace WPEFramework NMLOG_INFO("Posting onAvailableSSIDs event with %d SSIDs as,", filterResult.Length()); logSSIDs(LOG_LEVEL_INFO, filterResult); - _notificationLock.Lock(); - for (const auto callback : _notificationCallbacks) { - callback->onAvailableSSIDs(jsonOfFilterScanResults); + { + AvailableSSIDsData eventData{jsonOfFilterScanResults}; + enqueueEvent(NM_ON_AVAILABLESSIDS, std::move(eventData)); } - _notificationLock.Unlock(); } void NetworkManagerImplementation::startWiFiSignalQualityMonitor(int interval) { + LOG_ENTRY_FUNCTION(); if (m_isRunning.load()) { NMLOG_INFO("WiFiSignalQualityMonitor Thread is already running."); return; @@ -909,6 +1077,7 @@ namespace WPEFramework void NetworkManagerImplementation::stopWiFiSignalQualityMonitor() { + LOG_ENTRY_FUNCTION(); if (!m_isRunning.load()) return; // No thread to stop @@ -1093,6 +1262,7 @@ namespace WPEFramework void NetworkManagerImplementation::processMonitor(uint16_t interval) { + LOG_ENTRY_FUNCTION(); pid_t pid = getpid(); string path = "/proc/"; @@ -1144,6 +1314,7 @@ namespace WPEFramework void NetworkManagerImplementation::monitorThreadFunction(int interval) { + LOG_ENTRY_FUNCTION(); static Exchange::INetworkManager::WiFiSignalQuality oldSignalQuality = Exchange::INetworkManager::WIFI_SIGNAL_DISCONNECTED; NMLOG_INFO("WiFiSignalQualityMonitor thread started ! (%d)", interval); while (true) @@ -1182,6 +1353,7 @@ namespace WPEFramework void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state) { + LOG_ENTRY_FUNCTION(); /* start signal strength monitor when wifi connected */ if(INetworkManager::WiFiState::WIFI_STATE_CONNECTED == state) { @@ -1200,25 +1372,25 @@ namespace WPEFramework NMLOG_INFO("NM_WIFI_STATUS = %s", stateStr.c_str()); logTelemetry("NM_WIFI_STATUS", stateStr); #endif - _notificationLock.Lock(); - for (const auto callback : _notificationCallbacks) { - callback->onWiFiStateChange(state); + { + WiFiStateChangeData eventData{state}; + enqueueEvent(NM_ON_WIFISTATE_CHANGE, std::move(eventData)); } - _notificationLock.Unlock(); } void NetworkManagerImplementation::ReportWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality) { - _notificationLock.Lock(); - NMLOG_INFO("Posting onWiFiSignalQualityChange %d", strength); - for (const auto callback : _notificationCallbacks) { - callback->onWiFiSignalQualityChange(ssid, strength, noise, snr, quality); + LOG_ENTRY_FUNCTION(); + { + WiFiSignalQualityChangeData eventData{ssid, strength, noise, snr, quality}; + NMLOG_INFO("Posting onWiFiSignalQualityChange %d", strength); + enqueueEvent(NM_ON_WIFISIGNALQUALITY_CHANGE, std::move(eventData)); } - _notificationLock.Unlock(); } void NetworkManagerImplementation::logTelemetry(const std::string& eventName, const std::string& message) { + LOG_ENTRY_FUNCTION(); #if USE_TELEMETRY T2ERROR t2error = t2_event_s(eventName.c_str(), const_cast(message.c_str())); if (t2error != T2ERROR_SUCCESS) { @@ -1233,6 +1405,7 @@ namespace WPEFramework const Exchange::IPowerManager::PowerState newState, std::function sendAck) { + LOG_ENTRY_FUNCTION(); // Called from NetworkManagerPowerClient's power thread. NMLOG_DEBUG("OnPowerModePreChange: current=%d new=%d", static_cast(currentState), static_cast(newState)); @@ -1315,6 +1488,7 @@ namespace WPEFramework const Exchange::IPowerManager::PowerState currentState, const Exchange::IPowerManager::PowerState newState) { + LOG_ENTRY_FUNCTION(); NMLOG_INFO("OnPowerModeChanged: current=%d new=%d", static_cast(currentState), static_cast(newState)); if (currentState == Exchange::IPowerManager::PowerState::POWER_STATE_STANDBY_DEEP_SLEEP) { @@ -1446,6 +1620,7 @@ namespace WPEFramework Exchange::INetworkManager::IPAddress IpFamilyCache::toIPAddress() const { + LOG_ENTRY_FUNCTION(); Exchange::INetworkManager::IPAddress addr{}; /* Detect IP version from any available address. */ bool isIPv6 = false; diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index b7bffe91..bafe337f 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -30,6 +30,8 @@ #include #include #include +#include +#include using namespace std; @@ -209,20 +211,70 @@ namespace WPEFramework Core::JSON::DecUInt32 loglevel; }; + enum NMPublishEvents { + NM_ON_INTERFACESTATE_CHANGE = 0, + NM_ON_ACTIVEINTERFACE_CHANGE, + NM_ON_IPADDRESS_CHANGE, + NM_ON_INTERNETSTATUS_CHANGE, + NM_ON_AVAILABLESSIDS, + NM_ON_WIFISTATE_CHANGE, + NM_ON_WIFISIGNALQUALITY_CHANGE + }; - class Job : public Core::IDispatch { - public: - Job(function work) - : _work(work) - { - } - void Dispatch() override - { - _work(); - } + // Typed event data structures + struct InterfaceStateChangeData { + Exchange::INetworkManager::InterfaceState state; + string interface; + }; - private: - function _work; + struct ActiveInterfaceChangeData { + string prevActiveInterface; + string currentActiveInterface; + }; + + struct IPAddressChangeData { + string interface; + string ipversion; + string ipaddress; + Exchange::INetworkManager::IPStatus status; + }; + + struct InternetStatusChangeData { + Exchange::INetworkManager::InternetStatus prevState; + Exchange::INetworkManager::InternetStatus currState; + string interface; + }; + + struct AvailableSSIDsData { + string jsonResult; // Pre-serialized JSON string + }; + + struct WiFiStateChangeData { + Exchange::INetworkManager::WiFiState state; + }; + + struct WiFiSignalQualityChangeData { + string ssid; + int strength; + int noise; + int snr; + Exchange::INetworkManager::WiFiSignalQuality quality; + }; + + using EventDataVariant = std::variant< + std::monostate, + InterfaceStateChangeData, + ActiveInterfaceChangeData, + IPAddressChangeData, + InternetStatusChangeData, + AvailableSSIDsData, + WiFiStateChangeData, + WiFiSignalQualityChangeData + >; + + struct EventData { + NMPublishEvents event; + EventDataVariant data; }; public: @@ -344,6 +396,9 @@ namespace WPEFramework void monitorThreadFunction(int interval); int32_t logSSIDs(Logging level, const JsonArray &ssids); void processMonitor(uint16_t interval); + void eventThreadFunction(); + void enqueueEvent(NMPublishEvents event, EventDataVariant&& data); + void dispatchEvent(NMPublishEvents event, const EventDataVariant& data); private: std::list _notificationCallbacks; @@ -365,6 +420,12 @@ namespace WPEFramework std::atomic m_processMonThreadStop{false}; std::condition_variable m_processMonCondVar; + std::thread m_eventThread; + std::queue m_eventQueue; + std::mutex m_eventMutex; + std::condition_variable m_eventCondVar; + std::atomic m_eventThreadStop{false}; + std::atomic m_isRunning{false}; std::atomic m_stopThread{false}; std::mutex m_condVariableMutex; diff --git a/tests/l1Test/CMakeLists.txt b/tests/l1Test/CMakeLists.txt index dfae9937..ec7b8c37 100644 --- a/tests/l1Test/CMakeLists.txt +++ b/tests/l1Test/CMakeLists.txt @@ -44,7 +44,7 @@ if(ENABLE_ROUTER_DISCOVERY_TOOL) ) set_target_properties(${NM_ROUTER_DISCOVERY_L1_TEST} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) @@ -78,7 +78,7 @@ target_link_libraries(${NM_CLASS_L1_TEST} PRIVATE ) set_target_properties(${NM_CLASS_L1_TEST} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) diff --git a/tests/l2Test/legacy/CMakeLists.txt b/tests/l2Test/legacy/CMakeLists.txt index 5106f18e..ae168861 100644 --- a/tests/l2Test/legacy/CMakeLists.txt +++ b/tests/l2Test/legacy/CMakeLists.txt @@ -40,11 +40,11 @@ add_executable(${NM_LEGACY_NETWORK_UT} ) set_target_properties(${NM_LEGACY_WIFI_UT} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) set_target_properties(${NM_LEGACY_NETWORK_UT} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) diff --git a/tests/l2Test/libnm/CMakeLists.txt b/tests/l2Test/libnm/CMakeLists.txt index 9b1a44d0..17be8eea 100644 --- a/tests/l2Test/libnm/CMakeLists.txt +++ b/tests/l2Test/libnm/CMakeLists.txt @@ -49,7 +49,7 @@ add_executable(${NM_LIBNM_PROXY_L2_TEST} ) set_target_properties(${NM_LIBNM_PROXY_L2_TEST} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) diff --git a/tests/l2Test/rdk/CMakeLists.txt b/tests/l2Test/rdk/CMakeLists.txt index 3a94c63d..cc5ecc59 100644 --- a/tests/l2Test/rdk/CMakeLists.txt +++ b/tests/l2Test/rdk/CMakeLists.txt @@ -44,7 +44,7 @@ add_executable(${NM_RDK_PROXY_L2_TEST} ) set_target_properties(${NM_RDK_PROXY_L2_TEST} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) diff --git a/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp b/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp index 89be4686..1fb8a27c 100644 --- a/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp +++ b/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp @@ -235,6 +235,7 @@ TEST_F(NetworkManagerEventTest, onInterfaceStateChange) EXPECT_EQ(Core::ERROR_NONE, onInterfaceStateChange.Lock()); + sleep(5); EVENT_UNSUBSCRIBE(2, _T("onInterfaceStateChange"), _T("org.rdk.NetworkManager"), message); } @@ -464,6 +465,7 @@ TEST_F(NetworkManagerEventTest, onInterfaceStateChange2) _nmEventHandler(IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETWORK_MANAGER_EVENT_INTERFACE_CONNECTION_STATUS, &eventData, sizeof(eventData)); EXPECT_EQ(Core::ERROR_NONE, onInterfaceStateChange.Lock()); + sleep(5); EVENT_UNSUBSCRIBE(2, _T("onInterfaceStateChange"), _T("org.rdk.NetworkManager"), message); diff --git a/tools/plugincli/CMakeLists.txt b/tools/plugincli/CMakeLists.txt index 56883676..6784ff84 100644 --- a/tools/plugincli/CMakeLists.txt +++ b/tools/plugincli/CMakeLists.txt @@ -74,7 +74,7 @@ if(ENABLE_GNOME_GDBUS) ) set_target_properties(${PLUGIN_GDBUS_CLI} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) @@ -106,7 +106,7 @@ else() ) set_target_properties(${PLUGIN_LIBNM_CLI} PROPERTIES - CXX_STANDARD 11 + CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES ) From 32131d63c7b0b07207b014e721ac6093b995e8db Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Fri, 17 Jul 2026 14:56:05 -0400 Subject: [PATCH 14/32] Release of 3.4.0 Release of 3.4.0 --- CHANGELOG.md | 7 +++++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d262c5e6..d5dd29bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,13 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.4.0] - 2026-07-17 +### Fixed +- Fixed the logging failure within the inprogress plugins +- Added mutex before accessing the scan filters +- Added t2 event for IPv6 Public IP +- Added thread to publish the events without blocking the caller notification thread + ## [3.3.0] - 2026-06-12 ### Fixed - Fixed the memory leak that caused by the persistent m_nmClient and m_nmContext. diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d4cc1db..8fe5ee16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 3) -set(VERSION_MINOR 3) +set(VERSION_MINOR 4) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index f79bd812..84facd1e 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.3.0" + "version": "3.4.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 3d109f0a..978dd2e8 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.3.0** +**Version: 3.4.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.3.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.4.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 3c2c421d348d661ed0fbe00ff619367512fbae95 Mon Sep 17 00:00:00 2001 From: gururaajar <83449026+gururaajar@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:10:19 -0400 Subject: [PATCH 15/32] RDKEMW-22276: Coverity integration for networkmanager plugin (#331) * Added cov_build.sh script * Added all the required files for coverity * Addressed copilot reported issues * Addressed compilation issue * updated the comments * Changed the permission of workflow to read only --- .github/workflows/native_full_build.yml | 26 ++++ build_dependencies.sh | 193 ++++++++++++++++++++++++ cov_build.sh | 74 +++++++++ 3 files changed, 293 insertions(+) create mode 100644 .github/workflows/native_full_build.yml create mode 100644 build_dependencies.sh create mode 100644 cov_build.sh diff --git a/.github/workflows/native_full_build.yml b/.github/workflows/native_full_build.yml new file mode 100644 index 00000000..c7a10413 --- /dev/null +++ b/.github/workflows/native_full_build.yml @@ -0,0 +1,26 @@ +name: Build Component in Native Environment + +on: + push: + branches: [ main, develop, 'support/**', 'hotfix/**', 'topic/**' ] + pull_request: + branches: [ main, develop, 'support/**', 'hotfix/**', 'topic/**' ] + +permissions: + contents: read + +jobs: + build-networkmanager: + name: Build networkmanager component in github rdkcentral + # Ubuntu 24.04 provides libnm 1.46, closer to the version used in RDK(1.43.7). + runs-on: ubuntu-24.04 + + steps: + + - name: Checkout code + uses: actions/checkout@v3 + + - name: native build + run: | + sh -x build_dependencies.sh + sh -x cov_build.sh diff --git a/build_dependencies.sh b/build_dependencies.sh new file mode 100644 index 00000000..8aad62f3 --- /dev/null +++ b/build_dependencies.sh @@ -0,0 +1,193 @@ +#!/usr/bin/env bash +# +# build_dependencies.sh - Install system packages and build the Thunder +# ecosystem (ThunderTools, Thunder, ThunderInterfaces) plus the mock +# dependency files required to build networkmanager for Coverity. +# +# This is the first half of the old cov_build.sh: everything that prepares +# the environment. The component itself is built by cov_build.sh, which is +# run afterwards from the same workspace. +# +# Usage: +# ./build_dependencies.sh +# +# Override any of the environment variables below on the command line, e.g.: +# THUNDER_REF=R4.4.3 ./build_dependencies.sh +# + +# Re-exec under bash if started with a non-bash shell (e.g. `sh build_dependencies.sh`, +# where sh is dash). This script relies on bash features such as `pipefail` +# and ${BASH_SOURCE[0]}, which dash does not support. +if [ -z "${BASH_VERSION:-}" ]; then + exec bash "$0" "$@" +fi + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration (mirrors the workflow `env:` block) +# --------------------------------------------------------------------------- +BUILD_TYPE="${BUILD_TYPE:-Debug}" +THUNDER_REF="${THUNDER_REF:-R4.4.3}" +THUNDERTOOLS_REF="${THUNDERTOOLS_REF:-${THUNDER_REF}}" +INTERFACES_REF="${INTERFACES_REF:-${THUNDER_REF}}" + +# Root workspace directory. In CI this is ${{ github.workspace }}. +# The Thunder repositories and install tree live inside the networkmanager +# checkout so the layout is self-contained and shared with cov_build.sh. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}}" +NETWORKMANAGER_DIR="${NETWORKMANAGER_DIR:-${SCRIPT_DIR}}" +INSTALL_DIR="${WORKSPACE}/install/usr" +MODULE_PATH="${WORKSPACE}/install/tools/cmake" + +NPROC="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)" + +log() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; } + +# Use sudo only when it exists (CI build containers usually run as root and +# do not ship sudo). Otherwise run the command directly. +if command -v sudo >/dev/null 2>&1; then + SUDO="sudo" +else + SUDO="" +fi + +# --------------------------------------------------------------------------- +# Install system packages +# --------------------------------------------------------------------------- +log "Installing system packages" +${SUDO} apt-get update +${SUDO} apt-get install -y \ + build-essential \ + cmake \ + pkg-config \ + libglib2.0-dev \ + libnm-dev \ + libcurl4-openssl-dev \ + lcov \ + ninja-build + +# --------------------------------------------------------------------------- +# Install Python dependencies +# --------------------------------------------------------------------------- +log "Installing Python dependencies" +if command -v pip >/dev/null 2>&1; then + pip install --break-system-packages jsonref || pip install jsonref || true +elif command -v pip3 >/dev/null 2>&1; then + pip3 install --break-system-packages jsonref || pip3 install jsonref || true +else + echo "pip not found; skipping jsonref install" +fi + +# --------------------------------------------------------------------------- +# Clone Thunder repositories +# --------------------------------------------------------------------------- +clone_repo() { + # clone_repo + local url="$1" path="$2" ref="$3" + if [ ! -d "${path}/.git" ]; then + log "Cloning ${url} (${ref}) into ${path}" + git clone --branch "${ref}" "${url}" "${path}" + else + log "Repository ${path} already present; fetching ${ref}" + git -C "${path}" fetch origin "${ref}" + git -C "${path}" checkout "${ref}" + fi +} + +clone_repo "https://github.com/rdkcentral/ThunderTools" \ + "${WORKSPACE}/ThunderTools" "${THUNDERTOOLS_REF}" +clone_repo "https://github.com/rdkcentral/Thunder" \ + "${WORKSPACE}/Thunder" "${THUNDER_REF}" +clone_repo "https://github.com/rdkcentral/ThunderInterfaces" \ + "${WORKSPACE}/ThunderInterfaces" "${INTERFACES_REF}" + +# --------------------------------------------------------------------------- +# Apply Thunder SubscribeStub patch +# --------------------------------------------------------------------------- +log "Applying Thunder SubscribeStub patch" +( + cd "${WORKSPACE}/Thunder" + git apply "${NETWORKMANAGER_DIR}/tests/patches/thunder/SubscribeStub.patch" 2>/dev/null \ + || echo "Patch already applied or not needed" +) + +# --------------------------------------------------------------------------- +# Build ThunderTools +# --------------------------------------------------------------------------- +log "Building ThunderTools" +cmake \ + -S "${WORKSPACE}/ThunderTools" \ + -B "${WORKSPACE}/build/ThunderTools" \ + -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ + -DCMAKE_MODULE_PATH="${MODULE_PATH}" \ + -DGENERIC_CMAKE_MODULE_PATH="${MODULE_PATH}" +cmake --build "${WORKSPACE}/build/ThunderTools" --target install -j"${NPROC}" + +# --------------------------------------------------------------------------- +# Build Thunder +# --------------------------------------------------------------------------- +log "Building Thunder" +cmake \ + -S "${WORKSPACE}/Thunder" \ + -B "${WORKSPACE}/build/Thunder" \ + -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ + -DCMAKE_MODULE_PATH="${MODULE_PATH}" \ + -DBUILD_TYPE="${BUILD_TYPE}" \ + -DBINDING=127.0.0.1 \ + -DPORT=9998 +cmake --build "${WORKSPACE}/build/Thunder" --target install -j"${NPROC}" + +# --------------------------------------------------------------------------- +# Build ThunderInterfaces +# --------------------------------------------------------------------------- +log "Building ThunderInterfaces" +cmake \ + -S "${WORKSPACE}/ThunderInterfaces" \ + -B "${WORKSPACE}/build/ThunderInterfaces" \ + -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ + -DCMAKE_MODULE_PATH="${MODULE_PATH}" +cmake --build "${WORKSPACE}/build/ThunderInterfaces" --target install -j"${NPROC}" + +# --------------------------------------------------------------------------- +# Install IPowerManager header into the Thunder interfaces include path +# --------------------------------------------------------------------------- +log "Installing IPowerManager header" +IFACE_DIR="$(find "${INSTALL_DIR}/include" -maxdepth 2 -name "interfaces" -type d | head -1)" +if [ -z "${IFACE_DIR}" ] || [ ! -d "${IFACE_DIR}" ]; then + echo "Error: Thunder interfaces include dir not found under ${INSTALL_DIR}/include" >&2 + exit 1 +fi +cp "${NETWORKMANAGER_DIR}/tests/mocks/thunder/IPowerManager.h" "${IFACE_DIR}/" + +# --------------------------------------------------------------------------- +# Generate dependency files +# --------------------------------------------------------------------------- +log "Generating /etc/device.properties" +${SUDO} tee /etc/device.properties >/dev/null <<'EOF' +ETHERNET_INTERFACE=eth0 +WIFI_INTERFACE=wlan0 +DEFAULT_HOSTNAME=rdk_test_device +EOF + +# --------------------------------------------------------------------------- +# Generate IARM headers/stubs +# --------------------------------------------------------------------------- +log "Generating IARM headers" +mkdir -p "${INSTALL_DIR}/lib" +printf 'void __nm_cov_stub(void){}\n' | ${CC:-cc} -fPIC -shared -x c - -o "${INSTALL_DIR}/lib/libIARMBus.so" +printf 'void __nm_cov_stub(void){}\n' | ${CC:-cc} -fPIC -shared -x c - -o "${INSTALL_DIR}/lib/libmfrlib.so" +mkdir -p "${INSTALL_DIR}/include/rdk/iarmbus" +mkdir -p "${INSTALL_DIR}/include/rdk/iarmmgrs-hal" +touch "${INSTALL_DIR}/include/rdk/iarmbus/libIARM.h" +touch "${INSTALL_DIR}/include/rdk/iarmmgrs-hal/mfrMgr.h" +( + cd "${NETWORKMANAGER_DIR}/tests" + mkdir -p headers/rdk/iarmbus + mkdir -p headers/rdk/iarmmgrs-hal + touch headers/rdk/iarmmgrs-hal/mfrMgr.h + touch headers/rdk/iarmbus/libIARM.h headers/rdk/iarmbus/libIBus.h +) + +log "Dependencies ready. Run cov_build.sh to build networkmanager." diff --git a/cov_build.sh b/cov_build.sh new file mode 100644 index 00000000..72c2222f --- /dev/null +++ b/cov_build.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# +# cov_build.sh - Coverity-friendly build of networkmanager with the Gnome +# libnm proxy. +# +# This is the second half of the original cov_build.sh: it builds only the +# networkmanager component. The Thunder ecosystem and mock dependency files +# it relies on are produced by build_dependencies.sh, which must be run first +# from the same workspace. +# +# Usage: +# ./build_dependencies.sh +# ./cov_build.sh +# +# Override any of the environment variables below on the command line, e.g.: +# THUNDER_REF=R4.4.3 ./cov_build.sh +# + +# Re-exec under bash if started with a non-bash shell (e.g. `sh cov_build.sh`, +# where sh is dash). This script relies on bash features such as `pipefail` +# and ${BASH_SOURCE[0]}, which dash does not support. +if [ -z "${BASH_VERSION:-}" ]; then + exec bash "$0" "$@" +fi + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration (must match build_dependencies.sh so the paths line up) +# --------------------------------------------------------------------------- +# Optional cross-compile toolchain file (empty for native builds). +TOOLCHAIN_FILE="${TOOLCHAIN_FILE:-}" + +# Root workspace directory. In CI this is ${{ github.workspace }}. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +WORKSPACE="${WORKSPACE:-${SCRIPT_DIR}}" +NETWORKMANAGER_DIR="${NETWORKMANAGER_DIR:-${SCRIPT_DIR}}" +INSTALL_DIR="${WORKSPACE}/install/usr" +MODULE_PATH="${WORKSPACE}/install/tools/cmake" + +NPROC="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)" + +log() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; } + +# --------------------------------------------------------------------------- +# Build networkmanager with Gnome libnm Proxy +# --------------------------------------------------------------------------- +log "Building networkmanager with Gnome libnm Proxy" +NM_CXX_FLAGS=" -fprofile-arcs -ftest-coverage \ +-I ${NETWORKMANAGER_DIR}/tests/headers \ +-I ${NETWORKMANAGER_DIR}/tests/headers/rdk/iarmbus \ +-I ${NETWORKMANAGER_DIR}/tests/headers/rdk/iarmmgrs-hal \ +--include ${NETWORKMANAGER_DIR}/tests/mocks/Iarm.h \ +--include ${NETWORKMANAGER_DIR}/tests/mocks/mfrMgr.h " + +TOOLCHAIN_ARG=() +if [ -n "${TOOLCHAIN_FILE}" ]; then + TOOLCHAIN_ARG=(-DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN_FILE}") +fi + +cmake \ + -S "${NETWORKMANAGER_DIR}" \ + -B "${WORKSPACE}/build/networkmanager_libnm" \ + "${TOOLCHAIN_ARG[@]}" \ + -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \ + -DCMAKE_PREFIX_PATH="${INSTALL_DIR}" \ + -DCMAKE_MODULE_PATH="${MODULE_PATH}" \ + -DCMAKE_CXX_FLAGS="${NM_CXX_FLAGS}" \ + -DENABLE_GNOME_NETWORKMANAGER=ON \ + -DENABLE_LEGACY_PLUGINS=OFF \ + -DENABLE_UNIT_TESTING=ON \ + -DENABLE_PLUGIN_CLI=OFF \ + -DENABLE_MIGRATION_MFRMGR_SUPPORT=ON +cmake --build "${WORKSPACE}/build/networkmanager_libnm" --target install -j"${NPROC}" From 96eb081676e78dd3b54c03c11786b1bfce48853d Mon Sep 17 00:00:00 2001 From: Anand73-n Date: Sat, 1 Aug 2026 00:41:40 +0530 Subject: [PATCH 16/32] RDKEMW-22632: Order service start after PowerManager (#327) * RDKEMW-21821: Order service start after PowerManager Reason for change: Ensure PowerManager service is started before NetworkManager Test procedure: Reboot, then inspect the order units actually started Risks: low Priority: P1 Signed-off-by: Anand N Co-authored-by: Karunakaran A <48997923+karuna2git@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- plugin/CMakeLists.txt | 2 ++ plugin/NetworkManagerPowerClient.cpp | 3 ++- plugin/gnome/systemd/nm-powermanager.conf | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 plugin/gnome/systemd/nm-powermanager.conf diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 32d065dc..588a243d 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -160,6 +160,8 @@ if(ENABLE_GNOME_NETWORKMANAGER) DESTINATION ${CMAKE_INSTALL_PREFIX}/../lib/rdk) install(FILES gnome/systemd/gnome-networkmanager-migration.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/../lib/systemd/system/wpeframework-networkmanager.service.d) + install(FILES gnome/systemd/nm-powermanager.conf + DESTINATION ${CMAKE_INSTALL_PREFIX}/../lib/systemd/system/wpeframework-networkmanager.service.d) endif(ENABLE_GNOME_NETWORKMANAGER) #Generate Plugin configuration file diff --git a/plugin/NetworkManagerPowerClient.cpp b/plugin/NetworkManagerPowerClient.cpp index 3d6d2ed9..94a8694b 100644 --- a/plugin/NetworkManagerPowerClient.cpp +++ b/plugin/NetworkManagerPowerClient.cpp @@ -34,12 +34,13 @@ NetworkManagerPowerClient::NetworkManagerPowerClient(INetworkPowerCallback& call , mPreChangeNotification(*this) , mChangedNotification(*this) { - NMLOG_INFO("connecting to PowerManager"); + NMLOG_INFO("NetworkManagerPowerClient ctor"); if (auto r = Open(RPC::CommunicationTimeOut, Connector(), "org.rdk.PowerManager"); 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 PowerManager (error %u)", r); } + NMLOG_INFO("NetworkManagerPowerClient ctor ends"); } NetworkManagerPowerClient::~NetworkManagerPowerClient() diff --git a/plugin/gnome/systemd/nm-powermanager.conf b/plugin/gnome/systemd/nm-powermanager.conf new file mode 100644 index 00000000..7e8c3f01 --- /dev/null +++ b/plugin/gnome/systemd/nm-powermanager.conf @@ -0,0 +1,4 @@ +[Unit] +# Ensure NetworkManager starts after PowerManager +Wants=wpeframework-powermanager.service +After=wpeframework-powermanager.service From 9919e6f36c64fe0b602a3faf2299856f003ddc5b Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Sun, 2 Aug 2026 06:54:29 -0400 Subject: [PATCH 17/32] Release of 3.5.0 Release of 3.5.0 --- CHANGELOG.md | 5 +++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5dd29bf..55382106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.5.0] - 2026-08-02 +### Added +- Added Coverity WorkFlow +- Added PowerManager boot dependency + ## [3.4.0] - 2026-07-17 ### Fixed - Fixed the logging failure within the inprogress plugins diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fe5ee16..d21cefdb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 3) -set(VERSION_MINOR 4) +set(VERSION_MINOR 5) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 84facd1e..d5220509 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.4.0" + "version": "3.5.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 978dd2e8..b65a75fd 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.4.0** +**Version: 3.5.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.4.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.5.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 6c74d8ec4c2d10068fdfe7c224f04070aa5b8b2a Mon Sep 17 00:00:00 2001 From: jincysam87 <167995204+jincysam87@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:04:29 -0400 Subject: [PATCH 18/32] RDKEMW-23357 : Failed to Connect to Hidden SSID (#333) Reason for change: wifi connect call with invalid/hidden SSID results in IPC timeout Test Procedure: Issue wifi connect does not timeout even if SSID is invalid/hidden Risks: Medium Signed-off-by: jincysaramma_sam@cable.comcast.com --- plugin/gnome/NetworkManagerGnomeWIFI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 58872d48..c7506334 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -1404,6 +1404,7 @@ namespace WPEFramework /* ssid not found in scan list so add to known ssid it will do a scanning and connect */ if(ssidInfo.persist) { + deleteClientConnection(); if(addToKnownSSIDs(ssidInfo)) { NMLOG_DEBUG("Adding to known ssid '%s' ", ssidInfo.ssid.c_str()); From 27345cb9e24d98c94a6103bca03abe33641065b6 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Tue, 11 Aug 2026 14:11:38 -0400 Subject: [PATCH 19/32] Release of 3.6.0 Release of 3.6.0 --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55382106..851e39f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.6.0] - 2026-08-11 +### Fixed +- Fixed the issue with connecting to a SSID that is not present in scan list + ## [3.5.0] - 2026-08-02 ### Added - Added Coverity WorkFlow diff --git a/CMakeLists.txt b/CMakeLists.txt index d21cefdb..724fb37e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 3) -set(VERSION_MINOR 5) +set(VERSION_MINOR 6) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index d5220509..2922b983 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.5.0" + "version": "3.6.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index b65a75fd..fd6d97dc 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.5.0** +**Version: 3.6.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.5.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.6.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 146b3ac616e7693b1e968859b490bc7119f19ab6 Mon Sep 17 00:00:00 2001 From: tukken-comcast Date: Sat, 22 Aug 2026 05:50:42 +0530 Subject: [PATCH 20/32] RDK-62042: Improve performance of GetAvailableInterfaces and GetInterfaceState (#337) * RDK-62042: Improve performance of GetAvailableInterfaces and GetInterfaceState Serve interface-state reads from an event-maintained cache. GetAvailableInterfaces and GetInterfaceState on the Gnome backend created a throwaway NMClient per call (nm_client_new), which synchronously dumps NetworkManager's entire object model and cost ~300ms-1.4s, blowing the 100ms SLA for GetAvailableInterfaces. Serve both reads from a gnome-owned cache of raw NMDeviceState (plus MAC), maintained solely by the event monitor: - Record state on the startup device walk, device-added, and every notify::state transition; drop the entry on device-removed. - Capture the MAC and keep it in sync whenever NM reports a new HW address. - Derive enabled/connected at read time from a single canonical definition, removing the prior >= vs > drift between the two APIs. - Make the reads pure: drop their side-effect writes to the shared connected/enabled atomics (the event path is now the sole writer). - Treat an interface absent from the cache as omitted, and an empty interface list as a valid (successful) result rather than an error. The cache holds a libnm type, so it lives in the Gnome backend; the backend-agnostic NetworkManagerImplementation header stays libnm-free. Add microsecond-resolution [PERF] instrumentation for both reads across the JSON-RPC and impl layers. It is logged at DEBUG level so it stays silent in normal operation and can be enabled on demand for diagnostics. When either COM-RPC read takes one second or more, also emit a WARN so pathological latencies surface without enabling DEBUG. Gate the log level check ahead of message formatting in NetworkManagerLogger::logPrint. Previously vsnprintf ran unconditionally and the level was only checked afterward, so every disabled log still paid the formatting cost. This affects all logging: disabled logs (at any level) now short-circuit before formatting. The RDK-logger build gates on rdk_logger_is_logLevel_enabled and the native build on the configured level, so filtering stays authoritative for each variant. Rework the libnm L1 tests to drive GetAvailableInterfaces and GetInterfaceState through the event-state cache (the sole public writer) instead of mocking the per-call NMClient device enumeration, and reset the process-global cache in test setup for order-independent runs. * Updated include Function Name Updated include Function Name --------- Co-authored-by: Karunakaran A <48997923+karuna2git@users.noreply.github.com> --- plugin/NetworkManagerJsonRpc.cpp | 22 +++ plugin/NetworkManagerLogger.cpp | 14 +- plugin/gnome/NetworkManagerGnomeEvents.cpp | 67 ++++++++- plugin/gnome/NetworkManagerGnomeEvents.h | 17 +++ plugin/gnome/NetworkManagerGnomeProxy.cpp | 139 ++++-------------- tests/l2Test/libnm/l2_test_libnmproxy.cpp | 136 ++++++----------- tests/l2Test/libnm/l2_test_libnmproxyInit.cpp | 10 +- 7 files changed, 197 insertions(+), 208 deletions(-) diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index d49e6c5d..053ba8f7 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -21,6 +21,8 @@ #include "INetworkManager.h" #include "NetworkManagerJsonEnum.h" +#include + #define LOG_INPARAM() { string json; parameters.ToString(json); NMLOG_INFO("params=%s", json.c_str() ); } #define LOG_OUTPARAM() { string json; response.ToString(json); NMLOG_INFO("response=%s", json.c_str() ); } @@ -164,11 +166,19 @@ namespace WPEFramework Exchange::INetworkManager::IInterfaceDetailsIterator* _interfaces{}; + const auto tStart = std::chrono::steady_clock::now(); + if (_networkManager) rc = _networkManager->GetAvailableInterfaces(_interfaces); else rc = Core::ERROR_UNAVAILABLE; + const auto tAfterComRpc = std::chrono::steady_clock::now(); + const long long comRpcUs = static_cast(std::chrono::duration_cast(tAfterComRpc - tStart).count()); + NMLOG_DEBUG("[PERF] GetAvailableInterfaces COM-RPC call took %lld us", comRpcUs); + if (comRpcUs >= 1000000) + NMLOG_WARNING("[PERF] GetAvailableInterfaces COM-RPC call took %lld us (>= 1s)", comRpcUs); + if (Core::ERROR_NONE == rc) { if (_interfaces != nullptr) @@ -193,6 +203,11 @@ namespace WPEFramework } } + const auto tEnd = std::chrono::steady_clock::now(); + NMLOG_DEBUG("[PERF] GetAvailableInterfaces iterator drain+serialize took %lld us, total %lld us", + static_cast(std::chrono::duration_cast(tEnd - tAfterComRpc).count()), + static_cast(std::chrono::duration_cast(tEnd - tStart).count())); + returnJson(rc); } @@ -241,6 +256,8 @@ namespace WPEFramework LOG_INPARAM(); uint32_t rc = Core::ERROR_GENERAL; + const auto tStart = std::chrono::steady_clock::now(); + if (parameters.HasLabel("interface")) { const string interface = parameters["interface"].String(); @@ -259,6 +276,11 @@ namespace WPEFramework else rc = Core::ERROR_BAD_REQUEST; + const long long comRpcUs = static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - tStart).count()); + NMLOG_DEBUG("[PERF] GetInterfaceState COM-RPC call took %lld us", comRpcUs); + if (comRpcUs >= 1000000) + NMLOG_WARNING("[PERF] GetInterfaceState COM-RPC call took %lld us (>= 1s)", comRpcUs); + returnJson(rc); } diff --git a/plugin/NetworkManagerLogger.cpp b/plugin/NetworkManagerLogger.cpp index 6a58a1fe..99aa51bd 100644 --- a/plugin/NetworkManagerLogger.cpp +++ b/plugin/NetworkManagerLogger.cpp @@ -84,6 +84,15 @@ namespace NetworkManagerLogger { void logPrint(LogLevel level, const char* file, const char* func, int line, const char* format, ...) { + // Gate on level before formatting so disabled logs incur no vsnprintf cost. +#ifdef USE_RDK_LOGGER + if (!rdk_logger_is_logLevel_enabled(RDKLOGGER_MODULE_NAME, mapTordkLogLevel(level))) + return; +#else + if (gDefaultLogLevel < level) + return; +#endif + size_t n = 0; const short kFormatMessageSize = 1024; char formattedLog[kFormatMessageSize] = {0}; @@ -102,16 +111,13 @@ namespace NetworkManagerLogger { } formattedLog[kFormatMessageSize - 1] = '\0'; #ifdef USE_RDK_LOGGER - RDK_LOG(mapTordkLogLevel(level), RDKLOGGER_MODULE_NAME, "[%s +%d] %s\n", trimPath(file), line, formattedLog); + RDK_LOG(mapTordkLogLevel(level), RDKLOGGER_MODULE_NAME, "[%s +%d] %s : %s\n", trimPath(file), line, func, formattedLog); #else const char* levelMap[] = {"Fatal", "Error", "Warn", "Info", "Debug"}; struct timeval tv; struct tm* lt; const char* fileName = trimPath(file); - if (gDefaultLogLevel < level) - return; - gettimeofday(&tv, NULL); lt = localtime(&tv.tv_sec); diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 7a87cc20..35793d79 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include "Module.h" #include "NetworkManagerGnomeEvents.h" @@ -44,6 +45,49 @@ namespace WPEFramework extern NetworkManagerImplementation* _instance; static GnomeNetworkManagerEvents *_nmEventInstance = nullptr; + /* Gnome-owned interface-state cache (raw NMDeviceState + MAC per interface). + Written only by the event monitor; read by the proxy's pure readers. */ + static std::map _ifaceStateCache; + static std::mutex _ifaceStateCacheMutex; + + bool GnomeNetworkManagerEvents::isInterfaceStateEnabled(NMDeviceState state) + { + return (state >= NM_DEVICE_STATE_UNAVAILABLE); + } + + bool GnomeNetworkManagerEvents::isInterfaceStateConnected(NMDeviceState state) + { + return (state > NM_DEVICE_STATE_DISCONNECTED && state < NM_DEVICE_STATE_DEACTIVATING); + } + + void GnomeNetworkManagerEvents::updateInterfaceStateCache(const std::string& iface, NMDeviceState state, const char* mac) + { + if (iface.empty()) + return; + std::lock_guard lock(_ifaceStateCacheMutex); + InterfaceStateInfo& info = _ifaceStateCache[iface]; + info.state = state; + info.present = true; + if (mac != nullptr && mac[0] != '\0') + info.mac = mac; // keep in sync when NM reports a new/randomized HW address + } + + void GnomeNetworkManagerEvents::removeInterfaceStateCache(const std::string& iface) + { + std::lock_guard lock(_ifaceStateCacheMutex); + _ifaceStateCache.erase(iface); + } + + bool GnomeNetworkManagerEvents::getInterfaceStateCache(const std::string& iface, InterfaceStateInfo& out) + { + std::lock_guard lock(_ifaceStateCacheMutex); + auto it = _ifaceStateCache.find(iface); + if (it == _ifaceStateCache.end() || !it->second.present) + return false; + out = it->second; + return true; + } + static void primaryConnectionCb(NMClient *client, GParamSpec *param, NMEvents *nmEvents) { NMActiveConnection *primaryConn; @@ -276,8 +320,11 @@ namespace WPEFramework return; NMDeviceState deviceState; deviceState = nm_device_get_state(device); - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; NMDeviceStateReason reason = nm_device_get_state_reason(device); + updateInterfaceStateCache(ifname, deviceState, nm_device_get_hw_address(device)); if(ifname == nmUtils::wlanIface()) { if(!NM_IS_DEVICE_WIFI(device)) { @@ -469,7 +516,9 @@ namespace WPEFramework { if( ((device != NULL) && NM_IS_DEVICE(device)) ) { - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; if(ifname == nmUtils::wlanIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_ADDED, nmUtils::wlanIface()); NMLOG_INFO("WIFI device added: %s", ifname.c_str()); @@ -482,6 +531,7 @@ namespace WPEFramework /* ip events added only for eth0 and wlan0 */ if(ifname == nmUtils::ethIface() || ifname == nmUtils::wlanIface()) { + GnomeNetworkManagerEvents::updateInterfaceStateCache(ifname, nm_device_get_state(device), nm_device_get_hw_address(device)); g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); @@ -529,7 +579,9 @@ namespace WPEFramework { if( ((device != NULL) && NM_IS_DEVICE(device)) ) { - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; if(ifname == nmUtils::wlanIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); NMLOG_INFO("WIFI device removed: %s", ifname.c_str()); @@ -542,6 +594,10 @@ namespace WPEFramework return; // not a tracked interface } + /* Device is gone: drop it from the cache so the reads omit it + (matches the original live-device-list behaviour). */ + GnomeNetworkManagerEvents::removeInterfaceStateCache(ifname); + /* Disconnect all device-level signals (state, ip4/ip6-config changes). */ g_signal_handlers_disconnect_by_data(device, nmEvents); @@ -637,10 +693,13 @@ namespace WPEFramework NMDevice *device = NM_DEVICE(g_ptr_array_index(devices, count)); if( ((device != NULL) && NM_IS_DEVICE(device)) ) { - std::string ifname = nm_device_get_iface(device); + const char* iface = nm_device_get_iface(device); + if (!iface) continue; + std::string ifname = iface; if((ifname == nmUtils::ethIface()) || (ifname == nmUtils::wlanIface())) { NMDeviceState devState = nm_device_get_state(device); + GnomeNetworkManagerEvents::updateInterfaceStateCache(ifname, devState, nm_device_get_hw_address(device)); if(devState > NM_DEVICE_STATE_DISCONNECTED && devState <= NM_DEVICE_STATE_ACTIVATED) { diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index bf282de7..2bc31f9b 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -43,12 +43,29 @@ namespace WPEFramework { public: + /* Gnome-owned cache of live NM device state, kept out of the + backend-agnostic NetworkManagerImplementation (holds a libnm type). */ + struct InterfaceStateInfo { + NMDeviceState state = NM_DEVICE_STATE_UNKNOWN; + std::string mac; + bool present = false; + }; + static void onInterfaceStateChangeCb(uint8_t newState, std::string iface); // ReportInterfaceStateChange static void onActiveInterfaceChangeCb(std::string newInterface); // ReportActiveInterfaceChange static void onAvailableSSIDsCb(NMDeviceWifi *wifiDevice, GParamSpec *pspec, gpointer userData); // ReportAvailableSSIDs static void onWIFIStateChanged(uint8_t state); // ReportWiFiStateChange static void deviceStateChangeCb(NMDevice *device, GParamSpec *pspec, NMEvents *nmEvents); + /* Interface-state cache: sole writer is the event monitor; readers + (GetAvailableInterfaces / GetInterfaceState) are pure. */ + static void updateInterfaceStateCache(const std::string& iface, NMDeviceState state, const char* mac); + static void removeInterfaceStateCache(const std::string& iface); + static bool getInterfaceStateCache(const std::string& iface, InterfaceStateInfo& out); + /* Canonical enabled/connected definitions shared by both read APIs. */ + static bool isInterfaceStateEnabled(NMDeviceState state); + static bool isInterfaceStateConnected(NMDeviceState state); + public: static GnomeNetworkManagerEvents* getInstance(); bool startNetworkMangerEventMonitor(); diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 30d065e8..65b2fc4b 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -22,6 +22,7 @@ #include "NetworkManagerGnomeUtils.h" #include #include +#include using namespace WPEFramework; using namespace WPEFramework::Plugin; using namespace std; @@ -347,77 +348,37 @@ namespace WPEFramework std::vector interfaceList; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); - if(m_nmContext == nullptr) { - NMLOG_FATAL("NMContext is null"); - return Core::ERROR_GENERAL; - } - - NMClient *client = createProxyClient(m_nmContext); - if (client == nullptr) { - NMLOG_FATAL("Failed to create NMClient for GetAvailableInterfaces"); - return Core::ERROR_GENERAL; - } + const auto tEntry = std::chrono::steady_clock::now(); - GPtrArray *devices = const_cast(nm_client_get_devices(client)); - if (devices == NULL) { - NMLOG_ERROR("Failed to get device list."); - deleteProxyClient(client); - return rc; - } - - for (guint j = 0; j < devices->len; j++) + /* Serve from the event-maintained cache; no per-call NMClient. An interface + * absent from the cache (not yet reported by NM) is simply omitted; an empty + * list is a valid result (ground reality), not an error. */ + for (int i = 0; i < 2; ++i) { - NMDevice *device = NM_DEVICE(devices->pdata[j]); - if(device != NULL) - { - const char* ifacePtr = nm_device_get_iface(device); - if(ifacePtr == nullptr) - continue; - std::string ifaceStr = ifacePtr; - if(ifaceStr == wifiname || ifaceStr == ethname) // only wifi and ethenet taking - { - NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; - Exchange::INetworkManager::InterfaceDetails interface{}; - const char* macAddr = nm_device_get_hw_address(device); - if(macAddr != nullptr) { - interface.mac = macAddr; - } - deviceState = nm_device_get_state(device); - interface.enabled = (deviceState >= NM_DEVICE_STATE_UNAVAILABLE)? true : false; - if(deviceState > NM_DEVICE_STATE_DISCONNECTED && deviceState < NM_DEVICE_STATE_DEACTIVATING) - interface.connected = true; - else - interface.connected = false; + const std::string& ifname = (i == 0) ? ethname : wifiname; + GnomeNetworkManagerEvents::InterfaceStateInfo info; + if (!GnomeNetworkManagerEvents::getInterfaceStateCache(ifname, info)) + continue; - if(ifaceStr == wifiname) { - interface.type = INTERFACE_TYPE_WIFI; - interface.name = wifiname; - m_wlanConnected.store(interface.connected); - m_wlanEnabled.store(interface.enabled); - } - else if(ifaceStr == ethname) { - interface.type = INTERFACE_TYPE_ETHERNET; - interface.name = ethname; - m_ethConnected.store(interface.connected); - m_ethEnabled.store(interface.enabled); - } + Exchange::INetworkManager::InterfaceDetails interface{}; + interface.type = (i == 0) ? INTERFACE_TYPE_ETHERNET : INTERFACE_TYPE_WIFI; + interface.name = ifname; + interface.mac = info.mac; + interface.enabled = GnomeNetworkManagerEvents::isInterfaceStateEnabled(info.state); + interface.connected = GnomeNetworkManagerEvents::isInterfaceStateConnected(info.state); - interfaceList.push_back(interface); - rc = Core::ERROR_NONE; - } - } + interfaceList.push_back(interface); } - deleteProxyClient(client); - - if (rc != Core::ERROR_NONE) - return rc; - using Implementation = RPC::IteratorType; interfacesItr = Core::Service::Create(interfaceList); if(interfacesItr == nullptr) { return Core::ERROR_GENERAL; } + rc = Core::ERROR_NONE; + + NMLOG_DEBUG("[PERF] GetAvailableInterfaces (impl) total %lld us", + static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - tEntry).count())); return rc; } #if 0 @@ -640,65 +601,29 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::GetInterfaceState(const string& interface/* @in */, bool& isEnabled /* @out */) { isEnabled = false; - bool isIfaceFound = false; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); + const auto tEntry = std::chrono::steady_clock::now(); + if(interface.empty() || (wifiname != interface && ethname != interface)) { NMLOG_ERROR("interface: %s; not valied", interface.c_str()!=nullptr? interface.c_str():"empty"); return Core::ERROR_GENERAL; } - if(m_nmContext == nullptr) + /* Serve from the event-maintained cache; no per-call NMClient. */ + GnomeNetworkManagerEvents::InterfaceStateInfo info; + if (!GnomeNetworkManagerEvents::getInterfaceStateCache(interface, info)) { - NMLOG_WARNING("NMContext is null"); - return Core::ERROR_RPC_CALL_FAILED; - } - - NMClient *client = createProxyClient(m_nmContext); - if (client == nullptr) { - NMLOG_ERROR("Failed to create NMClient for GetInterfaceState"); - return Core::ERROR_RPC_CALL_FAILED; - } - - GPtrArray *devices = const_cast(nm_client_get_devices(client)); - - if (devices == NULL) { - NMLOG_ERROR("Failed to get device list."); - deleteProxyClient(client); + NMLOG_WARNING("%s : state not known yet", interface.c_str()); return Core::ERROR_GENERAL; } - for (guint j = 0; j < devices->len; j++) - { - NMDevice *device = NM_DEVICE(devices->pdata[j]); - if(device != NULL) - { - const char* iface = nm_device_get_iface(device); - if(iface != NULL) - { - std::string ifaceStr; - ifaceStr.assign(iface); - NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; - if(ifaceStr == interface) - { - isIfaceFound = true; - deviceState = nm_device_get_state(device); - isEnabled = (deviceState > NM_DEVICE_STATE_UNAVAILABLE) ? true : false; - NMLOG_INFO("%s : %s", ifaceStr.c_str(), isEnabled?"enabled":"disabled"); - break; - } - } - } - } - - deleteProxyClient(client); - - if(isIfaceFound) - return Core::ERROR_NONE; - else - NMLOG_ERROR("%s : not found", interface.c_str()); - return Core::ERROR_GENERAL; + isEnabled = GnomeNetworkManagerEvents::isInterfaceStateEnabled(info.state); + NMLOG_INFO("%s : %s", interface.c_str(), isEnabled?"enabled":"disabled"); + NMLOG_DEBUG("[PERF] GetInterfaceState (impl) total %lld us", + static_cast(std::chrono::duration_cast(std::chrono::steady_clock::now() - tEntry).count())); + return Core::ERROR_NONE; } /* @brief Get IP Address Of the Interface */ diff --git a/tests/l2Test/libnm/l2_test_libnmproxy.cpp b/tests/l2Test/libnm/l2_test_libnmproxy.cpp index 0a3dad69..a102c7ea 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxy.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxy.cpp @@ -35,6 +35,7 @@ #include "NetworkManagerImplementation.h" #include "NetworkManagerLogger.h" #include "NetworkManager.h" +#include "NetworkManagerGnomeEvents.h" #include using namespace WPEFramework; @@ -150,6 +151,11 @@ class NetworkManagerTest : public ::testing::Test { std::cerr << "Failed to create /etc/device.properties file." << std::endl; } } + + /* Interface-state cache is a process-global static; reset the known + interfaces so each test starts from a deterministic empty state. */ + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("eth0"); + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("wlan0"); } virtual ~NetworkManagerTest() override @@ -353,59 +359,37 @@ TEST_F(NetworkManagerTest, GetPrimaryInterface_empty) TEST_F(NetworkManagerTest, GetInterfaceState_Failed) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillRepeatedly(::testing::Return(nullptr)); - + /* wlan0 not present in the event cache -> state unknown -> failure */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"wlan0\"}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); } TEST_F(NetworkManagerTest, GetInterfaceState_WifiEth) { - GPtrArray* fakeDevices = g_ptr_array_new(); - - NMDevice *deviceDummy = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - g_ptr_array_add(fakeDevices, deviceDummy); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillRepeatedly(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); // disabled + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("wlan0", NM_DEVICE_STATE_ACTIVATED, "66:77:88:99:AA:BB"); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_UNMANAGED, "00:11:22:33:44:55"); // disabled EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"wlan0\"}"), response)); EXPECT_EQ(response, _T("{\"enabled\":true,\"success\":true}")); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"eth0\"}"), response)); EXPECT_EQ(response, _T("{\"enabled\":false,\"success\":true}")); - - g_object_unref(deviceDummy); - g_ptr_array_free(fakeDevices, TRUE); } TEST_F(NetworkManagerTest, GetInterfaceState_WifiEthNotFound) { - GPtrArray* fakeDevices = g_ptr_array_new(); - - NMDevice *deviceDummy = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - g_ptr_array_add(fakeDevices, deviceDummy); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillRepeatedly(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan1")); + /* Only eth0 known to the cache; querying wlan0 -> not found -> failure */ + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_ACTIVATED, "00:11:22:33:44:55"); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"wlan0\"}"), response)); EXPECT_EQ(response, _T("{\"success\":false}")); +} - g_object_unref(deviceDummy); - g_ptr_array_free(fakeDevices, TRUE); +TEST_F(NetworkManagerTest, GetInterfaceState_emptyInterface) +{ + /* Empty interface name is rejected before any cache lookup */ + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetInterfaceState"), _T("{\"interface\":\"\"}"), response)); + EXPECT_EQ(response, _T("{\"success\":false}")); } TEST_F(NetworkManagerTest, GetInterfaceState_unknown) @@ -421,38 +405,19 @@ TEST_F(NetworkManagerTest, GetInterfaceState_unknown) EXPECT_EQ(isEnabled, false); } -TEST_F(NetworkManagerTest, GetAvailableInterfaces_DevicesNull) +TEST_F(NetworkManagerTest, GetAvailableInterfaces_emptyCache) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillOnce(::testing::Return(nullptr)); + /* No interfaces reported by NM yet -> empty list is a valid (successful) result */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("eth0") == std::string::npos); + EXPECT_TRUE(response.find("wlan0") == std::string::npos); } TEST_F(NetworkManagerTest, GetAvailableInterfaces_Enabled) { - GPtrArray* fakeDevices = g_ptr_array_new(); - NMDevice *ethDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - NMDevice* wifiDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); - g_ptr_array_add(fakeDevices, wifiDevice); - g_ptr_array_add(fakeDevices, ethDevice); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillOnce(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(ethDevice)) - .WillOnce(::testing::Return("eth0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(wifiDevice)) - .WillOnce(::testing::Return("wlan0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(ethDevice)) - .WillOnce(::testing::Return("00:11:22:33:44:55")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(wifiDevice)) - .WillOnce(::testing::Return("66:77:88:99:AA:BB")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(ethDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(wifiDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNAVAILABLE)); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_ACTIVATED, "00:11:22:33:44:55"); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("wlan0", NM_DEVICE_STATE_UNAVAILABLE, "66:77:88:99:AA:BB"); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); EXPECT_TRUE(response.find("wlan0") != std::string::npos); @@ -460,40 +425,16 @@ TEST_F(NetworkManagerTest, GetAvailableInterfaces_Enabled) std::string expectedResponse = _T("{\"interfaces\":[") - _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":true,\"connected\":false},") - _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":true,\"connected\":true}") + _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":true,\"connected\":true},") + _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":true,\"connected\":false}") _T("],\"success\":true}"); EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethDevice); - g_object_unref(wifiDevice); - g_ptr_array_free(fakeDevices, TRUE); } TEST_F(NetworkManagerTest, GetAvailableInterfaces_disabled) { - GPtrArray* fakeDevices = g_ptr_array_new(); - NMDevice *ethDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); - NMDevice* wifiDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); - g_ptr_array_add(fakeDevices, wifiDevice); - g_ptr_array_add(fakeDevices, ethDevice); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_devices(::testing::_)) - .WillOnce(::testing::Return(fakeDevices)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(ethDevice)) - .WillOnce(::testing::Return("eth0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(wifiDevice)) - .WillOnce(::testing::Return("wlan0")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(ethDevice)) - .WillOnce(::testing::Return("00:11:22:33:44:55")); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_hw_address(wifiDevice)) - .WillOnce(::testing::Return("66:77:88:99:AA:BB")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(ethDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(wifiDevice)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_UNMANAGED, "00:11:22:33:44:55"); + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("wlan0", NM_DEVICE_STATE_UNMANAGED, "66:77:88:99:AA:BB"); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); EXPECT_TRUE(response.find("wlan0") != std::string::npos); @@ -501,14 +442,25 @@ TEST_F(NetworkManagerTest, GetAvailableInterfaces_disabled) std::string expectedResponse = _T("{\"interfaces\":[") - _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":false,\"connected\":false},") - _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":false,\"connected\":false}") + _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":false,\"connected\":false},") + _T("{\"type\":\"WIFI\",\"name\":\"wlan0\",\"mac\":\"66:77:88:99:AA:BB\",\"enabled\":false,\"connected\":false}") _T("],\"success\":true}"); EXPECT_EQ(response, expectedResponse); +} - g_object_unref(ethDevice); - g_object_unref(wifiDevice); - g_ptr_array_free(fakeDevices, TRUE); +TEST_F(NetworkManagerTest, GetAvailableInterfaces_onlyEthernet) +{ + /* Only eth0 reported by NM; wlan0 (absent from cache) is omitted from the list */ + Plugin::GnomeNetworkManagerEvents::updateInterfaceStateCache("eth0", NM_DEVICE_STATE_ACTIVATED, "00:11:22:33:44:55"); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); + EXPECT_TRUE(response.find("wlan0") == std::string::npos); + + std::string expectedResponse = + _T("{\"interfaces\":[") + _T("{\"type\":\"ETHERNET\",\"name\":\"eth0\",\"mac\":\"00:11:22:33:44:55\",\"enabled\":true,\"connected\":true}") + _T("],\"success\":true}"); + EXPECT_EQ(response, expectedResponse); } TEST_F(NetworkManagerTest, GetIPSettings_unknown_iface) diff --git a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp index b550b3d6..52d2a139 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp @@ -33,6 +33,7 @@ #include "NetworkManagerImplementation.h" #include "NetworkManagerLogger.h" #include "NetworkManager.h" +#include "NetworkManagerGnomeEvents.h" #include using namespace WPEFramework; @@ -123,6 +124,10 @@ class NetworkManagerInitTest : public ::testing::Test { virtual void SetUp() override { + /* Interface-state cache is a process-global static shared across suites; + clear the known interfaces so platform_init failure yields an empty cache. */ + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("eth0"); + Plugin::GnomeNetworkManagerEvents::removeInterfaceStateCache("wlan0"); } virtual ~NetworkManagerInitTest() override @@ -149,7 +154,10 @@ TEST_F(NetworkManagerInitTest, platformInit) NetworkManagerLogger::SetLevel(static_cast(NetworkManagerLogger::DEBUG_LEVEL)); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetAvailableInterfaces"), _T(""), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + /* platform_init failed (no NMClient) -> event cache never populated -> empty list, still success */ + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("eth0") == std::string::npos); + EXPECT_TRUE(response.find("wlan0") == std::string::npos); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetPrimaryInterface"), _T(""), response)); EXPECT_EQ(response, _T("{\"interface\":\"\",\"success\":true}")); From b70b39b5f32fcd6c31a908d0bb95de6166db3ce3 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Sat, 22 Aug 2026 11:27:48 -0400 Subject: [PATCH 21/32] Release of 3.7.0 Release of 3.7.0 --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 851e39f7..fe546edd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [3.7.0] - 2026-08-21 +### Changed +- Implemented a caching logic about the status of the interface & update based on events + ## [3.6.0] - 2026-08-11 ### Fixed - Fixed the issue with connecting to a SSID that is not present in scan list diff --git a/CMakeLists.txt b/CMakeLists.txt index 724fb37e..f02326d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 3) -set(VERSION_MINOR 6) +set(VERSION_MINOR 7) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 2922b983..014a649f 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.6.0" + "version": "3.7.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index fd6d97dc..74341a26 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.6.0** +**Version: 3.7.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.6.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.7.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 36c1c5b7a118a7d45f7cbf875be46ffb6e4d3855 Mon Sep 17 00:00:00 2001 From: me-ha-p Date: Tue, 25 Aug 2026 21:58:34 +0530 Subject: [PATCH 22/32] RDKEMW-21048: Mutex to access m_lastConnectedSSID in NMPlugin (#339) * RDKEMW-21048: Use mutex to access m_lastConnectedSSID in NetworkManager Plugin Reason for change: Use mutex to access m_lastConnectedSSID in NetworkManager Plugin. Priority: P2 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * Updated the logging and removed TAB Signed-off-by: Karunakaran A --------- Signed-off-by: Mehavarshni_Palaniswamy@comcast.com Signed-off-by: Karunakaran A --- plugin/NetworkManagerImplementation.cpp | 9 +++++---- plugin/NetworkManagerImplementation.h | 13 +++++++++++++ plugin/gnome/NetworkManagerGnomeProxy.cpp | 10 ++++++---- plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp | 5 +++-- plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp | 5 +++-- 5 files changed, 30 insertions(+), 12 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 1ba0509a..8325a945 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -1328,7 +1328,7 @@ namespace WPEFramework GetWiFiSignalQuality(ssid, strength, noise, snr, newSignalQuality); if (!ssid.empty()) - m_lastConnectedSSID = ssid; // last connected ssid used in wifiConnect + setLastConnectedSSID(ssid); // last connected ssid used in wifiConnect if (oldSignalQuality != newSignalQuality) { oldSignalQuality = newSignalQuality; @@ -1457,11 +1457,12 @@ namespace WPEFramework { if (m_wlanDisconnectedForSleep.load()) { - if (!m_lastConnectedSSID.empty()) + const std::string lastConnectedSSID = getLastConnectedSSID(); + if (!lastConnectedSSID.empty()) { NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — reconnecting to '%s'", - m_lastConnectedSSID.c_str()); - uint32_t rcWifiUp = ConnectToKnownSSID(m_lastConnectedSSID); + lastConnectedSSID.c_str()); + uint32_t rcWifiUp = ConnectToKnownSSID(lastConnectedSSID); if (rcWifiUp == Core::ERROR_NONE) { m_wlanDisconnectedForSleep.store(false); diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index bafe337f..1b6cb6c5 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -466,9 +466,22 @@ namespace WPEFramework m_defaultInterface = iface; } + void setLastConnectedSSID(const std::string& ssid) + { + std::lock_guard lock(m_lastConnectedSSIDMutex); + m_lastConnectedSSID = ssid; + } + + std::string getLastConnectedSSID() const + { + std::lock_guard lock(m_lastConnectedSSIDMutex); + return m_lastConnectedSSID; + } + private: string m_defaultInterface; mutable std::mutex m_defaultInterfaceMutex; + mutable std::mutex m_lastConnectedSSIDMutex; std::map, IpFamilyCache> m_ipCacheMap; mutable std::mutex m_ipCacheMutex; }; diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 65b2fc4b..057ba1bf 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -590,8 +590,9 @@ namespace WPEFramework if(enabled && interface == nmUtils::wlanIface() && _instance != NULL) { sleep(1); // wait for 1 sec to change the device state - NMLOG_INFO("Activating connection '%s' ...", _instance->m_lastConnectedSSID.c_str()); - wifi->activateKnownConnection(nmUtils::wlanIface(), _instance->m_lastConnectedSSID); + const string lastConnectedSSID = _instance->getLastConnectedSSID(); + NMLOG_INFO("Activating connection '%s' ...", lastConnectedSSID.c_str()); + wifi->activateKnownConnection(nmUtils::wlanIface(), lastConnectedSSID); } } @@ -814,8 +815,9 @@ namespace WPEFramework if(ssid.ssid.empty()) { - NMLOG_WARNING("ssid is empty activating last connected ssid !"); - if(_instance != NULL && wifi->activateKnownConnection(nmUtils::wlanIface(), _instance->m_lastConnectedSSID)) + const string lastConnectedSSID = _instance->getLastConnectedSSID(); + NMLOG_WARNING("ssid is empty activating last connected ssid (%s) !", lastConnectedSSID.c_str()); + if(_instance != NULL && wifi->activateKnownConnection(nmUtils::wlanIface(), lastConnectedSSID)) { rc = Core::ERROR_NONE; } diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp index 2b44c109..8c522440 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp @@ -979,8 +979,9 @@ namespace WPEFramework // Wait for 1 sec to change the device state sleep(1); if(interface == GnomeUtils::getWifiIfname() && _instance != nullptr) { - NMLOG_INFO("Activating connection '%s' ...", _instance->m_lastConnectedSSID.c_str()); - activateKnownConnection(GnomeUtils::getWifiIfname(), _instance->m_lastConnectedSSID); + const std::string lastConnectedSSID = _instance->getLastConnectedSSID(); + NMLOG_INFO("Activating connection '%s' ...", lastConnectedSSID.c_str()); + activateKnownConnection(GnomeUtils::getWifiIfname(), lastConnectedSSID); } else if(interface == GnomeUtils::getEthIfname()) { NMLOG_INFO("Activating connection 'Wired connection 1' ..."); diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp index 3cbb977e..0f8a0ddf 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp @@ -286,8 +286,9 @@ namespace WPEFramework if(ssid.ssid.empty() && _instance != NULL) { - NMLOG_WARNING("ssid is empty activating last connected ssid !"); - if(_nmGdbusClient->activateKnownConnection(GnomeUtils::getWifiIfname(), _instance->m_lastConnectedSSID)) + const string lastConnectedSSID = _instance->getLastConnectedSSID(); + NMLOG_WARNING("ssid is empty activating last connected ssid (%s) !", lastConnectedSSID.c_str()); + if(_nmGdbusClient->activateKnownConnection(GnomeUtils::getWifiIfname(), lastConnectedSSID)) rc = Core::ERROR_NONE; } else if(ssid.ssid.size() <= 32) From ef828542e7aa32cdcef71a167e1597f8860164d0 Mon Sep 17 00:00:00 2001 From: RAFI <103924677+cmuhammedrafi@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:21:59 +0530 Subject: [PATCH 23/32] RDK-61898: Internet Status Publishing in Deep Sleep Scenarios (#340) * connectivity monitor tirgger for connectivity check * updated connectivity ideal thread * updated error * internet status change trigger updated * updated log line * updated review commments * Comment line change Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * copilot review comment addressed --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- plugin/NetworkManagerConnectivity.cpp | 13 ++++--------- plugin/NetworkManagerImplementation.cpp | 4 ++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/plugin/NetworkManagerConnectivity.cpp b/plugin/NetworkManagerConnectivity.cpp index 93825d51..57243aae 100644 --- a/plugin/NetworkManagerConnectivity.cpp +++ b/plugin/NetworkManagerConnectivity.cpp @@ -634,21 +634,15 @@ namespace WPEFramework return false; } - string defaultIface = _instance->getDefaultInterface(); - - if(defaultIface.empty()) - { - NMLOG_WARNING("default interface not set"); - return false; - } - m_notify = true; m_switchToInitial = true; m_wakeupMonitoring = true; m_cmCv.notify_one(); + string defaultIface = _instance->getDefaultInterface(); NMLOG_INFO("switching to initial check - eth %s - wlan %s - default interface %s", - _instance->m_ethConnected.load()? "up":"down", _instance->m_wlanConnected.load()? "up":"down", defaultIface.c_str()); + _instance->m_ethConnected.load()? "up":"down", _instance->m_wlanConnected.load()? "up":"down", + defaultIface.empty() ? "none" : defaultIface.c_str()); return true; } @@ -704,6 +698,7 @@ namespace WPEFramework if(defaultIface.empty()) { NMLOG_WARNING("default interface not set"); + currentInternetState = INTERNET_NOT_AVAILABLE; if (InitialRetryCount == 0) m_notify = true; InitialRetryCount = 1; diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 8325a945..efdba7fa 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -1481,6 +1481,8 @@ namespace WPEFramework { NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — WiFi was not connected or was already down before sleep, skipping reconnect"); } + // DeepSleep wake (Network Standby OFF): re-verify connectivity so internet status is re-published. + connectivityMonitor.switchToInitialCheck(); } sendAck(); } @@ -1519,6 +1521,8 @@ namespace WPEFramework NMLOG_ERROR("OnPowerModeChanged: ReacquireDHCPLease(eth0) failed"); } } + // DeepSleep → Standby wake (Network Standby ON): re-verify connectivity so internet status is re-published. + connectivityMonitor.switchToInitialCheck(); } } From d605591e33dfa76d4d77689a1b782ad82dd80cea Mon Sep 17 00:00:00 2001 From: me-ha-p Date: Tue, 1 Sep 2026 20:47:51 +0530 Subject: [PATCH 24/32] RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Info (#336) * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * Updated to address tab and default value Signed-off-by: Karunakaran A * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com * RDKEMW-22842: onWiFiStateChange Event to Include Wi-Fi Profile Information Reason for change: onWiFiStateChange event currently reports only the Wi-Fi connection state. Enhance onWiFiStateChange event payload to include the connected or affected Wi-Fi SSID. Priority: P1 Test Procedure: Refer ticket Risks: Low Signed-off-by: Mehavarshni_Palaniswamy@comcast.com --------- Signed-off-by: Mehavarshni_Palaniswamy@comcast.com Signed-off-by: Karunakaran A Co-authored-by: mpalan315 Co-authored-by: Karunakaran A --- definition/NetworkManager.json | 8 +++- docs/NetworkManagerPlugin.md | 4 +- interface/INetworkManager.h | 2 +- plugin/NetworkManager.h | 6 +-- plugin/NetworkManagerImplementation.cpp | 41 +++++++++++------- plugin/NetworkManagerImplementation.h | 5 ++- plugin/NetworkManagerJsonRpc.cpp | 3 +- plugin/gnome/NetworkManagerGnomeEvents.cpp | 42 +++++++++++-------- plugin/gnome/NetworkManagerGnomeEvents.h | 2 +- plugin/gnome/NetworkManagerGnomeProxy.cpp | 7 ++-- plugin/gnome/NetworkManagerGnomeWIFI.cpp | 16 +++---- .../gnome/gdbus/NetworkManagerGdbusClient.cpp | 14 +++---- .../gnome/gdbus/NetworkManagerGdbusEvent.cpp | 2 +- .../gnome/gdbus/NetworkManagerGdbusProxy.cpp | 2 +- plugin/rdk/NetworkManagerRDKProxy.cpp | 4 +- tools/plugincli/NetworkManagerGdbusTest.cpp | 4 +- tools/plugincli/NetworkManagerLibnmTest.cpp | 2 +- 17 files changed, 97 insertions(+), 67 deletions(-) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 014a649f..b85d5e3c 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -1556,11 +1556,17 @@ "summary": "WiFi status", "type": "string", "example": "WIFI_STATE_CONNECTED" + }, + "ssid": { + "summary": "The SSID associated with the Wi-Fi profile causing the state transition. Disconnected state, contains the SSID associated with the connection that was disconnected. The SSID will be empty when WPS initiated and no AP found with WPS enabled.", + "type": "string", + "example": "myHomeSSID" } }, "required": [ "state", - "status" + "status", + "ssid" ] } }, diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 74341a26..31ff2ca9 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -1966,6 +1966,7 @@ Triggered when WIFI connection state get changed. The possible states are define | params | object | | | params.state | integer | WiFi State | | params.status | string | WiFi status | +| params.ssid | string | The SSID associated with the Wi-Fi profile causing the state transition. Disconnected state, contains the SSID associated with the connection that was disconnected | ### Example @@ -1975,7 +1976,8 @@ Triggered when WIFI connection state get changed. The possible states are define "method": "client.events.1.onWiFiStateChange", "params": { "state": 5, - "status": "WIFI_STATE_CONNECTED" + "status": "WIFI_STATE_CONNECTED", + "ssid": "myHomeSSID" } } ``` diff --git a/interface/INetworkManager.h b/interface/INetworkManager.h index ce8cb509..8e6cabea 100644 --- a/interface/INetworkManager.h +++ b/interface/INetworkManager.h @@ -286,7 +286,7 @@ namespace WPEFramework // WiFi Notifications that other processes can subscribe to virtual void onAvailableSSIDs(const string jsonOfScanResults /* @in */){}; - virtual void onWiFiStateChange(const WiFiState state /* @in */){}; + virtual void onWiFiStateChange(const WiFiState state /* @in */, const string ssid /* @in */){}; virtual void onWiFiSignalQualityChange(const string ssid /* @in */, const int strength /* @in */, const int noise /* @in */, const int snr /* @in */, const WiFiSignalQuality quality /* @in */){}; }; diff --git a/plugin/NetworkManager.h b/plugin/NetworkManager.h index 6a101bdf..d8a47e77 100644 --- a/plugin/NetworkManager.h +++ b/plugin/NetworkManager.h @@ -89,9 +89,9 @@ namespace WPEFramework _parent.onAvailableSSIDs(jsonOfScanResults); } - void onWiFiStateChange(const Exchange::INetworkManager::WiFiState state) override + void onWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid) override { - _parent.onWiFiStateChange(state); + _parent.onWiFiStateChange(state, ssid); } void onWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality) override @@ -262,7 +262,7 @@ namespace WPEFramework void onIPAddressChange(const string interface, const string ipversion, const string ipaddress, const Exchange::INetworkManager::IPStatus status); 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); + void onWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid); void onWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality); private: diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index efdba7fa..8d90d17e 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -424,7 +424,7 @@ namespace WPEFramework NetworkManagerLogger::SetLevel(static_cast(level)); platform_logging(static_cast(level)); NMLOG_DEBUG("loglevel %d", level); - return Core::ERROR_NONE; + return Core::ERROR_NONE; } /* @brief Get the network manager plugin log level */ @@ -626,8 +626,8 @@ namespace WPEFramework } void NetworkManagerImplementation::filterScanResults(JsonArray &ssids, - const std::vector& filterSsidslist, - const std::vector& filterFrequencies) + const std::vector& filterSsidslist, + const std::vector& filterFrequencies) { LOG_ENTRY_FUNCTION(); JsonArray result; @@ -818,7 +818,7 @@ namespace WPEFramework NMLOG_INFO("Publishing onWiFiStateChange Event"); const auto& eventData = std::get(data); for (const auto callback : callbacks) { - callback->onWiFiStateChange(eventData.state); + callback->onWiFiStateChange(eventData.state, eventData.ssid); callback->Release(); } } @@ -1037,7 +1037,7 @@ namespace WPEFramework NMLOG_DEBUG("Discovered %d SSIDs before filtering as,", filterResult.Length()); logSSIDs(LOG_LEVEL_DEBUG, filterResult); - // Snapshot filter vectors under lock, then release before calling filterScanResults + // Snapshot filter vectors under lock, then release before calling filterScanResults // to ensure exception-safety (std::stod can throw). std::vector ssidsSnapshot; std::vector frequenciesSnapshot; @@ -1164,7 +1164,7 @@ namespace WPEFramework { freqValue = value; } - else if (key == "LINKSPEED") + else if (key == "LINKSPEED") { linkSpeed = value; } @@ -1327,9 +1327,6 @@ namespace WPEFramework GetWiFiSignalQuality(ssid, strength, noise, snr, newSignalQuality); - if (!ssid.empty()) - setLastConnectedSSID(ssid); // last connected ssid used in wifiConnect - if (oldSignalQuality != newSignalQuality) { oldSignalQuality = newSignalQuality; NetworkManagerImplementation::ReportWiFiSignalQualityChange(ssid, strength, noise, snr, newSignalQuality); @@ -1351,29 +1348,46 @@ namespace WPEFramework m_stopThread.store(false); } - void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state) + void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid) { LOG_ENTRY_FUNCTION(); + + std::string reportSSID; + /* start signal strength monitor when wifi connected */ if(INetworkManager::WiFiState::WIFI_STATE_CONNECTED == state) { m_wlanConnected.store(true); + if(!ssid.empty()) + { + setLastConnectedSSID(ssid); + reportSSID = ssid; + } + else + { + reportSSID = getLastConnectedSSID(); + } startWiFiSignalQualityMonitor(DEFAULT_WIFI_SIGNAL_TEST_INTERVAL_SEC); } else { stopWiFiSignalQualityMonitor(); m_wlanConnected.store(false); /* Any other state is considered as WiFi not connected. */ + + if(INetworkManager::WiFiState::WIFI_STATE_DISCONNECTED == state) + reportSSID = getLastConnectedSSID(); /* previously connected SSID, or empty */ + else + reportSSID = ssid; /* SSID currently being attempted */ } - NMLOG_INFO("Posting onWiFiStateChange (%d)", state); + NMLOG_INFO("Posting onWiFiStateChange (%d) ssid: %s", state, reportSSID.c_str()); #if USE_TELEMETRY string stateStr = Core::EnumerateType(state).Data(); NMLOG_INFO("NM_WIFI_STATUS = %s", stateStr.c_str()); logTelemetry("NM_WIFI_STATUS", stateStr); #endif { - WiFiStateChangeData eventData{state}; + WiFiStateChangeData eventData{state, reportSSID}; enqueueEvent(NM_ON_WIFISTATE_CHANGE, std::move(eventData)); } } @@ -1460,8 +1474,7 @@ namespace WPEFramework const std::string lastConnectedSSID = getLastConnectedSSID(); if (!lastConnectedSSID.empty()) { - NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — reconnecting to '%s'", - lastConnectedSSID.c_str()); + NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — reconnecting to '%s'", lastConnectedSSID.c_str()); uint32_t rcWifiUp = ConnectToKnownSSID(lastConnectedSSID); if (rcWifiUp == Core::ERROR_NONE) { diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 1b6cb6c5..81f148cf 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -251,6 +251,7 @@ namespace WPEFramework struct WiFiStateChangeData { Exchange::INetworkManager::WiFiState state; + string ssid; }; struct WiFiSignalQualityChangeData { @@ -372,7 +373,7 @@ namespace WPEFramework void ReportIPAddressChange(const string interface, const string ipversion, const string ipaddress, const Exchange::INetworkManager::IPStatus status); 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); + void ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid); void ReportWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality); void logTelemetry(const std::string& eventName, const std::string& message); @@ -450,7 +451,6 @@ namespace WPEFramework std::atomic m_wlanEnabled; std::atomic m_ethDisconnectedForSleep; std::atomic m_wlanDisconnectedForSleep; - std::string m_lastConnectedSSID; GMainContext *m_nmContext{nullptr}; /* isolated context for per-call NMClient creation */ mutable ConnectivityMonitor connectivityMonitor; @@ -481,6 +481,7 @@ namespace WPEFramework private: string m_defaultInterface; mutable std::mutex m_defaultInterfaceMutex; + std::string m_lastConnectedSSID; mutable std::mutex m_lastConnectedSSIDMutex; std::map, IpFamilyCache> m_ipCacheMap; mutable std::mutex m_ipCacheMutex; diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index 053ba8f7..d16f5f0d 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -1127,12 +1127,13 @@ namespace WPEFramework Notify(_T("onAvailableSSIDs"), parameters); } - void NetworkManager::onWiFiStateChange(const Exchange::INetworkManager::WiFiState state) + void NetworkManager::onWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid) { JsonObject parameters; Core::JSON::EnumType iState{state}; parameters["state"] = JsonValue(state); parameters["status"] = iState.Data(); + parameters["ssid"] = ssid; LOG_INPARAM(); Notify(_T("onWiFiStateChange"), parameters); diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 35793d79..739c0076 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -332,32 +332,40 @@ namespace WPEFramework return; } std::string wifiState; + /* SSID NM is activating (pairing/connecting); empty once the activation is torn down */ + std::string attemptingSSID = ""; + if(NMActiveConnection *wifiActiveConn = nm_device_get_active_connection(device)) + { + const char* connId = nm_active_connection_get_id(wifiActiveConn); + if(connId != NULL) + attemptingSSID = connId; + } switch (reason) { case NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE: wifiState = "WIFI_STATE_UNINSTALLED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_UNINSTALLED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_UNINSTALLED, attemptingSSID); break; case NM_DEVICE_STATE_REASON_SSID_NOT_FOUND: wifiState = "WIFI_STATE_SSID_NOT_FOUND"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND, attemptingSSID); break; case NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT: // supplicant took too long to authenticate case NM_DEVICE_STATE_REASON_NO_SECRETS: wifiState = "WIFI_STATE_AUTHENTICATION_FAILED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_AUTHENTICATION_FAILED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_AUTHENTICATION_FAILED, attemptingSSID); break; case NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED: // 802.1x supplicant failed wifiState = "WIFI_STATE_ERROR"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_ERROR); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_ERROR, attemptingSSID); break; case NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED: // 802.1x supplicant configuration failed wifiState = "WIFI_STATE_CONNECTION_INTERRUPTED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_INTERRUPTED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_INTERRUPTED, attemptingSSID); break; case NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT: // 802.1x supplicant disconnected wifiState = "WIFI_STATE_INVALID_CREDENTIALS"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_INVALID_CREDENTIALS); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_INVALID_CREDENTIALS, attemptingSSID); break; default: { @@ -365,14 +373,14 @@ namespace WPEFramework { case NM_DEVICE_STATE_UNKNOWN: wifiState = "WIFI_STATE_UNINSTALLED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_UNINSTALLED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_UNINSTALLED, attemptingSSID); refreshIpFamilyCache(device, false); refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); break; case NM_DEVICE_STATE_UNMANAGED: wifiState = "WIFI_STATE_DISABLED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISABLED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISABLED, attemptingSSID); refreshIpFamilyCache(device, false); refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); @@ -381,23 +389,23 @@ namespace WPEFramework case NM_DEVICE_STATE_UNAVAILABLE: case NM_DEVICE_STATE_DISCONNECTED: wifiState = "WIFI_STATE_DISCONNECTED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISCONNECTED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISCONNECTED, attemptingSSID); refreshIpFamilyCache(device, false); refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_LINK_DOWN, nmUtils::wlanIface()); break; case NM_DEVICE_STATE_PREPARE: wifiState = "WIFI_STATE_PAIRING"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_PAIRING); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_PAIRING, attemptingSSID); break; case NM_DEVICE_STATE_CONFIG: wifiState = "WIFI_STATE_CONNECTING"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTING); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTING, attemptingSSID); break; case NM_DEVICE_STATE_IP_CONFIG: wifiState = "NM_DEVICE_STATE_IP_CONFIG"; GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_LINK_UP, nmUtils::wlanIface()); - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTED, attemptingSSID); break; case NM_DEVICE_STATE_IP_CHECK: wifiState = "NM_DEVICE_STATE_IP_CHECK"; @@ -409,7 +417,7 @@ namespace WPEFramework break; case NM_DEVICE_STATE_ACTIVATED: wifiState = "WIFI_STATE_CONNECTED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTED, attemptingSSID); #if USE_TELEMETRY { static std::string lastWlanGatewayMac; @@ -427,11 +435,11 @@ namespace WPEFramework break; case NM_DEVICE_STATE_DEACTIVATING: wifiState = "WIFI_STATE_CONNECTION_LOST"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_LOST); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_LOST, attemptingSSID); break; case NM_DEVICE_STATE_FAILED: wifiState = "WIFI_STATE_CONNECTION_FAILED"; - GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED); + GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED, attemptingSSID); break; case NM_DEVICE_STATE_NEED_AUTH: //GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_CONNECTION_INTERRUPTED); @@ -936,11 +944,11 @@ namespace WPEFramework _instance->ReportInterfaceStateChange(static_cast(newState), iface); } - void GnomeNetworkManagerEvents::onWIFIStateChanged(uint8_t state) + void GnomeNetworkManagerEvents::onWIFIStateChanged(uint8_t state, std::string ssid) { if(_instance != nullptr) { - _instance->ReportWiFiStateChange(static_cast(state)); + _instance->ReportWiFiStateChange(static_cast(state), ssid); #ifdef ENABLE_MIGRATION_MFRMGR_SUPPORT // Handle WiFi state changes for MfrMgr integration NetworkManagerMfrManager* mfrManager = NetworkManagerMfrManager::getInstance(); diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index 2bc31f9b..acf78a9c 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -54,7 +54,7 @@ namespace WPEFramework static void onInterfaceStateChangeCb(uint8_t newState, std::string iface); // ReportInterfaceStateChange static void onActiveInterfaceChangeCb(std::string newInterface); // ReportActiveInterfaceChange static void onAvailableSSIDsCb(NMDeviceWifi *wifiDevice, GParamSpec *pspec, gpointer userData); // ReportAvailableSSIDs - static void onWIFIStateChanged(uint8_t state); // ReportWiFiStateChange + static void onWIFIStateChanged(uint8_t state, std::string ssid = ""); // ReportWiFiStateChange static void deviceStateChangeCb(NMDevice *device, GParamSpec *pspec, NMEvents *nmEvents); /* Interface-state cache: sole writer is the event monitor; readers diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 057ba1bf..7c877f5f 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -590,7 +590,7 @@ namespace WPEFramework if(enabled && interface == nmUtils::wlanIface() && _instance != NULL) { sleep(1); // wait for 1 sec to change the device state - const string lastConnectedSSID = _instance->getLastConnectedSSID(); + const string lastConnectedSSID = getLastConnectedSSID(); NMLOG_INFO("Activating connection '%s' ...", lastConnectedSSID.c_str()); wifi->activateKnownConnection(nmUtils::wlanIface(), lastConnectedSSID); } @@ -815,9 +815,8 @@ namespace WPEFramework if(ssid.ssid.empty()) { - const string lastConnectedSSID = _instance->getLastConnectedSSID(); - NMLOG_WARNING("ssid is empty activating last connected ssid (%s) !", lastConnectedSSID.c_str()); - if(_instance != NULL && wifi->activateKnownConnection(nmUtils::wlanIface(), lastConnectedSSID)) + NMLOG_WARNING("ssid is empty activating last connected ssid !"); + if(_instance != NULL && wifi->activateKnownConnection(nmUtils::wlanIface(), getLastConnectedSSID())) { rc = Core::ERROR_NONE; } diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index c7506334..148e54ef 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -1366,14 +1366,14 @@ namespace WPEFramework if(ssidInfo.bssid.empty()) { NMLOG_INFO("'%s' Already connected !", connectedApInfo.ssid.c_str()); - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED, connectedApInfo.ssid); deleteClientConnection(); return true; } else if (strcasecmp(ssidInfo.bssid.c_str(), connectedApInfo.bssid.c_str()) == 0) { NMLOG_INFO("Already connected to the requested SSID '%s' with matching BSSID", ssidInfo.ssid.c_str()); - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED, connectedApInfo.ssid); deleteClientConnection(); return true; } @@ -2042,7 +2042,7 @@ namespace WPEFramework bool wpsActionTriggerd = false; if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTING); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTING, ""); for(int retry = 0; retry < WPS_RETRY_COUNT; retry++) { @@ -2105,7 +2105,7 @@ namespace WPEFramework if(state <= NM_DEVICE_STATE_DISCONNECTED) { if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND, wifiConnectInfo.ssid); // TODO post correct error code insted of WIFI_STATE_SSID_NOT_FOUND // sedning WIFI_STATE_SSID_NOT_FOUND to avoid UI stuck issue break; @@ -2124,7 +2124,7 @@ namespace WPEFramework // wifi state stuck in betwen disconnected and connected NMLOG_ERROR("WPS process failed"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND, wifiConnectInfo.ssid); // TODO post correct error code insted of WIFI_STATE_SSID_NOT_FOUND // sedning WIFI_STATE_SSID_NOT_FOUND to avoid UI stuck issue } @@ -2155,7 +2155,7 @@ namespace WPEFramework wpsComplete = true; //TODO Post SSID connected event ? if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED, wpsApInfo.ssid); } else @@ -2254,13 +2254,13 @@ namespace WPEFramework { NMLOG_WARNING("WPS AP not found"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND, ""); } else if(!wpsComplete) { NMLOG_INFO("WPS process Error"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED, wifiConnectInfo.ssid); } if(wpsContext != NULL) diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp index 8c522440..06388c02 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp @@ -2501,7 +2501,7 @@ namespace WPEFramework { NMLOG_INFO("'%s' already connected !", currentSSID.ssid.c_str()); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED, currentSSID.ssid); return true; } else @@ -2987,7 +2987,7 @@ namespace WPEFramework { NMLOG_WARNING("WPS process failed - device in disconnected state"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND, ssidinfo.ssid); break; } else if(devProperty.state > NM_DEVICE_STATE_NEED_AUTH) @@ -2995,7 +2995,7 @@ namespace WPEFramework NMLOG_INFO("WPS process completed successfully"); wpsComplete = true; if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED, ssidinfo.ssid); break; } @@ -3004,7 +3004,7 @@ namespace WPEFramework { NMLOG_ERROR("WPS process failed - timeout"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED, ssidinfo.ssid); break; } continue; @@ -3028,7 +3028,7 @@ namespace WPEFramework NMLOG_INFO("WPS process stopped - already connected to WPS AP '%s'", ssidinfo.ssid.c_str()); wpsComplete = true; if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTED, ssidinfo.ssid); break; } @@ -3064,13 +3064,13 @@ namespace WPEFramework { NMLOG_WARNING("WPS AP not found"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_SSID_NOT_FOUND, ssidinfo.ssid); } else if(!wpsComplete && m_wpsActionTriggered) { NMLOG_INFO("WPS process error"); if(_instance != nullptr) - _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED); + _instance->ReportWiFiStateChange(Exchange::INetworkManager::WIFI_STATE_CONNECTION_FAILED, ssidinfo.ssid); } NMLOG_INFO("WPS process thread exit"); diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp index d0c50c40..8811f5dc 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp @@ -690,7 +690,7 @@ namespace WPEFramework { NMLOG_DEBUG("wifi state changed: %d ; NM wifi: %s", state, wifiStateStr.c_str()); if(_instance != nullptr) - _instance->ReportWiFiStateChange(static_cast(state)); + _instance->ReportWiFiStateChange(static_cast(state), ""); } void NetworkManagerEvents::onAddressChangeCb(std::string iface, bool acquired, bool isIPv6, std::string ipAddress) diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp index 0f8a0ddf..33254fc4 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp @@ -286,7 +286,7 @@ namespace WPEFramework if(ssid.ssid.empty() && _instance != NULL) { - const string lastConnectedSSID = _instance->getLastConnectedSSID(); + const string lastConnectedSSID = getLastConnectedSSID(); NMLOG_WARNING("ssid is empty activating last connected ssid (%s) !", lastConnectedSSID.c_str()); if(_nmGdbusClient->activateKnownConnection(GnomeUtils::getWifiIfname(), lastConnectedSSID)) rc = Core::ERROR_NONE; diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index b6b70e1a..e8be4680 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -282,7 +282,7 @@ namespace WPEFramework Exchange::INetworkManager::WiFiState state = Exchange::INetworkManager::WIFI_STATE_DISCONNECTED; NMLOG_INFO("Event IARM_BUS_WIFI_MGR_EVENT_onWIFIStateChanged received; state=%d", e->data.wifiStateChange.state); state = to_wifi_state(e->data.wifiStateChange.state); - ::_instance->ReportWiFiStateChange(state); + ::_instance->ReportWiFiStateChange(state, ""); break; } case IARM_BUS_WIFI_MGR_EVENT_onError: @@ -290,7 +290,7 @@ namespace WPEFramework IARM_BUS_WiFiSrvMgr_EventData_t* e = (IARM_BUS_WiFiSrvMgr_EventData_t *) data; Exchange::INetworkManager::WiFiState state = errorcode_to_wifi_state(e->data.wifiError.code); NMLOG_INFO("Event IARM_BUS_WIFI_MGR_EVENT_onError received; code=%d", e->data.wifiError.code); - ::_instance->ReportWiFiStateChange(state); + ::_instance->ReportWiFiStateChange(state, ""); break; } default: diff --git a/tools/plugincli/NetworkManagerGdbusTest.cpp b/tools/plugincli/NetworkManagerGdbusTest.cpp index b44ee872..fd28a945 100644 --- a/tools/plugincli/NetworkManagerGdbusTest.cpp +++ b/tools/plugincli/NetworkManagerGdbusTest.cpp @@ -59,9 +59,9 @@ namespace WPEFramework { NMLOG_INFO("calling 'ReportAvailableSSIDs' cb"); } - void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state) + void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid) { - NMLOG_INFO("calling 'ReportWiFiStateChange' cb"); + NMLOG_INFO("calling 'ReportWiFiStateChange' cb (state=%d, ssid='%s')", state, ssid.c_str()); } void NetworkManagerImplementation::ReportWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality) { diff --git a/tools/plugincli/NetworkManagerLibnmTest.cpp b/tools/plugincli/NetworkManagerLibnmTest.cpp index 3a358b56..924a9f7a 100644 --- a/tools/plugincli/NetworkManagerLibnmTest.cpp +++ b/tools/plugincli/NetworkManagerLibnmTest.cpp @@ -58,7 +58,7 @@ namespace WPEFramework { NMLOG_INFO("calling 'ReportAvailableSSIDs' cb"); } - void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state) + void NetworkManagerImplementation::ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid) { NMLOG_INFO("calling 'ReportWiFiStateChange' cb"); } From e051d8ab2470638c823cd2412b2287b34bac3094 Mon Sep 17 00:00:00 2001 From: gururaajar <83449026+gururaajar@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:14:16 -0400 Subject: [PATCH 25/32] RDK-61724: Update Network Manager to publish a new event for route change. (#328) * onroutechange event implementation * Added macro to conditional compilation * Modified the code to have separate client to get connectivity status form the connectivity plugin * Added RFC for connectivity manager enabling in networkmanager * Added enable of USE_RFCAPI in proper place * updated with macro USE_CONNECTIVITYCHECKMGR * Added the documentation * Added Bridging of events between networkmanager and connectivity manager * Addressed the crash issue * To Fix the crash * Added reason for isconnectedtointernet API * Updated with compilation issue fix * Added cache logic to read the internet status from connectivitymgr plugin * Changed the reason into default instead of function overloading * Changed connected field to return true for LIMITED_INTERNET state only when connectivitymgr plugin is in use * Fixed compilation issue * Addressed copilot review comments * Addressed copilot review comments * Added more clarity to documentation for reason. Also added copilot review comments * Added reason to the oninternetstatuschange event * Addressed the review comment * Addressed copilot review comments * Addressed review comments * To fix L1 and L2 issue * For rdk proxy this test is not required * Update name of the RFC return code Update name of the RFC return code * Updated with review comments * Corrected variable name * Updated the missed change * Added header chrono * Fixed L1 and L2 issue * Fixed L1 and L2 issue * Change ConnectivityMonitor to non smart pointer to match old way * Fixed compilation issue * Addressed L1 test failure * To test * Removed unused code * Update NetworkManagerImplementation.cpp --------- Co-authored-by: Gururaaja E S R Co-authored-by: RAFI <103924677+cmuhammedrafi@users.noreply.github.com> Co-authored-by: Karunakaran A <48997923+karuna2git@users.noreply.github.com> --- .github/workflows/gdbus_proxy_L1_test.yml | 7 + .github/workflows/legacy_L1_L2_test.yml | 7 + .github/workflows/libnm_proxy_L1_test.yml | 7 + .github/workflows/rdk_proxy_L1_L2_test.yml | 7 + CMakeLists.txt | 11 + definition/NetworkManager.json | 55 ++- docs/NetworkManagerPlugin.md | 39 +- interface/INetworkManager.h | 14 +- plugin/CMakeLists.txt | 27 +- plugin/NetworkManager.cpp | 16 + plugin/NetworkManager.h | 13 +- plugin/NetworkManagerConnectivityClient.cpp | 342 ++++++++++++++++++ plugin/NetworkManagerConnectivityClient.h | 145 ++++++++ plugin/NetworkManagerImplementation.cpp | 229 ++++++++++-- plugin/NetworkManagerImplementation.h | 28 +- plugin/NetworkManagerJsonRpc.cpp | 27 +- plugin/gnome/NetworkManagerGnomeEvents.cpp | 26 ++ tests/l1Test/l1_test_connectivity.cpp | 2 +- .../l2_test_LegacyPlugin_NetworkAPIs.cpp | 8 +- tests/l2Test/libnm/CMakeLists.txt | 13 + tests/l2Test/rdk/CMakeLists.txt | 13 + tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp | 74 ++++ tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp | 160 ++++++++ tests/mocks/INetworkManagerMock.h | 2 +- tests/mocks/thunder/IConnectivityCheck.h | 73 ++++ tools/plugincli/NetworkManagerGdbusTest.cpp | 2 +- tools/plugincli/NetworkManagerLibnmTest.cpp | 2 +- 27 files changed, 1296 insertions(+), 53 deletions(-) create mode 100644 plugin/NetworkManagerConnectivityClient.cpp create mode 100644 plugin/NetworkManagerConnectivityClient.h create mode 100644 tests/mocks/thunder/IConnectivityCheck.h diff --git a/.github/workflows/gdbus_proxy_L1_test.yml b/.github/workflows/gdbus_proxy_L1_test.yml index 16edc038..ea62e016 100644 --- a/.github/workflows/gdbus_proxy_L1_test.yml +++ b/.github/workflows/gdbus_proxy_L1_test.yml @@ -112,6 +112,13 @@ jobs: IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Install IConnectivityCheck header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + CPC_IFACE_DIR="$(dirname "$IFACE_DIR")/interfaces_cpc/interfaces" + mkdir -p "$CPC_IFACE_DIR" + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IConnectivityCheck.h "$CPC_IFACE_DIR/" + - name: Build networkmanager with Gnome GDBUS Proxy run: > cmake diff --git a/.github/workflows/legacy_L1_L2_test.yml b/.github/workflows/legacy_L1_L2_test.yml index 03538a0a..2fc3eff0 100644 --- a/.github/workflows/legacy_L1_L2_test.yml +++ b/.github/workflows/legacy_L1_L2_test.yml @@ -115,6 +115,13 @@ jobs: IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Install IConnectivityCheck header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + CPC_IFACE_DIR="$(dirname "$IFACE_DIR")/interfaces_cpc/interfaces" + mkdir -p "$CPC_IFACE_DIR" + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IConnectivityCheck.h "$CPC_IFACE_DIR/" + - name: Generate IARM headers run: | touch install/usr/lib/libIARMBus.so diff --git a/.github/workflows/libnm_proxy_L1_test.yml b/.github/workflows/libnm_proxy_L1_test.yml index e5f64def..6a60ae3f 100644 --- a/.github/workflows/libnm_proxy_L1_test.yml +++ b/.github/workflows/libnm_proxy_L1_test.yml @@ -113,6 +113,13 @@ jobs: IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Install IConnectivityCheck header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + CPC_IFACE_DIR="$(dirname "$IFACE_DIR")/interfaces_cpc/interfaces" + mkdir -p "$CPC_IFACE_DIR" + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IConnectivityCheck.h "$CPC_IFACE_DIR/" + - name: Generate dependency files run: | sudo bash -c 'echo "ETHERNET_INTERFACE=eth0 diff --git a/.github/workflows/rdk_proxy_L1_L2_test.yml b/.github/workflows/rdk_proxy_L1_L2_test.yml index fc89bc38..3871ab94 100644 --- a/.github/workflows/rdk_proxy_L1_L2_test.yml +++ b/.github/workflows/rdk_proxy_L1_L2_test.yml @@ -112,6 +112,13 @@ jobs: IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IPowerManager.h "$IFACE_DIR/" + - name: Install IConnectivityCheck header + run: | + IFACE_DIR=$(find ${{github.workspace}}/install/usr/include -maxdepth 2 -name "interfaces" -type d | head -1) + CPC_IFACE_DIR="$(dirname "$IFACE_DIR")/interfaces_cpc/interfaces" + mkdir -p "$CPC_IFACE_DIR" + cp ${{github.workspace}}/networkmanager/tests/mocks/thunder/IConnectivityCheck.h "$CPC_IFACE_DIR/" + - name: Generate IARM headers run: | touch install/usr/lib/libIARMBus.so diff --git a/CMakeLists.txt b/CMakeLists.txt index f02326d7..cb836160 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 b85d5e3c..15c9ab53 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -609,17 +609,23 @@ "connected": { "summary": "`true` if internet connectivity is detected, otherwise `false`", "type": "boolean", - "example": true + "example": false }, "state": { "summary": "Internet state", "type": "integer", - "example": 3 + "example": 1 }, "status": { "summary": "Internet status", "type": "string", - "example": "FULLY_CONNECTED" + "example": "NO_INTERNET" + }, + "reason": { + "summary": "Reason for current status (present when status is NO_INTERNET)", + "type": "string", + "enum": ["NOT_CONFIGURED", "WAITING_INTERFACE", "WAITING_GATEWAY", "PROBE_FAILED"], + "example": "PROBE_FAILED" }, "success": { "$ref": "#/definitions/success" @@ -1437,6 +1443,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": { @@ -1467,27 +1503,32 @@ "prevState":{ "summary": "The previous internet connection state", "type": "integer", - "example": 1 + "example": 3 }, "prevStatus":{ "summary": "The previous internet connection status", "type": "string", - "example": "NO_INTERNET" + "example": "FULLY_CONNECTED" }, "state":{ "summary": "The internet connection state", "type": "integer", - "example": 4 + "example": 1 }, "status":{ "summary": "The internet connection status", "type": "string", - "example": "FULLY_CONNECTED" + "example": "NO_INTERNET" }, "interface":{ "summary": "The internet status change on default interface", "type": "string", "example": "wlan0" + }, + "reason":{ + "summary": "The ConnectivityCheckMgr reason when status is NO_INTERNET", + "type": "string", + "example": "PROBE_FAILED" } }, "required": [ diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 31ff2ca9..94efba09 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -740,6 +740,7 @@ Seeks whether the device has internet connectivity. This API might take up to 5s | result.connected | boolean | `true` if internet connectivity is detected, otherwise `false` | | result.state | integer | Internet state | | result.status | string | Internet status | +| result?.reason | string | *(optional)* Reason for current status (present when status is NO_INTERNET) | | result.success | boolean | Whether the request succeeded | ### Example @@ -768,8 +769,9 @@ Seeks whether the device has internet connectivity. This API might take up to 5s "ipversion": "IPv4", "interface": "wlan0", "connected": true, - "state": 3, - "status": "FULLY_CONNECTED", + "state": 1, + "status": "NO_INTERNET", + "reason": "PROBE_FAILED", "success": true } } @@ -1787,6 +1789,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 +1861,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 8e6cabea..d551f9de 100644 --- a/interface/INetworkManager.h +++ b/interface/INetworkManager.h @@ -229,8 +229,9 @@ namespace WPEFramework /* @brief Set ConnectivityTest Endpoints */ virtual uint32_t SetConnectivityTestEndpoints(IStringIterator* const endpoints /* @in */) = 0; - /* @brief Get Internet Connectivty Status */ - virtual uint32_t IsConnectedToInternet(string &ipversion /* @inout */, string &interface /* @inout */, InternetStatus& status /* @out */) = 0; + /* @brief Get Internet Connectivity Status */ + virtual uint32_t IsConnectedToInternet(string &ipversion /* @inout */, string &interface /* @inout */, InternetStatus& status /* @out */, string& reason /* @out */ = EmptyReason()) = 0; + /* @brief Get Authentication URL if the device is behind Captive Portal */ virtual uint32_t GetCaptivePortalURI(string &uri/* @out */) const = 0; @@ -282,17 +283,24 @@ 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 onInternetStatusChange(const InternetStatus prevState /* @in */, const InternetStatus currState /* @in */, const string interface /* @in */){}; + virtual void onInternetStatusChange(const InternetStatus prevState /* @in */, const InternetStatus currState /* @in */, const string interface /* @in */, const string reason /* @in */){}; // WiFi Notifications that other processes can subscribe to virtual void onAvailableSSIDs(const string jsonOfScanResults /* @in */){}; virtual void onWiFiStateChange(const WiFiState state /* @in */, const string ssid /* @in */){}; virtual void onWiFiSignalQualityChange(const string ssid /* @in */, const int strength /* @in */, const int noise /* @in */, const int snr /* @in */, const WiFiSignalQuality quality /* @in */){}; + virtual void onRouteChange(const string interface /* @in */, const string ipversion /* @in */, const string ipaddress /* @in */, const string gateway /* @in */, const string primarydns /* @in */){}; }; // Allow other processes to register/unregister from our notifications virtual uint32_t Register(INetworkManager::INotification* notification) = 0; virtual uint32_t Unregister(INetworkManager::INotification* notification) = 0; + private: + static string& EmptyReason() + { + static thread_local string reason; + return reason; + } }; } } diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt index 588a243d..6f19009d 100644 --- a/plugin/CMakeLists.txt +++ b/plugin/CMakeLists.txt @@ -76,15 +76,40 @@ set_target_properties(${MODULE_NAME} PROPERTIES CXX_STANDARD_REQUIRED YES FRAMEWORK FALSE) +if(USE_CONNECTIVITYCHECKMGR) + target_link_libraries(${MODULE_NAME} PRIVATE ${RFCAPI_LIBRARY}) + if(RFCAPI_INCLUDE_DIR) + target_include_directories(${MODULE_NAME} PRIVATE ${RFCAPI_INCLUDE_DIR}) + endif() +endif() 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.cpp b/plugin/NetworkManager.cpp index 10ed4598..aaeb6a8e 100644 --- a/plugin/NetworkManager.cpp +++ b/plugin/NetworkManager.cpp @@ -20,6 +20,11 @@ #include "NetworkManager.h" #include +#ifdef USE_CONNECTIVITYCHECKMGR +#include +#include "rfcapi.h" +#endif + namespace WPEFramework { namespace Plugin @@ -40,6 +45,7 @@ namespace WPEFramework _service(nullptr), _networkManagerImpl(nullptr), _networkManager(nullptr), + m_useConnectivityCheckMgr(false), _notification(this) { } @@ -95,6 +101,16 @@ namespace WPEFramework } else { +#ifdef USE_CONNECTIVITYCHECKMGR + RFC_ParamData_t rfcParam = {0}; + const WDMP_STATUS rfcRetCode = getRFCParameter( + const_cast("NetworkManager"), + const_cast("Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.Feature.ConnectivityCheckMgr.Enable"), + &rfcParam); + if (rfcRetCode == WDMP_SUCCESS || rfcRetCode == WDMP_ERR_DEFAULT_VALUE) { + m_useConnectivityCheckMgr = (strcasecmp(rfcParam.value, "true") == 0); + } +#endif SYSLOG(Logging::Startup, (_T("Configuring successful"))); } diff --git a/plugin/NetworkManager.h b/plugin/NetworkManager.h index d8a47e77..dea887e9 100644 --- a/plugin/NetworkManager.h +++ b/plugin/NetworkManager.h @@ -79,9 +79,14 @@ namespace WPEFramework _parent.onIPAddressChange(interface, ipversion, ipaddress, status); } - void onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) override + void onRouteChange(const string interface, const string ipversion, const string ipaddress, const string gateway, const string primarydns) override { - _parent.onInternetStatusChange(prevState, currState, interface); + _parent.onRouteChange(interface, ipversion, ipaddress, gateway, primarydns); + } + + void onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface, const string reason) override + { + _parent.onInternetStatusChange(prevState, currState, interface, reason); } void onAvailableSSIDs(const string jsonOfScanResults) override @@ -260,7 +265,8 @@ 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 onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface); + 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, const string reason); void onAvailableSSIDs(const string jsonOfScanResults); void onWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid); void onWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality); @@ -270,6 +276,7 @@ namespace WPEFramework PluginHost::IShell *_service; PluginHost::IPlugin* _networkManagerImpl; Exchange::INetworkManager *_networkManager; + bool m_useConnectivityCheckMgr; Core::Sink _notification; string m_publicIPAddress; string m_publicIPAddressType; diff --git a/plugin/NetworkManagerConnectivityClient.cpp b/plugin/NetworkManagerConnectivityClient.cpp new file mode 100644 index 00000000..db8eeaaa --- /dev/null +++ b/plugin/NetworkManagerConnectivityClient.cpp @@ -0,0 +1,342 @@ +/** +* 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 +#include + +using namespace WPEFramework; +using namespace WPEFramework::Exchange; +using namespace WPEFramework::Plugin; + +// --------------------------------------------------------------------------- +// NetworkManagerConnectivityClient +// --------------------------------------------------------------------------- + +NetworkManagerConnectivityClient::NetworkManagerConnectivityClient() + : mNotification(*this) +{ + NMLOG_INFO("ConnectivityCheckMgr client created; opening link asynchronously"); + mOpenThread = std::thread(&NetworkManagerConnectivityClient::openThreadLoop, this); +} + +NetworkManagerConnectivityClient::~NetworkManagerConnectivityClient() +{ + NMLOG_INFO("shutting down"); + { + std::lock_guard lock(mOpenMutex); + mStopOpenThread = true; + } + mOpenCv.notify_one(); + if (mOpenThread.joinable()) { + mOpenThread.join(); + } + + // Unregister without holding mLock to avoid deadlock when a notification + // arrives concurrently and tries to acquire mLock inside notifyInternetStatusChanged. + unregisterEvents(); + { + std::lock_guard lock(mLock); + if (mConnectivity != nullptr) { + mConnectivity->Release(); + mConnectivity = nullptr; + } + } + Close(Core::infinite); +} + +void NetworkManagerConnectivityClient::openThreadLoop() +{ + constexpr auto retryInterval = std::chrono::seconds(5); + + while (!mStopOpenThread.load()) { + NMLOG_INFO("connecting to ConnectivityCheckMgr"); + const uint32_t r = Open(RPC::CommunicationTimeOut, Connector(), "org.rdk.ConnectivityCheckMgr"); + if (r == Core::ERROR_NONE) { + // Connected; Operational() is invoked by the framework when the proxy is ready. + NMLOG_INFO("link to ConnectivityCheckMgr opened"); + return; + } + + NMLOG_WARNING("failed to open link to ConnectivityCheckMgr (error %u); retrying", r); + + std::unique_lock lock(mOpenMutex); + mOpenCv.wait_for(lock, retryInterval, [this] { return mStopOpenThread.load(); }); + } +} + +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"); + if (upAndRunning) { + { + std::lock_guard lock(mLock); + if (mConnectivity != nullptr) { + return; // already connected + } + mConnectivity = Interface(); + mHasCachedStatus = false; + mCachedStatus = Exchange::INetworkManager::INTERNET_UNKNOWN; + mCachedReason.clear(); + } + // Register without holding mLock: Register() can dispatch a synchronous + // notification on some Thunder builds, which would deadlock if mLock is held. + registerEvents(); + } else { + // Unregister without holding mLock for the same reason. + unregisterEvents(); + std::lock_guard lock(mLock); + if (mConnectivity != nullptr) { + mConnectivity->Release(); + mConnectivity = nullptr; + } + mHasCachedStatus = false; + mCachedStatus = Exchange::INetworkManager::INTERNET_UNKNOWN; + mCachedReason.clear(); + } +} + +Exchange::IConnectivityCheck* NetworkManagerConnectivityClient::acquireInterface() const +{ + std::lock_guard lock(mLock); + if (mConnectivity != nullptr) { + mConnectivity->AddRef(); + } + return mConnectivity; +} + +void NetworkManagerConnectivityClient::SetInternetStatusChangeHandler(InternetStatusChangeHandler handler) +{ + std::unique_lock lock(mLock); + mHandlerDrainCv.wait(lock, [this] { return !mHandlerClearInProgress; }); + + if (handler) { + mInternetStatusChangeHandler = std::move(handler); + return; + } + + // Clearing is a drain barrier. It must not be called by the active handler. + mHandlerClearInProgress = true; + mInternetStatusChangeHandler = nullptr; + mHandlerDrainCv.wait(lock, [this] { return mHandlersInFlight == 0; }); + mHandlerClearInProgress = false; + lock.unlock(); + mHandlerDrainCv.notify_all(); +} + +void NetworkManagerConnectivityClient::registerEvents() +{ + Exchange::IConnectivityCheck* connectivity = nullptr; + { + std::lock_guard lock(mLock); + if (mConnectivity == nullptr || mNotificationRegistered) { + return; + } + // Claim the flag before dropping mLock so a concurrent caller cannot register twice. + mNotificationRegistered = true; + connectivity = mConnectivity; + connectivity->AddRef(); + } + + const uint32_t r = connectivity->Register(&mNotification); + connectivity->Release(); + + if (r != Core::ERROR_NONE) { + NMLOG_ERROR("ConnectivityCheckMgr register(notification) failed (%u)", r); + std::lock_guard lock(mLock); + mNotificationRegistered = false; + return; + } + + NMLOG_INFO("registered for ConnectivityCheckMgr internet-status notifications"); +} + +void NetworkManagerConnectivityClient::unregisterEvents() +{ + Exchange::IConnectivityCheck* connectivity = nullptr; + { + std::lock_guard lock(mLock); + if (!mNotificationRegistered) { + return; + } + mNotificationRegistered = false; + if (mConnectivity == nullptr) { + return; + } + connectivity = mConnectivity; + connectivity->AddRef(); + } + + if (const uint32_t r = connectivity->Unregister(&mNotification); r != Core::ERROR_NONE) { + NMLOG_ERROR("ConnectivityCheckMgr unregister(notification) failed (%u)", r); + } + connectivity->Release(); +} + +void NetworkManagerConnectivityClient::notifyInternetStatusChanged(Exchange::IConnectivityCheck::InternetStatus status, + const std::string& reason) +{ + const NmInternetStatus mapped = mapStatus(status); + // Only NO_INTERNET carries a meaningful reason; keep the handler consistent with the cache. + const std::string filteredReason = (status == Exchange::IConnectivityCheck::NO_INTERNET) ? reason : std::string(); + + InternetStatusChangeHandler handler; + { + std::lock_guard lock(mLock); + mCachedStatus = mapped; + mCachedReason = filteredReason; + mHasCachedStatus = true; + if (mHandlerClearInProgress) { + return; + } + handler = mInternetStatusChangeHandler; + if (handler) { + ++mHandlersInFlight; + } + } + + if (!handler) { + return; + } + + struct HandlerCompletion { + explicit HandlerCompletion(NetworkManagerConnectivityClient& client) + : client(client) {} + ~HandlerCompletion() + { + client.completeInternetStatusChangeHandler(); + } + + NetworkManagerConnectivityClient& client; + } completion(*this); + + handler(mapped, filteredReason); +} + +void NetworkManagerConnectivityClient::completeInternetStatusChangeHandler() +{ + std::lock_guard lock(mLock); + --mHandlersInFlight; + if (mHandlersInFlight == 0) { + mHandlerDrainCv.notify_all(); + } +} + +void NetworkManagerConnectivityClient::Notification::OnInternetStatusChange( + const Exchange::IConnectivityCheck::InternetStatus status, + const string& reason) +{ + mParent.notifyInternetStatusChanged(status, reason); +} + +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() +{ + std::string reason; + return getInternetState(reason); +} + +NetworkManagerConnectivityClient::NmInternetStatus +NetworkManagerConnectivityClient::getInternetState(std::string& reason) +{ + LOG_ENTRY_FUNCTION(); + reason.clear(); + + { + std::lock_guard lock(mLock); + if (mHasCachedStatus) { + reason = mCachedReason; + return mCachedStatus; + } + } + + // Cold path only: no notification received yet, so seed the cache once. + Exchange::IConnectivityCheck* connectivity = acquireInterface(); + if (connectivity == nullptr) { + NMLOG_WARNING("ConnectivityCheckMgr not available; returning INTERNET_UNKNOWN"); + return Exchange::INetworkManager::INTERNET_UNKNOWN; + } + + Exchange::IConnectivityCheck::StatusInfo info{}; + const uint32_t r = connectivity->GetInternetStatus(info); + connectivity->Release(); + + if (r != Core::ERROR_NONE) { + NMLOG_ERROR("ConnectivityCheckMgr GetInternetStatus failed (%u)", r); + return Exchange::INetworkManager::INTERNET_UNKNOWN; + } + + const NmInternetStatus mapped = mapStatus(info.status); + if (info.status == Exchange::IConnectivityCheck::NO_INTERNET) { + reason = info.reason; + } + + { + std::lock_guard lock(mLock); + // A notification may have landed while the RPC was in flight; it is newer. + if (!mHasCachedStatus) { + mCachedStatus = mapped; + mCachedReason = reason; + mHasCachedStatus = true; + } + } + return mapped; +} + +std::string NetworkManagerConnectivityClient::getCaptivePortalURI() +{ + LOG_ENTRY_FUNCTION(); + std::string uri; + + Exchange::IConnectivityCheck* connectivity = acquireInterface(); + if (connectivity == nullptr) { + NMLOG_WARNING("ConnectivityCheckMgr not available; returning empty captive-portal URI"); + return uri; + } + + const uint32_t r = connectivity->GetCaptivePortalURI(uri); + connectivity->Release(); + + if (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..09b9c5d5 --- /dev/null +++ b/plugin/NetworkManagerConnectivityClient.h @@ -0,0 +1,145 @@ +/** +* 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" +// IConnectivityCheck is generated/installed by entservices-cpc-apis under interfaces_cpc, +// not the standard Thunder interfaces/ path. +#include +#include +#include +#include +#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; + using InternetStatusChangeHandler = std::function; + + 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 internet status and its current NO_INTERNET reason. */ + NmInternetStatus getInternetState(std::string& reason); + + /** Delegated captive-portal URI (empty when unavailable / not captive). */ + std::string getCaptivePortalURI(); + + /** Sets/clears callback invoked when ConnectivityCheckMgr publishes internet-status changes. */ + void SetInternetStatusChangeHandler(InternetStatusChangeHandler handler); + +private: + class Notification : public Exchange::IConnectivityCheck::INotification { + public: + explicit Notification(NetworkManagerConnectivityClient& parent) + : mParent(parent) {} + + void OnInternetStatusChange(const Exchange::IConnectivityCheck::InternetStatus status, + const string& reason) override; + + BEGIN_INTERFACE_MAP(Notification) + INTERFACE_ENTRY(Exchange::IConnectivityCheck::INotification) + END_INTERFACE_MAP + + private: + NetworkManagerConnectivityClient& mParent; + }; + + // SmartInterfaceType lifecycle callback. + void Operational(bool upAndRunning) override; + + void registerEvents(); + void unregisterEvents(); + void notifyInternetStatusChanged(Exchange::IConnectivityCheck::InternetStatus status, + const std::string& reason); + void completeInternetStatusChangeHandler(); + void openThreadLoop(); + + /* Returns an AddRef'd proxy (nullptr when unavailable) so the caller can invoke + * ConnectivityCheckMgr without holding mLock across the blocking COM-RPC call. */ + Exchange::IConnectivityCheck* acquireInterface() const; + + // 1:1 mapping ConnectivityCheckMgr InternetStatus -> NetworkManager InternetStatus. + static NmInternetStatus mapStatus(Exchange::IConnectivityCheck::InternetStatus status); + + mutable std::mutex mLock; + Exchange::IConnectivityCheck* mConnectivity{nullptr}; + Core::Sink mNotification; + InternetStatusChangeHandler mInternetStatusChangeHandler; + std::condition_variable mHandlerDrainCv; + uint32_t mHandlersInFlight{0}; + bool mHandlerClearInProgress{false}; + bool mNotificationRegistered{false}; // guarded by mLock + + // Status cache kept current by OnInternetStatusChange, so the hot + // isConnectedToInternet path needs no cross-process round trip. + NmInternetStatus mCachedStatus{Exchange::INetworkManager::INTERNET_UNKNOWN}; + std::string mCachedReason; + bool mHasCachedStatus{false}; + + // Open() runs on its own thread so plugin Configure() never blocks on, or + // re-enters, a ConnectivityCheckMgr that is not activated yet. + std::thread mOpenThread; + std::mutex mOpenMutex; + std::condition_variable mOpenCv; + std::atomic mStopOpenThread{false}; + + friend class NetworkManagerConnectivityClientTestAccess; +}; + +} // namespace Plugin +} // namespace WPEFramework diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 8d90d17e..ac828bda 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 @@ -83,7 +88,18 @@ namespace WPEFramework { NMLOG_INFO("NetworkManager Out-Of-Process Shutdown/Cleanup"); m_powerClient.reset(); - connectivityMonitor.stopConnectivityMonitor(); +#ifdef USE_CONNECTIVITYCHECKMGR + if (connectivityClient) { + // Clear the handler first so no in-flight callback can reach this + // (partially-destroyed) object, then tear down the COM-RPC client. + connectivityClient->SetInternetStatusChangeHandler(nullptr); + connectivityClient.reset(); + } +#endif + if(!m_useConnectivityCheckMgr) + { + connectivityMonitor.stopConnectivityMonitor(); + } _instance = nullptr; platform_deinit(); if(m_registrationThread.joinable()) @@ -171,6 +187,30 @@ 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 the built-in monitor (started by its constructor) so it does + * not run alongside the delegation client. */ + connectivityMonitor.stopConnectivityMonitor(); + if(!connectivityClient) + connectivityClient.reset(new NetworkManagerConnectivityClient()); + connectivityClient->SetInternetStatusChangeHandler( + [this](const Exchange::INetworkManager::InternetStatus status, const std::string& reason) { + OnDelegatedInternetStatusChange(status, reason); + }); + { + std::lock_guard lock(m_bridgedStatusMutex); + m_hasBridgedInternetStatus = false; + m_lastBridgedInternetStatus = Exchange::INetworkManager::INTERNET_UNKNOWN; + } + NMLOG_INFO("Connectivity delegated to ConnectivityCheckMgr (runtime selection)"); + } +#endif + /* STUN configuration copy */ m_stunEndpoint = config.stun.stunEndpoint.Value(); m_stunPort = config.stun.port.Value(); @@ -204,18 +244,22 @@ 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) + /* Only seed endpoints when none are active; endpoints restored from the + * EndpointManager cache on restart must not be overwritten. */ + if (!m_useConnectivityCheckMgr && connectivityMonitor.getConnectivityMonitorEndpoints().size() < 1) { - NMLOG_INFO("Use the connectivity endpoint from config"); - connectivityMonitor.setConnectivityMonitorEndpoints(connectEndpts); - } - else 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 (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); + } + else + { + NMLOG_INFO("Use the connectivity endpoint from config"); + connectivityMonitor.setConnectivityMonitorEndpoints(connectEndpts); + } } /* As all the configuration is set, lets instantiate platform */ @@ -226,6 +270,31 @@ 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; + } +#else + (void)config; + NMLOG_INFO("ConnectivityCheckMgr delegation not compiled in; using built-in monitor"); +#endif + return false; + } + /* @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 +342,15 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::GetConnectivityTestEndpoints(IStringIterator*& endpoints/* @out */) const { LOG_ENTRY_FUNCTION(); - std::vector tmpEndpoints = connectivityMonitor.getConnectivityMonitorEndpoints(); + /* Endpoints are owned by ConnectivityCheckMgr when delegation is active. */ + if(m_useConnectivityCheckMgr) + { + NMLOG_WARNING("GetConnectivityTestEndpoints is not supported while connectivity is delegated to ConnectivityCheckMgr"); + return Core::ERROR_NOT_SUPPORTED; + } + + std::vector tmpEndpoints; + tmpEndpoints = connectivityMonitor.getConnectivityMonitorEndpoints(); endpoints = (Core::Service::Create(tmpEndpoints)); if(endpoints == nullptr) { return Core::ERROR_GENERAL; @@ -286,6 +363,13 @@ namespace WPEFramework uint32_t NetworkManagerImplementation::SetConnectivityTestEndpoints(IStringIterator* const endpoints /* @in */) { LOG_ENTRY_FUNCTION(); + /* Endpoints are owned by ConnectivityCheckMgr when delegation is active. */ + if(m_useConnectivityCheckMgr) + { + NMLOG_WARNING("SetConnectivityTestEndpoints is not supported while connectivity is delegated to ConnectivityCheckMgr"); + return Core::ERROR_NOT_SUPPORTED; + } + std::vector tmpEndpoints; if(endpoints && (endpoints->Count() >= 1)) @@ -305,9 +389,10 @@ namespace WPEFramework } /* @brief Get Internet Connectivty Status */ - uint32_t NetworkManagerImplementation::IsConnectedToInternet(string &ipversion /* @inout */, string &interface /* @inout */, InternetStatus &result /* @out */) + uint32_t NetworkManagerImplementation::IsConnectedToInternet(string &ipversion /* @inout */, string &interface /* @inout */, InternetStatus &result /* @out */, string& reason /* @out */) { LOG_ENTRY_FUNCTION(); + reason.clear(); Exchange::INetworkManager::IPVersion curlIPversion = Exchange::INetworkManager::IP_ADDRESS_V4; bool ipVersionNotSpecified = false; @@ -332,7 +417,18 @@ 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(reason) + : Exchange::INetworkManager::INTERNET_UNKNOWN; + } + else +#endif + { + result = connectivityMonitor.getInternetState(interface, curlIPversion, ipVersionNotSpecified); + } if (Exchange::INetworkManager::IP_ADDRESS_V6 == curlIPversion) ipversion = "IPv6"; else @@ -348,7 +444,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.getCaptivePortalURI(); return Core::ERROR_NONE; } @@ -798,7 +899,7 @@ namespace WPEFramework NMLOG_INFO("Publishing onInternetStatusChange Event"); const auto& eventData = std::get(data); for (const auto callback : callbacks) { - callback->onInternetStatusChange(eventData.prevState, eventData.currState, eventData.interface); + callback->onInternetStatusChange(eventData.prevState, eventData.currState, eventData.interface, eventData.reason); callback->Release(); } } @@ -857,7 +958,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.switchToInitialCheck(); } else if(interface == "wlan0") { @@ -874,7 +976,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.switchToInitialCheck(); } } } @@ -965,7 +1068,8 @@ namespace WPEFramework if(isDefaultIface) { // As default interface is connected, switch connectivity monitor to initial check any way - connectivityMonitor.switchToInitialCheck(); + if(!m_useConnectivityCheckMgr) + connectivityMonitor.switchToInitialCheck(); } else NMLOG_DEBUG("No need to trigger connectivity monitor interface is %s", interface.c_str()); @@ -979,7 +1083,43 @@ namespace WPEFramework } } - void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) + 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; + } + + /* Snapshot the callbacks with an extra reference and invoke them outside + * _notificationLock; see dispatchEvent for the rationale. */ + std::list callbacks; + _notificationLock.Lock(); + for (auto* tmpCB : _notificationCallbacks) { + tmpCB->AddRef(); + callbacks.push_back(tmpCB); + } + _notificationLock.Unlock(); + + 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 : callbacks) { + callback->onRouteChange(interface, ipversion, settings.ipaddress, + settings.gateway, settings.primarydns); + callback->Release(); + } + } + + void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface, const string& reason) { LOG_ENTRY_FUNCTION(); #if USE_TELEMETRY @@ -994,7 +1134,8 @@ namespace WPEFramework } #endif { - InternetStatusChangeData eventData{prevState, currState, interface}; + const string noInternetReason = (currState == Exchange::INetworkManager::INTERNET_NOT_AVAILABLE) ? reason : string(); + InternetStatusChangeData eventData{prevState, currState, interface, noInternetReason}; NMLOG_INFO("Posting onInternetStatusChange with current state as %u", (unsigned)currState); enqueueEvent(NM_ON_INTERNETSTATUS_CHANGE, std::move(eventData)); } @@ -1006,6 +1147,40 @@ namespace WPEFramework #endif } + void NetworkManagerImplementation::OnDelegatedInternetStatusChange(const Exchange::INetworkManager::InternetStatus currState, const string& reason) + { + LOG_ENTRY_FUNCTION(); + +#ifdef USE_CONNECTIVITYCHECKMGR + if (!m_useConnectivityCheckMgr) { + NMLOG_DEBUG("Ignoring delegated internet-status event because delegation is disabled"); + return; + } + + Exchange::INetworkManager::InternetStatus prevState = Exchange::INetworkManager::INTERNET_UNKNOWN; + { + std::lock_guard lock(m_bridgedStatusMutex); + if (m_hasBridgedInternetStatus && m_lastBridgedInternetStatus == currState) { + NMLOG_DEBUG("Skipping duplicate delegated internet-status event state=%u", static_cast(currState)); + return; + } + if (m_hasBridgedInternetStatus) { + prevState = m_lastBridgedInternetStatus; + } + m_lastBridgedInternetStatus = currState; + m_hasBridgedInternetStatus = true; + } + + const string activeInterface = getDefaultInterface(); + NMLOG_INFO("Bridging ConnectivityCheckMgr internet-status event prev=%u curr=%u iface=%s", + static_cast(prevState), static_cast(currState), activeInterface.c_str()); + ReportInternetStatusChange(prevState, currState, activeInterface, reason); +#else + (void)currState; + (void)reason; +#endif + } + int32_t NetworkManagerImplementation::logSSIDs(Logging level, const JsonArray &ssids) { LOG_ENTRY_FUNCTION(); @@ -1495,7 +1670,10 @@ namespace WPEFramework NMLOG_INFO("OnPowerModePreChange: waking from DeepSleep — WiFi was not connected or was already down before sleep, skipping reconnect"); } // DeepSleep wake (Network Standby OFF): re-verify connectivity so internet status is re-published. - connectivityMonitor.switchToInitialCheck(); + if(!m_useConnectivityCheckMgr) + { + connectivityMonitor.switchToInitialCheck(); + } } sendAck(); } @@ -1535,7 +1713,10 @@ namespace WPEFramework } } // DeepSleep → Standby wake (Network Standby ON): re-verify connectivity so internet status is re-published. - connectivityMonitor.switchToInitialCheck(); + if(!m_useConnectivityCheckMgr) + { + connectivityMonitor.switchToInitialCheck(); + } } } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 81f148cf..e22923aa 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" @@ -243,6 +246,7 @@ namespace WPEFramework Exchange::INetworkManager::InternetStatus prevState; Exchange::INetworkManager::InternetStatus currState; string interface; + string reason; }; struct AvailableSSIDsData { @@ -342,7 +346,7 @@ namespace WPEFramework uint32_t SetConnectivityTestEndpoints(IStringIterator* const endpoints /* @in */) override; /* @brief Get Internet Connectivty Status */ - uint32_t IsConnectedToInternet(string &ipversion /* @inout */, string &interface /* @inout */, InternetStatus &result /* @out */) override; + uint32_t IsConnectedToInternet(string &ipversion /* @inout */, string &interface /* @inout */, InternetStatus &result /* @out */, string& reason /* @out */) override; /* @brief Get Authentication URL if the device is behind Captive Portal */ uint32_t GetCaptivePortalURI(string &endpoints/* @out */) const override; @@ -371,7 +375,10 @@ 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 ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface); + 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, const string& reason = string()); + void OnDelegatedInternetStatusChange(const Exchange::INetworkManager::InternetStatus currState, const string& reason = string()); void ReportAvailableSSIDs(const JsonArray &arrayofWiFiScanResults); void ReportWiFiStateChange(const Exchange::INetworkManager::WiFiState state, const string ssid); void ReportWiFiSignalQualityChange(const string ssid, const int strength, const int noise, const int snr, const Exchange::INetworkManager::WiFiSignalQuality quality); @@ -388,6 +395,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,19 @@ namespace WPEFramework std::atomic m_ethDisconnectedForSleep; std::atomic m_wlanDisconnectedForSleep; GMainContext *m_nmContext{nullptr}; /* isolated context for per-call NMClient creation */ + /* 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 ConnectivityMonitor connectivityMonitor; +#ifdef USE_CONNECTIVITYCHECKMGR + mutable std::unique_ptr connectivityClient; +#endif + std::mutex m_bridgedStatusMutex; + Exchange::INetworkManager::InternetStatus m_lastBridgedInternetStatus {Exchange::INetworkManager::INTERNET_UNKNOWN}; + bool m_hasBridgedInternetStatus {false}; string getDefaultInterface() const { diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index d16f5f0d..7429cba4 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -473,6 +473,7 @@ namespace WPEFramework Exchange::INetworkManager::InternetStatus result; string ipversion{}; string interface{}; + string reason{}; if (parameters.HasLabel("ipversion")) ipversion = parameters["ipversion"].String(); @@ -480,7 +481,7 @@ namespace WPEFramework interface = parameters["interface"].String(); if (_networkManager) - rc = _networkManager->IsConnectedToInternet(ipversion, interface, result); + rc = _networkManager->IsConnectedToInternet(ipversion, interface, result, reason); else rc = Core::ERROR_UNAVAILABLE; @@ -489,9 +490,13 @@ namespace WPEFramework Core::JSON::EnumType status(result); response["ipversion"] = ipversion; response["interface"] = interface; - response["connected"] = (Exchange::INetworkManager::InternetStatus::INTERNET_FULLY_CONNECTED == result); + response["connected"] = (Exchange::INetworkManager::InternetStatus::INTERNET_FULLY_CONNECTED == result + || (m_useConnectivityCheckMgr && + Exchange::INetworkManager::InternetStatus::INTERNET_LIMITED == result)); response["state"] = JsonValue(status); response["status"] = status.Data(); + if (result == Exchange::INetworkManager::InternetStatus::INTERNET_NOT_AVAILABLE && !reason.empty()) + response["reason"] = reason; } returnJson(rc); } @@ -1094,7 +1099,20 @@ namespace WPEFramework Notify(_T("onIPAddressChange"), parameters); } - void NetworkManager::onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) + 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, const string reason) { JsonObject parameters; Core::JSON::EnumType prevStatus(prevState); @@ -1104,6 +1122,9 @@ namespace WPEFramework parameters["state"] = JsonValue(currState); parameters["status"] = currStatus.Data(); parameters["interface"] = interface; + if (currState == Exchange::INetworkManager::INTERNET_NOT_AVAILABLE && !reason.empty()) { + parameters["reason"] = reason; + } LOG_INPARAM(); Notify(_T("onInternetStatusChange"), parameters); diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 739c0076..e2918b9c 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -241,6 +241,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 ipAddress = newCache.toIPAddress(); + ipAddress.ipversion = family; + _instance->ReportRouteChange(ifname, family, ipAddress); + } } static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) @@ -909,6 +926,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/l1Test/l1_test_connectivity.cpp b/tests/l1Test/l1_test_connectivity.cpp index f68a40c8..5ce58955 100644 --- a/tests/l1Test/l1_test_connectivity.cpp +++ b/tests/l1Test/l1_test_connectivity.cpp @@ -29,7 +29,7 @@ namespace WPEFramework namespace Plugin { NetworkManagerImplementation* _instance = nullptr; - void NetworkManagerImplementation::ReportInternetStatusChange(const InternetStatus prevState, const InternetStatus currState, const string interface) + void NetworkManagerImplementation::ReportInternetStatusChange(const InternetStatus prevState, const InternetStatus currState, const string interface, const string& reason) { return; } diff --git a/tests/l2Test/legacy/l2_test_LegacyPlugin_NetworkAPIs.cpp b/tests/l2Test/legacy/l2_test_LegacyPlugin_NetworkAPIs.cpp index d55de392..1eee1dcd 100644 --- a/tests/l2Test/legacy/l2_test_LegacyPlugin_NetworkAPIs.cpp +++ b/tests/l2Test/legacy/l2_test_LegacyPlugin_NetworkAPIs.cpp @@ -472,10 +472,10 @@ TEST_F(NetworkTest, isConnectedToInternet) { return static_cast(mockNetworkManager); })); - EXPECT_CALL(*mockNetworkManager, IsConnectedToInternet(::testing::_, ::testing::_, ::testing::_)) + EXPECT_CALL(*mockNetworkManager, IsConnectedToInternet(::testing::_, ::testing::_, ::testing::_, ::testing::_)) .Times(1) .WillOnce(::testing::Invoke( - [&](string& , string&, WPEFramework::Exchange::INetworkManager::InternetStatus& result) -> uint32_t + [&](string& , string&, WPEFramework::Exchange::INetworkManager::InternetStatus& result, string&) -> uint32_t { result = WPEFramework::Exchange::INetworkManager::InternetStatus::INTERNET_FULLY_CONNECTED; return Core::ERROR_NONE; @@ -505,10 +505,10 @@ TEST_F(NetworkTest, getInternetConnectionState) { return static_cast(mockNetworkManager); })); - EXPECT_CALL(*mockNetworkManager, IsConnectedToInternet(::testing::_, ::testing::_, ::testing::_)) + EXPECT_CALL(*mockNetworkManager, IsConnectedToInternet(::testing::_, ::testing::_, ::testing::_, ::testing::_)) .Times(1) .WillOnce(::testing::Invoke( - [&](const string&, const string&, Exchange::INetworkManager::InternetStatus& status) { + [&](const string&, const string&, Exchange::INetworkManager::InternetStatus& status, string&) { status = Exchange::INetworkManager::InternetStatus::INTERNET_CAPTIVE_PORTAL; return Core::ERROR_NONE; })); diff --git a/tests/l2Test/libnm/CMakeLists.txt b/tests/l2Test/libnm/CMakeLists.txt index 17be8eea..d05bf0a2 100644 --- a/tests/l2Test/libnm/CMakeLists.txt +++ b/tests/l2Test/libnm/CMakeLists.txt @@ -48,6 +48,12 @@ add_executable(${NM_LIBNM_PROXY_L2_TEST} ${PROXY_STUB_SOURCES} ) +if(USE_CONNECTIVITYCHECKMGR) + target_sources(${NM_LIBNM_PROXY_L2_TEST} PRIVATE + ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerConnectivityClient.cpp + ) +endif() + set_target_properties(${NM_LIBNM_PROXY_L2_TEST} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES @@ -183,4 +189,11 @@ target_link_libraries(${NM_LIBNM_PROXY_L2_TEST} PRIVATE ${GIO_LIBRARIES} ) +if(USE_CONNECTIVITYCHECKMGR) + target_link_libraries(${NM_LIBNM_PROXY_L2_TEST} PRIVATE ${RFCAPI_LIBRARY}) + if(RFCAPI_INCLUDE_DIR) + target_include_directories(${NM_LIBNM_PROXY_L2_TEST} PRIVATE ${RFCAPI_INCLUDE_DIR}) + endif() +endif() + install(TARGETS ${NM_LIBNM_PROXY_L2_TEST} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/tests/l2Test/rdk/CMakeLists.txt b/tests/l2Test/rdk/CMakeLists.txt index cc5ecc59..8e893960 100644 --- a/tests/l2Test/rdk/CMakeLists.txt +++ b/tests/l2Test/rdk/CMakeLists.txt @@ -43,6 +43,12 @@ add_executable(${NM_RDK_PROXY_L2_TEST} ${PROXY_STUB_SOURCES} ) +if(USE_CONNECTIVITYCHECKMGR) + target_sources(${NM_RDK_PROXY_L2_TEST} PRIVATE + ${CMAKE_SOURCE_DIR}/plugin/NetworkManagerConnectivityClient.cpp + ) +endif() + set_target_properties(${NM_RDK_PROXY_L2_TEST} PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES @@ -92,4 +98,11 @@ target_link_libraries(${NM_RDK_PROXY_L2_TEST} PRIVATE ${CURL_LIBRARIES} ) +if(USE_CONNECTIVITYCHECKMGR) + target_link_libraries(${NM_RDK_PROXY_L2_TEST} PRIVATE ${RFCAPI_LIBRARY}) + if(RFCAPI_INCLUDE_DIR) + target_include_directories(${NM_RDK_PROXY_L2_TEST} PRIVATE ${RFCAPI_INCLUDE_DIR}) + endif() +endif() + install(TARGETS ${NM_RDK_PROXY_L2_TEST} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) diff --git a/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp b/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp index 1fb8a27c..7c27e2b2 100644 --- a/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp +++ b/tests/l2Test/rdk/l2_test_rdkproxyEvent.cpp @@ -17,8 +17,12 @@ * limitations under the License. **/ #include +#include +#include +#include #include #include +#include #include #include @@ -40,6 +44,22 @@ using namespace WPEFramework; using ::testing::NiceMock; +#ifdef USE_CONNECTIVITYCHECKMGR +namespace WPEFramework { +namespace Plugin { +class NetworkManagerConnectivityClientTestAccess { +public: + static void Notify(NetworkManagerConnectivityClient& client, + const Exchange::IConnectivityCheck::InternetStatus status, + const std::string& reason) + { + client.notifyInternetStatusChanged(status, reason); + } +}; +} // namespace Plugin +} // namespace WPEFramework +#endif + class NetworkManagerEventTest : public ::testing::Test { protected: Core::ProxyType plugin; @@ -560,6 +580,60 @@ TEST_F(NetworkManagerEventTest, onInternetStatusChange_LimitedInternet) server.stop(); } */ + +#ifdef USE_CONNECTIVITYCHECKMGR +TEST(NetworkManagerConnectivityClientTest, ClearsHandlerAfterAdmittedCallbackCompletes) +{ + NetworkManagerConnectivityClient client; + std::promise callbackEntered; + std::future callbackEnteredFuture = callbackEntered.get_future(); + std::promise allowCallbackCompletion; + std::shared_future allowCallbackCompletionFuture = allowCallbackCompletion.get_future().share(); + std::promise handlerCleared; + std::future handlerClearedFuture = handlerCleared.get_future(); + + client.SetInternetStatusChangeHandler( + [&callbackEntered, allowCallbackCompletionFuture](const Exchange::INetworkManager::InternetStatus, + const std::string&) { + callbackEntered.set_value(); + allowCallbackCompletionFuture.wait(); + }); + + std::thread notificationThread([&client] { + NetworkManagerConnectivityClientTestAccess::Notify( + client, Exchange::IConnectivityCheck::FULLY_CONNECTED, std::string()); + }); + EXPECT_EQ(std::future_status::ready, callbackEnteredFuture.wait_for(std::chrono::seconds(1))); + + std::thread clearThread([&client, &handlerCleared] { + client.SetInternetStatusChangeHandler(nullptr); + handlerCleared.set_value(); + }); + EXPECT_EQ(std::future_status::timeout, handlerClearedFuture.wait_for(std::chrono::milliseconds(0))); + + allowCallbackCompletion.set_value(); + notificationThread.join(); + clearThread.join(); + EXPECT_EQ(std::future_status::ready, handlerClearedFuture.wait_for(std::chrono::milliseconds(0))); +} + +TEST(NetworkManagerConnectivityClientTest, DoesNotInvokeHandlerAfterClear) +{ + NetworkManagerConnectivityClient client; + std::atomic callbackCount{0}; + + client.SetInternetStatusChangeHandler( + [&callbackCount](const Exchange::INetworkManager::InternetStatus, const std::string&) { + ++callbackCount; + }); + client.SetInternetStatusChangeHandler(nullptr); + + NetworkManagerConnectivityClientTestAccess::Notify( + client, Exchange::IConnectivityCheck::FULLY_CONNECTED, std::string()); + + EXPECT_EQ(0u, callbackCount.load()); +} +#endif TEST_F(NetworkManagerEventTest, onInternetStatusChange_FULLY_CONNECTED) { EXPECT_CALL(*p_curlWrapsImplMock, curl_multi_perform(::testing::_, ::testing::_)) diff --git a/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp b/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp index ebae2073..7158090d 100644 --- a/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp +++ b/tests/l2Test/rdk/l2_test_rdkproxyImpl.cpp @@ -22,6 +22,8 @@ #include #include +#include +#include #include "NetworkManagerImplementation.h" #include "NetworkManager.h" #include "IarmBusMock.h" @@ -38,6 +40,88 @@ using namespace WPEFramework::Plugin; using IStringIterator = RPC::IIteratorType; using ::testing::NiceMock; +namespace { +class InternetStatusNotificationProbe : public Exchange::INetworkManager::INotification { +public: + void onInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, + const Exchange::INetworkManager::InternetStatus currState, + const string interface, + const string reason) override + { + std::lock_guard lock(mutex); + ++count; + lastPrev = prevState; + lastCurr = currState; + lastInterface = interface; + lastReason = reason; + cv.notify_all(); + } + + bool WaitForCount(const size_t expected, const std::chrono::milliseconds timeout) + { + std::unique_lock lock(mutex); + return cv.wait_for(lock, timeout, [&]() { return count >= expected; }); + } + + size_t Count() const + { + std::lock_guard lock(mutex); + return count; + } + + Exchange::INetworkManager::InternetStatus LastPrev() const + { + std::lock_guard lock(mutex); + return lastPrev; + } + + Exchange::INetworkManager::InternetStatus LastCurr() const + { + std::lock_guard lock(mutex); + return lastCurr; + } + + string LastInterface() const + { + std::lock_guard lock(mutex); + return lastInterface; + } + + string LastReason() const + { + std::lock_guard lock(mutex); + return lastReason; + } + + uint32_t AddRef() const override + { + return Core::ERROR_NONE; + } + + uint32_t Release() const override + { + return Core::ERROR_NONE; + } + + void* QueryInterface(const uint32_t id) override + { + if ((id == Exchange::INetworkManager::INotification::ID) || (id == Core::IUnknown::ID)) { + return static_cast(this); + } + return nullptr; + } + +private: + mutable std::mutex mutex; + std::condition_variable cv; + size_t count {0}; + Exchange::INetworkManager::InternetStatus lastPrev {Exchange::INetworkManager::INTERNET_UNKNOWN}; + Exchange::INetworkManager::InternetStatus lastCurr {Exchange::INetworkManager::INTERNET_UNKNOWN}; + string lastInterface; + string lastReason; +}; +} // namespace + class NetworkManagerImplTest : public ::testing::Test { protected: static Core::ProxyType NetworkManagerImplementation; @@ -261,3 +345,79 @@ TEST_F(NetworkManagerImplTest, SetConnectivityTestEndpoints_TooManyEndpoints) { endpoints->Release(); } +#ifdef USE_CONNECTIVITYCHECKMGR +TEST_F(NetworkManagerImplTest, DelegatedInternetStatusBridgePublishesAndDeduplicates) +{ + InternetStatusNotificationProbe notification; + ASSERT_EQ(interface->Register(¬ification), Core::ERROR_NONE); + + NetworkManagerImplementation->setDefaultInterface("eth0"); + NetworkManagerImplementation->OnDelegatedInternetStatusChange(Exchange::INetworkManager::INTERNET_LIMITED); + + EXPECT_TRUE(notification.WaitForCount(1, std::chrono::milliseconds(1000))); + EXPECT_EQ(notification.LastCurr(), Exchange::INetworkManager::INTERNET_LIMITED); + EXPECT_EQ(notification.LastInterface(), "eth0"); + + // Same state is deduplicated. + NetworkManagerImplementation->OnDelegatedInternetStatusChange(Exchange::INetworkManager::INTERNET_LIMITED); + EXPECT_EQ(notification.Count(), static_cast(1)); + + // New state transition is published. + NetworkManagerImplementation->OnDelegatedInternetStatusChange(Exchange::INetworkManager::INTERNET_FULLY_CONNECTED); + EXPECT_TRUE(notification.WaitForCount(2, std::chrono::milliseconds(1000))); + EXPECT_EQ(notification.LastPrev(), Exchange::INetworkManager::INTERNET_LIMITED); + EXPECT_EQ(notification.LastCurr(), Exchange::INetworkManager::INTERNET_FULLY_CONNECTED); + + EXPECT_EQ(interface->Unregister(¬ification), Core::ERROR_NONE); +} + +TEST_F(NetworkManagerImplTest, DelegatedNoInternetReasonIsPublishedOnlyForNoInternet) +{ + InternetStatusNotificationProbe notification; + ASSERT_EQ(interface->Register(¬ification), Core::ERROR_NONE); + + NetworkManagerImplementation->setDefaultInterface("eth0"); + NetworkManagerImplementation->OnDelegatedInternetStatusChange( + Exchange::INetworkManager::INTERNET_NOT_AVAILABLE, "PROBE_FAILED"); + + EXPECT_TRUE(notification.WaitForCount(1, std::chrono::milliseconds(1000))); + EXPECT_EQ(notification.LastCurr(), Exchange::INetworkManager::INTERNET_NOT_AVAILABLE); + EXPECT_EQ(notification.LastReason(), "PROBE_FAILED"); + + NetworkManagerImplementation->OnDelegatedInternetStatusChange( + Exchange::INetworkManager::INTERNET_FULLY_CONNECTED, "STALE_REASON"); + + EXPECT_TRUE(notification.WaitForCount(2, std::chrono::milliseconds(1000))); + EXPECT_EQ(notification.LastCurr(), Exchange::INetworkManager::INTERNET_FULLY_CONNECTED); + EXPECT_TRUE(notification.LastReason().empty()); + + EXPECT_EQ(interface->Unregister(¬ification), Core::ERROR_NONE); +} + +TEST_F(NetworkManagerImplTest, BuiltInMonitorInternetStatusEventOmitsReason) +{ + InternetStatusNotificationProbe notification; + ASSERT_EQ(interface->Register(¬ification), Core::ERROR_NONE); + + NetworkManagerImplementation->ReportInternetStatusChange( + Exchange::INetworkManager::INTERNET_UNKNOWN, + Exchange::INetworkManager::INTERNET_NOT_AVAILABLE, "eth0"); + + EXPECT_TRUE(notification.WaitForCount(1, std::chrono::milliseconds(1000))); + EXPECT_TRUE(notification.LastReason().empty()); + + EXPECT_EQ(interface->Unregister(¬ification), Core::ERROR_NONE); +} + +TEST_F(NetworkManagerImplTest, DelegatedInternetStatusBridgeIgnoredWhenDelegationDisabled) +{ + InternetStatusNotificationProbe notification; + ASSERT_EQ(interface->Register(¬ification), Core::ERROR_NONE); + + NetworkManagerImplementation->OnDelegatedInternetStatusChange(Exchange::INetworkManager::INTERNET_LIMITED); + EXPECT_FALSE(notification.WaitForCount(1, std::chrono::milliseconds(250))); + + EXPECT_EQ(interface->Unregister(¬ification), Core::ERROR_NONE); +} +#endif + diff --git a/tests/mocks/INetworkManagerMock.h b/tests/mocks/INetworkManagerMock.h index d9a21719..c7fd2408 100644 --- a/tests/mocks/INetworkManagerMock.h +++ b/tests/mocks/INetworkManagerMock.h @@ -31,7 +31,7 @@ class MockINetworkManager : public WPEFramework::Exchange::INetworkManager { MOCK_METHOD(uint32_t, SetStunEndpoint, (string const endpoint, const uint32_t port, const uint32_t timeout, const uint32_t cacheLifetime), (override)); MOCK_METHOD(uint32_t, GetConnectivityTestEndpoints, (IStringIterator*& endpoints), (const)); MOCK_METHOD(uint32_t, SetConnectivityTestEndpoints, (IStringIterator* const endpoints), (override)); - MOCK_METHOD(uint32_t, IsConnectedToInternet, (string& ipversion, string& interface, WPEFramework::Exchange::INetworkManager::InternetStatus& status), (override)); + MOCK_METHOD(uint32_t, IsConnectedToInternet, (string& ipversion, string& interface, WPEFramework::Exchange::INetworkManager::InternetStatus& status, string& reason), (override)); MOCK_METHOD(uint32_t, GetCaptivePortalURI, (string& uri), (const)); MOCK_METHOD(uint32_t, GetPublicIP, (string& interface, string& ipversion, string& ipaddress), (override)); MOCK_METHOD(uint32_t, Ping, (const string ipversion, const string endpoint, const uint32_t count, const uint16_t timeout, const string guid, string& response), (override)); diff --git a/tests/mocks/thunder/IConnectivityCheck.h b/tests/mocks/thunder/IConnectivityCheck.h new file mode 100644 index 00000000..7898ffbd --- /dev/null +++ b/tests/mocks/thunder/IConnectivityCheck.h @@ -0,0 +1,73 @@ +/** +* 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 + +/** + * Minimal CI stub for IConnectivityCheck — compatible with Thunder R4.4.3. + * + * This file is used only during CI builds (rdk proxy L1/L2 tests) where the + * full entservices-cpc-apis stack is not available. It defines exactly the + * surface consumed by NetworkManagerConnectivityClient and nothing more. + * + * DO NOT use this file outside of test/CI contexts. + */ + +#include + +namespace WPEFramework { +namespace Exchange { + + struct EXTERNAL IConnectivityCheck : virtual public Core::IUnknown { + + // Stub ID — not used for COM lookup in L1/L2 unit tests. + enum { ID = 0x8190 }; + + enum InternetStatus : uint8_t { + NO_INTERNET, + LIMITED_INTERNET, + CAPTIVE_PORTAL, + FULLY_CONNECTED, + UNKNOWN, + }; + + struct EXTERNAL StatusInfo { + InternetStatus status; + string reason; + string interface; + string ipversion; + }; + + struct EXTERNAL INotification : virtual public Core::IUnknown { + enum { ID = 0x8191 }; + + virtual void OnInternetStatusChange(const InternetStatus status, const string& reason) {} + virtual void OnExternalProbeResult(const InternetStatus status, const string& reason, + const string& captivePortalURI) {} + }; + + virtual Core::hresult GetInternetStatus(StatusInfo& info /* @out */) const {}; + virtual Core::hresult GetCaptivePortalURI(string& uri /* @out */) const {}; + + virtual Core::hresult Register(INotification* notification) {}; + virtual Core::hresult Unregister(INotification* notification) {}; + }; + +} // namespace Exchange +} // namespace WPEFramework diff --git a/tools/plugincli/NetworkManagerGdbusTest.cpp b/tools/plugincli/NetworkManagerGdbusTest.cpp index fd28a945..abeb5f38 100644 --- a/tools/plugincli/NetworkManagerGdbusTest.cpp +++ b/tools/plugincli/NetworkManagerGdbusTest.cpp @@ -51,7 +51,7 @@ namespace WPEFramework { NMLOG_INFO("calling 'ReportIPAddressChange' cb"); } - void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) + void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface, const string& reason) { NMLOG_INFO("calling 'ReportInternetStatusChange' cb"); } diff --git a/tools/plugincli/NetworkManagerLibnmTest.cpp b/tools/plugincli/NetworkManagerLibnmTest.cpp index 924a9f7a..34ece13c 100644 --- a/tools/plugincli/NetworkManagerLibnmTest.cpp +++ b/tools/plugincli/NetworkManagerLibnmTest.cpp @@ -50,7 +50,7 @@ namespace WPEFramework { NMLOG_INFO("calling 'ReportIPAddressChange' cb"); } - void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface) + void NetworkManagerImplementation::ReportInternetStatusChange(const Exchange::INetworkManager::InternetStatus prevState, const Exchange::INetworkManager::InternetStatus currState, const string interface, const string& reason) { NMLOG_INFO("calling 'ReportInternetStatusChange' cb"); } From 57281a7aba558b6deda27428f5f8a6516c8553bb Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Thu, 3 Sep 2026 11:10:04 -0400 Subject: [PATCH 26/32] Release of 4.0.0 Release of 4.0.0 --- CHANGELOG.md | 9 +++++++++ CMakeLists.txt | 4 ++-- definition/NetworkManager.json | 27 +++++++++++---------------- docs/NetworkManagerPlugin.md | 18 +++++++++--------- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe546edd..459b82e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,15 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [4.0.0] - 2026-09-03 +### Added +- Implemented a new `onRouteChange` event notify availability of IP Route either from WiFi or Ethernet +- Integrated new InternetConnectivityManager external component to identify the internet availability without having 204 endpoint +- Publishing onInternetStatusChange event upon wake-up +- Added mutex protection before accessing lastConnectedSSID +- Changed the onWiFiStateChange event to include SSID name +- Updated documentation for events and method descriptions. + ## [3.7.0] - 2026-08-21 ### Changed - Implemented a caching logic about the status of the interface & update based on events diff --git a/CMakeLists.txt b/CMakeLists.txt index cb836160..636dd3ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,8 +36,8 @@ if (NOT WPEFramework_FOUND AND NOT Thunder_FOUND) endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") -set(VERSION_MAJOR 3) -set(VERSION_MINOR 7) +set(VERSION_MAJOR 4) +set(VERSION_MINOR 0) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 15c9ab53..a4535b5d 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "3.7.0" + "version": "4.0.0" }, "definitions": { "success": { @@ -29,7 +29,7 @@ "autoconfig": { "summary": "`true` if DHCP is used, `false` if IP is configured manually", "type": "boolean", - "example": true + "example": false }, "dhcpserver": { "summary": "The DHCP Server address", @@ -409,9 +409,9 @@ } }, "SetIPSettings":{ - "summary": "Sets the IP settings for the given interface.", + "summary": "Sets the IP settings for the given interface. The `interface`, `ipversion`, and `autoconfig` parameters are mandatory. When `autoconfig` is `false`, the `ipaddress`, `prefix`, `gateway`, `primarydns`, and `secondarydns` parameters must also be provided.", "events":{ - "onAddressChange" : "Triggered when the device connects to router.", + "onIPAddressChange" : "Triggered when the device connects to router.", "onInternetStatusChange" : "Triggered when each IP address is lost or acquired." }, "params": { @@ -445,12 +445,7 @@ "required": [ "interface", "ipversion", - "autoconfig", - "ipaddress", - "prefix", - "gateway", - "primarydns", - "secondarydns" + "autoconfig" ] }, "result": { @@ -974,7 +969,7 @@ "summary": "Remove given SSID from saved SSIDs. This method just removes an entry from the list and of the list is having only one entry thats being removed, it will initiate a disconnect.", "events":{ "onWiFiStateChange" : "Triggered when Wifi state changes to DISCONNECTED", - "onAddressChange" : "Triggered when an IP Address is assigned or lost", + "onIPAddressChange" : "Triggered when an IP Address is assigned or lost", "onInternetStatusChange" : "Triggered when internet connection state changed" }, "params": { @@ -1004,7 +999,7 @@ "summary": "Connects to a saved SSID. The `ssid` parameter is mandatory. Returns failure if `ssid` is not specified or not found in the saved SSIDs list.", "events":{ "onWiFiStateChange" : "Triggered when Wifi state changes to CONNECTING, CONNECTED .", - "onAddressChange" : "Triggered when an IP Address is assigned or lost", + "onIPAddressChange" : "Triggered when an IP Address is assigned or lost", "onInternetStatusChange" : "Triggered when internet connection state changed" }, "params": { @@ -1031,7 +1026,7 @@ } }, "WiFiConnect":{ - "summary": "Initiates request to connect to the specified SSID with the given passphrase. Passphrase can be `null` when the network security is `NONE`. The security mode is decided based on the highest security mode provided by the SSID. Also when called with no arguments, this method attempts to connect to the saved SSID and password. See `AddToKnownSSIDs`.", + "summary": "Initiates request to connect to the specified SSID with the given passphrase. Passphrase can be `null` when the network security is `NONE`. The security mode is decided based on the highest security mode provided by the SSID. Also when called with no arguments, this method attempts to connect to the last connected SSID. See `AddToKnownSSIDs`.", "events":{ "onWiFiStateChange" : "Triggered when Wifi state changes to CONNECTING, CONNECTED ." }, @@ -1127,7 +1122,7 @@ "summary": "Disconnects from the currently connected SSID. A event will be posted upon completion", "events":{ "onWIFIStateChange" : "Triggered when Wifi state changes to DISCONNECTED (only if currently connected).", - "onAddressChange" : "Triggered when an IP Address is assigned or lost", + "onIPAddressChange" : "Triggered when an IP Address is assigned or lost", "onInternetStatusChange" : "Triggered when internet connection state changed" }, "result": { @@ -1192,7 +1187,7 @@ "summary": "Initiates a connection using Wifi Protected Setup (WPS). An existing connection will be disconnected before attempting to initiate a new connection. Failure in WPS pairing will trigger an error event.\n\nIf the `method` parameter is set to `SERIALIZED_PIN`, then RDK retrieves the serialized pin using the Manufacturer (MFR) API. If the `method` parameter is set to `PIN`, then RDK use the pin supplied as part of the request. If the `method` parameter is set to `PBC`, then RDK uses Push Button Configuration (PBC) to obtain the pin.", "events":{ "onWIFIStateChange" : "Triggered when Wifi state changes to DISCONNECTED (only if currently connected), CONNECTING, CONNECTED.", - "onAddressChange" : "Triggered when an IP Address is assigned or lost", + "onIPAddressChange" : "Triggered when an IP Address is assigned or lost", "onInternetStatusChange" : "Triggered when internet connection state changed" }, "params": { @@ -1411,7 +1406,7 @@ ] } }, - "onAddressChange":{ + "onIPAddressChange":{ "summary": "Triggered when an IP Address is assigned or lost.", "params": { "type": "object", diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 94efba09..aa7b7829 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 3.7.0** +**Version: 4.0.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 3.7.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.0.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity @@ -464,7 +464,7 @@ Gets the IP setting for the given interface. ## *SetIPSettings [method](#head.Methods)* -Sets the IP settings for the given interface. +Sets the IP settings for the given interface. The `interface`, `ipversion`, and `autoconfig` parameters are mandatory. When `autoconfig` is `false`, the `ipaddress`, `prefix`, `gateway`, `primarydns`, and `secondarydns` parameters must also be provided. Also see: [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) @@ -476,11 +476,11 @@ Also see: [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#e | 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.autoconfig | boolean | `true` if DHCP is used, `false` if IP is configured manually | -| params.ipaddress | string | The IP address | -| params.prefix | integer | The prefix number | -| params.gateway | string | The gateway address | -| params.primarydns | string | The primary DNS address | -| params.secondarydns | string | The secondary DNS address | +| params?.ipaddress | string | *(optional)* The IP address | +| params?.prefix | integer | *(optional)* The prefix number | +| params?.gateway | string | *(optional)* The gateway address | +| params?.primarydns | string | *(optional)* The primary DNS address | +| params?.secondarydns | string | *(optional)* The secondary DNS address | ### Result @@ -501,7 +501,7 @@ Also see: [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#e "params": { "interface": "wlan0", "ipversion": "IPv4", - "autoconfig": true, + "autoconfig": false, "ipaddress": "192.168.1.101", "prefix": 24, "gateway": "192.168.1.1", From 974c0ae919a0375b19283c0a2f93f6726175e1ae Mon Sep 17 00:00:00 2001 From: DevikaJaladi <155776845+DevikaJaladi@users.noreply.github.com> Date: Thu, 10 Sep 2026 19:08:47 -0400 Subject: [PATCH 27/32] RDKEMW-24853: Remove rescan request upon wake-up(NSM-ON) (#350) Reason for change: Remove explicit rescan request upon wake-up(NSM-ON). Test Procedure: Change the channel in AP or change the timeout for SAE GTK_REKEY when the box is in DeepSleep & wake-up; ensure WiFi reconnected with 10s; wake-up journey. Priority: P1 Risks: Low Signed-off-by: Devika Jaladi Co-authored-by: Karunakaran A --- plugin/NetworkManagerImplementation.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index ac828bda..d6819d40 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -1689,15 +1689,6 @@ namespace WPEFramework if (m_wlanEnabled.load() && m_wlanConnected.load()) { - // Waking from DeepSleep with Network Standby ON: the AP may have - // changed channel while the device slept (802.11 CSA). Trigger an - // active scan so the driver discovers the AP on its new channel. - NMLOG_INFO("OnPowerModeChanged: waking from DeepSleep, triggering active WiFi scan"); - if (StartWiFiScan(nullptr, nullptr) != Core::ERROR_NONE) - { - NMLOG_ERROR("OnPowerModeChanged: StartWiFiScan failed"); - } - NMLOG_INFO("OnPowerModeChanged: waking from DeepSleep, requesting DHCP lease on wlan0"); if (ReacquireDHCPLease("wlan0") != Core::ERROR_NONE) { From ecff51c73304e38ddb9f5d1131b5c670b898bb14 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Thu, 10 Sep 2026 19:15:15 -0400 Subject: [PATCH 28/32] Release of 4.1.0 Release of 4.1.0 --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 459b82e0..007e6fb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [4.1.0] - 2026-09-10 +### Changed +- Removed the explicit ReScan upon Wake-Up from DeepSleep because the Gnome NW sends SCAN requests with "AllowRoam" false which leads supplicant to Not to connect to WiFi; it ends-up scan-only + ## [4.0.0] - 2026-09-03 ### Added - Implemented a new `onRouteChange` event notify availability of IP Route either from WiFi or Ethernet diff --git a/CMakeLists.txt b/CMakeLists.txt index 636dd3ea..8d839aa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 4) -set(VERSION_MINOR 0) +set(VERSION_MINOR 1) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index a4535b5d..5a22a2d1 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "4.0.0" + "version": "4.1.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index aa7b7829..2fe6f9a1 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 4.0.0** +**Version: 4.1.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.0.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.1.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 10584afec294354a0e342c37ed8b7c007cfd9d1c Mon Sep 17 00:00:00 2001 From: tukken-comcast Date: Fri, 11 Sep 2026 21:56:44 +0530 Subject: [PATCH 29/32] RDKEMW-23908: Reduce default ping count from 3 to 1 and timeout from 3s to 1s (#345) * RDKEMW-23908: Reduce default ping count (3->1), timeout (3s->1s), interval (0.2s->0.002s) Reason for change: Current Ping method is synchronous and blocks other method calls while ping is executing. This fix is a mitigation. A proper fix requires methods like Ping that trigger long-running operations to be asynchronous. Test Procedure: Refer ticket Risks: Low Signed-off-by: Tony_Ukken2@comcast.com Co-authored-by: Karunakaran A <48997923+karuna2git@users.noreply.github.com> Co-authored-by: Karunakaran A --- definition/NetworkManager.json | 22 ++++++------ docs/NetworkManagerPlugin.md | 46 +++++++++++++------------ legacy/LegacyNetworkAPIs.cpp | 4 +-- plugin/NetworkManagerImplementation.cpp | 4 +-- plugin/NetworkManagerJsonRpc.cpp | 4 +-- 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 5a22a2d1..f89f6da1 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -69,7 +69,7 @@ "port":{ "summary": "STUN server port", "type": "integer", - "example": "3478" + "example": 3478 }, "endpoint":{ "summary": "The host name or IP address", @@ -79,7 +79,7 @@ "cacheLifetime":{ "summary": "STUN server cache timeout", "type": "integer", - "example": "0" + "example": 0 }, "state": { "summary": "The given State", @@ -92,7 +92,7 @@ "example": "" }, "count": { - "summary": "The number of requests to send. Default is 3.", + "summary": "The number of requests to send. Default is 1.", "type": "integer", "example": 10 }, @@ -104,7 +104,7 @@ "timeout":{ "summary": "Timeout", "type": "integer", - "example": "30" + "example": 30 }, "ssid":{ "summary": "The WiFi SSID Name", @@ -710,8 +710,10 @@ "count": { "$ref": "#/definitions/count" }, - "timeout": { - "$ref": "#/definitions/timeout" + "timeout":{ + "summary": "Timeout in seconds. Default is 1", + "type": "integer", + "example": 5 }, "guid": { "$ref": "#/definitions/guid" @@ -1121,7 +1123,7 @@ "WiFiDisconnect":{ "summary": "Disconnects from the currently connected SSID. A event will be posted upon completion", "events":{ - "onWIFIStateChange" : "Triggered when Wifi state changes to DISCONNECTED (only if currently connected).", + "onWiFiStateChange" : "Triggered when Wifi state changes to DISCONNECTED (only if currently connected).", "onIPAddressChange" : "Triggered when an IP Address is assigned or lost", "onInternetStatusChange" : "Triggered when internet connection state changed" }, @@ -1186,7 +1188,7 @@ "StartWPS":{ "summary": "Initiates a connection using Wifi Protected Setup (WPS). An existing connection will be disconnected before attempting to initiate a new connection. Failure in WPS pairing will trigger an error event.\n\nIf the `method` parameter is set to `SERIALIZED_PIN`, then RDK retrieves the serialized pin using the Manufacturer (MFR) API. If the `method` parameter is set to `PIN`, then RDK use the pin supplied as part of the request. If the `method` parameter is set to `PBC`, then RDK uses Push Button Configuration (PBC) to obtain the pin.", "events":{ - "onWIFIStateChange" : "Triggered when Wifi state changes to DISCONNECTED (only if currently connected), CONNECTING, CONNECTED.", + "onWiFiStateChange" : "Triggered when Wifi state changes to DISCONNECTED (only if currently connected), CONNECTING, CONNECTED.", "onIPAddressChange" : "Triggered when an IP Address is assigned or lost", "onInternetStatusChange" : "Triggered when internet connection state changed" }, @@ -1229,7 +1231,7 @@ "StopWPS":{ "summary": "Cancels the in-progress WPS pairing operation. The operation forcefully stops the in-progress pairing attempt and aborts the current scan. WPS pairing must be in-progress for the operation to succeed.", "events":{ - "onWIFIStateChange" : "Triggered when Wifi state changes to DISCONNECTED." + "onWiFiStateChange" : "Triggered when Wifi state changes to DISCONNECTED." }, "result": { "type": "object", @@ -1586,7 +1588,7 @@ "state":{ "summary": "WiFi State", "type": "integer", - "example": "5" + "example": 5 }, "status": { "summary": "WiFi status", diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 2fe6f9a1..6a8e9d29 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -448,7 +448,7 @@ Gets the IP setting for the given interface. "result": { "interface": "wlan0", "ipversion": "IPv4", - "autoconfig": true, + "autoconfig": false, "dhcpserver": "192.168.1.1", "ipaddress": "192.168.1.101", "prefix": 24, @@ -466,7 +466,7 @@ Gets the IP setting for the given interface. Sets the IP settings for the given interface. The `interface`, `ipversion`, and `autoconfig` parameters are mandatory. When `autoconfig` is `false`, the `ipaddress`, `prefix`, `gateway`, `primarydns`, and `secondarydns` parameters must also be provided. -Also see: [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) +Also see: [onIPAddressChange](#event.onIPAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) ### Parameters @@ -768,7 +768,7 @@ Seeks whether the device has internet connectivity. This API might take up to 5s "result": { "ipversion": "IPv4", "interface": "wlan0", - "connected": true, + "connected": false, "state": 1, "status": "NO_INTERNET", "reason": "PROBE_FAILED", @@ -885,8 +885,8 @@ Pings the specified endpoint with the specified number of packets. | params | object | | | params.endpoint | string | The host name or IP address | | params.ipversion | string | Either IPv4 or IPv6 | -| params?.count | integer | *(optional)* The number of requests to send. Default is 3 | -| params?.timeout | integer | *(optional)* Timeout | +| params?.count | integer | *(optional)* The number of requests to send. Default is 1 | +| params?.timeout | integer | *(optional)* Timeout in seconds. Default is 1 | | params?.guid | string | *(optional)* The globally unique identifier | ### Result @@ -919,7 +919,7 @@ Pings the specified endpoint with the specified number of packets. "endpoint": "45.57.221.20", "ipversion": "IPv4", "count": 10, - "timeout": 30, + "timeout": 5, "guid": "..." } } @@ -1201,7 +1201,7 @@ Saves the SSID, passphrase, and security mode for upcoming and future sessions. Remove given SSID from saved SSIDs. This method just removes an entry from the list and of the list is having only one entry thats being removed, it will initiate a disconnect. -Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) +Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onIPAddressChange](#event.onIPAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) ### Parameters @@ -1249,7 +1249,7 @@ Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onAddressChange](#even Connects to a saved SSID. The `ssid` parameter is mandatory. Returns failure if `ssid` is not specified or not found in the saved SSIDs list. -Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) +Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onIPAddressChange](#event.onIPAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) ### Parameters @@ -1295,7 +1295,7 @@ Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onAddressChange](#even ## *WiFiConnect [method](#head.Methods)* -Initiates request to connect to the specified SSID with the given passphrase. Passphrase can be `null` when the network security is `NONE`. The security mode is decided based on the highest security mode provided by the SSID. Also when called with no arguments, this method attempts to connect to the saved SSID and password. See `AddToKnownSSIDs`. +Initiates request to connect to the specified SSID with the given passphrase. Passphrase can be `null` when the network security is `NONE`. The security mode is decided based on the highest security mode provided by the SSID. Also when called with no arguments, this method attempts to connect to the last connected SSID. See `AddToKnownSSIDs`. Also see: [onWiFiStateChange](#event.onWiFiStateChange) @@ -1373,7 +1373,7 @@ Also see: [onWiFiStateChange](#event.onWiFiStateChange) Disconnects from the currently connected SSID. A event will be posted upon completion. -Also see: [onWIFIStateChange](#event.onWIFIStateChange), [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) +Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onIPAddressChange](#event.onIPAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) ### Parameters @@ -1471,7 +1471,7 @@ Initiates a connection using Wifi Protected Setup (WPS). An existing connection If the `method` parameter is set to `SERIALIZED_PIN`, then RDK retrieves the serialized pin using the Manufacturer (MFR) API. If the `method` parameter is set to `PIN`, then RDK use the pin supplied as part of the request. If the `method` parameter is set to `PBC`, then RDK uses Push Button Configuration (PBC) to obtain the pin. -Also see: [onWIFIStateChange](#event.onWIFIStateChange), [onAddressChange](#event.onAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) +Also see: [onWiFiStateChange](#event.onWiFiStateChange), [onIPAddressChange](#event.onIPAddressChange), [onInternetStatusChange](#event.onInternetStatusChange) ### Parameters @@ -1523,7 +1523,7 @@ Also see: [onWIFIStateChange](#event.onWIFIStateChange), [onAddressChange](#even Cancels the in-progress WPS pairing operation. The operation forcefully stops the in-progress pairing attempt and aborts the current scan. WPS pairing must be in-progress for the operation to succeed. -Also see: [onWIFIStateChange](#event.onWIFIStateChange) +Also see: [onWiFiStateChange](#event.onWiFiStateChange) ### Parameters @@ -1788,7 +1788,7 @@ NetworkManager interface events: | Event | Description | | :-------- | :-------- | | [onInterfaceStateChange](#event.onInterfaceStateChange) | Triggered when an interface state is changed | -| [onAddressChange](#event.onAddressChange) | Triggered when an IP Address is assigned or lost | +| [onIPAddressChange](#event.onIPAddressChange) | 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 | @@ -1831,8 +1831,8 @@ Triggered when an interface state is changed. The possible states are } ``` - -## *onAddressChange [event](#head.Notifications)* + +## *onIPAddressChange [event](#head.Notifications)* Triggered when an IP Address is assigned or lost. @@ -1851,7 +1851,7 @@ Triggered when an IP Address is assigned or lost. ```json { "jsonrpc": "2.0", - "method": "client.events.1.onAddressChange", + "method": "client.events.1.onIPAddressChange", "params": { "interface": "wlan0", "ipaddress": "192.168.1.101", @@ -1934,6 +1934,7 @@ Triggered when internet connection state changed.The possible internet connectio | params.state | integer | The internet connection state | | params.status | string | The internet connection status | | params.interface | string | The internet status change on default interface | +| params?.reason | string | *(optional)* The ConnectivityCheckMgr reason when status is NO_INTERNET | ### Example @@ -1942,11 +1943,12 @@ Triggered when internet connection state changed.The possible internet connectio "jsonrpc": "2.0", "method": "client.events.1.onInternetStatusChange", "params": { - "prevState": 1, - "prevStatus": "NO_INTERNET", - "state": 4, - "status": "FULLY_CONNECTED", - "interface": "wlan0" + "prevState": 3, + "prevStatus": "FULLY_CONNECTED", + "state": 1, + "status": "NO_INTERNET", + "interface": "wlan0", + "reason": "PROBE_FAILED" } } ``` @@ -2001,7 +2003,7 @@ Triggered when WIFI connection state get changed. The possible states are define | params | object | | | params.state | integer | WiFi State | | params.status | string | WiFi status | -| params.ssid | string | The SSID associated with the Wi-Fi profile causing the state transition. Disconnected state, contains the SSID associated with the connection that was disconnected | +| params.ssid | string | The SSID associated with the Wi-Fi profile causing the state transition. Disconnected state, contains the SSID associated with the connection that was disconnected. The SSID will be empty when WPS initiated and no AP found with WPS enabled | ### Example diff --git a/legacy/LegacyNetworkAPIs.cpp b/legacy/LegacyNetworkAPIs.cpp index 4e31974c..172e43f3 100644 --- a/legacy/LegacyNetworkAPIs.cpp +++ b/legacy/LegacyNetworkAPIs.cpp @@ -621,8 +621,8 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { { string guid{}; string ipversion{"IPv4"}; - uint32_t noOfRequest = 3; - uint16_t timeOutInSeconds = 3; + uint32_t noOfRequest = 1; + uint16_t timeOutInSeconds = 1; endpoint = parameters["endpoint"].String(); diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index d6819d40..801e5df4 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -552,11 +552,11 @@ namespace WPEFramework } if(0 == strcasecmp("IPv6", ipversion.c_str())) { - snprintf(cmd, sizeof(cmd), "ping6 -c %d -W %d -i 0.2 '%s' 2>&1", noOfRequest, timeOutInSeconds, endpoint.c_str()); + snprintf(cmd, sizeof(cmd), "ping6 -c %d -W %d -i 0.002 '%s' 2>&1", noOfRequest, timeOutInSeconds, endpoint.c_str()); } else { - snprintf(cmd, sizeof(cmd), "ping -c %d -W %d -i 0.2 '%s' 2>&1", noOfRequest, timeOutInSeconds, endpoint.c_str()); + snprintf(cmd, sizeof(cmd), "ping -c %d -W %d -i 0.002 '%s' 2>&1", noOfRequest, timeOutInSeconds, endpoint.c_str()); } NMLOG_DEBUG ("The Command is %s", cmd); diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index 7429cba4..6e309e6f 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -581,8 +581,8 @@ namespace WPEFramework { string guid{}; string ipversion{"IPv4"}; - uint32_t noOfRequest = 3; - uint16_t timeOutInSeconds = 3; + uint32_t noOfRequest = 1; + uint16_t timeOutInSeconds = 1; endpoint = parameters["endpoint"].String(); From b51acd6f4bf0ecc16c90d2b948afb4646021f81e Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Fri, 11 Sep 2026 12:30:19 -0400 Subject: [PATCH 30/32] Release of 4.2.0 Release of 4.2.0 --- CHANGELOG.md | 5 +++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 007e6fb9..f9efdbef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [4.2.0] - 2026-09-11 +### Changed +- Changed the default values for the number of ping request sent, the timeout and also the interval between the ping packets. +- Moving Ping and Trace methods as ASYNC is being designed + ## [4.1.0] - 2026-09-10 ### Changed - Removed the explicit ReScan upon Wake-Up from DeepSleep because the Gnome NW sends SCAN requests with "AllowRoam" false which leads supplicant to Not to connect to WiFi; it ends-up scan-only diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d839aa2..a860fcfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 4) -set(VERSION_MINOR 1) +set(VERSION_MINOR 2) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index f89f6da1..6cc48365 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "4.1.0" + "version": "4.2.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 6a8e9d29..62c3269c 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 4.1.0** +**Version: 4.2.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.1.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.2.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity From 4368c3cfea221e6217af212b4cdf55737aaad34c Mon Sep 17 00:00:00 2001 From: Karunakaran A <48997923+karuna2git@users.noreply.github.com> Date: Tue, 15 Sep 2026 16:33:02 -0400 Subject: [PATCH 31/32] RDKEMW-24199: GetKnownSSIDs must return appropriate response (#353) * RDKEMW-24199: GetKnownSSIDs must return appropriate response Reason for change: GetKnownSSIDs must return stored WiFi Profile information; when not available, empty; not failure. Priority: P1 Test Procedure: Invoke `GetKnownSSIDs` when connected/disconnected from WiFi Risks: Low Signed-off-by: Mehavarshni P Signed-off-by: Karunakaran A * Fixed Review comments Signed-off-by: Karunakaran A --------- Signed-off-by: Mehavarshni P Signed-off-by: Karunakaran A --- plugin/gnome/NetworkManagerGnomeProxy.cpp | 5 ----- plugin/gnome/NetworkManagerGnomeWIFI.cpp | 14 +++----------- plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp | 2 +- plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp | 2 -- plugin/rdk/NetworkManagerRDKProxy.cpp | 9 +++++---- plugin/rdk/NetworkManagerRDKProxy.h | 1 + tests/l2Test/rdk/l2_test_rdkproxy.cpp | 7 +++---- 7 files changed, 13 insertions(+), 27 deletions(-) diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 7c877f5f..6e797892 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -774,11 +774,6 @@ namespace WPEFramework } rc = Core::ERROR_NONE; } - else - { - NMLOG_INFO("known ssids not found !"); - rc = Core::ERROR_GENERAL; - } } return rc; diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index 148e54ef..87b3c0eb 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -1806,8 +1806,6 @@ namespace WPEFramework bool wifiManager::getKnownSSIDs(std::list& ssids) { - std::string ssidPrint{}; - if(!createClientNewConnection()) return false; @@ -1830,8 +1828,6 @@ namespace WPEFramework if(ssidStr != nullptr) { ssids.push_back(string(ssidStr)); - ssidPrint += ssidStr; - ssidPrint += ", "; free(ssidStr); } else @@ -1847,15 +1843,11 @@ namespace WPEFramework } } } - if (!ssids.empty()) - { - NMLOG_INFO("known wifi connections are %s", ssidPrint.c_str()); - deleteClientConnection(); - return true; - } + if (ssids.empty()) + ssids.push_back(string("")); deleteClientConnection(); - return false; + return true; } static void wifiScanCb(GObject *object, GAsyncResult *result, gpointer user_data) diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp index 06388c02..4f738fa0 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp @@ -1728,7 +1728,7 @@ namespace WPEFramework if(ssids.empty()) { NMLOG_WARNING("no Known SSID list empty"); - return false; + ssids.push_back(string("")); } return true; } diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp index ad1659a9..c161d621 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp @@ -541,8 +541,6 @@ namespace WPEFramework g_strfreev(paths); g_object_unref(sProxy); - if(pathsList.empty()) - return false; return true; } diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index e8be4680..f0732064 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -273,6 +273,7 @@ namespace WPEFramework newObject["frequency"] = object["frequency"]; ssidsUpdated.Add(newObject); } + ::_instance->ReportAvailableSSIDs(ssidsUpdated); break; } @@ -1040,14 +1041,14 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { memset(¶m, 0, sizeof(param)); - /* Must add new method to get all the known SSIDs but for now RDK-NM supports only one active SSID. So we repurpose this method */ - retVal = IARM_Bus_Call(IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_WIFI_MGR_API_getConnectedSSID, (void *)¶m, sizeof(param)); + /* Must add new method to get all the known SSIDs but for now RDK-NM supports only one saved SSID. */ + retVal = IARM_Bus_Call(IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_WIFI_MGR_API_getPairedSSID, (void *)¶m, sizeof(param)); if(retVal == IARM_RESULT_SUCCESS) { - auto &connectedSsid = param.data.getConnectedSSID; + auto &pairedSsid = param.data.getPairedSSID; std::list ssidList; - ssidList.push_back(string(connectedSsid.ssid)); + ssidList.push_back(string(pairedSsid.ssid)); NMLOG_INFO ("GetKnownSSIDs Success"); ssids = Core::Service::Create(ssidList); diff --git a/plugin/rdk/NetworkManagerRDKProxy.h b/plugin/rdk/NetworkManagerRDKProxy.h index 4a678d0b..967a38a3 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.h +++ b/plugin/rdk/NetworkManagerRDKProxy.h @@ -357,6 +357,7 @@ typedef struct _IARM_Bus_WiFiSrvMgr_SsidList_Param_t { #define IARM_BUS_WIFI_MGR_API_initiateWPSPairing2 "initiateWPSPairing2" /**< Initiate connection via WPS via either Push Button or PIN */ #define IARM_BUS_WIFI_MGR_API_cancelWPSPairing "cancelWPSPairing" /**< Cancel in-progress WPS */ #define IARM_BUS_WIFI_MGR_API_getConnectedSSID "getConnectedSSID" /**< Return properties of the currently connected SSID */ +#define IARM_BUS_WIFI_MGR_API_getPairedSSID "getPairedSSID" /**< Return the saved SSID */ #define IARM_BUS_WIFI_MGR_API_saveSSID "saveSSID" /**< Save SSID and passphrase */ #define IARM_BUS_WIFI_MGR_API_clearSSID "clearSSID" /**< Clear given SSID */ #define IARM_BUS_WIFI_MGR_API_connect "connect" /**< Connect with given or saved SSID and passphrase */ diff --git a/tests/l2Test/rdk/l2_test_rdkproxy.cpp b/tests/l2Test/rdk/l2_test_rdkproxy.cpp index 0d13cace..3f1cca33 100644 --- a/tests/l2Test/rdk/l2_test_rdkproxy.cpp +++ b/tests/l2Test/rdk/l2_test_rdkproxy.cpp @@ -711,11 +711,10 @@ TEST_F(NetworkManagerTest, GetKnownSSIDs_Success) { IARM_Bus_WiFiSrvMgr_Param_t mockParam = {}; mockParam.status = true; - strncpy(mockParam.data.getConnectedSSID.ssid, "TestNetwork", SSID_SIZE - 1); - mockParam.data.getConnectedSSID.securityMode = NET_WIFI_SECURITY_WPA_WPA2_PSK; + strncpy(mockParam.data.getPairedSSID.ssid, "TestNetwork", SSID_SIZE - 1); EXPECT_CALL(*p_iarmBusImplMock, IARM_Bus_Call(::testing::StrEq(IARM_BUS_NM_SRV_MGR_NAME), - ::testing::StrEq(IARM_BUS_WIFI_MGR_API_getConnectedSSID), + ::testing::StrEq(IARM_BUS_WIFI_MGR_API_getPairedSSID), ::testing::NotNull(), ::testing::_)) .WillOnce(::testing::DoAll( ::testing::Invoke([&mockParam](const char*, const char*, void* arg, size_t) { @@ -731,7 +730,7 @@ TEST_F(NetworkManagerTest, GetKnownSSIDs_Success) TEST_F(NetworkManagerTest, GetKnownSSIDs_Failed) { EXPECT_CALL(*p_iarmBusImplMock, IARM_Bus_Call(::testing::StrEq(IARM_BUS_NM_SRV_MGR_NAME), - ::testing::StrEq(IARM_BUS_WIFI_MGR_API_getConnectedSSID), + ::testing::StrEq(IARM_BUS_WIFI_MGR_API_getPairedSSID), ::testing::NotNull(), ::testing::_)) .WillOnce(::testing::Return(IARM_RESULT_IPCCORE_FAIL)); From f409fd1386e8e6a88f9a11194953ebe3f182a5e2 Mon Sep 17 00:00:00 2001 From: Karunakaran A Date: Tue, 15 Sep 2026 16:39:02 -0400 Subject: [PATCH 32/32] Release of 4.3.0 Release of 4.3.0 --- CHANGELOG.md | 4 ++++ CMakeLists.txt | 2 +- definition/NetworkManager.json | 2 +- docs/NetworkManagerPlugin.md | 4 ++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9efdbef..a5580cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ All notable changes to this RDK Service will be documented in this file. * Changes in CHANGELOG should be updated when commits are added to the main or release branches. There should be one CHANGELOG entry per JIRA Ticket. This is not enforced on sprint branches since there could be multiple changes for the same JIRA ticket during development. +## [4.3.0] - 2026-09-15 +### Fixed +- GetKnownSSIDs method is fixed to return empty when no saved profile found + ## [4.2.0] - 2026-09-11 ### Changed - Changed the default values for the number of ping request sent, the timeout and also the interval between the ping packets. diff --git a/CMakeLists.txt b/CMakeLists.txt index a860fcfa..20fd12f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 4) -set(VERSION_MINOR 2) +set(VERSION_MINOR 3) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 6cc48365..e3a0e475 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -8,7 +8,7 @@ "status": "production", "description": "A Unified `NetworkManager` plugin that allows you to manage Ethernet and Wifi interfaces on the device.", "sourcelocation": "https://github.com/rdkcentral/networkmanager/blob/main/definition/NetworkManager.json", - "version": "4.2.0" + "version": "4.3.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 62c3269c..f8864722 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 4.2.0** +**Version: 4.3.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.2.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 4.3.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity