diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 3321e57cd..ebb6eff19 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -207,4 +207,13 @@ static inline int hostapd_drv_set_authmode(struct hostapd_data *hapd, return hapd->driver->set_authmode(hapd->drv_priv, auth_algs); } +static inline void hostapd_drv_poll_client(struct hostapd_data *hapd, + const u8 *own_addr, const u8 *addr, + int qos) +{ + if (hapd->driver == NULL || hapd->driver->poll_client == NULL) + return; + hapd->driver->poll_client(hapd->drv_priv, own_addr, addr, qos); +} + #endif /* AP_DRV_OPS */ diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index 7020ac368..2dbf6ea73 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -549,6 +549,9 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, break; } break; + case EVENT_DRIVER_CLIENT_POLL_OK: + hostapd_client_poll_ok(hapd, data->client_poll.addr); + break; case EVENT_RX_FROM_UNKNOWN: hostapd_rx_from_unknown_sta(hapd, data->rx_from_unknown.frame, data->rx_from_unknown.len); diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c index 4b7fb2e24..2b24c3fc2 100644 --- a/src/ap/ieee802_11.c +++ b/src/ap/ieee802_11.c @@ -1735,6 +1735,32 @@ void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr, } +void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr) +{ + struct sta_info *sta; + struct hostapd_iface *iface = hapd->iface; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL && iface->num_bss > 1) { + size_t j; + for (j = 0; j < iface->num_bss; j++) { + hapd = iface->bss[j]; + sta = ap_get_sta(hapd, addr); + if (sta) + break; + } + } + if (sta == NULL) + return; + if (!(sta->flags & WLAN_STA_PENDING_POLL)) + return; + + wpa_printf(MSG_DEBUG, "STA " MACSTR " ACKed pending " + "activity poll", MAC2STR(sta->addr)); + sta->flags &= ~WLAN_STA_PENDING_POLL; +} + + void ieee802_11_rx_from_unknown(struct hostapd_data *hapd, const u8 *src, int wds) { diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h index 670640077..b35806008 100644 --- a/src/ap/ieee802_11.h +++ b/src/ap/ieee802_11.h @@ -75,5 +75,6 @@ u8 * hostapd_eid_roaming_consortium(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_time_adv(struct hostapd_data *hapd, u8 *eid); u8 * hostapd_eid_time_zone(struct hostapd_data *hapd, u8 *eid); int hostapd_update_time_adv(struct hostapd_data *hapd); +void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr); #endif /* IEEE802_11_H */ diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c index 958edd3f2..d82b9ce86 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -319,52 +319,10 @@ void ap_handle_timer(void *eloop_ctx, void *timeout_ctx) if (sta->timeout_next == STA_NULLFUNC && (sta->flags & WLAN_STA_ASSOC)) { -#ifndef CONFIG_NATIVE_WINDOWS - /* send data frame to poll STA and check whether this frame - * is ACKed */ - struct { - struct ieee80211_hdr hdr; - u16 qos_ctl; - } STRUCT_PACKED nulldata; - int size = sizeof(struct ieee80211_hdr); - - wpa_printf(MSG_DEBUG, " Polling STA with data frame"); + wpa_printf(MSG_DEBUG, " Polling STA"); sta->flags |= WLAN_STA_PENDING_POLL; - - os_memset(&nulldata, 0, sizeof(nulldata)); - if (hapd->driver && - os_strcmp(hapd->driver->name, "hostap") == 0) { - /* - * WLAN_FC_STYPE_NULLFUNC would be more appropriate, - * but it is apparently not retried so TX Exc events - * are not received for it. - */ - nulldata.hdr.frame_control = - IEEE80211_FC(WLAN_FC_TYPE_DATA, - WLAN_FC_STYPE_DATA); - } else { - if (sta->flags & WLAN_STA_WMM) { - nulldata.hdr.frame_control = - IEEE80211_FC(WLAN_FC_TYPE_DATA, - WLAN_FC_STYPE_QOS_NULL); - size = sizeof(nulldata); - } else - nulldata.hdr.frame_control = - IEEE80211_FC(WLAN_FC_TYPE_DATA, - WLAN_FC_STYPE_NULLFUNC); - } - - nulldata.hdr.frame_control |= host_to_le16(WLAN_FC_FROMDS); - os_memcpy(nulldata.hdr.IEEE80211_DA_FROMDS, sta->addr, - ETH_ALEN); - os_memcpy(nulldata.hdr.IEEE80211_BSSID_FROMDS, hapd->own_addr, - ETH_ALEN); - os_memcpy(nulldata.hdr.IEEE80211_SA_FROMDS, hapd->own_addr, - ETH_ALEN); - - if (hostapd_drv_send_mlme(hapd, &nulldata, size) < 0) - perror("ap_handle_timer: send"); -#endif /* CONFIG_NATIVE_WINDOWS */ + hostapd_drv_poll_client(hapd, hapd->own_addr, sta->addr, + sta->flags & WLAN_STA_WMM); } else if (sta->timeout_next != STA_REMOVE) { int deauth = sta->timeout_next == STA_DEAUTH; diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 3c9f91e4a..3c0ef2ae2 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2501,6 +2501,20 @@ struct wpa_driver_ops { * sched_scan is supported. */ int (*stop_sched_scan)(void *priv); + + /** + * poll_client - Probe (null data or such) the given station + * @priv: Private driver interface data + * @own_addr: MAC address of sending interface + * @addr: MAC address of the station to probe + * @qos: Indicates whether station is QoS station + * + * This function is used to verify whether an associated station is + * still present. This function does not need to be implemented if the + * driver provides such inactivity polling mechanism. + */ + void (*poll_client)(void *priv, const u8 *own_addr, + const u8 *addr, int qos); }; @@ -2918,7 +2932,15 @@ enum wpa_event_type { /** * EVENT_SCHED_SCAN_STOPPED - Scheduled scan was stopped */ - EVENT_SCHED_SCAN_STOPPED + EVENT_SCHED_SCAN_STOPPED, + + /** + * EVENT_DRIVER_CLIENT_POLL_OK - Station responded to poll + * + * This event indicates that the station responded to the poll + * initiated with @poll_client. + */ + EVENT_DRIVER_CLIENT_POLL_OK }; @@ -3472,6 +3494,14 @@ union wpa_event_data { const u8 *bssid; const u8 *replay_ctr; } driver_gtk_rekey; + + /** + * struct client_poll - Data for EVENT_DRIVER_CLIENT_POLL_OK events + * @addr: station address + */ + struct client_poll { + u8 addr[ETH_ALEN]; + } client_poll; }; /** diff --git a/src/drivers/driver_hostap.c b/src/drivers/driver_hostap.c index ad9a02273..7a97ed529 100644 --- a/src/drivers/driver_hostap.c +++ b/src/drivers/driver_hostap.c @@ -1143,6 +1143,33 @@ static struct hostapd_hw_modes * hostap_get_hw_feature_data(void *priv, return mode; } + +static void wpa_driver_hostap_poll_client(void *priv, const u8 *own_addr, + const u8 *addr, int qos) +{ + struct ieee80211_hdr hdr; + + os_memset(&hdr, 0, sizeof(hdr)); + + /* + * WLAN_FC_STYPE_NULLFUNC would be more appropriate, + * but it is apparently not retried so TX Exc events + * are not received for it. + * This is the reason the driver overrides the default + * handling. + */ + hdr.frame_control = IEEE80211_FC(WLAN_FC_TYPE_DATA, + WLAN_FC_STYPE_DATA); + + hdr.frame_control |= + host_to_le16(WLAN_FC_FROMDS); + os_memcpy(hdr.IEEE80211_DA_FROMDS, addr, ETH_ALEN); + os_memcpy(hdr.IEEE80211_BSSID_FROMDS, own_addr, ETH_ALEN); + os_memcpy(hdr.IEEE80211_SA_FROMDS, own_addr, ETH_ALEN); + + hostap_send_mlme(priv, (u8 *)&hdr, sizeof(hdr)); +} + #else /* HOSTAPD */ struct wpa_driver_hostap_data { @@ -1650,6 +1677,7 @@ const struct wpa_driver_ops wpa_driver_hostap_ops = { .get_hw_feature_data = hostap_get_hw_feature_data, .set_ap_wps_ie = hostap_set_ap_wps_ie, .set_freq = hostap_set_freq, + .poll_client = wpa_driver_hostap_poll_client, #else /* HOSTAPD */ .get_bssid = wpa_driver_hostap_get_bssid, .get_ssid = wpa_driver_hostap_get_ssid, diff --git a/src/drivers/driver_ndis.c b/src/drivers/driver_ndis.c index d77cc1029..31339f5aa 100644 --- a/src/drivers/driver_ndis.c +++ b/src/drivers/driver_ndis.c @@ -3328,5 +3328,6 @@ const struct wpa_driver_ops wpa_driver_ndis_ops = { NULL /* add_tspec */, NULL /* add_sta_node */, NULL /* sched_scan */, - NULL /* stop_sched_scan */ + NULL /* stop_sched_scan */, + NULL /* poll_client */ }; diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index 5693f59bd..0c653766b 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -7329,6 +7329,43 @@ static void nl80211_set_rekey_info(void *priv, const u8 *kek, const u8 *kck, } +static void nl80211_poll_client(void *priv, const u8 *own_addr, const u8 *addr, + int qos) +{ + struct i802_bss *bss = priv; + struct { + struct ieee80211_hdr hdr; + u16 qos_ctl; + } STRUCT_PACKED nulldata; + size_t size; + + /* Send data frame to poll STA and check whether this frame is ACKed */ + + os_memset(&nulldata, 0, sizeof(nulldata)); + + if (qos) { + nulldata.hdr.frame_control = + IEEE80211_FC(WLAN_FC_TYPE_DATA, + WLAN_FC_STYPE_QOS_NULL); + size = sizeof(nulldata); + } else { + nulldata.hdr.frame_control = + IEEE80211_FC(WLAN_FC_TYPE_DATA, + WLAN_FC_STYPE_NULLFUNC); + size = sizeof(struct ieee80211_hdr); + } + + nulldata.hdr.frame_control |= host_to_le16(WLAN_FC_FROMDS); + os_memcpy(nulldata.hdr.IEEE80211_DA_FROMDS, addr, ETH_ALEN); + os_memcpy(nulldata.hdr.IEEE80211_BSSID_FROMDS, own_addr, ETH_ALEN); + os_memcpy(nulldata.hdr.IEEE80211_SA_FROMDS, own_addr, ETH_ALEN); + + if (wpa_driver_nl80211_send_mlme(bss, (u8 *) &nulldata, size) < 0) + wpa_printf(MSG_DEBUG, "nl80211_send_null_frame: Failed to " + "send poll frame"); +} + + const struct wpa_driver_ops wpa_driver_nl80211_ops = { .name = "nl80211", .desc = "Linux nl80211/cfg80211", @@ -7399,4 +7436,5 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = { .remove_pmkid = nl80211_remove_pmkid, .flush_pmkid = nl80211_flush_pmkid, .set_rekey_info = nl80211_set_rekey_info, + .poll_client = nl80211_poll_client, }; diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c index 7b0cd2118..8f83a6bae 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -566,6 +566,16 @@ void ap_tx_status(void *ctx, const u8 *addr, } +void ap_client_poll_ok(void *ctx, const u8 *addr) +{ +#ifdef NEED_AP_MLME + struct wpa_supplicant *wpa_s = ctx; + if (wpa_s->ap_iface) + hostapd_client_poll_ok(wpa_s->ap_iface->bss[0], addr); +#endif /* NEED_AP_MLME */ +} + + void ap_rx_from_unknown_sta(void *ctx, const u8 *frame, size_t len) { #ifdef NEED_AP_MLME diff --git a/wpa_supplicant/ap.h b/wpa_supplicant/ap.h index b913be3e2..b94ead7f0 100644 --- a/wpa_supplicant/ap.h +++ b/wpa_supplicant/ap.h @@ -41,6 +41,7 @@ int ap_ctrl_iface_wpa_get_status(struct wpa_supplicant *wpa_s, char *buf, size_t buflen, int verbose); void ap_tx_status(void *ctx, const u8 *addr, const u8 *buf, size_t len, int ack); +void ap_client_poll_ok(void *ctx, const u8 *addr); void ap_rx_from_unknown_sta(void *ctx, const u8 *frame, size_t len); void ap_mgmt_rx(void *ctx, struct rx_mgmt *rx_mgmt); void ap_mgmt_tx_cb(void *ctx, const u8 *buf, size_t len, u16 stype, int ok); diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c index 43b22ab54..232a7768f 100644 --- a/wpa_supplicant/events.c +++ b/wpa_supplicant/events.c @@ -2027,6 +2027,9 @@ void wpa_supplicant_event(void *ctx, enum wpa_event_type event, #endif /* CONFIG_AP */ break; #ifdef CONFIG_AP + case EVENT_DRIVER_CLIENT_POLL_OK: + ap_client_poll_ok(wpa_s, data->client_poll.addr); + break; case EVENT_RX_FROM_UNKNOWN: if (wpa_s->ap_iface == NULL) break;