From 79b2494a1a2ebd79cd3ba07d1e1adf1713007142 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 27 Apr 2026 18:17:44 +0530 Subject: [PATCH 1/8] AI agent's initial code modifications as per approved plan --- plugin/NetworkManagerImplementation.cpp | 14 +- plugin/NetworkManagerImplementation.h | 39 ++- plugin/gnome/NetworkManagerGnomeEvents.cpp | 251 ++++++++++++------ plugin/gnome/NetworkManagerGnomeEvents.h | 5 + plugin/gnome/NetworkManagerGnomeProxy.cpp | 99 ++++--- .../gnome/gdbus/NetworkManagerGdbusClient.cpp | 83 +++--- plugin/rdk/NetworkManagerRDKProxy.cpp | 43 +-- 7 files changed, 354 insertions(+), 180 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index b4b5bca6..83bb7279 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -654,8 +654,11 @@ namespace WPEFramework { if(interface == "eth0") { - m_ethIPv4Address = {}; - m_ethIPv6Address = {}; + { + std::lock_guard lock(m_ipCacheMutex); + m_ethIPv4Cache.clear(); + m_ethIPv6Cache.clear(); + } 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 @@ -663,8 +666,11 @@ namespace WPEFramework } else if(interface == "wlan0") { - m_wlanIPv4Address = {}; - m_wlanIPv6Address = {}; + { + std::lock_guard lock(m_ipCacheMutex); + m_wlanIPv4Cache.clear(); + m_wlanIPv6Cache.clear(); + } m_wlanConnected.store(false); bool triggerConnectivityCheck; if(m_ethConnected.load()) diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index f5bd49b1..88a24c4c 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -27,6 +27,7 @@ #include #include #include +#include using namespace std; @@ -62,6 +63,35 @@ namespace WPEFramework { namespace Plugin { + /* Per-interface, per-address-family cache populated by libnm events. */ + struct IpFamilyCache { + bool valid = false; + std::set globalAddresses; // all non-link-local addresses + std::string linkLocalAddress; // fe80:: for IPv6 (ula field), or 169.254.x.x for IPv4 + uint32_t prefix = 0; // prefix of the first global address + std::string gateway; + std::string primarydns; + std::string secondarydns; + std::string dhcpserver; + bool autoconfig = false; + + Exchange::INetworkManager::IPAddress toIPAddress(bool isIPv6) const { + Exchange::INetworkManager::IPAddress addr{}; + addr.ipversion = isIPv6 ? "IPv6" : "IPv4"; + addr.autoconfig = autoconfig; + addr.dhcpserver = dhcpserver; + addr.ula = linkLocalAddress; + addr.prefix = prefix; + addr.gateway = gateway; + addr.primarydns = primarydns; + addr.secondarydns = secondarydns; + if (!globalAddresses.empty()) + addr.ipaddress = *globalAddresses.begin(); + return addr; + } + void clear() { *this = IpFamilyCache{}; } + }; + class NetworkManagerImplementation : public Exchange::INetworkManager { enum NetworkEvents @@ -315,10 +345,11 @@ namespace WPEFramework std::mutex m_condVariableMutex; std::condition_variable m_condVariable; public: - IPAddress m_ethIPv4Address; - IPAddress m_wlanIPv4Address; - IPAddress m_ethIPv6Address; - IPAddress m_wlanIPv6Address; + IpFamilyCache m_ethIPv4Cache; + IpFamilyCache m_wlanIPv4Cache; + IpFamilyCache m_ethIPv6Cache; + IpFamilyCache m_wlanIPv6Cache; + mutable std::mutex m_ipCacheMutex; std::atomic m_ethConnected; std::atomic m_wlanConnected; std::atomic m_ethEnabled; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 761c39fb..852ffca1 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -30,6 +30,11 @@ #include "NetworkManagerGnomeUtils.h" #include "NetworkManagerImplementation.h" #include "INetworkManager.h" +#include +#include +#ifndef IN_IS_ADDR_LINKLOCAL +#define IN_IS_ADDR_LINKLOCAL(a) ((((uint32_t)ntohl(a)) & 0xffff0000U) == 0xa9fe0000U) +#endif #ifdef ENABLE_MIGRATION_MFRMGR_SUPPORT #include "NetworkManagerGnomeMfrMgr.h" #endif @@ -261,99 +266,169 @@ namespace WPEFramework } } - static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + /* Build a fresh IpFamilyCache from current libnm state for one device/family, + swap it into _instance under the cache mutex, then emit acquired/lost events + for any address-set differences outside the lock. */ + void refreshIpFamilyCache(NMDevice* device, bool isIPv6) { - if (!ipConfig) { - NMLOG_ERROR("IP config is null"); + if (!device || !NM_IS_DEVICE(device) || !_instance) return; - } - - NMDevice *device = (NMDevice*)userData; - if((device == NULL) || (!NM_IS_DEVICE(device))) - return; const char* iface = nm_device_get_iface(device); - if(iface == NULL) - return; + if (!iface) return; std::string ifname = iface; - GPtrArray *addresses = nm_ip_config_get_addresses(ipConfig); - if (!addresses) { - NMLOG_ERROR("No addresses found"); - return; - } - else { - if(addresses->len == 0) { - GnomeNetworkManagerEvents::onAddressChangeCb(ifname, "", false, false); - return; + bool isEth = (ifname == nmUtils::ethIface()); + bool isWlan = (ifname == nmUtils::wlanIface()); + if (!isEth && !isWlan) return; + + /* Build the new snapshot locally (no locks held during NM calls). */ + IpFamilyCache newCache; + NMActiveConnection* conn = nm_device_get_active_connection(device); + if (conn) { + /* autoconfig: method "auto" or "dhcp" → true */ + NMConnection* nmConn = NM_CONNECTION(nm_active_connection_get_connection(conn)); + if (nmConn) { + NMSettingIPConfig* ipSetting = isIPv6 + ? NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(nmConn)) + : NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(nmConn)); + if (ipSetting) { + const char* method = nm_setting_ip_config_get_method(ipSetting); + newCache.autoconfig = method && + (g_strcmp0(method, "auto") == 0 || g_strcmp0(method, "dhcp") == 0); + } + } + + NMIPConfig* ipConfig = isIPv6 + ? nm_active_connection_get_ip6_config(conn) + : nm_active_connection_get_ip4_config(conn); + + if (ipConfig) { + GPtrArray* addresses = nm_ip_config_get_addresses(ipConfig); + bool firstGlobal = true; + if (addresses) { + for (guint i = 0; i < addresses->len; i++) { + NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(addresses, i); + if (!addr) continue; + const char* addrStr = nm_ip_address_get_address(addr); + if (!addrStr) continue; + std::string addrString = addrStr; + if (isIPv6) { + if (addrString.compare(0, 5, "fe80:") == 0) { + newCache.linkLocalAddress = addrString; + } else { + newCache.globalAddresses.insert(addrString); + if (firstGlobal) { + firstGlobal = false; + newCache.prefix = nm_ip_address_get_prefix(addr); + } + } + } else { + struct sockaddr_in sa{}; + if (inet_pton(AF_INET, addrString.c_str(), &sa.sin_addr) == 1 && + IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr)) { + newCache.linkLocalAddress = addrString; + } else { + newCache.globalAddresses.insert(addrString); + if (firstGlobal) { + firstGlobal = false; + newCache.prefix = nm_ip_address_get_prefix(addr); + } + } + } + } + } + + const char* gw = nm_ip_config_get_gateway(ipConfig); + if (gw) newCache.gateway = gw; + + char** dnsArr = (char**)nm_ip_config_get_nameservers(ipConfig); + if (dnsArr) { + if (dnsArr[0]) newCache.primarydns = dnsArr[0]; + if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; + } + + NMDhcpConfig* dhcpConfig = isIPv6 + ? nm_active_connection_get_dhcp6_config(conn) + : nm_active_connection_get_dhcp4_config(conn); + if (dhcpConfig) { + const char* server = nm_dhcp_config_get_one_option(dhcpConfig, "dhcp_server_identifier"); + if (server) newCache.dhcpserver = server; + } + + newCache.valid = true; } } - for (guint i = 0; i < addresses->len; ++i) { - NMIPAddress *address = (NMIPAddress *)g_ptr_array_index(addresses, i); - if(address == NULL) - { - NMLOG_WARNING("IPv4 address is null"); - continue; + /* Swap new snapshot into instance cache under mutex; collect old addresses. */ + std::set oldAddresses; + { + std::lock_guard lock(_instance->m_ipCacheMutex); + IpFamilyCache* cache = nullptr; + if (isEth) + cache = isIPv6 ? &_instance->m_ethIPv6Cache : &_instance->m_ethIPv4Cache; + else + cache = isIPv6 ? &_instance->m_wlanIPv6Cache : &_instance->m_wlanIPv4Cache; + oldAddresses = cache->globalAddresses; + *cache = newCache; + } + + /* Emit address acquired/lost events from set diff (outside the lock). */ + std::string family = isIPv6 ? "IPv6" : "IPv4"; + for (const auto& addr : newCache.globalAddresses) { + if (oldAddresses.find(addr) == oldAddresses.end()) { + NMLOG_INFO("IP acquired: %s %s %s", ifname.c_str(), family.c_str(), addr.c_str()); + _instance->ReportIPAddressChange(ifname, family, addr, Exchange::INetworkManager::IP_ACQUIRED); } - if (nm_ip_address_get_family(address) == AF_INET) { - const char *ipAddress = nm_ip_address_get_address(address); - if(ipAddress != NULL) { - GnomeNetworkManagerEvents::onAddressChangeCb(iface, ipAddress, true, false); - } + } + for (const auto& addr : oldAddresses) { + if (newCache.globalAddresses.find(addr) == newCache.globalAddresses.end()) { + NMLOG_INFO("IP lost: %s %s %s", ifname.c_str(), family.c_str(), addr.c_str()); + _instance->ReportIPAddressChange(ifname, family, addr, Exchange::INetworkManager::IP_LOST); } } } - static void ip6ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) { - if (!ipConfig) { - NMLOG_ERROR("ip config is null"); - return; - } + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, false); + } + static void ip6ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + { NMDevice *device = (NMDevice*)userData; - if( ((device != NULL) && NM_IS_DEVICE(device)) ) - { - const char* iface = nm_device_get_iface(device); - if(iface == NULL) - return; - std::string ifname = iface; - GPtrArray *addresses = nm_ip_config_get_addresses(ipConfig); - if (!addresses) { - NMLOG_ERROR("No addresses found"); - return; - } - else { - if(addresses->len == 0) { - GnomeNetworkManagerEvents::onAddressChangeCb(ifname, "", false, true); - return; - } - } + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, true); + } - for (guint i = 0; i < addresses->len; ++i) { - NMIPAddress *address = (NMIPAddress *)g_ptr_array_index(addresses, i); - if(address == NULL) - { - NMLOG_WARNING("IPv6 address is null"); - continue; - } - if (nm_ip_address_get_family(address) == AF_INET6) { - const char *ipaddr = nm_ip_address_get_address(address); - //int prefix = nm_ip_address_get_prefix(address); - if(ipaddr != NULL) { - std::string ipAddress = ipaddr; - if (ipAddress.compare(0, 5, "fe80:") == 0 || - ipAddress.compare(0, 6, "fe80::") == 0) { - NMLOG_DEBUG("%s It's link-local ip", ipAddress.c_str()); - continue; // It's link-local so skiping - } - GnomeNetworkManagerEvents::onAddressChangeCb(iface, ipAddress, true, true); - break; // SLAAC protocol may include multip ipv6 address posting only one Global address - } - } - } - } + /* Called when the ip4-config or ip6-config object on a device is replaced + (e.g. after reconnect). Re-attaches notify handlers to the new object. */ + static void ip4ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) + { + if (!device || !NM_IS_DEVICE(device)) return; + NMIPConfig* ipv4Config = nm_device_get_ip4_config(device); + if (ipv4Config) { + g_signal_handlers_disconnect_by_func(ipv4Config, (gpointer)ip4ChangedCb, device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); + } + refreshIpFamilyCache(device, false); + } + + static void ip6ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) + { + if (!device || !NM_IS_DEVICE(device)) return; + NMIPConfig* ipv6Config = nm_device_get_ip6_config(device); + if (ipv6Config) { + g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); + } + refreshIpFamilyCache(device, true); } static void deviceAddedCB(NMClient *client, NMDevice *device, NMEvents *nmEvents) @@ -374,15 +449,21 @@ namespace WPEFramework if(ifname == nmUtils::ethIface() || ifname == nmUtils::wlanIface()) { g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); + g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); + g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); // TODO call notify::" NM_DEVICE_ACTIVE_CONNECTION if needed NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); if (ipv4Config) { - g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); } if (ipv6Config) { - g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); } if (NM_IS_DEVICE_WIFI(device)) @@ -492,6 +573,8 @@ namespace WPEFramework /* Register device state change event */ g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); + g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); + g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); if(NM_IS_DEVICE_WIFI(device)) { nmEvents->wifiDevice = NM_DEVICE_WIFI(device); g_signal_connect(nmEvents->wifiDevice, "notify::" NM_DEVICE_WIFI_LAST_SCAN, G_CALLBACK(GnomeNetworkManagerEvents::onAvailableSSIDsCb), nmEvents); @@ -500,18 +583,24 @@ namespace WPEFramework NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); if (ipv4Config) { - ip4ChangedCb(ipv4Config, NULL, device); // posting event if interface already connected - g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); } else NMLOG_WARNING("IPv4 config is null for device: %s, No IPv4 monitor", ifname.c_str()); if (ipv6Config) { - ip6ChangedCb(ipv6Config, NULL, device); - g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); } else NMLOG_WARNING("IPv6 config is null for device: %s, No IPv6 monitor", ifname.c_str()); + + /* Seed the IP cache from current state for already-connected devices. */ + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); } else NMLOG_DEBUG("device type not eth/wifi %s", ifname.c_str()); diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index ee29ce6d..76d5b4bb 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -39,6 +39,11 @@ namespace WPEFramework std::string ifnameEth0; } NMEvents; + /* Refresh the per-interface/per-family IP cache from current libnm state and + emit acquired/lost events for address-set differences. Called from both + GnomeEvents signal callbacks and the GetIPSettings fallback path. */ + void refreshIpFamilyCache(NMDevice* device, bool isIPv6); + class GnomeNetworkManagerEvents { diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index b661a8de..dd9e8a9f 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -714,35 +714,38 @@ namespace WPEFramework ipversionStr = ipversion; } - // Add caching optimization similar to RDK proxy - if (wifiname == interface) + // Serve from event-driven cache when available { - if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && !m_wlanIPv4Address.ipaddress.empty()) + std::lock_guard lock(m_ipCacheMutex); + if (wifiname == interface) { - NMLOG_DEBUG("%s IPv4 address from cache", wifiname.c_str()); - result = m_wlanIPv4Address; - return Core::ERROR_NONE; - } - else if(nmUtils::caseInsensitiveCompare(ipversion, "IPV6") && !m_wlanIPv6Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv6 address from cache", wifiname.c_str()); - result = m_wlanIPv6Address; - return Core::ERROR_NONE; - } - } - else if (ethname == interface) - { - if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && !m_ethIPv4Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv4 address from cache", ethname.c_str()); - result = m_ethIPv4Address; - return Core::ERROR_NONE; + if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && m_wlanIPv4Cache.valid) + { + NMLOG_DEBUG("%s IPv4 address from cache", wifiname.c_str()); + result = m_wlanIPv4Cache.toIPAddress(false); + return Core::ERROR_NONE; + } + else if(nmUtils::caseInsensitiveCompare(ipversion, "IPV6") && m_wlanIPv6Cache.valid) + { + NMLOG_DEBUG("%s IPv6 address from cache", wifiname.c_str()); + result = m_wlanIPv6Cache.toIPAddress(true); + return Core::ERROR_NONE; + } } - else if(nmUtils::caseInsensitiveCompare(ipversion, "IPV6") && !m_ethIPv6Address.ipaddress.empty()) + else if (ethname == interface) { - NMLOG_DEBUG("%s IPv6 address from cache", ethname.c_str()); - result = m_ethIPv6Address; - return Core::ERROR_NONE; + if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && m_ethIPv4Cache.valid) + { + NMLOG_DEBUG("%s IPv4 address from cache", ethname.c_str()); + result = m_ethIPv4Cache.toIPAddress(false); + return Core::ERROR_NONE; + } + else if(nmUtils::caseInsensitiveCompare(ipversion, "IPV6") && m_ethIPv6Cache.valid) + { + NMLOG_DEBUG("%s IPv6 address from cache", ethname.c_str()); + result = m_ethIPv6Cache.toIPAddress(true); + return Core::ERROR_NONE; + } } } @@ -884,19 +887,28 @@ namespace WPEFramework if(result.ipaddress.empty()) { NMLOG_WARNING("Only link-local IPv4 available on %s, not returning it", interface.c_str()); - // Clear cache for link-local only + std::lock_guard lock(m_ipCacheMutex); if(ethname == interface) - m_ethIPv4Address = IPAddress(); + m_ethIPv4Cache.clear(); else if(wifiname == interface) - m_wlanIPv4Address = IPAddress(); + m_wlanIPv4Cache.clear(); return Core::ERROR_GENERAL; } - // Cache the IPv4 address - if(ethname == interface) - m_ethIPv4Address = result; - else if(wifiname == interface) - m_wlanIPv4Address = result; + // Cache the IPv4 result (fallback — event-driven cache not yet populated) + { + std::lock_guard lock(m_ipCacheMutex); + IpFamilyCache* c = (ethname == interface) ? &m_ethIPv4Cache : &m_wlanIPv4Cache; + c->clear(); + c->globalAddresses.insert(result.ipaddress); + c->prefix = result.prefix; + c->gateway = result.gateway; + c->primarydns = result.primarydns; + c->secondarydns = result.secondarydns; + c->dhcpserver = result.dhcpserver; + c->autoconfig = result.autoconfig; + c->valid = true; + } } } if((result.ipaddress.empty() && !(nmUtils::caseInsensitiveCompare(ipversion, "IPV4"))) || nmUtils::caseInsensitiveCompare(ipversion, "IPV6")) @@ -957,11 +969,22 @@ namespace WPEFramework if(dhcpserver) result.dhcpserver = dhcpserver; } - // Cache the IPv6 address - if(ethname == interface) - m_ethIPv6Address = result; - else if(wifiname == interface) - m_wlanIPv6Address = result; + // Cache the IPv6 result (fallback — event-driven cache not yet populated) + { + std::lock_guard lock(m_ipCacheMutex); + IpFamilyCache* c = (ethname == interface) ? &m_ethIPv6Cache : &m_wlanIPv6Cache; + c->clear(); + if (!result.ipaddress.empty()) + c->globalAddresses.insert(result.ipaddress); + c->linkLocalAddress = result.ula; + c->prefix = result.prefix; + c->gateway = result.gateway; + c->primarydns = result.primarydns; + c->secondarydns = result.secondarydns; + c->dhcpserver = result.dhcpserver; + c->autoconfig = result.autoconfig; + c->valid = true; + } } } if(result.ipaddress.empty()) diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp index 2b44c109..f7aa827c 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusClient.cpp @@ -1113,36 +1113,40 @@ namespace WPEFramework ipversionStr = ipversion; } - // Add caching optimization similar to RDK proxy - if (wifiname == interface) + // Serve from event-driven cache when available { - if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && !_instance->m_wlanIPv4Address.ipaddress.empty()) + std::lock_guard lock(_instance->m_ipCacheMutex); + if (wifiname == interface) { - NMLOG_DEBUG("%s IPv4 address from cache", wifiname.c_str()); - result = _instance->m_wlanIPv4Address; - return true; + if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && _instance->m_wlanIPv4Cache.valid) + { + NMLOG_DEBUG("%s IPv4 address from cache", wifiname.c_str()); + result = _instance->m_wlanIPv4Cache.toIPAddress(false); + return true; + } + else if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV6") && _instance->m_wlanIPv6Cache.valid) + { + NMLOG_DEBUG("%s IPv6 address from cache", wifiname.c_str()); + result = _instance->m_wlanIPv6Cache.toIPAddress(true); + return true; + } } - else if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV6") && !_instance->m_wlanIPv6Address.ipaddress.empty()) + else if (ethname == interface) { - NMLOG_DEBUG("%s IPv6 address from cache", wifiname.c_str()); - result = _instance->m_wlanIPv6Address; - return true; + if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && _instance->m_ethIPv4Cache.valid) + { + NMLOG_DEBUG("%s IPv4 address from cache", ethname.c_str()); + result = _instance->m_ethIPv4Cache.toIPAddress(false); + return true; + } + else if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV6") && _instance->m_ethIPv6Cache.valid) + { + NMLOG_DEBUG("%s IPv6 address from cache", ethname.c_str()); + result = _instance->m_ethIPv6Cache.toIPAddress(true); + return true; + } } } - else if (ethname == interface) - { - if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV4") && !_instance->m_ethIPv4Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv4 address from cache", ethname.c_str()); - result = _instance->m_ethIPv4Address; - return true; - } - else if(nmUtils::caseInsensitiveCompare(ipversionStr, "IPV6") && !_instance->m_ethIPv6Address.ipaddress.empty()) - { - NMLOG_DEBUG("%s IPv6 address from cache", ethname.c_str()); - result = _instance->m_ethIPv6Address; - return true; - } } if(!GnomeUtils::getDeviceInfoByIfname(m_dbus, interface.c_str(), devInfo)) @@ -1551,22 +1555,27 @@ namespace WPEFramework result.secondarydns = ""; } - // Cache the IP address + // Cache the IP address (fallback path — populate IpFamilyCache from result) if(!result.ipaddress.empty()) { + std::lock_guard lock(_instance->m_ipCacheMutex); + bool isIPv6 = (g_strcmp0(result.ipversion.c_str(), "IPv6") == 0); + IpFamilyCache* c = nullptr; if(g_strcmp0(result.ipversion.c_str(), "IPv4") == 0) - { - if(ethname == interface) - _instance->m_ethIPv4Address = result; - else if(wifiname == interface) - _instance->m_wlanIPv4Address = result; - } - else if(g_strcmp0(result.ipversion.c_str(), "IPv6") == 0) - { - if(ethname == interface) - _instance->m_ethIPv6Address = result; - else if(wifiname == interface) - _instance->m_wlanIPv6Address = result; + c = (ethname == interface) ? &_instance->m_ethIPv4Cache : &_instance->m_wlanIPv4Cache; + else if(isIPv6) + c = (ethname == interface) ? &_instance->m_ethIPv6Cache : &_instance->m_wlanIPv6Cache; + if (c) { + c->clear(); + c->globalAddresses.insert(result.ipaddress); + c->linkLocalAddress = result.ula; + c->prefix = result.prefix; + c->gateway = result.gateway; + c->primarydns = result.primarydns; + c->secondarydns = result.secondarydns; + c->dhcpserver = result.dhcpserver; + c->autoconfig = result.autoconfig; + c->valid = true; } } diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index f218d61c..0fb704dc 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -720,27 +720,29 @@ namespace WPEFramework } if ("wlan0" == interface) { - if("IPv4" == ipversionStr && !m_wlanIPv4Address.ipaddress.empty()) + std::lock_guard lock(m_ipCacheMutex); + if("IPv4" == ipversionStr && m_wlanIPv4Cache.valid) { - address = m_wlanIPv4Address; + address = m_wlanIPv4Cache.toIPAddress(false); return Core::ERROR_NONE; } - else if("IPv6" == ipversionStr && !m_wlanIPv6Address.ipaddress.empty()) + else if("IPv6" == ipversionStr && m_wlanIPv6Cache.valid) { - address = m_wlanIPv6Address; + address = m_wlanIPv6Cache.toIPAddress(true); return Core::ERROR_NONE; } } else if ("eth0" == interface) { - if("IPv4" == ipversionStr && !m_ethIPv4Address.ipaddress.empty()) + std::lock_guard lock(m_ipCacheMutex); + if("IPv4" == ipversionStr && m_ethIPv4Cache.valid) { - address = m_ethIPv4Address; + address = m_ethIPv4Cache.toIPAddress(false); return Core::ERROR_NONE; } - else if("IPv6" == ipversionStr && !m_ethIPv6Address.ipaddress.empty()) + else if("IPv6" == ipversionStr && m_ethIPv6Cache.valid) { - address = m_ethIPv6Address; + address = m_ethIPv6Cache.toIPAddress(true); return Core::ERROR_NONE; } } @@ -776,19 +778,28 @@ namespace WPEFramework { address.ipversion = string ("IPv4"); address.prefix = NetmaskToPrefix(iarmData.netmask); - if("eth0" == interface) - m_ethIPv4Address = address; - else if("wlan0" == interface) - m_wlanIPv4Address = address; + std::lock_guard lock(m_ipCacheMutex); + IpFamilyCache* c = ("eth0" == interface) ? &m_ethIPv4Cache : &m_wlanIPv4Cache; + c->clear(); + c->globalAddresses.insert(address.ipaddress); + c->prefix = address.prefix; c->gateway = address.gateway; + c->primarydns = address.primarydns; c->secondarydns = address.secondarydns; + c->dhcpserver = address.dhcpserver; c->autoconfig = address.autoconfig; + c->valid = true; } else if (0 == strcasecmp("ipv6", iarmData.ipversion)) { address.ipversion = string ("IPv6"); address.prefix = std::atoi(iarmData.netmask); - if("eth0" == interface) - m_ethIPv6Address = address; - else if("wlan0" == interface) - m_wlanIPv6Address = address; + std::lock_guard lock(m_ipCacheMutex); + IpFamilyCache* c = ("eth0" == interface) ? &m_ethIPv6Cache : &m_wlanIPv6Cache; + c->clear(); + if (!address.ipaddress.empty()) c->globalAddresses.insert(address.ipaddress); + c->linkLocalAddress = address.ula; + c->prefix = address.prefix; c->gateway = address.gateway; + c->primarydns = address.primarydns; c->secondarydns = address.secondarydns; + c->dhcpserver = address.dhcpserver; c->autoconfig = address.autoconfig; + c->valid = true; } rc = Core::ERROR_NONE; From 8e2adbe530fb3feb58534ba974a50637a9fc987e Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 27 Apr 2026 20:34:49 +0530 Subject: [PATCH 2/8] 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 --- plugin/NetworkManagerImplementation.h | 12 +- plugin/gnome/NetworkManagerGnomeEvents.cpp | 140 ++++++++++----------- plugin/gnome/NetworkManagerGnomeEvents.h | 1 - 3 files changed, 76 insertions(+), 77 deletions(-) diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 88a24c4c..48f08134 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -66,9 +67,8 @@ namespace WPEFramework /* Per-interface, per-address-family cache populated by libnm events. */ struct IpFamilyCache { bool valid = false; - std::set globalAddresses; // all non-link-local addresses + std::map globalAddresses; // key=address, value=prefix length std::string linkLocalAddress; // fe80:: for IPv6 (ula field), or 169.254.x.x for IPv4 - uint32_t prefix = 0; // prefix of the first global address std::string gateway; std::string primarydns; std::string secondarydns; @@ -81,12 +81,14 @@ namespace WPEFramework addr.autoconfig = autoconfig; addr.dhcpserver = dhcpserver; addr.ula = linkLocalAddress; - addr.prefix = prefix; addr.gateway = gateway; addr.primarydns = primarydns; addr.secondarydns = secondarydns; - if (!globalAddresses.empty()) - addr.ipaddress = *globalAddresses.begin(); + if (!globalAddresses.empty()) { + const auto& first = *globalAddresses.begin(); + addr.ipaddress = first.first; + addr.prefix = first.second; + } return addr; } void clear() { *this = IpFamilyCache{}; } diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 852ffca1..f2bca9ed 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -305,7 +305,6 @@ namespace WPEFramework if (ipConfig) { GPtrArray* addresses = nm_ip_config_get_addresses(ipConfig); - bool firstGlobal = true; if (addresses) { for (guint i = 0; i < addresses->len; i++) { NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(addresses, i); @@ -317,11 +316,7 @@ namespace WPEFramework if (addrString.compare(0, 5, "fe80:") == 0) { newCache.linkLocalAddress = addrString; } else { - newCache.globalAddresses.insert(addrString); - if (firstGlobal) { - firstGlobal = false; - newCache.prefix = nm_ip_address_get_prefix(addr); - } + newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); } } else { struct sockaddr_in sa{}; @@ -329,11 +324,7 @@ namespace WPEFramework IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr)) { newCache.linkLocalAddress = addrString; } else { - newCache.globalAddresses.insert(addrString); - if (firstGlobal) { - firstGlobal = false; - newCache.prefix = nm_ip_address_get_prefix(addr); - } + newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); } } } @@ -360,8 +351,8 @@ namespace WPEFramework } } - /* Swap new snapshot into instance cache under mutex; collect old addresses. */ - std::set oldAddresses; + /* Swap new snapshot into instance cache under mutex; collect old address keys. */ + std::set oldKeys; { std::lock_guard lock(_instance->m_ipCacheMutex); IpFamilyCache* cache = nullptr; @@ -369,22 +360,22 @@ namespace WPEFramework cache = isIPv6 ? &_instance->m_ethIPv6Cache : &_instance->m_ethIPv4Cache; else cache = isIPv6 ? &_instance->m_wlanIPv6Cache : &_instance->m_wlanIPv4Cache; - oldAddresses = cache->globalAddresses; + for (const auto& kv : cache->globalAddresses) oldKeys.insert(kv.first); *cache = newCache; } - /* Emit address acquired/lost events from set diff (outside the lock). */ + /* Emit address acquired/lost events from map-key diff (outside the lock). */ std::string family = isIPv6 ? "IPv6" : "IPv4"; - for (const auto& addr : newCache.globalAddresses) { - if (oldAddresses.find(addr) == oldAddresses.end()) { - NMLOG_INFO("IP acquired: %s %s %s", ifname.c_str(), family.c_str(), addr.c_str()); - _instance->ReportIPAddressChange(ifname, family, addr, Exchange::INetworkManager::IP_ACQUIRED); + for (const auto& kv : newCache.globalAddresses) { + if (oldKeys.find(kv.first) == oldKeys.end()) { + NMLOG_INFO("IP acquired: %s %s %s", ifname.c_str(), family.c_str(), kv.first.c_str()); + _instance->ReportIPAddressChange(ifname, family, kv.first, Exchange::INetworkManager::IP_ACQUIRED); } } - for (const auto& addr : oldAddresses) { - if (newCache.globalAddresses.find(addr) == newCache.globalAddresses.end()) { - NMLOG_INFO("IP lost: %s %s %s", ifname.c_str(), family.c_str(), addr.c_str()); - _instance->ReportIPAddressChange(ifname, family, addr, Exchange::INetworkManager::IP_LOST); + for (const auto& key : oldKeys) { + if (newCache.globalAddresses.find(key) == newCache.globalAddresses.end()) { + NMLOG_INFO("IP lost: %s %s %s", ifname.c_str(), family.c_str(), key.c_str()); + _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); } } } @@ -403,6 +394,21 @@ namespace WPEFramework refreshIpFamilyCache(device, true); } + /* Called when DHCP options change mid-lease (e.g. renewed with different server/options). */ + static void dhcp4OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, false); + } + + static void dhcp6OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, true); + } + /* Called when the ip4-config or ip6-config object on a device is replaced (e.g. after reconnect). Re-attaches notify handlers to the new object. */ static void ip4ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) @@ -415,6 +421,15 @@ namespace WPEFramework g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); } + /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ + NMActiveConnection* conn4 = nm_device_get_active_connection(device); + if (conn4) { + NMDhcpConfig* dhcp4 = nm_active_connection_get_dhcp4_config(conn4); + if (dhcp4) { + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + g_signal_connect(dhcp4, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + } + } refreshIpFamilyCache(device, false); } @@ -428,6 +443,15 @@ namespace WPEFramework g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); } + /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ + NMActiveConnection* conn6 = nm_device_get_active_connection(device); + if (conn6) { + NMDhcpConfig* dhcp6 = nm_active_connection_get_dhcp6_config(conn6); + if (dhcp6) { + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); + g_signal_connect(dhcp6, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); + } + } refreshIpFamilyCache(device, true); } @@ -451,7 +475,6 @@ namespace WPEFramework g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(GnomeNetworkManagerEvents::deviceStateChangeCb), nmEvents); g_signal_connect(device, "notify::ip4-config", G_CALLBACK(ip4ConfigChangedCb), nmEvents); g_signal_connect(device, "notify::ip6-config", G_CALLBACK(ip6ConfigChangedCb), nmEvents); - // TODO call notify::" NM_DEVICE_ACTIVE_CONNECTION if needed NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); if (ipv4Config) { @@ -466,6 +489,17 @@ namespace WPEFramework g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); } + /* Subscribe to DHCP option changes so dhcpserver stays current mid-lease. */ + NMActiveConnection* connAdded = nm_device_get_active_connection(device); + if (connAdded) { + NMDhcpConfig* dhcp4Added = nm_active_connection_get_dhcp4_config(connAdded); + NMDhcpConfig* dhcp6Added = nm_active_connection_get_dhcp6_config(connAdded); + if (dhcp4Added) + g_signal_connect(dhcp4Added, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + if (dhcp6Added) + g_signal_connect(dhcp6Added, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); + } + if (NM_IS_DEVICE_WIFI(device)) { // Register signal handler for WiFi scanning events to detect when scan operations complete @@ -598,6 +632,17 @@ namespace WPEFramework else NMLOG_WARNING("IPv6 config is null for device: %s, No IPv6 monitor", ifname.c_str()); + /* Subscribe to DHCP option changes so dhcpserver stays current mid-lease. */ + NMActiveConnection* connInit = nm_device_get_active_connection(device); + if (connInit) { + NMDhcpConfig* dhcp4Init = nm_active_connection_get_dhcp4_config(connInit); + NMDhcpConfig* dhcp6Init = nm_active_connection_get_dhcp6_config(connInit); + if (dhcp4Init) + g_signal_connect(dhcp4Init, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + if (dhcp6Init) + g_signal_connect(dhcp6Init, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); + } + /* Seed the IP cache from current state for already-connected devices. */ refreshIpFamilyCache(device, false); refreshIpFamilyCache(device, true); @@ -803,53 +848,6 @@ namespace WPEFramework } } - void GnomeNetworkManagerEvents::onAddressChangeCb(std::string iface, std::string ipAddress, bool acquired, bool isIPv6) - { - /* - * notify::addresses g signal only send ipaddress when accuired time only. - * we need to post ip address when ipaddress lost case also so we caching the ip address per interface - */ - static std::map ipv6Map; - static std::map ipv4Map; - - if(acquired) - { - if (isIPv6) - { - if (ipv6Map[iface].find(ipAddress) == std::string::npos) { // same ip comes multiple time so avoding that - ipv6Map[iface] = ipAddress; - } - else // same ip not posting - return; - } - else - { - ipv4Map[iface] = ipAddress; - } - } - else - { - if (isIPv6) - { - ipAddress = ipv6Map[iface]; - ipv6Map[iface].clear(); - } - else - { - ipAddress = ipv4Map[iface]; - ipv4Map[iface].clear(); - } - if(ipAddress.empty()) - return; // empty ip address not posting event - } - Exchange::INetworkManager::IPStatus ipStatus{}; - if (acquired) - ipStatus = Exchange::INetworkManager::IP_ACQUIRED; - if(_instance != nullptr) - _instance->ReportIPAddressChange(iface, isIPv6?"IPv6":"IPv4", ipAddress, ipStatus); - NMLOG_INFO("iface:%s - ipaddress:%s - %s - %s", iface.c_str(), ipAddress.c_str(), acquired?"acquired":"lost", isIPv6?"isIPv6":"isIPv4"); - } - bool GnomeNetworkManagerEvents::apToJsonObject(NMAccessPoint *ap, JsonObject& ssidObj) { GBytes *ssid = NULL; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index 76d5b4bb..8f9ad429 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -49,7 +49,6 @@ namespace WPEFramework public: static void onInterfaceStateChangeCb(uint8_t newState, std::string iface); // ReportInterfaceStateChange - static void onAddressChangeCb(std::string iface, std::string ipAddress, bool acqired, bool isIPv6); // ReportIPAddressChange static void onActiveInterfaceChangeCb(std::string newInterface); // ReportActiveInterfaceChange static void onAvailableSSIDsCb(NMDeviceWifi *wifiDevice, GParamSpec *pspec, gpointer userData); // ReportAvailableSSIDs static void onWIFIStateChanged(uint8_t state); // ReportWiFiStateChange From 73d387950a1cb6bd03dbd704bfdffeb2f0943e20 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 27 Apr 2026 21:47:46 +0530 Subject: [PATCH 3/8] 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 . --- plugin/NetworkManagerImplementation.h | 10 +++++++++- plugin/gnome/NetworkManagerGnomeEvents.cpp | 2 +- plugin/gnome/NetworkManagerGnomeProxy.cpp | 2 +- plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp | 3 +-- plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp | 3 +-- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 48f08134..84d853c5 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -28,7 +28,6 @@ #include #include #include -#include using namespace std; @@ -64,6 +63,15 @@ namespace WPEFramework { namespace Plugin { + /* Returns true if the given string is an IPv6 link-local address (fe80::/10): + first byte 0xfe, second byte with top two bits == 10 (0x80..0xbf). */ + inline 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; + } + /* Per-interface, per-address-family cache populated by libnm events. */ struct IpFamilyCache { bool valid = false; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index f2bca9ed..856fa507 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -313,7 +313,7 @@ namespace WPEFramework if (!addrStr) continue; std::string addrString = addrStr; if (isIPv6) { - if (addrString.compare(0, 5, "fe80:") == 0) { + if (isIPv6LinkLocal(addrString)) { newCache.linkLocalAddress = addrString; } else { newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index dd9e8a9f..d7bab0da 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -936,7 +936,7 @@ namespace WPEFramework } if(!ipStr.empty()) { - if (ipStr.compare(0, 5, "fe80:") == 0 || ipStr.compare(0, 6, "fe80::") == 0) + if (isIPv6LinkLocal(ipStr)) { result.ula = ipStr; NMLOG_DEBUG("link-local ip: %s", result.ula.c_str()); diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp index d0c50c40..d80f71cf 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusEvent.cpp @@ -710,8 +710,7 @@ namespace WPEFramework } else // acquired { - if (ipAddress.compare(0, 5, "fe80:") == 0 || - ipAddress.compare(0, 6, "fe80::") == 0) { + if (isIPv6LinkLocal(ipAddress)) { NMLOG_DEBUG("It's link-local ip"); return; // It's link-local } diff --git a/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp b/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp index ad1659a9..8e2dc9e4 100644 --- a/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp +++ b/plugin/gnome/gdbus/NetworkManagerGdbusUtils.cpp @@ -107,8 +107,7 @@ namespace WPEFramework if(address!= NULL) { ipAddress = address; // strlen of "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF" - if (ipAddress.compare(0, 5, "fe80:") == 0 || - ipAddress.compare(0, 6, "fe80::") == 0) { // It's link-local starts with fe80 + if (isIPv6LinkLocal(ipAddress)) { // It's link-local (fe80::/10) NMLOG_DEBUG("link-local ip: %s", address); } else From 2d77a21925849056331d23ebdf858f8a96cc6d72 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Thu, 30 Apr 2026 18:22:54 +0530 Subject: [PATCH 4/8] 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. --- plugin/gnome/NetworkManagerGnomeProxy.cpp | 6 ++---- plugin/rdk/NetworkManagerRDKProxy.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index d7bab0da..a6e77b44 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -900,8 +900,7 @@ namespace WPEFramework std::lock_guard lock(m_ipCacheMutex); IpFamilyCache* c = (ethname == interface) ? &m_ethIPv4Cache : &m_wlanIPv4Cache; c->clear(); - c->globalAddresses.insert(result.ipaddress); - c->prefix = result.prefix; + c->globalAddresses.insert({result.ipaddress, result.prefix}); c->gateway = result.gateway; c->primarydns = result.primarydns; c->secondarydns = result.secondarydns; @@ -975,9 +974,8 @@ namespace WPEFramework IpFamilyCache* c = (ethname == interface) ? &m_ethIPv6Cache : &m_wlanIPv6Cache; c->clear(); if (!result.ipaddress.empty()) - c->globalAddresses.insert(result.ipaddress); + c->globalAddresses.insert({result.ipaddress, result.prefix}); c->linkLocalAddress = result.ula; - c->prefix = result.prefix; c->gateway = result.gateway; c->primarydns = result.primarydns; c->secondarydns = result.secondarydns; diff --git a/plugin/rdk/NetworkManagerRDKProxy.cpp b/plugin/rdk/NetworkManagerRDKProxy.cpp index 0fb704dc..39cc5124 100644 --- a/plugin/rdk/NetworkManagerRDKProxy.cpp +++ b/plugin/rdk/NetworkManagerRDKProxy.cpp @@ -781,8 +781,8 @@ namespace WPEFramework std::lock_guard lock(m_ipCacheMutex); IpFamilyCache* c = ("eth0" == interface) ? &m_ethIPv4Cache : &m_wlanIPv4Cache; c->clear(); - c->globalAddresses.insert(address.ipaddress); - c->prefix = address.prefix; c->gateway = address.gateway; + c->globalAddresses.insert({address.ipaddress, address.prefix}); + c->gateway = address.gateway; c->primarydns = address.primarydns; c->secondarydns = address.secondarydns; c->dhcpserver = address.dhcpserver; c->autoconfig = address.autoconfig; c->valid = true; @@ -794,9 +794,9 @@ namespace WPEFramework std::lock_guard lock(m_ipCacheMutex); IpFamilyCache* c = ("eth0" == interface) ? &m_ethIPv6Cache : &m_wlanIPv6Cache; c->clear(); - if (!address.ipaddress.empty()) c->globalAddresses.insert(address.ipaddress); + if (!address.ipaddress.empty()) c->globalAddresses.insert({address.ipaddress, address.prefix}); c->linkLocalAddress = address.ula; - c->prefix = address.prefix; c->gateway = address.gateway; + c->gateway = address.gateway; c->primarydns = address.primarydns; c->secondarydns = address.secondarydns; c->dhcpserver = address.dhcpserver; c->autoconfig = address.autoconfig; c->valid = true; From bc99e39bb3418628c43097074c0fb1f8aa20ce3e Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Tue, 5 May 2026 16:07:38 +0530 Subject: [PATCH 5/8] 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. --- plugin/gnome/NetworkManagerGnomeEvents.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 856fa507..d8ee5612 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -300,8 +300,8 @@ namespace WPEFramework } NMIPConfig* ipConfig = isIPv6 - ? nm_active_connection_get_ip6_config(conn) - : nm_active_connection_get_ip4_config(conn); + ? nm_device_get_ip6_config(device) + : nm_device_get_ip4_config(device); if (ipConfig) { GPtrArray* addresses = nm_ip_config_get_addresses(ipConfig); From 3d9dc1cba10cf4e475220ae6ef751faf35d341ff Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Tue, 5 May 2026 19:13:11 +0530 Subject: [PATCH 6/8] 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. --- plugin/gnome/NetworkManagerGnomeEvents.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index d8ee5612..4b8976be 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -333,9 +333,9 @@ namespace WPEFramework const char* gw = nm_ip_config_get_gateway(ipConfig); if (gw) newCache.gateway = gw; - char** dnsArr = (char**)nm_ip_config_get_nameservers(ipConfig); - if (dnsArr) { - if (dnsArr[0]) newCache.primarydns = dnsArr[0]; + const char* const* dnsArr = nm_ip_config_get_nameservers(ipConfig); + if (dnsArr && dnsArr[0]) { + newCache.primarydns = dnsArr[0]; if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; } From 5d0d19a0e111f59c198ce2cc89b311920e5b404a Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Fri, 8 May 2026 21:35:43 +0530 Subject: [PATCH 7/8] 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. --- plugin/NetworkManagerImplementation.cpp | 14 ++++ plugin/gnome/NetworkManagerGnomeEvents.cpp | 83 +++++++++++++--------- 2 files changed, 62 insertions(+), 35 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 83bb7279..39994778 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -654,11 +654,18 @@ namespace WPEFramework { if(interface == "eth0") { + std::map lostIPv4, lostIPv6; { std::lock_guard lock(m_ipCacheMutex); + lostIPv4 = m_ethIPv4Cache.globalAddresses; + lostIPv6 = m_ethIPv6Cache.globalAddresses; m_ethIPv4Cache.clear(); m_ethIPv6Cache.clear(); } + for (const auto& kv : lostIPv4) + ReportIPAddressChange(interface, "IPv4", kv.first, Exchange::INetworkManager::IP_LOST); + for (const auto& kv : lostIPv6) + ReportIPAddressChange(interface, "IPv6", kv.first, Exchange::INetworkManager::IP_LOST); 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 @@ -666,11 +673,18 @@ namespace WPEFramework } else if(interface == "wlan0") { + std::map lostIPv4, lostIPv6; { std::lock_guard lock(m_ipCacheMutex); + lostIPv4 = m_wlanIPv4Cache.globalAddresses; + lostIPv6 = m_wlanIPv6Cache.globalAddresses; m_wlanIPv4Cache.clear(); m_wlanIPv6Cache.clear(); } + for (const auto& kv : lostIPv4) + ReportIPAddressChange(interface, "IPv4", kv.first, Exchange::INetworkManager::IP_LOST); + for (const auto& kv : lostIPv6) + ReportIPAddressChange(interface, "IPv6", kv.first, Exchange::INetworkManager::IP_LOST); m_wlanConnected.store(false); bool triggerConnectivityCheck; if(m_ethConnected.load()) diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 4b8976be..e7d8ce0f 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -130,11 +130,15 @@ namespace WPEFramework case NM_DEVICE_STATE_UNKNOWN: wifiState = "WIFI_STATE_UNINSTALLED"; GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_UNINSTALLED); + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); break; case NM_DEVICE_STATE_UNMANAGED: wifiState = "WIFI_STATE_DISABLED"; GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISABLED); + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); isWlanDisabled = true; break; @@ -142,6 +146,8 @@ namespace WPEFramework case NM_DEVICE_STATE_DISCONNECTED: wifiState = "WIFI_STATE_DISCONNECTED"; GnomeNetworkManagerEvents::onWIFIStateChanged(Exchange::INetworkManager::WIFI_STATE_DISCONNECTED); + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_LINK_DOWN, nmUtils::wlanIface()); break; case NM_DEVICE_STATE_PREPARE: @@ -217,11 +223,15 @@ namespace WPEFramework { case NM_DEVICE_STATE_UNKNOWN: case NM_DEVICE_STATE_UNMANAGED: + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::ethIface()); isEthDisabled = true; break; case NM_DEVICE_STATE_UNAVAILABLE: case NM_DEVICE_STATE_DISCONNECTED: + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_LINK_DOWN, nmUtils::ethIface()); break; case NM_DEVICE_STATE_PREPARE: @@ -298,47 +308,50 @@ namespace WPEFramework (g_strcmp0(method, "auto") == 0 || g_strcmp0(method, "dhcp") == 0); } } + } - NMIPConfig* ipConfig = isIPv6 - ? nm_device_get_ip6_config(device) - : nm_device_get_ip4_config(device); - - if (ipConfig) { - GPtrArray* addresses = nm_ip_config_get_addresses(ipConfig); - if (addresses) { - for (guint i = 0; i < addresses->len; i++) { - NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(addresses, i); - if (!addr) continue; - const char* addrStr = nm_ip_address_get_address(addr); - if (!addrStr) continue; - std::string addrString = addrStr; - if (isIPv6) { - if (isIPv6LinkLocal(addrString)) { - newCache.linkLocalAddress = addrString; - } else { - newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); - } + /* IP config read is device-level and does not require an active connection. */ + NMIPConfig* ipConfig = isIPv6 + ? nm_device_get_ip6_config(device) + : nm_device_get_ip4_config(device); + + if (ipConfig) { + GPtrArray* addresses = nm_ip_config_get_addresses(ipConfig); + if (addresses) { + for (guint i = 0; i < addresses->len; i++) { + NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(addresses, i); + if (!addr) continue; + const char* addrStr = nm_ip_address_get_address(addr); + if (!addrStr) continue; + std::string addrString = addrStr; + if (isIPv6) { + if (isIPv6LinkLocal(addrString)) { + newCache.linkLocalAddress = addrString; } else { - struct sockaddr_in sa{}; - if (inet_pton(AF_INET, addrString.c_str(), &sa.sin_addr) == 1 && - IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr)) { - newCache.linkLocalAddress = addrString; - } else { - newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); - } + newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); + } + } else { + struct sockaddr_in sa{}; + if (inet_pton(AF_INET, addrString.c_str(), &sa.sin_addr) == 1 && + IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr)) { + newCache.linkLocalAddress = addrString; + } else { + newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); } } } + } - const char* gw = nm_ip_config_get_gateway(ipConfig); - if (gw) newCache.gateway = gw; + const char* gw = nm_ip_config_get_gateway(ipConfig); + if (gw) newCache.gateway = gw; - const char* const* dnsArr = nm_ip_config_get_nameservers(ipConfig); - if (dnsArr && dnsArr[0]) { - newCache.primarydns = dnsArr[0]; - if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; - } + const char* const* dnsArr = nm_ip_config_get_nameservers(ipConfig); + if (dnsArr && dnsArr[0]) { + newCache.primarydns = dnsArr[0]; + if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; + } + if (conn) { NMDhcpConfig* dhcpConfig = isIPv6 ? nm_active_connection_get_dhcp6_config(conn) : nm_active_connection_get_dhcp4_config(conn); @@ -346,9 +359,9 @@ namespace WPEFramework const char* server = nm_dhcp_config_get_one_option(dhcpConfig, "dhcp_server_identifier"); if (server) newCache.dhcpserver = server; } - - newCache.valid = true; } + + newCache.valid = true; } /* Swap new snapshot into instance cache under mutex; collect old address keys. */ From 90e059f89d45a810989f7c73b2963b62bb939006 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Wed, 13 May 2026 15:50:21 +0530 Subject: [PATCH 8/8] 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. --- plugin/NetworkManagerImplementation.cpp | 28 +++------------------- plugin/gnome/NetworkManagerGnomeEvents.cpp | 20 ++++++++++------ 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 39994778..51ce6013 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -654,18 +654,6 @@ namespace WPEFramework { if(interface == "eth0") { - std::map lostIPv4, lostIPv6; - { - std::lock_guard lock(m_ipCacheMutex); - lostIPv4 = m_ethIPv4Cache.globalAddresses; - lostIPv6 = m_ethIPv6Cache.globalAddresses; - m_ethIPv4Cache.clear(); - m_ethIPv6Cache.clear(); - } - for (const auto& kv : lostIPv4) - ReportIPAddressChange(interface, "IPv4", kv.first, Exchange::INetworkManager::IP_LOST); - for (const auto& kv : lostIPv6) - ReportIPAddressChange(interface, "IPv6", kv.first, Exchange::INetworkManager::IP_LOST); 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 @@ -673,18 +661,6 @@ namespace WPEFramework } else if(interface == "wlan0") { - std::map lostIPv4, lostIPv6; - { - std::lock_guard lock(m_ipCacheMutex); - lostIPv4 = m_wlanIPv4Cache.globalAddresses; - lostIPv6 = m_wlanIPv6Cache.globalAddresses; - m_wlanIPv4Cache.clear(); - m_wlanIPv6Cache.clear(); - } - for (const auto& kv : lostIPv4) - ReportIPAddressChange(interface, "IPv4", kv.first, Exchange::INetworkManager::IP_LOST); - for (const auto& kv : lostIPv6) - ReportIPAddressChange(interface, "IPv6", kv.first, Exchange::INetworkManager::IP_LOST); m_wlanConnected.store(false); bool triggerConnectivityCheck; if(m_ethConnected.load()) @@ -795,7 +771,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); } diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index e7d8ce0f..033af3ee 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -292,9 +292,17 @@ namespace WPEFramework bool isWlan = (ifname == nmUtils::wlanIface()); if (!isEth && !isWlan) return; - /* Build the new snapshot locally (no locks held during NM calls). */ + /* Build the new snapshot locally (no locks held during NM calls). + * Skip the NM read when the device is in a disconnected/down state + * so that newCache stays empty and the diff emits IP_LOST for every + * address still in the cache. This also prevents spurious + * "IP acquired" events from intermediate NM signals (nameserver, + * gateway clearing) that fire after the cache has been emptied + * but before NM clears addresses on the config object. */ + NMDeviceState devState = nm_device_get_state(device); + bool skipRead = (devState <= NM_DEVICE_STATE_DISCONNECTED); IpFamilyCache newCache; - NMActiveConnection* conn = nm_device_get_active_connection(device); + NMActiveConnection* conn = skipRead ? nullptr : nm_device_get_active_connection(device); if (conn) { /* autoconfig: method "auto" or "dhcp" → true */ NMConnection* nmConn = NM_CONNECTION(nm_active_connection_get_connection(conn)); @@ -311,9 +319,9 @@ namespace WPEFramework } /* IP config read is device-level and does not require an active connection. */ - NMIPConfig* ipConfig = isIPv6 - ? nm_device_get_ip6_config(device) - : nm_device_get_ip4_config(device); + NMIPConfig* ipConfig = skipRead ? nullptr + : (isIPv6 ? nm_device_get_ip6_config(device) + : nm_device_get_ip4_config(device)); if (ipConfig) { GPtrArray* addresses = nm_ip_config_get_addresses(ipConfig); @@ -381,13 +389,11 @@ namespace WPEFramework std::string family = isIPv6 ? "IPv6" : "IPv4"; for (const auto& kv : newCache.globalAddresses) { if (oldKeys.find(kv.first) == oldKeys.end()) { - NMLOG_INFO("IP acquired: %s %s %s", ifname.c_str(), family.c_str(), kv.first.c_str()); _instance->ReportIPAddressChange(ifname, family, kv.first, Exchange::INetworkManager::IP_ACQUIRED); } } for (const auto& key : oldKeys) { if (newCache.globalAddresses.find(key) == newCache.globalAddresses.end()) { - NMLOG_INFO("IP lost: %s %s %s", ifname.c_str(), family.c_str(), key.c_str()); _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); } }