From 5a4deebaf2b1a1010479882fc61ce8596b681682 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 27 Apr 2026 18:17:44 +0530 Subject: [PATCH 01/17] 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 ++++---- 5 files changed, 281 insertions(+), 127 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()) From 83d6f56042267934ffdae4e33ccf21a16f9ac0f2 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 27 Apr 2026 20:34:49 +0530 Subject: [PATCH 02/17] 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 a610cd4ddd5d78d7e0f2fb734ff53f1c799fdcad Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 27 Apr 2026 21:47:46 +0530 Subject: [PATCH 03/17] 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 +- 3 files changed, 11 insertions(+), 3 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()); From 87f879a3f7e96d7d6d2e104632a623fd3f1db8d1 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Thu, 30 Apr 2026 18:22:54 +0530 Subject: [PATCH 04/17] 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 ++---- 1 file changed, 2 insertions(+), 4 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; From f378d53e346e24e96cf77eba3204f60dcdb59314 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Tue, 5 May 2026 16:07:38 +0530 Subject: [PATCH 05/17] 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 478ef3411a53773583619c5dc27b3106290dcf76 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Tue, 5 May 2026 19:13:11 +0530 Subject: [PATCH 06/17] 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 d683658d2a2b4f5260a05dccfe2fecfaa92ace7e Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Fri, 8 May 2026 21:35:43 +0530 Subject: [PATCH 07/17] 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 2fecf5cc543a97c9739519e7a00367fc90744cab Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Wed, 13 May 2026 15:50:21 +0530 Subject: [PATCH 08/17] 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); } } From 8ae1886fb9d1f1b933ab36feb9ed27cbaa45c11b Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Fri, 15 May 2026 12:29:54 +0530 Subject: [PATCH 09/17] refactor: replace named IpFamilyCache fields with map-based storage Replace four hardcoded cache fields (m_ethIPv4Cache, m_wlanIPv4Cache, m_ethIPv6Cache, m_wlanIPv6Cache) with a single std::map keyed by (interface, ipFamily) pair. Add getIpCache() convenience accessor. Change toIPAddress(bool isIPv6) to toIPAddress() with auto-detection via inet_pton, since the cache is already partitioned by IP family. This removes ~60 lines of repetitive if/else-if interface selection boilerplate across all backends (GnomeProxy, GdbusClient, RDKProxy) and eliminates hardcoded interface-name assumptions from the storage layer. --- plugin/NetworkManagerImplementation.h | 24 ++++-- plugin/gnome/NetworkManagerGnomeEvents.cpp | 10 +-- plugin/gnome/NetworkManagerGnomeProxy.cpp | 92 +++++++--------------- 3 files changed, 52 insertions(+), 74 deletions(-) diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 84d853c5..31b2d755 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -83,8 +83,17 @@ namespace WPEFramework std::string dhcpserver; bool autoconfig = false; - Exchange::INetworkManager::IPAddress toIPAddress(bool isIPv6) const { + Exchange::INetworkManager::IPAddress toIPAddress() const { Exchange::INetworkManager::IPAddress addr{}; + /* Auto-detect IP version: check global addresses first, then link-local. */ + bool isIPv6 = false; + if (!globalAddresses.empty()) { + struct in6_addr sa6{}; + isIPv6 = (inet_pton(AF_INET6, globalAddresses.begin()->first.c_str(), &sa6) == 1); + } else if (!linkLocalAddress.empty()) { + struct in6_addr sa6{}; + isIPv6 = (inet_pton(AF_INET6, linkLocalAddress.c_str(), &sa6) == 1); + } addr.ipversion = isIPv6 ? "IPv6" : "IPv4"; addr.autoconfig = autoconfig; addr.dhcpserver = dhcpserver; @@ -355,11 +364,16 @@ namespace WPEFramework std::mutex m_condVariableMutex; std::condition_variable m_condVariable; public: - IpFamilyCache m_ethIPv4Cache; - IpFamilyCache m_wlanIPv4Cache; - IpFamilyCache m_ethIPv6Cache; - IpFamilyCache m_wlanIPv6Cache; + std::map, IpFamilyCache> m_ipCacheMap; mutable std::mutex m_ipCacheMutex; + + /* Convenience accessor — returns a reference to the cache entry for the + given interface + address-family pair, inserting a default-constructed + entry if none exists yet. Caller MUST hold m_ipCacheMutex. */ + IpFamilyCache& getIpCache(const std::string& iface, const std::string& ipFamily) + { + return m_ipCacheMap[{iface, ipFamily}]; + } 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 033af3ee..3a1bf378 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -376,13 +376,9 @@ namespace WPEFramework std::set oldKeys; { 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; - for (const auto& kv : cache->globalAddresses) oldKeys.insert(kv.first); - *cache = newCache; + IpFamilyCache& cache = _instance->getIpCache(ifname, isIPv6 ? "IPv6" : "IPv4"); + for (const auto& kv : cache.globalAddresses) oldKeys.insert(kv.first); + cache = newCache; } /* Emit address acquired/lost events from map-key diff (outside the lock). */ diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index a6e77b44..d17647be 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -681,7 +681,6 @@ namespace WPEFramework const char* dhcpserver; NMSettingConnection *settings = NULL; NMDevice *device = NULL; - string ipversionStr; std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); @@ -705,47 +704,18 @@ namespace WPEFramework return Core::ERROR_GENERAL; } - if(ipversion.empty()) - { - ipversionStr = "IPV4"; - } - else - { - ipversionStr = ipversion; - } + string ipversionStr = ipversion.empty() ? "IPv4" : ipversion; // Serve from event-driven cache when available { std::lock_guard lock(m_ipCacheMutex); - if (wifiname == interface) - { - 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 (ethname == interface) + std::string family = nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6") ? "IPv6" : "IPv4"; + auto it = m_ipCacheMap.find({interface, family}); + if (it != m_ipCacheMap.end() && it->second.valid) { - 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; - } + NMLOG_DEBUG("%s %s address from cache", interface.c_str(), family.c_str()); + result = it->second.toIPAddress(); + return Core::ERROR_NONE; } } @@ -825,7 +795,7 @@ namespace WPEFramework result.autoconfig = isAutoConnectEnabled(conn); - if(ipversion.empty() || nmUtils::caseInsensitiveCompare(ipversion, "IPV4")) + if (nmUtils::caseInsensitiveCompare(ipversionStr, "IPv4")) { const GPtrArray *ipByte = nullptr; result.ipversion = "IPv4"; @@ -888,29 +858,27 @@ namespace WPEFramework { NMLOG_WARNING("Only link-local IPv4 available on %s, not returning it", interface.c_str()); std::lock_guard lock(m_ipCacheMutex); - if(ethname == interface) - m_ethIPv4Cache.clear(); - else if(wifiname == interface) - m_wlanIPv4Cache.clear(); + getIpCache(interface, "IPv4").clear(); return Core::ERROR_GENERAL; } // 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, 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; + IpFamilyCache& c = getIpCache(interface, "IPv4"); + c.clear(); + c.globalAddresses.insert({result.ipaddress, 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")) + if ((result.ipaddress.empty() && !nmUtils::caseInsensitiveCompare(ipversionStr, "IPv4")) + || nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6")) { std::string ipStr; const GPtrArray *ipArray = nullptr; @@ -971,17 +939,17 @@ namespace WPEFramework // 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(); + IpFamilyCache& c = getIpCache(interface, "IPv6"); + c.clear(); if (!result.ipaddress.empty()) - c->globalAddresses.insert({result.ipaddress, result.prefix}); - c->linkLocalAddress = result.ula; - c->gateway = result.gateway; - c->primarydns = result.primarydns; - c->secondarydns = result.secondarydns; - c->dhcpserver = result.dhcpserver; - c->autoconfig = result.autoconfig; - c->valid = true; + c.globalAddresses.insert({result.ipaddress, result.prefix}); + c.linkLocalAddress = result.ula; + c.gateway = result.gateway; + c.primarydns = result.primarydns; + c.secondarydns = result.secondarydns; + c.dhcpserver = result.dhcpserver; + c.autoconfig = result.autoconfig; + c.valid = true; } } } From d0c9a27c746bf67f78b51ba414a096bc11b30798 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 18 May 2026 13:28:07 +0530 Subject: [PATCH 10/17] fix(ip-cache): populate IPAddress.ula with actual ULA, not link-local MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The IPAddress.ula field was incorrectly being populated with IPv6 link-local addresses (fe80::/10). ULA (Unique Local Address, fc00::/7) is a different address class — analogous to RFC 1918 private IPv4. Changes: - Add isIPv6ULA() helper using the same bitmask as NetworkManager's nm_ip6_addr_is_ula(): (s6_addr32[0] & 0xfe000000) == 0xfc000000 - Add ulaAddress field to IpFamilyCache, separate from linkLocalAddress - Map IpFamilyCache::ulaAddress to IPAddress::ula in toIPAddress() - Add three-way IPv6 classification (link-local / ULA / global) in GnomeEvents, GnomeProxy, GdbusClient, GdbusEvent, and RDK proxy - ULA addresses are NOT reported as global: they do not go into globalAddresses and do not trigger IP_ACQUIRED/IP_LOST events - Fix pre-existing gdbus bug: globalAddresses.insert() now uses the correct {address, prefix} pair instead of bare string - Fix NetworkManager.json example to use valid ULA (fd00::/8 prefix) - Fix docs example: IPv4 result should have empty ula field - Replace IN_IS_ADDR_LINKLOCAL macro with isIPv4LinkLocal() inline helper for consistency with the IPv6 helpers; remove now-unnecessary arpa/inet.h includes from GnomeProxy and GnomeEvents --- definition/NetworkManager.json | 4 ++-- docs/NetworkManagerPlugin.md | 2 +- plugin/NetworkManagerImplementation.h | 21 +++++++++++++++++++-- plugin/gnome/NetworkManagerGnomeEvents.cpp | 11 ++++------- plugin/gnome/NetworkManagerGnomeProxy.cpp | 15 +++++++-------- 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/definition/NetworkManager.json b/definition/NetworkManager.json index 65fb6e6c..8aac650d 100644 --- a/definition/NetworkManager.json +++ b/definition/NetworkManager.json @@ -47,9 +47,9 @@ "example": 24 }, "ula": { - "summary": "The IPv6 Unified Local Address", + "summary": "The IPv6 Unique Local Address", "type": "string", - "example": "d00:410:2016::" + "example": "fd00:410:2016::" }, "gateway": { "summary": "The gateway address", diff --git a/docs/NetworkManagerPlugin.md b/docs/NetworkManagerPlugin.md index 2c31542f..dde1cae4 100644 --- a/docs/NetworkManagerPlugin.md +++ b/docs/NetworkManagerPlugin.md @@ -453,7 +453,7 @@ Gets the IP setting for the given interface. "ipaddress": "192.168.1.101", "prefix": 24, "gateway": "192.168.1.1", - "ula": "d00:410:2016::", + "ula": "", "primarydns": "192.168.1.1", "secondarydns": "192.168.1.2", "success": true diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index 31b2d755..a17be022 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -63,6 +63,14 @@ namespace WPEFramework { namespace Plugin { + /* Returns true if the given string is an IPv4 link-local address (169.254.0.0/16). */ + inline bool isIPv4LinkLocal(const std::string& addr) + { + struct in_addr sa{}; + return inet_pton(AF_INET, addr.c_str(), &sa) == 1 && + (ntohl(sa.s_addr) & 0xffff0000u) == 0xa9fe0000u; + } + /* 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) @@ -72,11 +80,20 @@ namespace WPEFramework sa6.s6_addr[0] == 0xfe && (sa6.s6_addr[1] & 0xc0) == 0x80; } + /* Returns true if the given string is an IPv6 Unique Local Address (ULA, fc00::/7). */ + inline bool isIPv6ULA(const std::string& addr) + { + struct in6_addr sa6{}; + return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && + (ntohl(sa6.s6_addr32[0]) & 0xfe000000u) == 0xfc000000u; + } + /* Per-interface, per-address-family cache populated by libnm events. */ struct IpFamilyCache { bool valid = false; std::map globalAddresses; // key=address, value=prefix length - std::string linkLocalAddress; // fe80:: for IPv6 (ula field), or 169.254.x.x for IPv4 + std::string linkLocalAddress; // fe80:: for IPv6, or 169.254.x.x for IPv4 + std::string ulaAddress; // fc00::/7 — IPv6 Unique Local Address std::string gateway; std::string primarydns; std::string secondarydns; @@ -97,7 +114,7 @@ namespace WPEFramework addr.ipversion = isIPv6 ? "IPv6" : "IPv4"; addr.autoconfig = autoconfig; addr.dhcpserver = dhcpserver; - addr.ula = linkLocalAddress; + addr.ula = ulaAddress; addr.gateway = gateway; addr.primarydns = primarydns; addr.secondarydns = secondarydns; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 3a1bf378..42117cda 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -31,10 +31,7 @@ #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 @@ -335,13 +332,13 @@ namespace WPEFramework if (isIPv6) { if (isIPv6LinkLocal(addrString)) { newCache.linkLocalAddress = addrString; + } else if (isIPv6ULA(addrString)) { + newCache.ulaAddress = addrString; } else { 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)) { + if (isIPv4LinkLocal(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 d17647be..6fbb10cb 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -22,9 +22,6 @@ #include "NetworkManagerGnomeUtils.h" #include #include -#include - -#define IN_IS_ADDR_LINKLOCAL(a) ((((uint32_t)ntohl(a)) & 0xffff0000U) == 0xa9fe0000U) using namespace WPEFramework; using namespace WPEFramework::Plugin; using namespace std; @@ -802,7 +799,6 @@ namespace WPEFramework ip4_config = nm_active_connection_get_ip4_config(conn); NMIPAddress *ipAddr = NULL; std::string ipStr; - struct sockaddr_in sa; if (ip4_config) ipByte = nm_ip_config_get_addresses(ip4_config); else @@ -822,8 +818,7 @@ namespace WPEFramework if(!ipStr.empty()) { // Skip link-local IPv4 addresses (169.254.x.x) - inet_pton(AF_INET, ipStr.c_str(), &(sa.sin_addr)); - if(IN_IS_ADDR_LINKLOCAL(sa.sin_addr.s_addr)) + if(isIPv4LinkLocal(ipStr)) { NMLOG_DEBUG("Skipping link-local IPv4 address: %s", ipStr.c_str()); continue; @@ -904,9 +899,13 @@ namespace WPEFramework if(!ipStr.empty()) { if (isIPv6LinkLocal(ipStr)) + { + NMLOG_DEBUG("link-local ip: %s", ipStr.c_str()); + } + else if (isIPv6ULA(ipStr)) { result.ula = ipStr; - NMLOG_DEBUG("link-local ip: %s", result.ula.c_str()); + NMLOG_DEBUG("ULA ip: %s", ipStr.c_str()); } else { @@ -943,7 +942,7 @@ namespace WPEFramework c.clear(); if (!result.ipaddress.empty()) c.globalAddresses.insert({result.ipaddress, result.prefix}); - c.linkLocalAddress = result.ula; + c.ulaAddress = result.ula; c.gateway = result.gateway; c.primarydns = result.primarydns; c.secondarydns = result.secondarydns; From 3ac157b15188affc05e89a33970322b728f00c71 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Mon, 18 May 2026 17:47:54 +0530 Subject: [PATCH 11/17] feat(ip-cache): filter MAC-based EUI-64 global IPv6 from GetIPSettings Store interface HW address in IpFamilyCache.macAddress (populated from nm_device_get_hw_address). toIPAddress() iterates globalAddresses preferring non-MAC-based globals; falls back to MAC-based if all are EUI-64 derived. Direct-read (fallback) path in GnomeProxy also filters at selection time. Adds isIPv6MacBased() helper to detect EUI-64 addresses derived from the interface MAC. --- plugin/NetworkManagerImplementation.cpp | 35 ++++++++++++++++++++++ plugin/NetworkManagerImplementation.h | 28 +++++++++++++++-- plugin/gnome/NetworkManagerGnomeEvents.cpp | 2 ++ plugin/gnome/NetworkManagerGnomeProxy.cpp | 16 ++++++++-- 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 51ce6013..4f718db4 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "NetworkManagerImplementation.h" #if USE_TELEMETRY @@ -1182,5 +1183,39 @@ namespace WPEFramework } #endif } + + bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr) + { + if (ipv6Addr.empty() || macAddr.empty()) + { + NMLOG_ERROR("ipv6 addr or mac addr is empty"); + return false; + } + if (macAddr.size() < 12) + { + NMLOG_ERROR("mac addr is less than 12 chars: %s", macAddr.c_str()); + return false; + } + + // Normalise MAC: lowercase, remove colons, insert "fffe" in the middle, + // then drop the first two characters (the EUI-64 modified byte is in the IPv6 address). + std::string tmpMac(macAddr); // "A8:11:FC:FD:1E:8D" // example + std::transform(tmpMac.begin(), tmpMac.end(), tmpMac.begin(), ::tolower); // "a8:11:fc:fd:1e:8d" + tmpMac.erase(std::remove(tmpMac.begin(), tmpMac.end(), ':'), tmpMac.end()); // "a811fcfd1e8d" + tmpMac.insert(6, "fffe"); // "a811fcfffefd1e8d" + tmpMac.erase(0, 2); // "11fcfffefd1e8d" + + // Normalise IPv6: lowercase, remove colons. + std::string tmpIpv6(ipv6Addr); + std::transform(tmpIpv6.begin(), tmpIpv6.end(), tmpIpv6.begin(), ::tolower); + tmpIpv6.erase(std::remove(tmpIpv6.begin(), tmpIpv6.end(), ':'), tmpIpv6.end()); + + if (tmpIpv6.find(tmpMac) != std::string::npos) + { + NMLOG_INFO("MAC %s based global v6 address %s", macAddr.c_str(), ipv6Addr.c_str()); + return true; + } + return false; + } } } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index a17be022..d267cc3b 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -88,12 +88,16 @@ namespace WPEFramework (ntohl(sa6.s6_addr32[0]) & 0xfe000000u) == 0xfc000000u; } + /* Returns true if the given global IPv6 address is derived (EUI-64) from the MAC. */ + bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr); + /* Per-interface, per-address-family cache populated by libnm events. */ struct IpFamilyCache { bool valid = false; std::map globalAddresses; // key=address, value=prefix length std::string linkLocalAddress; // fe80:: for IPv6, or 169.254.x.x for IPv4 std::string ulaAddress; // fc00::/7 — IPv6 Unique Local Address + std::string macAddress; // interface HW address (for MAC-based IPv6 filtering) std::string gateway; std::string primarydns; std::string secondarydns; @@ -119,9 +123,27 @@ namespace WPEFramework addr.primarydns = primarydns; addr.secondarydns = secondarydns; if (!globalAddresses.empty()) { - const auto& first = *globalAddresses.begin(); - addr.ipaddress = first.first; - addr.prefix = first.second; + /* Prefer non-MAC-based global; fall back to first if all are MAC-based. */ + const std::string* chosen = nullptr; + uint32_t chosenPrefix = 0; + for (const auto& kv : globalAddresses) { + if (!chosen) { + chosen = &kv.first; + chosenPrefix = kv.second; + } + if (isIPv6 && !macAddress.empty() + && isIPv6MacBased(kv.first, macAddress)) { + continue; // skip MAC-based, keep looking + } + // Found a non-MAC-based global — use it + chosen = &kv.first; + chosenPrefix = kv.second; + break; + } + if (chosen) { + addr.ipaddress = *chosen; + addr.prefix = chosenPrefix; + } } return addr; } diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 42117cda..7cff591c 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -299,6 +299,8 @@ namespace WPEFramework NMDeviceState devState = nm_device_get_state(device); bool skipRead = (devState <= NM_DEVICE_STATE_DISCONNECTED); IpFamilyCache newCache; + const char* hwAddr = nm_device_get_hw_address(device); + if (hwAddr) newCache.macAddress = hwAddr; NMActiveConnection* conn = skipRead ? nullptr : nm_device_get_active_connection(device); if (conn) { /* autoconfig: method "auto" or "dhcp" → true */ diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 6fbb10cb..0b5b853c 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -863,6 +863,8 @@ namespace WPEFramework IpFamilyCache& c = getIpCache(interface, "IPv4"); c.clear(); c.globalAddresses.insert({result.ipaddress, result.prefix}); + const char* hw = nm_device_get_hw_address(device); + if (hw) c.macAddress = hw; c.gateway = result.gateway; c.primarydns = result.primarydns; c.secondarydns = result.secondarydns; @@ -909,10 +911,16 @@ namespace WPEFramework } else { - result.prefix = nm_ip_address_get_prefix(ipAddr); - if(result.ipaddress.empty()) // SLAAC mutiple ip not added + uint32_t pfx = nm_ip_address_get_prefix(ipAddr); + const char* hw = nm_device_get_hw_address(device); + std::string macStr = hw ? hw : ""; + bool isMacBased = !macStr.empty() && isIPv6MacBased(ipStr, macStr); + if(result.ipaddress.empty() || isMacBased == false) { result.ipaddress = ipStr; - NMLOG_DEBUG("global ip %s/%d", ipStr.c_str(), result.prefix); + result.prefix = pfx; + } + NMLOG_DEBUG("global ip %s/%d%s", ipStr.c_str(), pfx, + isMacBased ? " (MAC-based)" : ""); } } } @@ -943,6 +951,8 @@ namespace WPEFramework if (!result.ipaddress.empty()) c.globalAddresses.insert({result.ipaddress, result.prefix}); c.ulaAddress = result.ula; + const char* hwc = nm_device_get_hw_address(device); + if (hwc) c.macAddress = hwc; c.gateway = result.gateway; c.primarydns = result.primarydns; c.secondarydns = result.secondarydns; From 4c418c70b9de232dc530818a87de592eed5ab686 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Wed, 20 May 2026 12:39:26 +0530 Subject: [PATCH 12/17] refactor(GetIPSettings): remove fallback path, serve exclusively from event-driven cache The event thread seeds the IP cache synchronously during startup (via refreshIpFamilyCache) before entering g_main_loop_run(), so the cache is always populated before any RPC can arrive. The fallback path that read directly from the RPC thread's m_nmClient was effectively dead code. Remove the ~250-line fallback (m_nmClient reads, connection lookups, IPv4/IPv6 address iteration, and fallback cache writes). On cache miss, return Core::ERROR_GENERAL with a warning log. Also remove the now-unused isAutoConnectEnabled() helper and its 10 local variable declarations that only served the fallback. --- plugin/gnome/NetworkManagerGnomeProxy.cpp | 289 +--------------------- 1 file changed, 4 insertions(+), 285 deletions(-) diff --git a/plugin/gnome/NetworkManagerGnomeProxy.cpp b/plugin/gnome/NetworkManagerGnomeProxy.cpp index 0b5b853c..20211393 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -646,39 +646,9 @@ namespace WPEFramework return Core::ERROR_GENERAL; } - bool static isAutoConnectEnabled(NMActiveConnection* activeConn) - { - NMConnection *connection = NM_CONNECTION(nm_active_connection_get_connection(activeConn)); - if(connection == NULL) - return false; - - NMSettingIPConfig *ipConfig = nm_connection_get_setting_ip4_config(connection); - if(ipConfig) - { - const char* ipConfMethod = nm_setting_ip_config_get_method (ipConfig); - if(ipConfMethod != NULL && g_strcmp0(ipConfMethod, "auto") == 0) - return true; - else - NMLOG_WARNING("ip configuration: %s", ipConfMethod != NULL? ipConfMethod: "null"); - } - - return false; - } - /* @brief Get IP Address Of the Interface */ uint32_t NetworkManagerImplementation::GetIPSettings(string& interface /* @inout */, const string &ipversion /* @in */, IPAddress& result /* @out */) { - NMActiveConnection *conn = NULL; - NMIPConfig *ip4_config = NULL; - NMIPConfig *ip6_config = NULL; - const gchar *gateway = NULL; - char **dnsArr = NULL; - NMDhcpConfig *dhcp4_config = NULL; - NMDhcpConfig *dhcp6_config = NULL; - const char* dhcpserver; - NMSettingConnection *settings = NULL; - NMDevice *device = NULL; - std::string wifiname = nmUtils::wlanIface(), ethname = nmUtils::ethIface(); if(interface.empty()) @@ -702,11 +672,11 @@ namespace WPEFramework } string ipversionStr = ipversion.empty() ? "IPv4" : ipversion; + std::string family = nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6") ? "IPv6" : "IPv4"; - // Serve from event-driven cache when available + // Serve from event-driven cache { std::lock_guard lock(m_ipCacheMutex); - std::string family = nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6") ? "IPv6" : "IPv4"; auto it = m_ipCacheMap.find({interface, family}); if (it != m_ipCacheMap.end() && it->second.valid) { @@ -716,259 +686,8 @@ namespace WPEFramework } } - if(m_nmClient == nullptr) - { - NMLOG_WARNING("NMClient is null"); - return Core::ERROR_RPC_CALL_FAILED; - } - - /* Drain any pending D-Bus property-change events queued on m_nmContext - * before reading libnm GObject state. Because m_nmContext is isolated - * from the event thread, nobody else can run it — so this loop is - * single-threaded and safe. It ensures m_nmClient reflects the latest state - * from NetworkManager before we start iterating connections/addresses. */ - if (m_nmContext) { - for (int i = 0; i < 100 && g_main_context_iteration(m_nmContext, FALSE); ++i){ - // Intentional empty body: just flushing the event queue - } - } - - device = nm_client_get_device_by_iface(m_nmClient, interface.c_str()); - if (device == NULL) - { - NMLOG_FATAL("libnm doesn't have device corresponding to %s", interface.c_str()); - return Core::ERROR_GENERAL; - } - - NMDeviceState deviceState = NM_DEVICE_STATE_UNKNOWN; - deviceState = nm_device_get_state(device); - if(deviceState < NM_DEVICE_STATE_DISCONNECTED) - { - NMLOG_WARNING("%s state is not a valid state: (%d)", interface.c_str(), deviceState); - return Core::ERROR_GENERAL; - } - - // if(ipversion.empty()) - // NMLOG_DEBUG("ipversion is empty default value IPv4"); - - const GPtrArray *connections = nm_client_get_active_connections(m_nmClient); - if(connections == NULL) - { - NMLOG_WARNING("no active connection; ip is not assigned to interface"); - return Core::ERROR_GENERAL; - } - - for (guint i = 0; i < connections->len; i++) - { - if(connections->pdata[i] == NULL) - continue; - - NMActiveConnection *connection = NM_ACTIVE_CONNECTION(connections->pdata[i]); - if (connection == nullptr) - continue; - - NMRemoteConnection* retConn = nm_active_connection_get_connection(connection); - if(retConn == NULL) - { - NMLOG_INFO("remote connection is null"); - continue; - } - - settings = nm_connection_get_setting_connection(NM_CONNECTION(retConn)); - if(settings == NULL) - continue; - if (g_strcmp0(nm_setting_connection_get_interface_name(settings), interface.c_str()) == 0) - { - conn = connection; - break; - } - } - - if (conn == NULL) - { - NMLOG_WARNING("no active connection on %s interface", interface.c_str()); - return Core::ERROR_GENERAL; - } - - result.autoconfig = isAutoConnectEnabled(conn); - - if (nmUtils::caseInsensitiveCompare(ipversionStr, "IPv4")) - { - const GPtrArray *ipByte = nullptr; - result.ipversion = "IPv4"; - ip4_config = nm_active_connection_get_ip4_config(conn); - NMIPAddress *ipAddr = NULL; - std::string ipStr; - if (ip4_config) - ipByte = nm_ip_config_get_addresses(ip4_config); - else - NMLOG_WARNING("no IPv4 configurtion on %s", interface.c_str()); - if(ipByte) - { - for (guint i = 0; i < ipByte->len; i++) - { - ipStr.clear(); - ipAddr = static_cast(ipByte->pdata[i]); - if(ipAddr) - { - const char* addr = nm_ip_address_get_address(ipAddr); - if(addr) - ipStr = addr; - } - if(!ipStr.empty()) - { - // Skip link-local IPv4 addresses (169.254.x.x) - if(isIPv4LinkLocal(ipStr)) - { - NMLOG_DEBUG("Skipping link-local IPv4 address: %s", ipStr.c_str()); - continue; - } - result.ipaddress = ipStr; - result.prefix = nm_ip_address_get_prefix(ipAddr); - NMLOG_DEBUG("IPv4 addr: %s/%d", result.ipaddress.c_str(), result.prefix); - } - } - gateway = nm_ip_config_get_gateway(ip4_config); - if(gateway) - result.gateway = gateway; - dnsArr = (char **)nm_ip_config_get_nameservers(ip4_config); - if(dnsArr) - { - if(dnsArr[0]) - result.primarydns = std::string(dnsArr[0]); - if(dnsArr[1]) - result.secondarydns = std::string(dnsArr[1]); - } - dhcp4_config = nm_active_connection_get_dhcp4_config(conn); - if(dhcp4_config) - { - dhcpserver = nm_dhcp_config_get_one_option (dhcp4_config, "dhcp_server_identifier"); - if(dhcpserver) - result.dhcpserver = dhcpserver; - } - result.ula = ""; - - // Check if only link-local IPv4 is available (no valid global address found) - if(result.ipaddress.empty()) - { - NMLOG_WARNING("Only link-local IPv4 available on %s, not returning it", interface.c_str()); - std::lock_guard lock(m_ipCacheMutex); - getIpCache(interface, "IPv4").clear(); - return Core::ERROR_GENERAL; - } - - // Cache the IPv4 result (fallback — event-driven cache not yet populated) - { - std::lock_guard lock(m_ipCacheMutex); - IpFamilyCache& c = getIpCache(interface, "IPv4"); - c.clear(); - c.globalAddresses.insert({result.ipaddress, result.prefix}); - const char* hw = nm_device_get_hw_address(device); - if (hw) c.macAddress = hw; - 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(ipversionStr, "IPv4")) - || nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6")) - { - std::string ipStr; - const GPtrArray *ipArray = nullptr; - result.ipversion = "IPv6"; - NMIPAddress *ipAddr = nullptr; - ip6_config = nm_active_connection_get_ip6_config(conn); - if(ip6_config) - ipArray = nm_ip_config_get_addresses(ip6_config); - else - NMLOG_WARNING("no IPv6 configurtion on %s", interface.c_str()); - if(ipArray) - { - for (guint i = 0; i < ipArray->len; i++) - { - ipStr.clear(); - ipAddr = static_cast(ipArray->pdata[i]); - if(ipAddr) - { - const char* addr = nm_ip_address_get_address(ipAddr); - if(addr) - ipStr = addr; - } - if(!ipStr.empty()) - { - if (isIPv6LinkLocal(ipStr)) - { - NMLOG_DEBUG("link-local ip: %s", ipStr.c_str()); - } - else if (isIPv6ULA(ipStr)) - { - result.ula = ipStr; - NMLOG_DEBUG("ULA ip: %s", ipStr.c_str()); - } - else - { - uint32_t pfx = nm_ip_address_get_prefix(ipAddr); - const char* hw = nm_device_get_hw_address(device); - std::string macStr = hw ? hw : ""; - bool isMacBased = !macStr.empty() && isIPv6MacBased(ipStr, macStr); - if(result.ipaddress.empty() || isMacBased == false) { - result.ipaddress = ipStr; - result.prefix = pfx; - } - NMLOG_DEBUG("global ip %s/%d%s", ipStr.c_str(), pfx, - isMacBased ? " (MAC-based)" : ""); - } - } - } - - gateway = nm_ip_config_get_gateway(ip6_config); - if(gateway) - result.gateway= gateway; - dnsArr = (char **)nm_ip_config_get_nameservers(ip6_config); - if(dnsArr) - { - if(dnsArr[0]) - result.primarydns = std::string(dnsArr[0]); - if(dnsArr[1]) - result.secondarydns = std::string(dnsArr[1]); - } - dhcp6_config = nm_active_connection_get_dhcp6_config(conn); - if(dhcp6_config) - { - dhcpserver = nm_dhcp_config_get_one_option (dhcp6_config, "dhcp_server_identifier"); - if(dhcpserver) - result.dhcpserver = dhcpserver; - } - // Cache the IPv6 result (fallback — event-driven cache not yet populated) - { - std::lock_guard lock(m_ipCacheMutex); - IpFamilyCache& c = getIpCache(interface, "IPv6"); - c.clear(); - if (!result.ipaddress.empty()) - c.globalAddresses.insert({result.ipaddress, result.prefix}); - c.ulaAddress = result.ula; - const char* hwc = nm_device_get_hw_address(device); - if (hwc) c.macAddress = hwc; - 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()) - { - result.autoconfig = true; - if(ipversion.empty()) - result.ipversion = "IPv4"; - } - return Core::ERROR_NONE; + NMLOG_WARNING("IP cache not populated for %s %s", interface.c_str(), family.c_str()); + return Core::ERROR_GENERAL; } /* @brief Set IP Address Of the Interface */ From 0229d8e23136e0b5cef19d140343cc5096c08f4c Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Wed, 20 May 2026 21:26:47 +0530 Subject: [PATCH 13/17] refactor(ip-cache): split address containers and move helpers out of header - Split IpFamilyCache into three containers: globalAddresses (map, event-diffable), linkLocalAddresses (set), uniqueLocalAddresses (set) - Move isIPv4LinkLocal, isIPv6LinkLocal, isIPv6ULA, toIPAddress from inline header definitions to NetworkManagerImplementation.cpp - Add explicit constructors to GlobalAddressInfo for C++11 compatibility - Simplify event diffing in refreshIpFamilyCache to operate on globalAddresses keys directly without type filtering --- plugin/NetworkManagerImplementation.cpp | 72 +++++++++++++++++ plugin/NetworkManagerImplementation.h | 91 ++++++---------------- plugin/gnome/NetworkManagerGnomeEvents.cpp | 32 ++++---- 3 files changed, 114 insertions(+), 81 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 4f718db4..61bb27ec 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -1184,6 +1184,27 @@ namespace WPEFramework #endif } + bool isIPv4LinkLocal(const std::string& addr) + { + struct in_addr sa{}; + return inet_pton(AF_INET, addr.c_str(), &sa) == 1 && + (ntohl(sa.s_addr) & 0xffff0000u) == 0xa9fe0000u; + } + + bool isIPv6LinkLocal(const std::string& addr) + { + struct in6_addr sa6{}; + return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && + sa6.s6_addr[0] == 0xfe && (sa6.s6_addr[1] & 0xc0) == 0x80; + } + + bool isIPv6ULA(const std::string& addr) + { + struct in6_addr sa6{}; + return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && + (ntohl(sa6.s6_addr32[0]) & 0xfe000000u) == 0xfc000000u; + } + bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr) { if (ipv6Addr.empty() || macAddr.empty()) @@ -1217,5 +1238,56 @@ namespace WPEFramework } return false; } + + Exchange::INetworkManager::IPAddress IpFamilyCache::toIPAddress() const + { + Exchange::INetworkManager::IPAddress addr{}; + /* Detect IP version from any available address. */ + bool isIPv6 = false; + { + const std::string* sample = nullptr; + if (!globalAddresses.empty()) + sample = &globalAddresses.begin()->first; + else if (!uniqueLocalAddresses.empty()) + sample = &(*uniqueLocalAddresses.begin()); + else if (!linkLocalAddresses.empty()) + sample = &(*linkLocalAddresses.begin()); + if (sample) { + struct in6_addr sa6{}; + isIPv6 = (inet_pton(AF_INET6, sample->c_str(), &sa6) == 1); + } + } + addr.ipversion = isIPv6 ? "IPv6" : "IPv4"; + addr.autoconfig = autoconfig; + addr.dhcpserver = dhcpserver; + addr.ula = uniqueLocalAddresses.empty() ? "" : *uniqueLocalAddresses.begin(); + addr.gateway = gateway; + addr.primarydns = primarydns; + addr.secondarydns = secondarydns; + /* Prefer non-MAC-based global; fall back to MAC-based if all are MAC-based. */ + const std::string* bestGlobal = nullptr; + uint32_t bestPrefix = 0; + const std::string* fallbackMac = nullptr; + uint32_t fallbackMacPrefix = 0; + for (const auto& kv : globalAddresses) { + if (kv.second.type == ADDR_GLOBAL) { + bestGlobal = &kv.first; + bestPrefix = kv.second.prefix; + break; + } + if (!fallbackMac) { + fallbackMac = &kv.first; + fallbackMacPrefix = kv.second.prefix; + } + } + if (bestGlobal) { + addr.ipaddress = *bestGlobal; + addr.prefix = bestPrefix; + } else if (fallbackMac) { + addr.ipaddress = *fallbackMac; + addr.prefix = fallbackMacPrefix; + } + return addr; + } } } diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index d267cc3b..dd45db27 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -28,6 +28,7 @@ #include #include #include +#include using namespace std; @@ -64,89 +65,43 @@ namespace WPEFramework namespace Plugin { /* Returns true if the given string is an IPv4 link-local address (169.254.0.0/16). */ - inline bool isIPv4LinkLocal(const std::string& addr) - { - struct in_addr sa{}; - return inet_pton(AF_INET, addr.c_str(), &sa) == 1 && - (ntohl(sa.s_addr) & 0xffff0000u) == 0xa9fe0000u; - } - - /* 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; - } + bool isIPv4LinkLocal(const std::string& addr); + + /* Returns true if the given string is an IPv6 link-local address (fe80::/10). */ + bool isIPv6LinkLocal(const std::string& addr); /* Returns true if the given string is an IPv6 Unique Local Address (ULA, fc00::/7). */ - inline bool isIPv6ULA(const std::string& addr) - { - struct in6_addr sa6{}; - return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && - (ntohl(sa6.s6_addr32[0]) & 0xfe000000u) == 0xfc000000u; - } + bool isIPv6ULA(const std::string& addr); /* Returns true if the given global IPv6 address is derived (EUI-64) from the MAC. */ bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr); + /* Sub-classification of global-scope addresses in the IP cache. */ + enum GlobalAddressType : uint8_t { + ADDR_GLOBAL, // non-MAC-based global (preferred by GetIPSettings) + ADDR_GLOBAL_MAC_BASED, // EUI-64 global derived from interface MAC (fallback) + }; + + struct GlobalAddressInfo { + uint32_t prefix; + GlobalAddressType type; + GlobalAddressInfo() : prefix(0), type(ADDR_GLOBAL) {} + GlobalAddressInfo(uint32_t p, GlobalAddressType t) : prefix(p), type(t) {} + }; + /* Per-interface, per-address-family cache populated by libnm events. */ struct IpFamilyCache { bool valid = false; - std::map globalAddresses; // key=address, value=prefix length - std::string linkLocalAddress; // fe80:: for IPv6, or 169.254.x.x for IPv4 - std::string ulaAddress; // fc00::/7 — IPv6 Unique Local Address - std::string macAddress; // interface HW address (for MAC-based IPv6 filtering) + std::map globalAddresses; // event-diffable global addresses + std::set linkLocalAddresses; // fe80::/10 or 169.254.x.x — not diffed for events + std::set uniqueLocalAddresses; // fc00::/7 (IPv6 only) — not diffed for events std::string gateway; std::string primarydns; std::string secondarydns; std::string dhcpserver; bool autoconfig = false; - Exchange::INetworkManager::IPAddress toIPAddress() const { - Exchange::INetworkManager::IPAddress addr{}; - /* Auto-detect IP version: check global addresses first, then link-local. */ - bool isIPv6 = false; - if (!globalAddresses.empty()) { - struct in6_addr sa6{}; - isIPv6 = (inet_pton(AF_INET6, globalAddresses.begin()->first.c_str(), &sa6) == 1); - } else if (!linkLocalAddress.empty()) { - struct in6_addr sa6{}; - isIPv6 = (inet_pton(AF_INET6, linkLocalAddress.c_str(), &sa6) == 1); - } - addr.ipversion = isIPv6 ? "IPv6" : "IPv4"; - addr.autoconfig = autoconfig; - addr.dhcpserver = dhcpserver; - addr.ula = ulaAddress; - addr.gateway = gateway; - addr.primarydns = primarydns; - addr.secondarydns = secondarydns; - if (!globalAddresses.empty()) { - /* Prefer non-MAC-based global; fall back to first if all are MAC-based. */ - const std::string* chosen = nullptr; - uint32_t chosenPrefix = 0; - for (const auto& kv : globalAddresses) { - if (!chosen) { - chosen = &kv.first; - chosenPrefix = kv.second; - } - if (isIPv6 && !macAddress.empty() - && isIPv6MacBased(kv.first, macAddress)) { - continue; // skip MAC-based, keep looking - } - // Found a non-MAC-based global — use it - chosen = &kv.first; - chosenPrefix = kv.second; - break; - } - if (chosen) { - addr.ipaddress = *chosen; - addr.prefix = chosenPrefix; - } - } - return addr; - } + Exchange::INetworkManager::IPAddress toIPAddress() const; void clear() { *this = IpFamilyCache{}; } }; diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 7cff591c..4387c356 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -299,8 +299,6 @@ namespace WPEFramework NMDeviceState devState = nm_device_get_state(device); bool skipRead = (devState <= NM_DEVICE_STATE_DISCONNECTED); IpFamilyCache newCache; - const char* hwAddr = nm_device_get_hw_address(device); - if (hwAddr) newCache.macAddress = hwAddr; NMActiveConnection* conn = skipRead ? nullptr : nm_device_get_active_connection(device); if (conn) { /* autoconfig: method "auto" or "dhcp" → true */ @@ -323,27 +321,35 @@ namespace WPEFramework : 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); + GPtrArray* ipAddresses = nm_ip_config_get_addresses(ipConfig); + std::string macAddr; + if (isIPv6) { + const char* hw = nm_device_get_hw_address(device); + if (hw) macAddr = hw; + } + if (ipAddresses) { + for (guint i = 0; i < ipAddresses->len; i++) { + NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(ipAddresses, i); if (!addr) continue; const char* addrStr = nm_ip_address_get_address(addr); if (!addrStr) continue; std::string addrString = addrStr; + uint32_t prefix = nm_ip_address_get_prefix(addr); if (isIPv6) { if (isIPv6LinkLocal(addrString)) { - newCache.linkLocalAddress = addrString; + newCache.linkLocalAddresses.insert(addrString); } else if (isIPv6ULA(addrString)) { - newCache.ulaAddress = addrString; + newCache.uniqueLocalAddresses.insert(addrString); } else { - newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); + GlobalAddressType type = (!macAddr.empty() && isIPv6MacBased(addrString, macAddr)) + ? ADDR_GLOBAL_MAC_BASED : ADDR_GLOBAL; + newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, type}); } } else { if (isIPv4LinkLocal(addrString)) { - newCache.linkLocalAddress = addrString; + newCache.linkLocalAddresses.insert(addrString); } else { - newCache.globalAddresses.emplace(addrString, nm_ip_address_get_prefix(addr)); + newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, ADDR_GLOBAL}); } } } @@ -371,7 +377,7 @@ namespace WPEFramework newCache.valid = true; } - /* Swap new snapshot into instance cache under mutex; collect old address keys. */ + /* Swap new snapshot into instance cache under mutex; collect old global address keys. */ std::set oldKeys; { std::lock_guard lock(_instance->m_ipCacheMutex); @@ -380,7 +386,7 @@ namespace WPEFramework cache = newCache; } - /* Emit address acquired/lost events from map-key diff (outside the lock). */ + /* Emit address acquired/lost events from global-address key diff (outside the lock). */ std::string family = isIPv6 ? "IPv6" : "IPv4"; for (const auto& kv : newCache.globalAddresses) { if (oldKeys.find(kv.first) == oldKeys.end()) { From 9a4aab611c2b58b563af987477a47acb4528477e Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Fri, 22 May 2026 20:29:42 +0530 Subject: [PATCH 14/17] fix: address PR review feedback on IP cache helpers and proxy - isIPv6ULA: replace non-portable s6_addr32 with s6_addr[0] byte check - isIPv6MacBased: rewrite with binary EUI-64 comparison via inet_pton and memcmp, fixing broken string matching from inet_ntop zero suppression - Add parseMac() helper accepting both colon-separated and bare hex MACs - Add #include and for memcmp/sscanf - cleanupSignalHandlers: explicitly disconnect DHCP option callbacks - Remove stale 'GetIPSettings fallback path' comment - GetIPSettings: return ERROR_NONE with empty result when cache has no entry for the requested interface+family, since absence of an address is not an error - GetIPSettings: reset result to IPAddress{} with correct ipversion before cache lookup, and preserve requested family after toIPAddress() - Remove onAddressChangeCb L2 test: the old callback was replaced by event-driven cache diffs in refreshIpFamilyCache(); the test had no assertions and exercised only dead compatibility code - deviceAddedCB: seed IP cache after attaching signal handlers so GetIPSettings returns data immediately for hotplugged devices - Encapsulate m_ipCacheMap/m_ipCacheMutex as private; expose lookupIpCache() and swapIpCache() locked accessors to prevent unsynchronised access from outside the class - lookupIpCache returns IPAddress directly (via toIPAddress() under the lock) instead of copying the entire IpFamilyCache by value - refreshIpFamilyCache: make this an internal helper function - deviceRemovedCB: disconnect all IP-config, DHCP, and device-level signal handlers symmetrically with deviceAddedCB, and clear the IP cache (emitting IP_LOST events) to prevent stale entries and handler accumulation on device removal/re-addition --- plugin/NetworkManagerImplementation.cpp | 87 +++- plugin/NetworkManagerImplementation.h | 16 +- plugin/gnome/NetworkManagerGnomeEvents.cpp | 456 ++++++++++-------- plugin/gnome/NetworkManagerGnomeEvents.h | 5 - plugin/gnome/NetworkManagerGnomeProxy.cpp | 21 +- .../l2Test/libnm/l2_test_libnmproxyEvent.cpp | 25 - 6 files changed, 335 insertions(+), 275 deletions(-) diff --git a/plugin/NetworkManagerImplementation.cpp b/plugin/NetworkManagerImplementation.cpp index 61bb27ec..7ec1a3f9 100644 --- a/plugin/NetworkManagerImplementation.cpp +++ b/plugin/NetworkManagerImplementation.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "NetworkManagerImplementation.h" #if USE_TELEMETRY @@ -1202,43 +1204,84 @@ namespace WPEFramework { struct in6_addr sa6{}; return inet_pton(AF_INET6, addr.c_str(), &sa6) == 1 && - (ntohl(sa6.s6_addr32[0]) & 0xfe000000u) == 0xfc000000u; + (sa6.s6_addr[0] & 0xfe) == 0xfc; } - bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr) + /* Parse a MAC string into 6 bytes. Accepts both "AA:BB:CC:DD:EE:FF" and "aabbccddeeff". */ + static bool parseMac(const std::string& mac, uint8_t out[6]) { - if (ipv6Addr.empty() || macAddr.empty()) + unsigned int b[6]; + if (mac.size() >= 17 && + sscanf(mac.c_str(), "%02x:%02x:%02x:%02x:%02x:%02x", + &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]) == 6) { - NMLOG_ERROR("ipv6 addr or mac addr is empty"); - return false; + for (int i = 0; i < 6; ++i) out[i] = static_cast(b[i]); + return true; } - if (macAddr.size() < 12) + if (mac.size() >= 12 && + sscanf(mac.c_str(), "%02x%02x%02x%02x%02x%02x", + &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]) == 6) { - NMLOG_ERROR("mac addr is less than 12 chars: %s", macAddr.c_str()); - return false; + for (int i = 0; i < 6; ++i) out[i] = static_cast(b[i]); + return true; } + return false; + } - // Normalise MAC: lowercase, remove colons, insert "fffe" in the middle, - // then drop the first two characters (the EUI-64 modified byte is in the IPv6 address). - std::string tmpMac(macAddr); // "A8:11:FC:FD:1E:8D" // example - std::transform(tmpMac.begin(), tmpMac.end(), tmpMac.begin(), ::tolower); // "a8:11:fc:fd:1e:8d" - tmpMac.erase(std::remove(tmpMac.begin(), tmpMac.end(), ':'), tmpMac.end()); // "a811fcfd1e8d" - tmpMac.insert(6, "fffe"); // "a811fcfffefd1e8d" - tmpMac.erase(0, 2); // "11fcfffefd1e8d" - - // Normalise IPv6: lowercase, remove colons. - std::string tmpIpv6(ipv6Addr); - std::transform(tmpIpv6.begin(), tmpIpv6.end(), tmpIpv6.begin(), ::tolower); - tmpIpv6.erase(std::remove(tmpIpv6.begin(), tmpIpv6.end(), ':'), tmpIpv6.end()); + bool isIPv6MacBased(const std::string& ipv6Addr, const std::string& macAddr) + { + struct in6_addr sa6{}; + uint8_t mac[6]; + if (inet_pton(AF_INET6, ipv6Addr.c_str(), &sa6) != 1 || !parseMac(macAddr, mac)) + return false; - if (tmpIpv6.find(tmpMac) != std::string::npos) + /* Build 8-byte EUI-64 identifier from 6-byte MAC: + mac[0..2] | ff:fe | mac[3..5], then flip the U/L bit (bit 1) of byte 0. */ + uint8_t eui64[8]; + eui64[0] = mac[0] ^ 0x02; // flip Universal/Local bit + eui64[1] = mac[1]; + eui64[2] = mac[2]; + eui64[3] = 0xff; + eui64[4] = 0xfe; + eui64[5] = mac[3]; + eui64[6] = mac[4]; + eui64[7] = mac[5]; + + /* Compare against the interface-ID (last 8 bytes) of the IPv6 address. */ + if (memcmp(&sa6.s6_addr[8], eui64, 8) == 0) { - NMLOG_INFO("MAC %s based global v6 address %s", macAddr.c_str(), ipv6Addr.c_str()); + NMLOG_DEBUG("MAC %s based global v6 address %s", macAddr.c_str(), ipv6Addr.c_str()); + return true; + } + return false; + } + + bool NetworkManagerImplementation::lookupIpCache( + const std::string& iface, const std::string& ipFamily, + Exchange::INetworkManager::IPAddress& out) const + { + std::lock_guard lock(m_ipCacheMutex); + auto it = m_ipCacheMap.find({iface, ipFamily}); + if (it != m_ipCacheMap.end() && it->second.valid) { + out = it->second.toIPAddress(); return true; } return false; } + std::set NetworkManagerImplementation::swapIpCache( + const std::string& iface, const std::string& ipFamily, + IpFamilyCache newCache) + { + std::set oldKeys; + std::lock_guard lock(m_ipCacheMutex); + IpFamilyCache& cache = m_ipCacheMap[{iface, ipFamily}]; + for (const auto& kv : cache.globalAddresses) + oldKeys.insert(kv.first); + cache = std::move(newCache); + return oldKeys; + } + Exchange::INetworkManager::IPAddress IpFamilyCache::toIPAddress() const { Exchange::INetworkManager::IPAddress addr{}; diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index dd45db27..d9f996a5 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -358,16 +358,12 @@ namespace WPEFramework std::mutex m_condVariableMutex; std::condition_variable m_condVariable; public: - std::map, IpFamilyCache> m_ipCacheMap; - mutable std::mutex m_ipCacheMutex; + bool lookupIpCache(const std::string& iface, const std::string& ipFamily, + Exchange::INetworkManager::IPAddress& out) const; + std::set swapIpCache(const std::string& iface, + const std::string& ipFamily, + IpFamilyCache newCache); - /* Convenience accessor — returns a reference to the cache entry for the - given interface + address-family pair, inserting a default-constructed - entry if none exists yet. Caller MUST hold m_ipCacheMutex. */ - IpFamilyCache& getIpCache(const std::string& iface, const std::string& ipFamily) - { - return m_ipCacheMap[{iface, ipFamily}]; - } std::atomic m_ethConnected; std::atomic m_wlanConnected; std::atomic m_ethEnabled; @@ -392,6 +388,8 @@ namespace WPEFramework private: string m_defaultInterface; mutable std::mutex m_defaultInterfaceMutex; + std::map, IpFamilyCache> m_ipCacheMap; + mutable std::mutex m_ipCacheMutex; }; } } diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 4387c356..2a42802c 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -76,6 +76,206 @@ namespace WPEFramework } } + /* Refresh the per-interface/per-family IP cache from current libnm state and + emit acquired/lost events for address-set differences. + + Build a fresh IpFamilyCache from current libnm state for one device/family, + swap it into _instance under the cache mutex, then emit acquired/lost events + for any address-set differences outside the lock. */ + static void refreshIpFamilyCache(NMDevice* device, bool isIPv6) + { + if (!device || !NM_IS_DEVICE(device) || !_instance) + return; + + const char* iface = nm_device_get_iface(device); + if (!iface) return; + std::string ifname = iface; + + bool isEth = (ifname == nmUtils::ethIface()); + bool isWlan = (ifname == nmUtils::wlanIface()); + if (!isEth && !isWlan) return; + + /* Build the new snapshot locally (no locks held during NM calls). + * Skip the NM read when the device is in a disconnected/down state + * so that newCache stays empty and the diff emits IP_LOST for every + * address still in the cache. This also prevents spurious + * "IP acquired" events from intermediate NM signals (nameserver, + * gateway clearing) that fire after the cache has been emptied + * but before NM clears addresses on the config object. */ + NMDeviceState devState = nm_device_get_state(device); + bool skipRead = (devState <= NM_DEVICE_STATE_DISCONNECTED); + IpFamilyCache newCache; + NMActiveConnection* conn = skipRead ? nullptr : nm_device_get_active_connection(device); + if (conn) { + /* autoconfig: method "auto" or "dhcp" → true */ + NMConnection* nmConn = NM_CONNECTION(nm_active_connection_get_connection(conn)); + if (nmConn) { + NMSettingIPConfig* ipSetting = isIPv6 + ? NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(nmConn)) + : NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(nmConn)); + if (ipSetting) { + const char* method = nm_setting_ip_config_get_method(ipSetting); + newCache.autoconfig = method && + (g_strcmp0(method, "auto") == 0 || g_strcmp0(method, "dhcp") == 0); + } + } + } + + /* IP config read is device-level and does not require an active connection. */ + NMIPConfig* ipConfig = skipRead ? nullptr + : (isIPv6 ? nm_device_get_ip6_config(device) + : nm_device_get_ip4_config(device)); + + if (ipConfig) { + GPtrArray* ipAddresses = nm_ip_config_get_addresses(ipConfig); + std::string macAddr; + if (isIPv6) { + const char* hw = nm_device_get_hw_address(device); + if (hw) macAddr = hw; + } + if (ipAddresses) { + for (guint i = 0; i < ipAddresses->len; i++) { + NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(ipAddresses, i); + if (!addr) continue; + const char* addrStr = nm_ip_address_get_address(addr); + if (!addrStr) continue; + std::string addrString = addrStr; + uint32_t prefix = nm_ip_address_get_prefix(addr); + if (isIPv6) { + if (isIPv6LinkLocal(addrString)) { + newCache.linkLocalAddresses.insert(addrString); + } else if (isIPv6ULA(addrString)) { + newCache.uniqueLocalAddresses.insert(addrString); + } else { + GlobalAddressType type = (!macAddr.empty() && isIPv6MacBased(addrString, macAddr)) + ? ADDR_GLOBAL_MAC_BASED : ADDR_GLOBAL; + newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, type}); + } + } else { + if (isIPv4LinkLocal(addrString)) { + newCache.linkLocalAddresses.insert(addrString); + } else { + newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, ADDR_GLOBAL}); + } + } + } + } + + const char* gw = nm_ip_config_get_gateway(ipConfig); + if (gw) newCache.gateway = gw; + + const char* const* dnsArr = nm_ip_config_get_nameservers(ipConfig); + if (dnsArr && dnsArr[0]) { + newCache.primarydns = dnsArr[0]; + if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; + } + + if (conn) { + 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; + } + + /* Swap new snapshot into instance cache; collect old global address keys for diff. */ + std::set oldKeys = _instance->swapIpCache( + ifname, isIPv6 ? "IPv6" : "IPv4", newCache); + + /* Emit address acquired/lost events from global-address key diff (outside the lock). */ + std::string family = isIPv6 ? "IPv6" : "IPv4"; + for (const auto& kv : newCache.globalAddresses) { + if (oldKeys.find(kv.first) == oldKeys.end()) { + _instance->ReportIPAddressChange(ifname, family, kv.first, Exchange::INetworkManager::IP_ACQUIRED); + } + } + for (const auto& key : oldKeys) { + if (newCache.globalAddresses.find(key) == newCache.globalAddresses.end()) { + _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); + } + } + } + + static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, false); + } + + static void ip6ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, true); + } + + /* Called when DHCP options change mid-lease (e.g. renewed with different server/options). */ + static void dhcp4OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, false); + } + + static void dhcp6OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) + { + NMDevice *device = (NMDevice*)userData; + if (!device || !NM_IS_DEVICE(device)) return; + refreshIpFamilyCache(device, true); + } + + /* Called when the ip4-config or ip6-config object on a device is replaced + (e.g. after reconnect). Re-attaches notify handlers to the new object. */ + static void ip4ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) + { + if (!device || !NM_IS_DEVICE(device)) return; + NMIPConfig* ipv4Config = nm_device_get_ip4_config(device); + if (ipv4Config) { + g_signal_handlers_disconnect_by_func(ipv4Config, (gpointer)ip4ChangedCb, device); + g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); + g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); + } + /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ + 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); + } + + static void ip6ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) + { + if (!device || !NM_IS_DEVICE(device)) return; + NMIPConfig* ipv6Config = nm_device_get_ip6_config(device); + if (ipv6Config) { + g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); + g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); + g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); + } + /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ + 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); + } + void GnomeNetworkManagerEvents::deviceStateChangeCb(NMDevice *device, GParamSpec *pspec, NMEvents *nmEvents) { static bool isEthDisabled = false; @@ -273,208 +473,6 @@ namespace WPEFramework } } - /* 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 (!device || !NM_IS_DEVICE(device) || !_instance) - return; - - const char* iface = nm_device_get_iface(device); - if (!iface) return; - std::string ifname = iface; - - bool isEth = (ifname == nmUtils::ethIface()); - bool isWlan = (ifname == nmUtils::wlanIface()); - if (!isEth && !isWlan) return; - - /* Build the new snapshot locally (no locks held during NM calls). - * Skip the NM read when the device is in a disconnected/down state - * so that newCache stays empty and the diff emits IP_LOST for every - * address still in the cache. This also prevents spurious - * "IP acquired" events from intermediate NM signals (nameserver, - * gateway clearing) that fire after the cache has been emptied - * but before NM clears addresses on the config object. */ - NMDeviceState devState = nm_device_get_state(device); - bool skipRead = (devState <= NM_DEVICE_STATE_DISCONNECTED); - IpFamilyCache newCache; - NMActiveConnection* conn = skipRead ? nullptr : nm_device_get_active_connection(device); - if (conn) { - /* autoconfig: method "auto" or "dhcp" → true */ - NMConnection* nmConn = NM_CONNECTION(nm_active_connection_get_connection(conn)); - if (nmConn) { - NMSettingIPConfig* ipSetting = isIPv6 - ? NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip6_config(nmConn)) - : NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(nmConn)); - if (ipSetting) { - const char* method = nm_setting_ip_config_get_method(ipSetting); - newCache.autoconfig = method && - (g_strcmp0(method, "auto") == 0 || g_strcmp0(method, "dhcp") == 0); - } - } - } - - /* IP config read is device-level and does not require an active connection. */ - NMIPConfig* ipConfig = skipRead ? nullptr - : (isIPv6 ? nm_device_get_ip6_config(device) - : nm_device_get_ip4_config(device)); - - if (ipConfig) { - GPtrArray* ipAddresses = nm_ip_config_get_addresses(ipConfig); - std::string macAddr; - if (isIPv6) { - const char* hw = nm_device_get_hw_address(device); - if (hw) macAddr = hw; - } - if (ipAddresses) { - for (guint i = 0; i < ipAddresses->len; i++) { - NMIPAddress* addr = (NMIPAddress*)g_ptr_array_index(ipAddresses, i); - if (!addr) continue; - const char* addrStr = nm_ip_address_get_address(addr); - if (!addrStr) continue; - std::string addrString = addrStr; - uint32_t prefix = nm_ip_address_get_prefix(addr); - if (isIPv6) { - if (isIPv6LinkLocal(addrString)) { - newCache.linkLocalAddresses.insert(addrString); - } else if (isIPv6ULA(addrString)) { - newCache.uniqueLocalAddresses.insert(addrString); - } else { - GlobalAddressType type = (!macAddr.empty() && isIPv6MacBased(addrString, macAddr)) - ? ADDR_GLOBAL_MAC_BASED : ADDR_GLOBAL; - newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, type}); - } - } else { - if (isIPv4LinkLocal(addrString)) { - newCache.linkLocalAddresses.insert(addrString); - } else { - newCache.globalAddresses.emplace(addrString, GlobalAddressInfo{prefix, ADDR_GLOBAL}); - } - } - } - } - - const char* gw = nm_ip_config_get_gateway(ipConfig); - if (gw) newCache.gateway = gw; - - const char* const* dnsArr = nm_ip_config_get_nameservers(ipConfig); - if (dnsArr && dnsArr[0]) { - newCache.primarydns = dnsArr[0]; - if (dnsArr[1]) newCache.secondarydns = dnsArr[1]; - } - - if (conn) { - 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; - } - - /* Swap new snapshot into instance cache under mutex; collect old global address keys. */ - std::set oldKeys; - { - std::lock_guard lock(_instance->m_ipCacheMutex); - IpFamilyCache& cache = _instance->getIpCache(ifname, isIPv6 ? "IPv6" : "IPv4"); - for (const auto& kv : cache.globalAddresses) oldKeys.insert(kv.first); - cache = newCache; - } - - /* Emit address acquired/lost events from global-address key diff (outside the lock). */ - std::string family = isIPv6 ? "IPv6" : "IPv4"; - for (const auto& kv : newCache.globalAddresses) { - if (oldKeys.find(kv.first) == oldKeys.end()) { - _instance->ReportIPAddressChange(ifname, family, kv.first, Exchange::INetworkManager::IP_ACQUIRED); - } - } - for (const auto& key : oldKeys) { - if (newCache.globalAddresses.find(key) == newCache.globalAddresses.end()) { - _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); - } - } - } - - static void ip4ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) - { - NMDevice *device = (NMDevice*)userData; - if (!device || !NM_IS_DEVICE(device)) return; - refreshIpFamilyCache(device, false); - } - - static void ip6ChangedCb(NMIPConfig *ipConfig, GParamSpec *pspec, gpointer userData) - { - NMDevice *device = (NMDevice*)userData; - if (!device || !NM_IS_DEVICE(device)) return; - refreshIpFamilyCache(device, true); - } - - /* Called when DHCP options change mid-lease (e.g. renewed with different server/options). */ - static void dhcp4OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) - { - NMDevice *device = (NMDevice*)userData; - if (!device || !NM_IS_DEVICE(device)) return; - refreshIpFamilyCache(device, false); - } - - static void dhcp6OptionsCb(NMDhcpConfig *dhcpConfig, GParamSpec *pspec, gpointer userData) - { - NMDevice *device = (NMDevice*)userData; - if (!device || !NM_IS_DEVICE(device)) return; - refreshIpFamilyCache(device, true); - } - - /* Called when the ip4-config or ip6-config object on a device is replaced - (e.g. after reconnect). Re-attaches notify handlers to the new object. */ - static void ip4ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) - { - if (!device || !NM_IS_DEVICE(device)) return; - NMIPConfig* ipv4Config = nm_device_get_ip4_config(device); - if (ipv4Config) { - g_signal_handlers_disconnect_by_func(ipv4Config, (gpointer)ip4ChangedCb, device); - g_signal_connect(ipv4Config, "notify::addresses", G_CALLBACK(ip4ChangedCb), device); - g_signal_connect(ipv4Config, "notify::gateway", G_CALLBACK(ip4ChangedCb), device); - g_signal_connect(ipv4Config, "notify::nameservers", G_CALLBACK(ip4ChangedCb), device); - } - /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ - 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); - } - - static void ip6ConfigChangedCb(NMDevice *device, GParamSpec *pspec, gpointer userData) - { - if (!device || !NM_IS_DEVICE(device)) return; - NMIPConfig* ipv6Config = nm_device_get_ip6_config(device); - if (ipv6Config) { - g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); - g_signal_connect(ipv6Config, "notify::addresses", G_CALLBACK(ip6ChangedCb), device); - g_signal_connect(ipv6Config, "notify::gateway", G_CALLBACK(ip6ChangedCb), device); - g_signal_connect(ipv6Config, "notify::nameservers", G_CALLBACK(ip6ChangedCb), device); - } - /* Re-attach DHCP options handler to the (possibly new) DHCP config object. */ - 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); - } - static void deviceAddedCB(NMClient *client, NMDevice *device, NMEvents *nmEvents) { if( ((device != NULL) && NM_IS_DEVICE(device)) ) @@ -520,6 +518,11 @@ namespace WPEFramework g_signal_connect(dhcp6Added, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); } + /* Seed the IP cache so GetIPSettings works immediately if the + device already has an address (e.g. hotplug in activated state). */ + refreshIpFamilyCache(device, false); + refreshIpFamilyCache(device, true); + if (NM_IS_DEVICE_WIFI(device)) { // Register signal handler for WiFi scanning events to detect when scan operations complete @@ -540,14 +543,48 @@ namespace WPEFramework std::string ifname = nm_device_get_iface(device); if(ifname == nmUtils::wlanIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::wlanIface()); - g_signal_handlers_disconnect_by_func(device, (gpointer)GnomeNetworkManagerEvents::deviceStateChangeCb, nmEvents); NMLOG_INFO("WIFI device removed: %s", ifname.c_str()); } else if(ifname == nmUtils::ethIface()) { GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_REMOVED, nmUtils::ethIface()); - g_signal_handlers_disconnect_by_func(device, (gpointer)GnomeNetworkManagerEvents::deviceStateChangeCb, nmEvents); NMLOG_INFO("ETHERNET device removed: %s", ifname.c_str()); } + else { + return; // not a tracked interface + } + + /* Disconnect all device-level signals (state, ip4/ip6-config changes). */ + g_signal_handlers_disconnect_by_data(device, nmEvents); + + /* Disconnect IP config property signals (addresses, gateway, nameservers). */ + NMIPConfig *ipv4Config = nm_device_get_ip4_config(device); + NMIPConfig *ipv6Config = nm_device_get_ip6_config(device); + if (ipv4Config) + g_signal_handlers_disconnect_by_func(ipv4Config, (gpointer)ip4ChangedCb, device); + if (ipv6Config) + g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); + + /* Disconnect DHCP option signals. */ + NMActiveConnection* conn = nm_device_get_active_connection(device); + if (conn) { + NMDhcpConfig* dhcp4 = nm_active_connection_get_dhcp4_config(conn); + NMDhcpConfig* dhcp6 = nm_active_connection_get_dhcp6_config(conn); + if (dhcp4) + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + if (dhcp6) + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); + } + + /* Clear IP cache for the removed device (emits IP_LOST for any cached addresses). */ + if (_instance) { + for (const char* family : {"IPv4", "IPv6"}) { + IpFamilyCache empty; + std::set oldKeys = _instance->swapIpCache(ifname, family, empty); + for (const auto& key : oldKeys) { + _instance->ReportIPAddressChange(ifname, family, key, Exchange::INetworkManager::IP_LOST); + } + } + } } // guint disconnected_count = g_signal_handlers_disconnect_matched( _nmEventInstance->activeConn, @@ -747,6 +784,17 @@ namespace WPEFramework if (ipv6Config) { g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); } + + // Clean up DHCP option signals + NMActiveConnection* conn = nm_device_get_active_connection(device); + if (conn) { + NMDhcpConfig* dhcp4 = nm_active_connection_get_dhcp4_config(conn); + NMDhcpConfig* dhcp6 = nm_active_connection_get_dhcp6_config(conn); + if (dhcp4) + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + if (dhcp6) + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); + } } } } diff --git a/plugin/gnome/NetworkManagerGnomeEvents.h b/plugin/gnome/NetworkManagerGnomeEvents.h index 8f9ad429..bf282de7 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.h +++ b/plugin/gnome/NetworkManagerGnomeEvents.h @@ -39,11 +39,6 @@ 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 20211393..d124461d 100644 --- a/plugin/gnome/NetworkManagerGnomeProxy.cpp +++ b/plugin/gnome/NetworkManagerGnomeProxy.cpp @@ -674,20 +674,21 @@ namespace WPEFramework string ipversionStr = ipversion.empty() ? "IPv4" : ipversion; std::string family = nmUtils::caseInsensitiveCompare(ipversionStr, "IPv6") ? "IPv6" : "IPv4"; + result = IPAddress{}; + result.ipversion = family; + // Serve from event-driven cache + if (lookupIpCache(interface, family, result)) { - std::lock_guard lock(m_ipCacheMutex); - auto it = m_ipCacheMap.find({interface, family}); - if (it != m_ipCacheMap.end() && it->second.valid) - { - NMLOG_DEBUG("%s %s address from cache", interface.c_str(), family.c_str()); - result = it->second.toIPAddress(); - return Core::ERROR_NONE; - } + NMLOG_DEBUG("%s %s address from cache", interface.c_str(), family.c_str()); + result.ipversion = family; + } + else + { + NMLOG_DEBUG("no %s address on %s", family.c_str(), interface.c_str()); } - NMLOG_WARNING("IP cache not populated for %s %s", interface.c_str(), family.c_str()); - return Core::ERROR_GENERAL; + return Core::ERROR_NONE; } /* @brief Set IP Address Of the Interface */ diff --git a/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp b/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp index c587688a..8ad14f24 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp @@ -208,31 +208,6 @@ TEST_F(NetworkManagerEventTest, onInterfaceStateChangeCb) WPEFramework::Plugin::GnomeNetworkManagerEvents::onInterfaceStateChangeCb(Exchange::INetworkManager::INTERFACE_DISABLED, "eth0"); } -TEST_F(NetworkManagerEventTest, onAddressChangeCb) -{ - // Test acquiring IPv4 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "192.168.1.100", true, false); - - // Test acquiring IPv6 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "2001:db8::1", true, true); - - // Test acquiring same IPv6 address again (should skip posting) - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "2001:db8::1", true, true); - - // Test acquiring different IPv6 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "2001:db8::2", true, true); - - // Test losing IPv4 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "", false, false); - - // Test losing IPv6 address - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth0", "", false, true); - - // Test losing IP on interface with empty cache (should skip posting) - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth1", "", false, false); - WPEFramework::Plugin::GnomeNetworkManagerEvents::onAddressChangeCb("eth1", "", false, true); -} - TEST_F(NetworkManagerEventTest, onAvailableSSIDsCb) { GPtrArray* fakeDevices = g_ptr_array_new(); From 2e789900276d9f22ebf8421409a9001fdf2198f5 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Thu, 28 May 2026 21:44:00 +0530 Subject: [PATCH 15/17] fix(gnome): use device-level DHCP config API to fix empty dhcpserver in GetIPSettings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ActiveConnection's Dhcp4Config property is not populated until the connection reaches ACTIVATED state, but ip4ChangedCb fires earlier (when addresses appear on the IP config object). At that point, nm_active_connection_get_dhcp4_config() returns NULL, causing refreshIpFamilyCache() to store an empty dhcpserver in the cache. Switch to nm_device_get_dhcp4_config() / nm_device_get_dhcp6_config() which read from the Device object's Dhcp4Config property. This property is set when the device enters IP_CONFIG state and its options are populated before the IP address appears — ensuring dhcpserver is available when the cache is built. Apply the same fix to DHCP signal handler connections in ip4ConfigChangedCb, ip6ConfigChangedCb, deviceAddedCB, networkMangerEventMonitor, deviceRemovedCB, and cleanupSignalHandlers. --- plugin/gnome/NetworkManagerGnomeEvents.cpp | 96 +++++++++------------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/plugin/gnome/NetworkManagerGnomeEvents.cpp b/plugin/gnome/NetworkManagerGnomeEvents.cpp index 2a42802c..7a87cc20 100644 --- a/plugin/gnome/NetworkManagerGnomeEvents.cpp +++ b/plugin/gnome/NetworkManagerGnomeEvents.cpp @@ -170,14 +170,12 @@ namespace WPEFramework 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); - if (dhcpConfig) { - const char* server = nm_dhcp_config_get_one_option(dhcpConfig, "dhcp_server_identifier"); - if (server) newCache.dhcpserver = server; - } + NMDhcpConfig* dhcpConfig = isIPv6 + ? nm_device_get_dhcp6_config(device) + : nm_device_get_dhcp4_config(device); + if (dhcpConfig) { + const char* server = nm_dhcp_config_get_one_option(dhcpConfig, "dhcp_server_identifier"); + if (server) newCache.dhcpserver = server; } newCache.valid = true; @@ -243,13 +241,10 @@ namespace WPEFramework 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); - } + NMDhcpConfig* dhcp4 = nm_device_get_dhcp4_config(device); + if (dhcp4) { + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + g_signal_connect(dhcp4, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); } refreshIpFamilyCache(device, false); } @@ -265,13 +260,10 @@ namespace WPEFramework 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); - } + NMDhcpConfig* dhcp6 = nm_device_get_dhcp6_config(device); + if (dhcp6) { + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); + g_signal_connect(dhcp6, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); } refreshIpFamilyCache(device, true); } @@ -508,15 +500,12 @@ namespace WPEFramework } /* 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); - } + NMDhcpConfig* dhcp4Added = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6Added = nm_device_get_dhcp6_config(device); + if (dhcp4Added) + g_signal_connect(dhcp4Added, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + if (dhcp6Added) + g_signal_connect(dhcp6Added, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); /* Seed the IP cache so GetIPSettings works immediately if the device already has an address (e.g. hotplug in activated state). */ @@ -565,15 +554,12 @@ namespace WPEFramework g_signal_handlers_disconnect_by_func(ipv6Config, (gpointer)ip6ChangedCb, device); /* Disconnect DHCP option signals. */ - NMActiveConnection* conn = nm_device_get_active_connection(device); - if (conn) { - NMDhcpConfig* dhcp4 = nm_active_connection_get_dhcp4_config(conn); - NMDhcpConfig* dhcp6 = nm_active_connection_get_dhcp6_config(conn); - if (dhcp4) - g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); - if (dhcp6) - g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); - } + NMDhcpConfig* dhcp4 = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6 = nm_device_get_dhcp6_config(device); + if (dhcp4) + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + if (dhcp6) + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); /* Clear IP cache for the removed device (emits IP_LOST for any cached addresses). */ if (_instance) { @@ -690,15 +676,12 @@ namespace WPEFramework 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); - } + NMDhcpConfig* dhcp4Init = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6Init = nm_device_get_dhcp6_config(device); + if (dhcp4Init) + g_signal_connect(dhcp4Init, "notify::options", G_CALLBACK(dhcp4OptionsCb), device); + if (dhcp6Init) + g_signal_connect(dhcp6Init, "notify::options", G_CALLBACK(dhcp6OptionsCb), device); /* Seed the IP cache from current state for already-connected devices. */ refreshIpFamilyCache(device, false); @@ -786,15 +769,12 @@ namespace WPEFramework } // Clean up DHCP option signals - NMActiveConnection* conn = nm_device_get_active_connection(device); - if (conn) { - NMDhcpConfig* dhcp4 = nm_active_connection_get_dhcp4_config(conn); - NMDhcpConfig* dhcp6 = nm_active_connection_get_dhcp6_config(conn); - if (dhcp4) - g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); - if (dhcp6) - g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); - } + NMDhcpConfig* dhcp4 = nm_device_get_dhcp4_config(device); + NMDhcpConfig* dhcp6 = nm_device_get_dhcp6_config(device); + if (dhcp4) + g_signal_handlers_disconnect_by_func(dhcp4, (gpointer)dhcp4OptionsCb, device); + if (dhcp6) + g_signal_handlers_disconnect_by_func(dhcp6, (gpointer)dhcp6OptionsCb, device); } } } From 5fcde2082fd98be8a0a59c0e15e94237f7205e86 Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Wed, 3 Jun 2026 21:21:57 +0530 Subject: [PATCH 16/17] build(backends): gate legacy IP members and export backend macros --- CMakeLists.txt | 8 ++++++++ plugin/NetworkManagerImplementation.h | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a04ad16b..8c9e1d74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,6 +56,14 @@ option(USE_RDK_LOGGER "Enable RDK Logger for logging" OFF ) option(ENABLE_UNIT_TESTING "Enable unit tests" OFF) option(USE_TELEMETRY "Enable Telemetry T2 support" OFF) +# Backend identity macros are consumed by shared headers; define them globally +# so all targets (plugin, tests, tools) compile against the same API surface. +if(ENABLE_GNOME_NETWORKMANAGER AND ENABLE_GNOME_GDBUS) + add_compile_definitions(NM_BACKEND_GDBUS=1) +elseif(NOT ENABLE_GNOME_NETWORKMANAGER) + add_compile_definitions(NM_BACKEND_RDK=1) +endif() + if (USE_TELEMETRY) find_package(T2 REQUIRED) add_compile_definitions(USE_TELEMETRY=1) diff --git a/plugin/NetworkManagerImplementation.h b/plugin/NetworkManagerImplementation.h index d9f996a5..b55aeb2d 100644 --- a/plugin/NetworkManagerImplementation.h +++ b/plugin/NetworkManagerImplementation.h @@ -358,6 +358,12 @@ namespace WPEFramework std::mutex m_condVariableMutex; std::condition_variable m_condVariable; public: +#if defined(NM_BACKEND_GDBUS) || defined(NM_BACKEND_RDK) + IPAddress m_ethIPv4Address; + IPAddress m_wlanIPv4Address; + IPAddress m_ethIPv6Address; + IPAddress m_wlanIPv6Address; +#endif bool lookupIpCache(const std::string& iface, const std::string& ipFamily, Exchange::INetworkManager::IPAddress& out) const; std::set swapIpCache(const std::string& iface, From 0d02c210e12cd9912641d7ca9ecce4e50039be0b Mon Sep 17 00:00:00 2001 From: Tony Ukken Date: Thu, 4 Jun 2026 14:31:29 +0530 Subject: [PATCH 17/17] test(libnm): update L1 tests for cache-based GetIPSettings and refreshIpFamilyCache - Remove 5 GetIPSettings error-path tests that exercised libnm API call sequences no longer present in the cache-based implementation - Replace 5 GetIPSettings data tests with cache-based equivalents that populate IpFamilyCache via swapIpCache() and verify the JSON-RPC response - Fix 5 event tests (disconnected/unmanaged/unknown for wlan0 and eth0) by changing nm_device_get_iface and nm_device_get_state from WillOnce to WillRepeatedly to accommodate additional calls from refreshIpFamilyCache() - Fix platformInit test: GetIPSettings now returns success:true with empty data for valid interfaces when the cache is empty --- tests/l2Test/libnm/l2_test_libnmproxy.cpp | 389 ++++-------------- .../l2Test/libnm/l2_test_libnmproxyEvent.cpp | 20 +- tests/l2Test/libnm/l2_test_libnmproxyInit.cpp | 2 +- 3 files changed, 92 insertions(+), 319 deletions(-) diff --git a/tests/l2Test/libnm/l2_test_libnmproxy.cpp b/tests/l2Test/libnm/l2_test_libnmproxy.cpp index e4f9b1ec..ea34fd2d 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxy.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxy.cpp @@ -515,24 +515,15 @@ TEST_F(NetworkManagerTest, GetIPSettings_unknown_iface) EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_invalidDevice) +TEST_F(NetworkManagerTest, GetIPSettings_emptyCache) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); -} - -TEST_F(NetworkManagerTest, GetIPSettings_invalid_state) -{ - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); - + /* With no cache populated, GetIPSettings should still succeed but return no IP data */ EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv4\"") != std::string::npos); + /* No ipaddress key when cache is empty */ + EXPECT_TRUE(response.find("\"ipaddress\"") == std::string::npos); } TEST_F(NetworkManagerTest, GetIPSettings_interface_Empty) @@ -548,254 +539,91 @@ TEST_F(NetworkManagerTest, GetIPSettings_GetPrimary_failed) EXPECT_EQ(Core::ERROR_GENERAL, NetworkManagerImpl2->GetIPSettings(interface, ipversion, address)); } -TEST_F(NetworkManagerTest, GetIPSettings_Invalid_ActiveConnection) +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_fromCache) { - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate IPv4 cache for eth0, then verify GetIPSettings returns cached data */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.globalAddresses["192.168.1.2"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.1"; + cache.primarydns = "8.8.8.8"; + cache.secondarydns = "8.8.4.4"; + cache.dhcpserver = "192.168.1.11"; + cache.autoconfig = true; + NetworkManagerImpl->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); -} - -TEST_F(NetworkManagerTest, GetIPSettings_Invalid_Connection) -{ - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *nullConnection = static_cast(NULL); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, nullConnection); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); -} - -TEST_F(NetworkManagerTest, GetIPSettings_valid_ConnectionSettingsEmpty) -{ - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - EXPECT_TRUE(response.find("\"success\":false") != std::string::npos); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipversion\":\"IPv4\"") != std::string::npos); + EXPECT_TRUE(response.find("\"autoconfig\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.2\"") != std::string::npos); + EXPECT_TRUE(response.find("\"prefix\":24") != std::string::npos); + EXPECT_TRUE(response.find("\"gateway\":\"192.168.1.1\"") != std::string::npos); + EXPECT_TRUE(response.find("\"primarydns\":\"8.8.8.8\"") != std::string::npos); + EXPECT_TRUE(response.find("\"secondarydns\":\"8.8.4.4\"") != std::string::npos); + EXPECT_TRUE(response.find("\"dhcpserver\":\"192.168.1.11\"") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config) +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_autoconfig) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate IPv4 cache with autoconfig=true but no IP address */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + NetworkManagerImpl->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); std::string expectedResponse = _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_configAutoConftrue) +TEST_F(NetworkManagerTest, GetIPSettings_ipv4_staticConfig) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_ip_config_get_method(::testing::_)) - .WillOnce(::testing::Return("auto")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(retConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate IPv4 cache with autoconfig=false (static config) */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = false; + cache.globalAddresses["192.168.1.100"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.1"; + NetworkManagerImpl->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - std::string expectedResponse = - _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); - EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"autoconfig\":false") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"192.168.1.100\"") != std::string::npos); } -TEST_F(NetworkManagerTest, GetIPSettings_ipv4_configAutoConfNull) +TEST_F(NetworkManagerTest, GetIPSettings_wlan0_fromCache) { - GPtrArray* dummyActiveConn = g_ptr_array_new(); - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_ip_config_get_method(::testing::_)) - .WillOnce(::testing::Return("not auto")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(retConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); - - EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); - std::string expectedResponse = - _T("{\"interface\":\"eth0\",\"ipversion\":\"IPv4\",\"autoconfig\":true,\"success\":true}"); - EXPECT_EQ(response, expectedResponse); - - g_object_unref(ethActiveConn); - g_ptr_array_free(dummyActiveConn, TRUE); + /* Populate IPv4 cache for wlan0 */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["10.0.0.5"] = Plugin::GlobalAddressInfo(8, Plugin::ADDR_GLOBAL); + cache.gateway = "10.0.0.1"; + cache.primarydns = "1.1.1.1"; + NetworkManagerImpl->swapIpCache("wlan0", "IPv4", cache); + + EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"wlan0\"}"), response)); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); + EXPECT_TRUE(response.find("\"interface\":\"wlan0\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ipaddress\":\"10.0.0.5\"") != std::string::npos); } TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config_valid) { - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - NMIPAddress* ipv4Addr = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - - GPtrArray* dummyActiveConn = g_ptr_array_new(); - GPtrArray* ipvAddr = g_ptr_array_new(); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - g_ptr_array_add(ipvAddr, ipv4Addr); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_dhcp_config_get_one_option(::testing::_, ::testing::_)) - .WillOnce(::testing::Return("192.168.1.11")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_dhcp4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100170))); - - const char* fakeDnsServers[] = {"8.8.8.8", "8.8.4.4", nullptr}; - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_nameservers(::testing::_)) - .WillOnce(::testing::Return(fakeDnsServers)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_gateway(::testing::_)) - .WillOnce(::testing::Return("192.168.1.0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_address(::testing::_)) - .WillOnce(::testing::Return("192.168.1.2")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_addresses(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(ipvAddr))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip4_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100171))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate full IPv4 cache for eth0 and verify all fields */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["192.168.1.2"] = Plugin::GlobalAddressInfo(24, Plugin::ADDR_GLOBAL); + cache.gateway = "192.168.1.0"; + cache.primarydns = "8.8.8.8"; + cache.secondarydns = "8.8.4.4"; + cache.dhcpserver = "192.168.1.11"; + NetworkManagerImpl->swapIpCache("eth0", "IPv4", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\"}"), response)); @@ -807,70 +635,21 @@ TEST_F(NetworkManagerTest, GetIPSettings_ipv4_config_valid) EXPECT_TRUE(response.find("\"ula\":\"\"") != std::string::npos); EXPECT_TRUE(response.find("\"dhcpserver\":\"192.168.1.11\"") != std::string::npos); EXPECT_TRUE(response.find("\"gateway\":\"192.168.1.0\"") != std::string::npos); - - g_object_unref(ethActiveConn); - g_object_unref(retConn); - g_object_unref(ipv4Addr); - g_ptr_array_free(dummyActiveConn, TRUE); - g_ptr_array_free(ipvAddr, TRUE); } TEST_F(NetworkManagerTest, GetIPSettings_ipv6_config_valid) { - NMActiveConnection *ethActiveConn = static_cast(g_object_new(NM_TYPE_ACTIVE_CONNECTION, NULL)); - NMRemoteConnection* retConn = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - NMIPAddress* ipv6Addr = static_cast(g_object_new(NM_TYPE_REMOTE_CONNECTION, NULL)); - - GPtrArray* dummyActiveConn = g_ptr_array_new(); - GPtrArray* ipvAddr = g_ptr_array_new(); - g_ptr_array_add(dummyActiveConn, ethActiveConn); - g_ptr_array_add(ipvAddr, ipv6Addr); - g_ptr_array_add(ipvAddr, reinterpret_cast(0x100176)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_dhcp_config_get_one_option(::testing::_, ::testing::_)) - .WillOnce(::testing::Return("2001:db8::1")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_dhcp6_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100170))); - - const char* fakeDnsServers[] = {"2001:4860:4860::8888", "2001:4860:4860::8844", nullptr}; - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_nameservers(::testing::_)) - .WillOnce(::testing::Return(fakeDnsServers)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_gateway(::testing::_)) - .WillOnce(::testing::Return("2001:4860:4860::1")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_prefix(::testing::_)) - .WillOnce(::testing::Return(64)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_address_get_address(::testing::_)) - .WillOnce(::testing::Return("2001:db8:1:2:3:4:5:6")) - .WillOnce(::testing::Return("fe80::1234:5678:abcd:ef01")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_ip_config_get_addresses(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(ipvAddr))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_ip6_config(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100171))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_setting_connection_get_interface_name(::testing::_)) - .WillOnce(::testing::Return("eth0")); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_connection_get_setting_connection(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100173))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_active_connection_get_connection(::testing::_)) - .WillOnce(::testing::Return(retConn)) - .WillOnce(::testing::Return(reinterpret_cast(NULL))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_ACTIVATED)); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_active_connections(::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(dummyActiveConn))); - - EXPECT_CALL(*p_libnmWrapsImplMock, nm_client_get_device_by_iface(::testing::_, ::testing::_)) - .WillOnce(::testing::Return(reinterpret_cast(0x100178))); + /* Populate full IPv6 cache for eth0 and verify all fields */ + Plugin::IpFamilyCache cache; + cache.valid = true; + cache.autoconfig = true; + cache.globalAddresses["2001:db8:1:2:3:4:5:6"] = Plugin::GlobalAddressInfo(64, Plugin::ADDR_GLOBAL); + cache.uniqueLocalAddresses.insert("fd12::1234:5678:abcd:ef01"); + cache.gateway = "2001:4860:4860::1"; + cache.primarydns = "2001:4860:4860::8888"; + cache.secondarydns = "2001:4860:4860::8844"; + cache.dhcpserver = "2001:db8::1"; + NetworkManagerImpl->swapIpCache("eth0", "IPv6", cache); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"eth0\", \"ipversion\":\"IPv6\"}"), response)); @@ -880,16 +659,10 @@ TEST_F(NetworkManagerTest, GetIPSettings_ipv6_config_valid) EXPECT_TRUE(response.find("\"primarydns\":\"2001:4860:4860::8888\"") != std::string::npos); EXPECT_TRUE(response.find("\"interface\":\"eth0\"") != std::string::npos); EXPECT_TRUE(response.find("\"ipaddress\":\"2001:db8:1:2:3:4:5:6\"") != std::string::npos); - EXPECT_TRUE(response.find("\"ula\":\"fe80::1234:5678:abcd:ef01\"") != std::string::npos); + EXPECT_TRUE(response.find("\"ula\":\"fd12::1234:5678:abcd:ef01\"") != std::string::npos); EXPECT_TRUE(response.find("\"prefix\":64") != std::string::npos); EXPECT_TRUE(response.find("\"dhcpserver\":\"2001:db8::1\"") != std::string::npos); EXPECT_TRUE(response.find("\"gateway\":\"2001:4860:4860::1\"") != std::string::npos); - - g_object_unref(ethActiveConn); - g_object_unref(retConn); - g_object_unref(ipv6Addr); - g_ptr_array_free(dummyActiveConn, TRUE); - g_ptr_array_free(ipvAddr, TRUE); } TEST_F(NetworkManagerTest, SetInterfaceState_deviceFailed_wlan0) diff --git a/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp b/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp index 8ad14f24..a86bd07e 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyEvent.cpp @@ -347,9 +347,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_disconnected) { NMDevice *wifiDummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")); + .WillRepeatedly(::testing::Return("wlan0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(wifiDummyDevice), nullptr, nullptr); @@ -359,9 +359,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_unmanaged) { NMDevice *wifiDummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")); + .WillRepeatedly(::testing::Return("wlan0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(wifiDummyDevice), nullptr, nullptr); @@ -467,9 +467,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_unknown) { NMDevice *wifiDummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_WIFI, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNKNOWN)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_UNKNOWN)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("wlan0")); + .WillRepeatedly(::testing::Return("wlan0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(wifiDummyDevice), nullptr, nullptr); @@ -479,9 +479,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_eth0_unmanaged) { NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_UNMANAGED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("eth0")); + .WillRepeatedly(::testing::Return("eth0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(DummyDevice), nullptr, nullptr); @@ -492,9 +492,9 @@ TEST_F(NetworkManagerEventTest, deviceStateChangeCb_eth0_disconnected) { NMDevice *DummyDevice = static_cast(g_object_new(NM_TYPE_DEVICE_ETHERNET, NULL)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state(::testing::_)) - .WillOnce(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); + .WillRepeatedly(::testing::Return(NM_DEVICE_STATE_DISCONNECTED)); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_iface(::testing::_)) - .WillOnce(::testing::Return("eth0")); + .WillRepeatedly(::testing::Return("eth0")); EXPECT_CALL(*p_libnmWrapsImplMock, nm_device_get_state_reason(::testing::_)) .WillOnce(::testing::Return(NM_DEVICE_STATE_REASON_NONE)); WPEFramework::Plugin::GnomeNetworkManagerEvents::deviceStateChangeCb(reinterpret_cast(DummyDevice), nullptr, nullptr); diff --git a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp index e47f24fb..b550b3d6 100644 --- a/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp +++ b/tests/l2Test/libnm/l2_test_libnmproxyInit.cpp @@ -158,7 +158,7 @@ TEST_F(NetworkManagerInitTest, platformInit) EXPECT_EQ(response, _T("{\"success\":false}")); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("GetIPSettings"), _T("{\"interface\":\"wlan0\"}"), response)); - EXPECT_EQ(response, _T("{\"success\":false}")); + EXPECT_TRUE(response.find("\"success\":true") != std::string::npos); EXPECT_EQ(Core::ERROR_NONE, handler.Invoke(connection, _T("SetHostname"), _T("{\"hostname\":\"test-host\"}"), response)); EXPECT_EQ(response, _T("{\"success\":false}"));