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 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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