diff --git a/CHANGELOG.md b/CHANGELOG.md index a904e2f7..7fa5294b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,19 @@ 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. +## [2.2.0] - 2026-04-30 +### Added +- Added new method to connection to specific known SSID +- Extended WiFiConnect method to support BSSID and specific Band +- Defaulted to use RDKLogger and avoided redundant logging for few methods +- Added Minimal Ethernet Connection Profile for migration handling +- General improvements on RPC methods & crash resilience + +## [2.1.0] - 2026-03-20 +### Added +- Added T2 eventing from NetworkManager +- Updated documentation of the plugin + ## [2.0.0] - 2026-02-11 ### Changed - The onAvailableSSIDs event signature changed to report strength, noise & frequency as Number diff --git a/CMakeLists.txt b/CMakeLists.txt index 50e77090..99ffdb2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") set(VERSION_MAJOR 2) -set(VERSION_MINOR 0) +set(VERSION_MINOR 2) set(VERSION_PATCH 0) add_compile_definitions(NETWORKMANAGER_MAJOR_VERSION=${VERSION_MAJOR}) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 3dff5571..2252aed0 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.0.0" + "version": "2.2.0" }, "definitions": { "success": { diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index a041f624..9e4d969c 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -2,7 +2,7 @@ # NetworkManager Plugin -**Version: 2.0.0** +**Version: 2.2.0** **Status: :black_circle::black_circle::black_circle:** @@ -23,7 +23,7 @@ org.rdk.NetworkManager interface for Thunder framework. ## Scope -This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 2.0.0). It includes detailed specification about its methods provided and notifications sent. +This document describes purpose and functionality of the org.rdk.NetworkManager interface (version 2.2.0). It includes detailed specification about its methods provided and notifications sent. ## Case Sensitivity diff --git a/legacy/LegacyNetworkAPIs.cpp b/legacy/LegacyNetworkAPIs.cpp index d5924ee5..071466cd 100644 --- a/legacy/LegacyNetworkAPIs.cpp +++ b/legacy/LegacyNetworkAPIs.cpp @@ -783,6 +783,9 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { } } endpointsIter = (Core::Service::Create(endpoints)); + if (endpointsIter == nullptr) { + returnJson(rc); + } auto _nwmgr = m_service->QueryInterfaceByCallsign(NETWORK_MANAGER_CALLSIGN); if (_nwmgr) diff --git a/legacy/LegacyWiFiManagerAPIs.cpp b/legacy/LegacyWiFiManagerAPIs.cpp index 12e91b0a..9132ed09 100644 --- a/legacy/LegacyWiFiManagerAPIs.cpp +++ b/legacy/LegacyWiFiManagerAPIs.cpp @@ -653,6 +653,9 @@ namespace WPEFramework } ssids = (Core::Service::Create(inputSSIDlist)); + if (ssids == nullptr) { + returnJson(rc); + } } auto _nwmgr = m_service->QueryInterfaceByCallsign(NETWORK_MANAGER_CALLSIGN); diff --git a/plugin/NetworkManagerConnectivity.cpp b/plugin/NetworkManagerConnectivity.cpp index 3d9b8892..93825d51 100644 --- a/plugin/NetworkManagerConnectivity.cpp +++ b/plugin/NetworkManagerConnectivity.cpp @@ -575,7 +575,7 @@ namespace WPEFramework NMCONNECTIVITY_CURL_HEAD_REQUEST, ipversionLocal, interface); if (interface.empty()) - interface = _instance->m_defaultInterface; + interface = _instance->getDefaultInterface(); return testInternet.getInternetState(); } @@ -634,7 +634,9 @@ namespace WPEFramework return false; } - if(_instance->m_defaultInterface.empty()) + string defaultIface = _instance->getDefaultInterface(); + + if(defaultIface.empty()) { NMLOG_WARNING("default interface not set"); return false; @@ -646,7 +648,7 @@ namespace WPEFramework m_cmCv.notify_one(); NMLOG_INFO("switching to initial check - eth %s - wlan %s - default interface %s", - _instance->m_ethConnected.load()? "up":"down", _instance->m_wlanConnected.load()? "up":"down", _instance->m_defaultInterface.c_str()); + _instance->m_ethConnected.load()? "up":"down", _instance->m_wlanConnected.load()? "up":"down", defaultIface.c_str()); return true; } @@ -658,7 +660,8 @@ namespace WPEFramework { NMLOG_INFO("notifying internet state %s", getInternetStateString(newInternetState)); Exchange::INetworkManager::InternetStatus newState = newInternetState; - _instance->ReportInternetStatusChange(oldState , newState, _instance->m_defaultInterface); + string defaultIface = _instance->getDefaultInterface(); + _instance->ReportInternetStatusChange(oldState, newState, defaultIface); m_InternetState = newInternetState; oldState = newState; // 'm_InternetState' not exactly previous state, it may change to unknow when interface changed } @@ -694,68 +697,73 @@ namespace WPEFramework m_notify = true; InitialRetryCount = 1; } - else if(_instance->m_defaultInterface.empty()) - { - NMLOG_WARNING("default interface not set"); - if (InitialRetryCount == 0) - m_notify = true; - InitialRetryCount = 1; - } - else if (m_switchToInitial) + else { - if (InitialRetryCount == 0) - m_notify = true; - NMLOG_INFO("Initial connectivity check - index:%d, current state:%s, interface:%s", InitialRetryCount, getInternetStateString(currentInternetState), _instance->m_defaultInterface.c_str()); - timeoutInSec = NMCONNECTIVITY_MONITOR_MIN_INTERVAL; - TestConnectivity testInternet(m_endpoint(), NMCONNECTIVITY_CURL_REQUEST_TIMEOUT_MS, - NMCONNECTIVITY_CURL_HEAD_REQUEST, 2, _instance->m_defaultInterface); - currentInternetState = testInternet.getInternetState(); + string defaultIface = _instance->getDefaultInterface(); - if (currentInternetState == INTERNET_NOT_AVAILABLE) { - NMLOG_DEBUG("interface connected but no internet"); - InitialRetryCount = 1; // continue same check for 5 sec - } - else { - if(currentInternetState == INTERNET_CAPTIVE_PORTAL) - m_captiveURI = testInternet.getCaptivePortal(); - - if (currentInternetState != m_InternetState) { - NMLOG_DEBUG("initial connectivity state change from %s to %s", getInternetStateString(m_InternetState), getInternetStateString(currentInternetState)); - m_InternetState = currentInternetState; - InitialRetryCount = 1; // reset retry count to get continuous 3 same state + if(defaultIface.empty()) + { + NMLOG_WARNING("default interface not set"); + if (InitialRetryCount == 0) m_notify = true; - } - InitialRetryCount++; + InitialRetryCount = 1; } - - if (InitialRetryCount > NM_CONNECTIVITY_MONITOR_RETRY_COUNT) { - m_switchToInitial = false; - m_notify = true; - NMLOG_INFO("switching to ideal ccm check interface: %s", _instance->m_defaultInterface.c_str()); - } - } - else - { - // ideal case check every 30 sec happenses when captive portal or limited internet - timeoutInSec = NMCONNECTIVITY_MONITOR_RETRY_INTERVAL; - InitialRetryCount = 0; - - if(m_InternetState != INTERNET_FULLY_CONNECTED) + else if (m_switchToInitial) { + if (InitialRetryCount == 0) + m_notify = true; + NMLOG_INFO("Initial connectivity check - index:%d, current state:%s, interface:%s", InitialRetryCount, getInternetStateString(currentInternetState), defaultIface.c_str()); + timeoutInSec = NMCONNECTIVITY_MONITOR_MIN_INTERVAL; TestConnectivity testInternet(m_endpoint(), NMCONNECTIVITY_CURL_REQUEST_TIMEOUT_MS, - NMCONNECTIVITY_CURL_HEAD_REQUEST, 2, _instance->m_defaultInterface); // check both IP versions + NMCONNECTIVITY_CURL_HEAD_REQUEST, 2, defaultIface); currentInternetState = testInternet.getInternetState(); - if (currentInternetState == INTERNET_CAPTIVE_PORTAL) // if captive portal found copy the URL - m_captiveURI = testInternet.getCaptivePortal(); + if (currentInternetState == INTERNET_NOT_AVAILABLE) { + NMLOG_DEBUG("interface connected but no internet"); + InitialRetryCount = 1; // continue same check for 5 sec + } + else { + if(currentInternetState == INTERNET_CAPTIVE_PORTAL) + m_captiveURI = testInternet.getCaptivePortal(); + + if (currentInternetState != m_InternetState) { + NMLOG_DEBUG("initial connectivity state change from %s to %s", getInternetStateString(m_InternetState), getInternetStateString(currentInternetState)); + m_InternetState = currentInternetState; + InitialRetryCount = 1; // reset retry count to get continuous 3 same state + m_notify = true; + } + InitialRetryCount++; + } - if (currentInternetState != m_InternetState) - { - NMLOG_INFO("ideal connectivity state change from %s to %s", getInternetStateString(m_InternetState), getInternetStateString(currentInternetState)); - m_switchToInitial = true; + if (InitialRetryCount > NM_CONNECTIVITY_MONITOR_RETRY_COUNT) { + m_switchToInitial = false; m_notify = true; - InitialRetryCount = 1; - timeoutInSec = NMCONNECTIVITY_MONITOR_MIN_INTERVAL; // retry in 5 sec + NMLOG_INFO("switching to ideal ccm check interface: %s", defaultIface.c_str()); + } + } + else + { + // ideal case check every 30 sec happenses when captive portal or limited internet + timeoutInSec = NMCONNECTIVITY_MONITOR_RETRY_INTERVAL; + InitialRetryCount = 0; + + if(m_InternetState != INTERNET_FULLY_CONNECTED) + { + TestConnectivity testInternet(m_endpoint(), NMCONNECTIVITY_CURL_REQUEST_TIMEOUT_MS, + NMCONNECTIVITY_CURL_HEAD_REQUEST, 2, defaultIface); // check both IP versions + currentInternetState = testInternet.getInternetState(); + + if (currentInternetState == INTERNET_CAPTIVE_PORTAL) // if captive portal found copy the URL + m_captiveURI = testInternet.getCaptivePortal(); + + if (currentInternetState != m_InternetState) + { + NMLOG_INFO("ideal connectivity state change from %s to %s", getInternetStateString(m_InternetState), getInternetStateString(currentInternetState)); + m_switchToInitial = true; + m_notify = true; + InitialRetryCount = 1; + timeoutInSec = NMCONNECTIVITY_MONITOR_MIN_INTERVAL; // retry in 5 sec + } } } } diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 56cedd93..b4b5bca6 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -73,6 +73,7 @@ namespace WPEFramework NMLOG_INFO("NetworkManager Out-Of-Process Shutdown/Cleanup"); connectivityMonitor.stopConnectivityMonitor(); _instance = nullptr; + platform_deinit(); if(m_registrationThread.joinable()) { m_registrationThread.join(); @@ -250,6 +251,9 @@ namespace WPEFramework LOG_ENTRY_FUNCTION(); std::vector tmpEndpoints = connectivityMonitor.getConnectivityMonitorEndpoints(); endpoints = (Core::Service::Create(tmpEndpoints)); + if(endpoints == nullptr) { + return Core::ERROR_GENERAL; + } return Core::ERROR_NONE; } @@ -311,7 +315,7 @@ namespace WPEFramework ipversion = "IPv4"; if(interface.empty()) - interface = m_defaultInterface; + interface = getDefaultInterface(); return Core::ERROR_NONE; } @@ -337,7 +341,7 @@ namespace WPEFramework NMLOG_DEBUG("Primary interface: %s, eth0: [enabled=%d, connected=%d], wlan0: [enabled=%d, connected=%d]", interface.c_str(), m_ethEnabled.load(), m_ethConnected.load(), m_wlanEnabled.load(), m_wlanConnected.load()); - m_defaultInterface = interface; + setDefaultInterface(interface); return Core::ERROR_NONE; } @@ -364,7 +368,7 @@ namespace WPEFramework ipversion = "IPv4"; if (interface.empty()) - interface = m_defaultInterface; + interface = getDefaultInterface(); ipaddress = result.public_ip; #if USE_TELEMETRY @@ -637,7 +641,9 @@ namespace WPEFramework using Implementation = RPC::IteratorType; security = Core::Service::Create(modeInfo); - + if (security == nullptr) { + return Core::ERROR_GENERAL; + } return Core::ERROR_NONE; } @@ -651,7 +657,7 @@ namespace WPEFramework m_ethIPv4Address = {}; m_ethIPv6Address = {}; m_ethConnected.store(false); - m_defaultInterface = "wlan0"; // If WiFi is connected, make it the default interface + setDefaultInterface("wlan0"); // If WiFi is connected, make it the default interface // As default interface is changed to wlan0, switch connectivity monitor to initial check connectivityMonitor.switchToInitialCheck(); } @@ -660,10 +666,11 @@ namespace WPEFramework m_wlanIPv4Address = {}; m_wlanIPv6Address = {}; m_wlanConnected.store(false); + bool triggerConnectivityCheck; if(m_ethConnected.load()) - m_defaultInterface = "eth0"; // If Ethernet is connected, make it the default interface - - if(m_defaultInterface == interface) + setDefaultInterface("eth0"); // If Ethernet is connected, make it the default interface + triggerConnectivityCheck = (getDefaultInterface() == interface); + if(triggerConnectivityCheck) { // When WiFi is disconnected while Ethernet is connected, we don't need to trigger connectivity monitor. // For WiFi-only state and WiFi disconnected, we should trigger connectivity monitor. @@ -752,12 +759,14 @@ namespace WPEFramework } // FIXME : Availability of ip address for a given interface does not mean that its the default interface. This hardcoding will work for RDKProxy but not for Gnome. + bool isDefaultIface; if (m_ethConnected.load() && m_wlanConnected.load()) - m_defaultInterface = "eth0"; + setDefaultInterface("eth0"); else - m_defaultInterface = interface; + setDefaultInterface(interface); + isDefaultIface = (getDefaultInterface() == interface); - if(m_defaultInterface == interface) { + if(isDefaultIface) { // As default interface is connected, switch connectivity monitor to initial check any way connectivityMonitor.switchToInitialCheck(); } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 8a0c33d0..f5bd49b1 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -26,6 +26,7 @@ #include #include #include +#include using namespace std; @@ -34,6 +35,10 @@ using namespace std; #include "NetworkManagerConnectivity.h" #include "NetworkManagerStunClient.h" +/* Forward declarations to avoid pulling GLib/libnm headers into this header */ +typedef struct _NMClient NMClient; +typedef struct _GMainContext GMainContext; + /* * Receiver thermal noise + BW factor + assumed noise figure (NF) (dB) * for a 20MHz channel, @@ -274,6 +279,7 @@ namespace WPEFramework private: void platform_init(void); + void platform_deinit(void); void platform_logging(const NetworkManagerLogger::LogLevel& level); void getInitialConnectionState(void); void executeExternally(NetworkEvents event, const string commandToExecute, string& response); @@ -317,9 +323,26 @@ namespace WPEFramework std::atomic m_wlanConnected; std::atomic m_ethEnabled; std::atomic m_wlanEnabled; - string m_defaultInterface; std::string m_lastConnectedSSID; + NMClient *m_nmClient{nullptr}; /* proxy NMClient — bound to m_nmContext */ + GMainContext *m_nmContext{nullptr}; /* isolated context, not the global default */ mutable ConnectivityMonitor connectivityMonitor; + + string getDefaultInterface() const + { + std::lock_guard lock(m_defaultInterfaceMutex); + return m_defaultInterface; + } + + void setDefaultInterface(const string& iface) + { + std::lock_guard lock(m_defaultInterfaceMutex); + m_defaultInterface = iface; + } + + private: + string m_defaultInterface; + mutable std::mutex m_defaultInterfaceMutex; }; } } diff --git a/plugin/NetworkManagerJsonRpc.cpp b/plugin/NetworkManagerJsonRpc.cpp index 22af0cdb..5ed0b60f 100644 --- a/plugin/NetworkManagerJsonRpc.cpp +++ b/plugin/NetworkManagerJsonRpc.cpp @@ -429,6 +429,9 @@ namespace WPEFramework } } endpointsIter = (Core::Service::Create(endpoints)); + if(endpointsIter == nullptr){ + returnJson(rc); + } if (_networkManager) rc = _networkManager->SetConnectivityTestEndpoints(endpointsIter); @@ -664,6 +667,9 @@ namespace WPEFramework } } ssids = (Core::Service::Create(ssidslist)); + if(ssids == nullptr){ + returnJson(rc); + } } if (_networkManager) diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index a36a28c1..b661a8de 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -25,7 +25,6 @@ #include #define IN_IS_ADDR_LINKLOCAL(a) ((((uint32_t)ntohl(a)) & 0xffff0000U) == 0xa9fe0000U) -static NMClient *client = NULL; using namespace WPEFramework; using namespace WPEFramework::Plugin; using namespace std; @@ -196,7 +195,7 @@ namespace WPEFramework const GPtrArray *connections = NULL; NMConnection *connection = NULL; - if (client == nullptr) { + if (m_nmClient == nullptr) { NMLOG_ERROR("NMClient is NULL"); return Core::ERROR_GENERAL; } @@ -207,7 +206,7 @@ namespace WPEFramework return Core::ERROR_BAD_REQUEST; } - connections = nm_client_get_connections(client); + connections = nm_client_get_connections(m_nmClient); if (connections == NULL || connections->len == 0) { NMLOG_ERROR("Could not get nm connections"); @@ -250,6 +249,12 @@ namespace WPEFramework return Core::ERROR_NONE; } + void NetworkManagerImplementation::platform_deinit() + { + if(m_nmClient) { g_object_unref(m_nmClient); m_nmClient = nullptr; } + if(m_nmContext) { g_main_context_unref(m_nmContext); m_nmContext = nullptr; } + } + void NetworkManagerImplementation::platform_logging(const NetworkManagerLogger::LogLevel& level) { /* set networkmanager daemon log level based on current plugin log level */ @@ -264,24 +269,35 @@ namespace WPEFramework GError *error = NULL; // initialize the NMClient object - client = nm_client_new(NULL, &error); - if (client == NULL) { + // Create an isolated GMainContext so this m_nmClient's D-Bus socket is NOT a + // source on the global default context. The event thread runs the default + // context via g_main_loop_run(); without isolation it would own and mutate + // this m_nmClient's GObjects concurrently with the RPC thread. + m_nmContext = g_main_context_new(); + g_main_context_push_thread_default(m_nmContext); + m_nmClient = nm_client_new(NULL, &error); + g_main_context_pop_thread_default(m_nmContext); + if (m_nmClient == NULL) { if (error) { NMLOG_FATAL("Error initializing NMClient: %s", error->message); g_error_free(error); } + if (m_nmContext) { + g_main_context_unref(m_nmContext); + m_nmContext = nullptr; + } return; } nmUtils::getDeviceProperties(); // get interface name form '/etc/device.proprties' - modifyDefaultConnConfig(client); - NMDeviceState ethState = ifaceState(client, nmUtils::ethIface()); + modifyDefaultConnConfig(m_nmClient); + NMDeviceState ethState = ifaceState(m_nmClient, nmUtils::ethIface()); if(ethState > NM_DEVICE_STATE_DISCONNECTED && ethState < NM_DEVICE_STATE_DEACTIVATING) - m_defaultInterface = nmUtils::ethIface(); + setDefaultInterface(nmUtils::ethIface()); else - m_defaultInterface = nmUtils::wlanIface(); + setDefaultInterface(nmUtils::wlanIface()); - NMLOG_INFO("default interface is %s", m_defaultInterface.c_str()); + NMLOG_INFO("default interface is %s", getDefaultInterface().c_str()); // getInitialConnectionState function not called here, as event monitor will report the initial state nmEvent = GnomeNetworkManagerEvents::getInstance(); nmEvent->startNetworkMangerEventMonitor(); @@ -294,12 +310,17 @@ namespace WPEFramework std::vector interfaceList; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); - if(client == nullptr) { - NMLOG_FATAL("client connection null:"); + if(m_nmClient == nullptr) { + NMLOG_FATAL("NMClient is null"); return Core::ERROR_GENERAL; } - GPtrArray *devices = const_cast(nm_client_get_devices(client)); + if (m_nmContext) { + for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ + // Intentional empty body: just flushing the event queue + } + } + GPtrArray *devices = const_cast(nm_client_get_devices(m_nmClient)); if (devices == NULL) { NMLOG_ERROR("Failed to get device list."); return Core::ERROR_GENERAL; @@ -350,6 +371,9 @@ namespace WPEFramework using Implementation = RPC::IteratorType; interfacesItr = Core::Service::Create(interfaceList); + if(interfacesItr == nullptr) { + return Core::ERROR_GENERAL; + } return rc; } #if 0 @@ -422,12 +446,13 @@ namespace WPEFramework return rc; } #endif + uint32_t NetworkManagerImplementation::SetInterfaceState(const string& interface/* @in */, const bool enabled /* @in */) { - if(client == nullptr) + if(m_nmClient == nullptr) { - NMLOG_WARNING("client connection null:"); + NMLOG_WARNING("NMClient is null"); return Core::ERROR_RPC_CALL_FAILED; } @@ -437,33 +462,127 @@ namespace WPEFramework return Core::ERROR_GENERAL; } - if(!wifi->setInterfaceState(interface, enabled)) + // For ethernet enable: run BOOT_MIGRATION cleanup first, then setInterfaceState + if(enabled && interface == nmUtils::ethIface()) { - NMLOG_ERROR("interface state change failed"); - return Core::ERROR_GENERAL; - } + // Check boot type and delete all ethernet NM connections if BOOT_MIGRATION + { + const char* bootFile = "/tmp/bootType"; + std::ifstream file(bootFile); - NMLOG_INFO("interface %s state: %s", interface.c_str(), enabled ? "enabled" : "disabled"); - // update the interface global cache state - if(interface == nmUtils::wlanIface() && _instance != NULL) - _instance->m_wlanEnabled.store(enabled); - else if(interface == nmUtils::ethIface() && _instance != NULL) - _instance->m_ethEnabled.store(enabled); + if(file.is_open()) + { + std::string line, bootTypeValue; + while(std::getline(file, line)) + { + const std::string key = "BOOT_TYPE="; + auto pos = line.find(key); + if(pos != std::string::npos) + { + bootTypeValue = line.substr(pos + key.size()); + break; + } + } + + if(bootTypeValue == "BOOT_MIGRATION") + { + NMLOG_INFO("BOOT_MIGRATION detected, deleting all wired NM connections"); + + // Bring down the ethernet interface before wiping its connections + // so NM doesn't immediately re-activate them during deletion. + NMDevice *ethDev = nm_client_get_device_by_iface(m_nmClient, interface.c_str()); + if(ethDev) + { + GError *discError = nullptr; + if(!nm_device_disconnect(ethDev, nullptr, &discError)) + { + NMLOG_WARNING("Failed to disconnect %s before migration cleanup: %s", + interface.c_str(), + discError ? discError->message : "unknown error"); + if(discError) g_error_free(discError); + } + } + + const GPtrArray *connections = nm_client_get_connections(m_nmClient); + if(connections && connections->len > 0) + { + /* Snapshot the list before iterating: nm_client_get_connections() + * returns an internal array that can be mutated as connections + * are removed, so we must not iterate it while deleting. */ + GPtrArray *snapshot = g_ptr_array_new_full(connections->len, g_object_unref); + for(guint i = 0; i < connections->len; ++i) + { + NMRemoteConnection *conn = NM_REMOTE_CONNECTION(connections->pdata[i]); + if(!conn) continue; + NMSettingConnection *sCon = nm_connection_get_setting_connection(NM_CONNECTION(conn)); + if(!sCon) continue; + const char *connType = nm_setting_connection_get_connection_type(sCon); + if(g_strcmp0(connType, NM_SETTING_WIRED_SETTING_NAME) != 0) + { + NMLOG_DEBUG("Skipping non-wired connection type: %s", connType ? connType : "null"); + continue; + } + g_ptr_array_add(snapshot, g_object_ref(conn)); + } + + for(guint i = 0; i < snapshot->len; ++i) + { + NMRemoteConnection *conn = NM_REMOTE_CONNECTION(snapshot->pdata[i]); + GError *error = nullptr; + if(!nm_remote_connection_delete(conn, nullptr, &error)) + { + const char *connId = nm_connection_get_id(NM_CONNECTION(conn)); + NMLOG_ERROR("Failed to delete connection %s: %s", + connId ? connId : "", + error ? error->message : "unknown error"); + if(error) g_error_free(error); + } + } + g_ptr_array_unref(snapshot); + } + } + } + } + + NMLOG_INFO("Adding minimal ethernet connection profile ..."); + wifi->addMinimalEthernetConnection(nmUtils::ethIface()); + + if(!wifi->setInterfaceState(interface, enabled)) + { + NMLOG_ERROR("interface state change failed"); + return Core::ERROR_GENERAL; + } + + NMLOG_INFO("interface %s state: %s", interface.c_str(), enabled ? "enabled" : "disabled"); + if(_instance != NULL) + _instance->m_ethEnabled.store(enabled); - if(enabled) - { sleep(1); // wait for 1 sec to change the device state + NMLOG_INFO("Activating connection 'Wired connection 1' ..."); + // default wired connection name is 'Wired connection 1' + wifi->activateKnownConnection(nmUtils::ethIface(), "Wired connection 1"); + } + else + { + if(!wifi->setInterfaceState(interface, enabled)) + { + NMLOG_ERROR("interface state change failed"); + return Core::ERROR_GENERAL; + } + + NMLOG_INFO("interface %s state: %s", interface.c_str(), enabled ? "enabled" : "disabled"); + // update the interface global cache state if(interface == nmUtils::wlanIface() && _instance != NULL) + _instance->m_wlanEnabled.store(enabled); + else if(interface == nmUtils::ethIface() && _instance != NULL) + _instance->m_ethEnabled.store(enabled); + + if(enabled && interface == nmUtils::wlanIface() && _instance != NULL) { + sleep(1); // wait for 1 sec to change the device state NMLOG_INFO("Activating connection '%s' ...", _instance->m_lastConnectedSSID.c_str()); wifi->activateKnownConnection(nmUtils::wlanIface(), _instance->m_lastConnectedSSID); } - else if(interface == nmUtils::ethIface()) - { - NMLOG_INFO("Activating connection 'Wired connection 1' ..."); - // default wired connection name is 'Wired connection 1' - wifi->activateKnownConnection(nmUtils::ethIface(), "Wired connection 1"); - } } return Core::ERROR_NONE; @@ -475,19 +594,26 @@ namespace WPEFramework bool isIfaceFound = false; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); - if(client == nullptr) - { - NMLOG_WARNING("client connection null:"); - return Core::ERROR_RPC_CALL_FAILED; - } - if(interface.empty() || (wifiname != interface && ethname != interface)) { NMLOG_ERROR("interface: %s; not valied", interface.c_str()!=nullptr? interface.c_str():"empty"); return Core::ERROR_GENERAL; } - GPtrArray *devices = const_cast(nm_client_get_devices(client)); + if(m_nmClient == nullptr) + { + NMLOG_WARNING("NMClient is null"); + return Core::ERROR_RPC_CALL_FAILED; + } + + if (m_nmContext) { + for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ + // Intentional empty body: just flushing the event queue + } + } + + GPtrArray *devices = const_cast(nm_client_get_devices(m_nmClient)); + if (devices == NULL) { NMLOG_ERROR("Failed to get device list."); return Core::ERROR_GENERAL; @@ -559,12 +685,6 @@ namespace WPEFramework std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); - if(client == nullptr) - { - NMLOG_WARNING("client connection null:"); - return Core::ERROR_RPC_CALL_FAILED; - } - if(interface.empty()) { if(Core::ERROR_NONE != GetPrimaryInterface(interface)) @@ -626,7 +746,24 @@ namespace WPEFramework } } - device = nm_client_get_device_by_iface(client, interface.c_str()); + if(m_nmClient == nullptr) + { + NMLOG_WARNING("NMClient is null"); + return Core::ERROR_RPC_CALL_FAILED; + } + + /* Drain any pending D-Bus property-change events queued on m_nmContext + * before reading libnm GObject state. Because m_nmContext is isolated + * from the event thread, nobody else can run it — so this loop is + * single-threaded and safe. It ensures m_nmClient reflects the latest state + * from NetworkManager before we start iterating connections/addresses. */ + if (m_nmContext) { + for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ + // Intentional empty body: just flushing the event queue + } + } + + device = nm_client_get_device_by_iface(m_nmClient, interface.c_str()); if (device == NULL) { NMLOG_FATAL("libnm doesn't have device corresponding to %s", interface.c_str()); @@ -644,7 +781,7 @@ namespace WPEFramework // if(ipversion.empty()) // NMLOG_DEBUG("ipversion is empty default value IPv4"); - const GPtrArray *connections = nm_client_get_active_connections(client); + const GPtrArray *connections = nm_client_get_active_connections(m_nmClient); if(connections == NULL) { NMLOG_WARNING("no active connection; ip is not assigned to interface"); @@ -701,9 +838,14 @@ namespace WPEFramework { for (guint i = 0; i < ipByte->len; i++) { + ipStr.clear(); ipAddr = static_cast(ipByte->pdata[i]); if(ipAddr) - ipStr = nm_ip_address_get_address(ipAddr); + { + const char* addr = nm_ip_address_get_address(ipAddr); + if(addr) + ipStr = addr; + } if(!ipStr.empty()) { // Skip link-local IPv4 addresses (169.254.x.x) @@ -713,7 +855,7 @@ namespace WPEFramework NMLOG_DEBUG("Skipping link-local IPv4 address: %s", ipStr.c_str()); continue; } - result.ipaddress = nm_ip_address_get_address(ipAddr); + result.ipaddress = ipStr; result.prefix = nm_ip_address_get_prefix(ipAddr); NMLOG_DEBUG("IPv4 addr: %s/%d", result.ipaddress.c_str(), result.prefix); } @@ -772,9 +914,14 @@ namespace WPEFramework { for (guint i = 0; i < ipArray->len; i++) { + ipStr.clear(); ipAddr = static_cast(ipArray->pdata[i]); if(ipAddr) - ipStr = nm_ip_address_get_address(ipAddr); + { + const char* addr = nm_ip_address_get_address(ipAddr); + if(addr) + ipStr = addr; + } if(!ipStr.empty()) { if (ipStr.compare(0, 5, "fe80:") == 0 || ipStr.compare(0, 6, "fe80::") == 0) @@ -888,6 +1035,9 @@ namespace WPEFramework if (!ssidList.empty()) { ssids = Core::Service::Create(ssidList); + if(ssids == nullptr) { + return Core::ERROR_GENERAL; + } rc = Core::ERROR_NONE; } else diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.cpp b/plugin/gnome/NetworkManagerGnomeWIFI.cpp index e0a6abae..2348124d 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.cpp +++ b/plugin/gnome/NetworkManagerGnomeWIFI.cpp @@ -655,6 +655,66 @@ namespace WPEFramework return connection; } + static void addMinimalEthernetConnectionCb(GObject *client, GAsyncResult *result, gpointer user_data) + { + GError *error = NULL; + wifiManager *_wifiManager = static_cast(user_data); + NMRemoteConnection *remoteConn = nm_client_add_connection2_finish(NM_CLIENT(client), result, NULL, &error); + if (error) { + if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + NMLOG_DEBUG("addMinimalEthernetConnection operation was cancelled"); + g_error_free(error); + if (remoteConn) + g_object_unref(remoteConn); + if (_wifiManager->m_loop && g_main_loop_is_running(_wifiManager->m_loop)) { + g_main_loop_quit(_wifiManager->m_loop); + } + return; // do not alter m_isSuccess on cancellation + } + NMLOG_ERROR("addMinimalEthernetConnection error: %s", error->message); + _wifiManager->m_isSuccess = false; + g_error_free(error); + } + else if (!remoteConn) { + NMLOG_ERROR("addMinimalEthernetConnection failed"); + _wifiManager->m_isSuccess = false; + } + else { + NMLOG_INFO("addMinimalEthernetConnection success"); + _wifiManager->m_isSuccess = true; + g_object_unref(remoteConn); + } + g_main_loop_quit(_wifiManager->m_loop); + } + + bool wifiManager::addMinimalEthernetConnection(std::string iface) + { + if (!createClientNewConnection()) + return false; + + NMConnection *ethConn = createMinimalEthernetConnection(iface); + if (ethConn == NULL) + { + NMLOG_ERROR("Failed to create minimal ethernet connection"); + deleteClientConnection(); + return false; + } + + GVariant *connSettings = nm_connection_to_dbus(ethConn, NM_CONNECTION_SERIALIZE_ALL); + g_object_unref(ethConn); + + m_isSuccess = false; + nm_client_add_connection2(m_client, + connSettings, + NM_SETTINGS_ADD_CONNECTION2_FLAG_TO_DISK, + NULL, TRUE, m_cancellable, + addMinimalEthernetConnectionCb, this); + g_variant_unref(connSettings); + wait(m_loop); + deleteClientConnection(); + return m_isSuccess; + } + static bool connectionBuilder(const Exchange::INetworkManager::WiFiConnectTo& ssidinfo, NMConnection *m_connection, bool iswpsAP = false) { if(ssidinfo.ssid.empty() || ssidinfo.ssid.length() > 32) @@ -1819,7 +1879,11 @@ namespace WPEFramework error = NULL; } else - NMLOG_ERROR("NetworkManager cleint create failed"); + NMLOG_ERROR("NetworkManager client create failed"); + g_main_context_pop_thread_default(wpsContext); + g_main_context_release(wpsContext); + g_main_context_unref(wpsContext); + wpsContext = NULL; break; } @@ -1827,6 +1891,10 @@ namespace WPEFramework if(wifidevice == NULL) { NMLOG_ERROR("Failed to get device list."); + g_main_context_pop_thread_default(wpsContext); + g_main_context_release(wpsContext); + g_main_context_unref(wpsContext); + wpsContext = NULL; break; } @@ -1866,6 +1934,10 @@ namespace WPEFramework if(ApList == NULL) { NMLOG_ERROR("Aplist Error !"); + g_main_context_pop_thread_default(wpsContext); + g_main_context_release(wpsContext); + g_main_context_unref(wpsContext); + wpsContext = NULL; break; } @@ -1958,6 +2030,11 @@ namespace WPEFramework { /* if wps action not triggerd do a scanning request */ nm_device_wifi_request_scan(NM_DEVICE_WIFI(wifidevice), NULL, &error); + if(error) { + NMLOG_WARNING("WiFi scan request failed: %s", error->message); + g_error_free(error); + error = NULL; + } } g_main_context_pop_thread_default(wpsContext); @@ -2122,24 +2199,27 @@ namespace WPEFramework // that can cause networking issues. nm_device_disconnect_async(device, nullptr, disconnectCb, this); wait(m_loop); - // Wait until device is truly disconnected + + // Identify the correct context + GMainContext *device_context = g_main_loop_get_context(m_loop); int retry = 24; // 12 seconds NMDeviceState oldDevState = NM_DEVICE_STATE_UNKNOWN; while (retry-- > 0) { - /* Force glib event processing to update state - * This below line will create an uncertain time wait. We are taking a fixed time interval of 12 seconds. - */ - // while (g_main_context_iteration(NULL, FALSE)); - g_usleep(500 * 1000); // give some time to NM to process the request + // If there are multiple messages backed up, process a bounded number + // of pending iterations so this path cannot stall indefinitely if the + // context keeps receiving new work. + while (g_main_context_iteration(device_context, FALSE)); + + // Fetch the updated state deviceState = nm_device_get_state(device); - if(oldDevState != deviceState) - { + if(oldDevState != deviceState) { oldDevState = deviceState; - NMLOG_WARNING("Device state: %d", deviceState); + NMLOG_WARNING("Device state: %d Retry: %d", deviceState, retry); } - - if (deviceState <= NM_DEVICE_STATE_DISCONNECTED) + if (deviceState <= NM_DEVICE_STATE_DISCONNECTED) { break; + } + g_usleep(500 * 1000); // give some time to NM to process the request } } } diff --git a/plugin/gnome/NetworkManagerGnomeWIFI.h b/plugin/gnome/NetworkManagerGnomeWIFI.h index 69799506..51164aa5 100644 --- a/plugin/gnome/NetworkManagerGnomeWIFI.h +++ b/plugin/gnome/NetworkManagerGnomeWIFI.h @@ -67,6 +67,7 @@ namespace WPEFramework bool setInterfaceState(std::string interface, bool enabled); bool setIpSettings(const string interface, const Exchange::INetworkManager::IPAddress &address); bool setPrimaryInterface(const string interface); + bool addMinimalEthernetConnection(std::string iface); private: NMDevice *getWifiDevice(); diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp index 6e848836..73aa49d9 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusProxy.cpp @@ -90,6 +90,11 @@ namespace WPEFramework nmUtils::setNetworkManagerlogLevelToTrace(); } + void NetworkManagerImplementation::platform_deinit() + { + return; + } + void NetworkManagerImplementation::platform_init() { ::_instance = this; @@ -104,14 +109,14 @@ namespace WPEFramework if(_nmGdbusClient->getDeviceInfo(GnomeUtils::getEthIfname(), ethDevInfo)) { if(ethDevInfo.state > NM_DEVICE_STATE_DISCONNECTED && ethDevInfo.state < NM_DEVICE_STATE_DEACTIVATING) - m_defaultInterface = GnomeUtils::getEthIfname(); + setDefaultInterface(GnomeUtils::getEthIfname()); else - m_defaultInterface = GnomeUtils::getWifiIfname(); + setDefaultInterface(GnomeUtils::getWifiIfname()); } else - m_defaultInterface = GnomeUtils::getWifiIfname(); + setDefaultInterface(GnomeUtils::getWifiIfname()); - NMLOG_INFO("default interface is %s", m_defaultInterface.c_str()); + NMLOG_INFO("default interface is %s", getDefaultInterface().c_str()); // Start event monitoring _nmGdbusEvents->startNetworkMangerEventMonitor(); @@ -136,6 +141,9 @@ namespace WPEFramework NMLOG_ERROR("GetAvailableInterfaces failed"); using Implementation = RPC::IteratorType; interfacesItr = Core::Service::Create(interfaceList); + if(interfacesItr == nullptr){ + return Core::ERROR_GENERAL; + } return rc; } @@ -148,7 +156,7 @@ namespace WPEFramework { if(_nmGdbusClient->getDefaultInterface(interface)) { - _instance->m_defaultInterface = interface; + _instance->setDefaultInterface(interface); return Core::ERROR_NONE; } else @@ -234,6 +242,9 @@ namespace WPEFramework if(_nmGdbusClient->getKnownSSIDs(ssidList) && !ssidList.empty()) { ssids = Core::Service::Create(ssidList); + if(ssids == nullptr) { + return Core::ERROR_GENERAL; + } rc = Core::ERROR_NONE; } else diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index 670b0a6f..f218d61c 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -399,8 +399,8 @@ namespace WPEFramework if(iface.connected) { NMLOG_INFO("'%s' interface is connected", iface.name.c_str()); - if(m_defaultInterface != iface.name) - ReportActiveInterfaceChange(m_defaultInterface, iface.name); + if(getDefaultInterface() != iface.name) + ReportActiveInterfaceChange(getDefaultInterface(), iface.name); Exchange::INetworkManager::IPAddress addrv4; Exchange::INetworkManager::IPAddress addrv6; std::string ipversion = "IPv4"; @@ -444,6 +444,11 @@ namespace WPEFramework return Core::ERROR_NONE; } + void NetworkManagerImplementation::platform_deinit() + { + return; + } + void NetworkManagerImplementation::platform_init() { LOG_ENTRY_FUNCTION(); @@ -562,6 +567,9 @@ namespace WPEFramework } using Implementation = RPC::IteratorType; interfacesItr = Core::Service::Create(interfaceList); + if(interfacesItr == nullptr) { + return Core::ERROR_GENERAL; + } rc = Core::ERROR_NONE; } @@ -583,7 +591,8 @@ namespace WPEFramework if (IARM_RESULT_SUCCESS == IARM_Bus_Call(IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_getDefaultInterface, (void*)&defaultRoute, sizeof(defaultRoute))) { NMLOG_INFO ("Call to %s for %s returned interface = %s, gateway = %s", IARM_BUS_NM_SRV_MGR_NAME, IARM_BUS_NETSRVMGR_API_getDefaultInterface, defaultRoute.interface, defaultRoute.gateway); - interface = m_defaultInterface = defaultRoute.interface; + interface = defaultRoute.interface; + setDefaultInterface(defaultRoute.interface); rc = Core::ERROR_NONE; } else @@ -699,7 +708,7 @@ namespace WPEFramework if(interface.empty()) { - interface = m_defaultInterface; + interface = getDefaultInterface(); } if(ipversion.empty()) { @@ -1037,6 +1046,9 @@ const string CIDR_PREFIXES[CIDR_NETMASK_IP_LEN+1] = { NMLOG_INFO ("GetKnownSSIDs Success"); ssids = Core::Service::Create(ssidList); + if(ssids == nullptr) { + return Core::ERROR_GENERAL; + } rc = Core::ERROR_NONE; } else diff --git a/tests/l2Test/libnm/l2_test_libnmproxy.cpp b/tests/l2Test/libnm/l2_test_libnmproxy.cpp index bd633dd7..e4f9b1ec 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxy.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxy.cpp @@ -770,7 +770,6 @@ TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config_valid) .WillOnce(::testing::Return("192.168.1.0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_address(::testing::_)) - .WillOnce(::testing::Return("192.168.1.2")) .WillOnce(::testing::Return("192.168.1.2")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_addresses(::testing::_)) @@ -1371,4 +1370,4 @@ TEST_F(NetworkManagerTest, SetIPSettings_static_wlan0_autoConffalse) g_object_unref(dummyActiveConn); g_object_unref(dummyRemoteConn); -} \ No newline at end of file +}