From aecd270c801724dcacf4033119a83b7bdef1023e Mon Sep 17 00:00:00 2001 From: aadhithan01 <64963550+aadhithan01@users.noreply.github.com> Date: Tue, 19 May 2026 17:28:06 +0530 Subject: [PATCH 1/3] Fix: wan_transition_ipv6_down() race condition skipping DHCPv6 self-heal Root Cause: Race condition between interface state machine thread and DHCP event queue worker. When wan_transition_ipv6_down() executes before the 'DHCPv6 client stopped' event is processed, Dhcp6cStatus remains DHCPC_STARTED even though dibbler-client process is dead. Self-heal restart is incorrectly skipped, leaving IPv6 permanently DOWN. Fix: In addition to checking Dhcp6cStatus flag, also verify the actual dibbler-client process is still alive via WanMgr_IsPIDRunning(). If the PID is stale (process dead), restart the client regardless of the status flag state. Signed-off-by: aadhithan01 <64963550+aadhithan01@users.noreply.github.com> --- source/WanManager/wanmgr_interface_sm.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/source/WanManager/wanmgr_interface_sm.c b/source/WanManager/wanmgr_interface_sm.c index c858e368..b44a86c3 100644 --- a/source/WanManager/wanmgr_interface_sm.c +++ b/source/WanManager/wanmgr_interface_sm.c @@ -2927,11 +2927,18 @@ static eWanState_t wan_transition_ipv6_down(WanMgr_IfaceSM_Controller_t* pWanIfa } else { - - if(p_VirtIf->IP.Dhcp6cStatus != DHCPC_STARTED) + /* + * Fix race condition: The "DHCPv6 client stopped" event may not yet be + * processed by the DHCP event queue worker when this transition runs. + * In that case, Dhcp6cStatus is still DHCPC_STARTED even though the + * actual dibbler-client process has already exited. + * Also verify the actual process is still running before skipping restart. + */ + if(p_VirtIf->IP.Dhcp6cStatus != DHCPC_STARTED || + (p_VirtIf->IP.Dhcp6cPid > 0 && WanMgr_IsPIDRunning(p_VirtIf->IP.Dhcp6cPid) != TRUE)) { /* Start DHCPv6 Client */ - CcspTraceInfo(("%s %d - Starting dibbler-client on interface %s \n", __FUNCTION__, __LINE__, p_VirtIf->Name)); + CcspTraceInfo(("%s %d - Starting dibbler-client on interface %s (Dhcp6cStatus=%d, Pid=%d)\n", __FUNCTION__, __LINE__, p_VirtIf->Name, p_VirtIf->IP.Dhcp6cStatus, p_VirtIf->IP.Dhcp6cPid)); WanManager_StartDhcpv6Client(p_VirtIf, pInterface->IfaceType); CcspTraceInfo(("%s %d - SELFHEAL - Started dibbler-client on interface %s\n", __FUNCTION__, __LINE__, p_VirtIf->Name)); } From b876b46bbf6edf867e4bede2ec8a2357334439f9 Mon Sep 17 00:00:00 2001 From: aadhithan01 <64963550+aadhithan01@users.noreply.github.com> Date: Tue, 19 May 2026 18:43:12 +0530 Subject: [PATCH 2/3] Fix: treat Dhcp6cPid<=1 as invalid for self-heal PID check Previous fix used (Dhcp6cPid > 0 && !IsPIDRunning(Pid)) but Dhcp6cPid=1 (placeholder set by self-heal via DHCP Manager) means IsPIDRunning(1) always returns TRUE since PID 1 is init. Self-heal skipped on repeat. Now triggers self-heal when ANY of: 1. Dhcp6cStatus != DHCPC_STARTED 2. Dhcp6cPid <= 1 (invalid/placeholder) 3. Dhcp6cPid > 1 but process dead --- source/WanManager/wanmgr_interface_sm.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/source/WanManager/wanmgr_interface_sm.c b/source/WanManager/wanmgr_interface_sm.c index b44a86c3..47937bfe 100644 --- a/source/WanManager/wanmgr_interface_sm.c +++ b/source/WanManager/wanmgr_interface_sm.c @@ -2932,10 +2932,15 @@ static eWanState_t wan_transition_ipv6_down(WanMgr_IfaceSM_Controller_t* pWanIfa * processed by the DHCP event queue worker when this transition runs. * In that case, Dhcp6cStatus is still DHCPC_STARTED even though the * actual dibbler-client process has already exited. - * Also verify the actual process is still running before skipping restart. + * + * Self-heal conditions - restart DHCPv6 client if ANY of: + * 1. Status is not DHCPC_STARTED (event was processed, client confirmed stopped) + * 2. PID is <= 1 (invalid/placeholder - PID 1 is init, not dhcp6c) + * 3. PID > 1 but process is no longer running (confirmed dead) */ if(p_VirtIf->IP.Dhcp6cStatus != DHCPC_STARTED || - (p_VirtIf->IP.Dhcp6cPid > 0 && WanMgr_IsPIDRunning(p_VirtIf->IP.Dhcp6cPid) != TRUE)) + p_VirtIf->IP.Dhcp6cPid <= 1 || + WanMgr_IsPIDRunning(p_VirtIf->IP.Dhcp6cPid) != TRUE) { /* Start DHCPv6 Client */ CcspTraceInfo(("%s %d - Starting dibbler-client on interface %s (Dhcp6cStatus=%d, Pid=%d)\n", __FUNCTION__, __LINE__, p_VirtIf->Name, p_VirtIf->IP.Dhcp6cStatus, p_VirtIf->IP.Dhcp6cPid)); From b4cb18007fabd30d15f34545e6046212bc44edfb Mon Sep 17 00:00:00 2001 From: aadhithan01 <64963550+aadhithan01@users.noreply.github.com> Date: Mon, 25 May 2026 15:57:51 +0530 Subject: [PATCH 3/3] Fix DHCPv6 self-heal race condition on first disable/enable cycle In FEATURE_RDKB_DHCP_MANAGER builds, when DHCPManager externally stops the DHCPv6 client, the CLIENT_STOPPED event arrives after LEASE_DEL has already triggered wan_transition_ipv6_down. By the time ISM checks Dhcp6cStatus, it is stale (DHCPC_STARTED) causing the self-heal to skip. Fix: In the DHCP_CLIENT_STOPPED event handler, detect external stops by checking if Dhcp6cStatus is still DHCPC_STARTED (WanManager_StopDhcpv6Client sets it to STOPPED synchronously before events arrive). If external, restart the client immediately. Revert wan_transition_ipv6_down to original logic (secondary self-heal for subsequent cycles remains intact). --- source/WanManager/wanmgr_dhcp_event_handler.c | 38 +++++++++++++++++-- source/WanManager/wanmgr_interface_sm.c | 17 +-------- source/WanManager/wanmgr_net_utils.c | 22 ++++++++--- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/source/WanManager/wanmgr_dhcp_event_handler.c b/source/WanManager/wanmgr_dhcp_event_handler.c index 556e4fee..dbc7e562 100644 --- a/source/WanManager/wanmgr_dhcp_event_handler.c +++ b/source/WanManager/wanmgr_dhcp_event_handler.c @@ -24,6 +24,8 @@ #include "wanmgr_dhcp_client_events.h" #include "wanmgr_net_utils.h" #include "wanmgr_dhcpv6_apis.h" + +extern WANMGR_DATA_ST gWanMgrDataBase; #ifdef FEATURE_MAPE #include "wanmgr_map_apis.h" #endif @@ -144,8 +146,22 @@ void WanMgr_ProcessDhcpClientEvent(DhcpEventThreadArgs *eventData) break; case DHCP_CLIENT_STOPPED: - CcspTraceInfo(("%s-%d : DHCPv4 client stopped for %s\n", __FUNCTION__, __LINE__, pVirtIf->Name)); - pVirtIf->IP.Dhcp4cStatus = DHCPC_STOPPED; + CcspTraceInfo(("%s-%d : DHCPv4 client stopped for %s (Dhcp4cStatus=%d)\n", __FUNCTION__, __LINE__, pVirtIf->Name, pVirtIf->IP.Dhcp4cStatus)); + if (pVirtIf->IP.Dhcp4cStatus == DHCPC_STARTED) + { + /* Client was stopped externally (not by WanManager). + * WanManager_StopDhcpv4Client sets Dhcp4cStatus=STOPPED + * synchronously before any event arrives, so DHCPC_STARTED + * here means the stop was NOT initiated by WanManager. + * Restart the client to maintain IPv4 connectivity. */ + pVirtIf->IP.Dhcp4cStatus = DHCPC_STOPPED; + CcspTraceInfo(("%s-%d : SELFHEAL - Restarting DHCPv4 client for %s\n", __FUNCTION__, __LINE__, pVirtIf->Name)); + WanManager_StartDhcpv4Client(pVirtIf, gWanMgrDataBase.IfaceCtrl.pIface[pVirtIf->baseIfIdx].data.Name, gWanMgrDataBase.IfaceCtrl.pIface[pVirtIf->baseIfIdx].data.IfaceType); + } + else + { + pVirtIf->IP.Dhcp4cStatus = DHCPC_STOPPED; + } break; case DHCP_CLIENT_FAILED: @@ -207,8 +223,22 @@ void WanMgr_ProcessDhcpClientEvent(DhcpEventThreadArgs *eventData) break; case DHCP_CLIENT_STOPPED: - CcspTraceInfo(("%s-%d : DHCPv6 client stopped for %s\n", __FUNCTION__, __LINE__, pVirtIf->Name)); - pVirtIf->IP.Dhcp6cStatus = DHCPC_STOPPED; + CcspTraceInfo(("%s-%d : DHCPv6 client stopped for %s (Dhcp6cStatus=%d)\n", __FUNCTION__, __LINE__, pVirtIf->Name, pVirtIf->IP.Dhcp6cStatus)); + if (pVirtIf->IP.Dhcp6cStatus == DHCPC_STARTED) + { + /* Client was stopped externally (not by WanManager). + * WanManager_StopDhcpv6Client sets Dhcp6cStatus=STOPPED + * synchronously before any event arrives, so DHCPC_STARTED + * here means the stop was NOT initiated by WanManager. + * Restart the client to maintain IPv6 connectivity. */ + pVirtIf->IP.Dhcp6cStatus = DHCPC_STOPPED; + CcspTraceInfo(("%s-%d : SELFHEAL - Restarting DHCPv6 client for %s\n", __FUNCTION__, __LINE__, pVirtIf->Name)); + WanManager_StartDhcpv6Client(pVirtIf, gWanMgrDataBase.IfaceCtrl.pIface[pVirtIf->baseIfIdx].data.IfaceType); + } + else + { + pVirtIf->IP.Dhcp6cStatus = DHCPC_STOPPED; + } break; case DHCP_CLIENT_FAILED: diff --git a/source/WanManager/wanmgr_interface_sm.c b/source/WanManager/wanmgr_interface_sm.c index 47937bfe..03eeb95d 100644 --- a/source/WanManager/wanmgr_interface_sm.c +++ b/source/WanManager/wanmgr_interface_sm.c @@ -2927,23 +2927,10 @@ static eWanState_t wan_transition_ipv6_down(WanMgr_IfaceSM_Controller_t* pWanIfa } else { - /* - * Fix race condition: The "DHCPv6 client stopped" event may not yet be - * processed by the DHCP event queue worker when this transition runs. - * In that case, Dhcp6cStatus is still DHCPC_STARTED even though the - * actual dibbler-client process has already exited. - * - * Self-heal conditions - restart DHCPv6 client if ANY of: - * 1. Status is not DHCPC_STARTED (event was processed, client confirmed stopped) - * 2. PID is <= 1 (invalid/placeholder - PID 1 is init, not dhcp6c) - * 3. PID > 1 but process is no longer running (confirmed dead) - */ - if(p_VirtIf->IP.Dhcp6cStatus != DHCPC_STARTED || - p_VirtIf->IP.Dhcp6cPid <= 1 || - WanMgr_IsPIDRunning(p_VirtIf->IP.Dhcp6cPid) != TRUE) + if(p_VirtIf->IP.Dhcp6cStatus != DHCPC_STARTED) { /* Start DHCPv6 Client */ - CcspTraceInfo(("%s %d - Starting dibbler-client on interface %s (Dhcp6cStatus=%d, Pid=%d)\n", __FUNCTION__, __LINE__, p_VirtIf->Name, p_VirtIf->IP.Dhcp6cStatus, p_VirtIf->IP.Dhcp6cPid)); + CcspTraceInfo(("%s %d - Starting dibbler-client on interface %s \n", __FUNCTION__, __LINE__, p_VirtIf->Name)); WanManager_StartDhcpv6Client(p_VirtIf, pInterface->IfaceType); CcspTraceInfo(("%s %d - SELFHEAL - Started dibbler-client on interface %s\n", __FUNCTION__, __LINE__, p_VirtIf->Name)); } diff --git a/source/WanManager/wanmgr_net_utils.c b/source/WanManager/wanmgr_net_utils.c index 44341ca8..82ca93c6 100644 --- a/source/WanManager/wanmgr_net_utils.c +++ b/source/WanManager/wanmgr_net_utils.c @@ -546,17 +546,22 @@ ANSC_STATUS WanManager_StopDhcpv6Client(DML_VIRTUAL_IFACE* pVirtIf, DHCP_RELEASE snprintf( dmlName, sizeof(dmlName), "%s.Enable", pVirtIf->IP.DHCPv6Iface ); snprintf( dmlValue, sizeof(dmlValue), "%s", "false"); } + /* Set status to STOPPED before issuing the stop request to eliminate the + * race where the STOPPED event arrives (and the worker thread processes it) + * before WanMgr_RdkBus_SetParamValues returns. Without this, the event + * handler would see Dhcp6cStatus==STARTED and trigger a spurious self-heal + * restart for an intentional stop. */ + pVirtIf->IP.Dhcp6cStatus = DHCPC_STOPPED; + pVirtIf->IP.Dhcp6cPid = 0; + WanMgr_UnSubscribeDhcpClientEvents(pVirtIf->IP.DHCPv6Iface); if (ANSC_STATUS_SUCCESS == WanMgr_RdkBus_SetParamValues(DHCPMGR_COMPONENT_NAME, DHCPMGR_DBUS_PATH, dmlName, dmlValue, ccsp_boolean, TRUE)) { CcspTraceInfo(("%s %d - Successfully set [%s] to DHCP Manager \n", __FUNCTION__, __LINE__, pVirtIf->Name)); - pVirtIf->IP.Dhcp6cStatus = DHCPC_STOPPED; - pVirtIf->IP.Dhcp6cPid = 0; } else { CcspTraceInfo(("%s %d - Failed setting [%s] to DHCP Manager \n", __FUNCTION__, __LINE__, pVirtIf->Name)); } - WanMgr_UnSubscribeDhcpClientEvents(pVirtIf->IP.DHCPv6Iface); return ANSC_STATUS_SUCCESS; #else if (is_release_required == STOP_DHCP_WITH_RELEASE) @@ -662,17 +667,22 @@ ANSC_STATUS WanManager_StopDhcpv4Client(DML_VIRTUAL_IFACE* pVirtIf, DHCP_RELEASE snprintf( dmlName, sizeof(dmlName), "%s.Enable", pVirtIf->IP.DHCPv4Iface ); snprintf( dmlValue, sizeof(dmlValue), "%s", "false"); } + /* Set status to STOPPED before issuing the stop request to eliminate the + * race where the STOPPED event arrives (and the worker thread processes it) + * before WanMgr_RdkBus_SetParamValues returns. Without this, the event + * handler would see Dhcp4cStatus==STARTED and trigger a spurious self-heal + * restart for an intentional stop. */ + pVirtIf->IP.Dhcp4cStatus = DHCPC_STOPPED; + pVirtIf->IP.Dhcp4cPid = 0; + WanMgr_UnSubscribeDhcpClientEvents(pVirtIf->IP.DHCPv4Iface); if (ANSC_STATUS_SUCCESS == WanMgr_RdkBus_SetParamValues(DHCPMGR_COMPONENT_NAME, DHCPMGR_DBUS_PATH, dmlName, dmlValue, ccsp_boolean, TRUE)) { CcspTraceInfo(("%s %d - Successfully set [%s] to DHCP Manager \n", __FUNCTION__, __LINE__, pVirtIf->Name)); - pVirtIf->IP.Dhcp4cStatus = DHCPC_STOPPED; - pVirtIf->IP.Dhcp4cPid = 0; } else { CcspTraceInfo(("%s %d - Failed setting [%s] to DHCP Manager \n", __FUNCTION__, __LINE__, pVirtIf->Name)); } - WanMgr_UnSubscribeDhcpClientEvents(pVirtIf->IP.DHCPv4Iface); if (IsReleaseNeeded == STOP_DHCP_WITH_RELEASE) { //TODO: sleep added to allow dhcpv4 client to send release before interface deconfig. This should be handled by dhcpmanager itself.