--- a/wpa_supplicant/mesh_mpm.c +++ b/wpa_supplicant/mesh_mpm.c @@ -586,6 +586,7 @@ int mesh_mpm_close_peer(struct wpa_supplicant *wpa_s, { struct hostapd_data *hapd; struct sta_info *sta; + struct mesh_conf *conf; if (!wpa_s->ifmsh) { wpa_msg(wpa_s, MSG_INFO, "Mesh is not prepared yet"); @@ -599,7 +600,30 @@ int mesh_mpm_close_peer(struct wpa_supplicant *wpa_s, return -1; } - return mesh_mpm_plink_close(hapd, sta, wpa_s) == 0 ? 0 : -1; + if (mesh_mpm_plink_close(hapd, sta, wpa_s) != 0) + return -1; + + /* + * CicadaTAK: a single-peer close arms the holding timer, exactly as every + * other path into HOLDING does (plink_timer's confirm/retry expiry and + * mesh_mpm_fsm's CLS_ACPT/reject cases). mesh_mpm_plink_close() arms + * nothing, so when its one Close frame (or the peer's reply) is lost the + * peer stays in HOLDING forever: the kernel keeps the station, and + * mesh_mpm_add_peer() ignores every new-peer event for it. plink_timer's + * HOLDING case frees it (mesh_mpm_fsm_restart -> ap_free_sta) after + * dot11MeshHoldingTimeout. Not done in mesh_mpm_plink_close() itself: that + * is also the per-station callback of the leave paths (mesh_mpm_deinit, + * the kick-out-all branch), which free every station right after. + */ + conf = wpa_s->ifmsh->mconf; + sta->mpm_close_reason = WLAN_REASON_MESH_PEERING_CANCELLED; + eloop_register_timeout(conf->dot11MeshHoldingTimeout / 1000, + (conf->dot11MeshHoldingTimeout % 1000) * 1000, + plink_timer, wpa_s, sta); + wpa_msg(wpa_s, MSG_INFO, "CICADATAK-HOLD: mesh plink with " MACSTR + " closed locally; holding timer %d ms", + MAC2STR(sta->addr), conf->dot11MeshHoldingTimeout); + return 0; }