From: Johannes Berg This offloads the station polling to driver wrappers, which may offload it again to the driver. The hostap driver wrapper uses "real" data frames while nl80211 uses null data frames. Also add a specific event to indicate that a poll was successful for future use with the nl80211 driver. --- src/ap/ap_drv_ops.h | 9 ++++++++ src/ap/drv_callbacks.c | 3 ++ src/ap/ieee802_11.c | 26 +++++++++++++++++++++++ src/ap/ieee802_11.h | 1 + src/ap/sta_info.c | 47 ++--------------------------------------- src/drivers/driver.h | 28 ++++++++++++++++++++++++- src/drivers/driver_hostap.c | 27 ++++++++++++++++++++++++ src/drivers/driver_ndis.c | 3 +- src/drivers/driver_nl80211.c | 36 ++++++++++++++++++++++++++++++++ wpa_supplicant/ap.c | 9 ++++++++ wpa_supplicant/ap.h | 1 + wpa_supplicant/events.c | 3 ++ 12 files changed, 147 insertions(+), 46 deletions(-) diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 3321e57..ebb6eff 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 7020ac3..2dbf6ea 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 4b7fb2e..2b24c3f 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 6706400..b358060 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 958edd3..af6f973 100644 --- a/src/ap/sta_info.c +++ b/src/ap/sta_info.c @@ -319,52 +319,11 @@ 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 3c9f91e..a77c67e 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2501,6 +2501,16 @@ 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 + */ + void (*poll_client)(void *priv, const u8 *own_addr, + const u8 *addr, int qos); }; @@ -2918,7 +2928,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 +3490,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 ad9a022..c5319d3 100644 --- a/src/drivers/driver_hostap.c +++ b/src/drivers/driver_hostap.c @@ -1143,6 +1143,32 @@ 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 +1676,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 d77cc10..31339f5 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 5693f59..a6bed10 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -7329,6 +7329,41 @@ 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; + + /* 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); + + 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); + + 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) + perror("nl80211_send_null_frame: send"); +} + + const struct wpa_driver_ops wpa_driver_nl80211_ops = { .name = "nl80211", .desc = "Linux nl80211/cfg80211", @@ -7399,4 +7434,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 7b0cd21..9c5df26 100644 --- a/wpa_supplicant/ap.c +++ b/wpa_supplicant/ap.c @@ -566,6 +566,15 @@ 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; + 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 b913be3..b94ead7 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 43b22ab..232a776 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; -- 1.7.6.3