| /* |
| * Received frame processing |
| * Copyright (c) 2010, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "utils/includes.h" |
| |
| #include "utils/common.h" |
| #include "utils/radiotap.h" |
| #include "utils/radiotap_iter.h" |
| #include "common/ieee802_11_defs.h" |
| #include "wlantest.h" |
| |
| |
| static struct wlantest_sta * rx_get_sta(struct wlantest *wt, |
| const struct ieee80211_hdr *hdr, |
| size_t len, int *to_ap) |
| { |
| u16 fc; |
| const u8 *sta_addr, *bssid; |
| struct wlantest_bss *bss; |
| |
| *to_ap = 0; |
| if (hdr->addr1[0] & 0x01) |
| return NULL; /* Ignore group addressed frames */ |
| |
| fc = le_to_host16(hdr->frame_control); |
| switch (WLAN_FC_GET_TYPE(fc)) { |
| case WLAN_FC_TYPE_MGMT: |
| if (len < 24) |
| return NULL; |
| bssid = hdr->addr3; |
| if (os_memcmp(bssid, hdr->addr2, ETH_ALEN) == 0) { |
| sta_addr = hdr->addr1; |
| *to_ap = 0; |
| } else { |
| if (os_memcmp(bssid, hdr->addr1, ETH_ALEN) != 0) |
| return NULL; /* Unsupported STA-to-STA frame */ |
| sta_addr = hdr->addr2; |
| *to_ap = 1; |
| } |
| break; |
| case WLAN_FC_TYPE_DATA: |
| if (len < 24) |
| return NULL; |
| switch (fc & (WLAN_FC_TODS | WLAN_FC_FROMDS)) { |
| case 0: |
| return NULL; /* IBSS not supported */ |
| case WLAN_FC_FROMDS: |
| sta_addr = hdr->addr1; |
| bssid = hdr->addr2; |
| *to_ap = 0; |
| break; |
| case WLAN_FC_TODS: |
| sta_addr = hdr->addr2; |
| bssid = hdr->addr1; |
| *to_ap = 1; |
| break; |
| case WLAN_FC_TODS | WLAN_FC_FROMDS: |
| return NULL; /* WDS not supported */ |
| default: |
| return NULL; |
| } |
| break; |
| case WLAN_FC_TYPE_CTRL: |
| if (WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_PSPOLL && |
| len >= 16) { |
| sta_addr = hdr->addr2; |
| bssid = hdr->addr1; |
| *to_ap = 1; |
| } else |
| return NULL; |
| break; |
| default: |
| return NULL; |
| } |
| |
| bss = bss_find(wt, bssid); |
| if (bss == NULL) |
| return NULL; |
| return sta_find(bss, sta_addr); |
| } |
| |
| |
| static void rx_update_ps(struct wlantest *wt, const struct ieee80211_hdr *hdr, |
| size_t len, struct wlantest_sta *sta, int to_ap) |
| { |
| u16 fc, type, stype; |
| |
| if (sta == NULL) |
| return; |
| |
| fc = le_to_host16(hdr->frame_control); |
| type = WLAN_FC_GET_TYPE(fc); |
| stype = WLAN_FC_GET_STYPE(fc); |
| |
| if (!to_ap) { |
| if (sta->pwrmgt && !sta->pspoll) { |
| u16 seq_ctrl = le_to_host16(hdr->seq_ctrl); |
| wpa_printf(MSG_DEBUG, "AP " MACSTR " sent a frame " |
| "(%u:%u) to a sleeping STA " MACSTR |
| " (seq=%u)", |
| MAC2STR(sta->bss->bssid), |
| type, stype, MAC2STR(sta->addr), |
| WLAN_GET_SEQ_SEQ(seq_ctrl)); |
| } else |
| sta->pspoll = 0; |
| return; |
| } |
| |
| sta->pspoll = 0; |
| |
| if (type == WLAN_FC_TYPE_DATA || type == WLAN_FC_TYPE_MGMT || |
| (type == WLAN_FC_TYPE_CTRL && stype == WLAN_FC_STYPE_PSPOLL)) { |
| /* |
| * In theory, the PS state changes only at the end of the frame |
| * exchange that is ACKed by the AP. However, most cases are |
| * handled with this simpler implementation that does not |
| * maintain state through the frame exchange. |
| */ |
| if (sta->pwrmgt && !(fc & WLAN_FC_PWRMGT)) { |
| wpa_printf(MSG_DEBUG, "STA " MACSTR " woke up from " |
| "sleep", MAC2STR(sta->addr)); |
| sta->pwrmgt = 0; |
| } else if (!sta->pwrmgt && (fc & WLAN_FC_PWRMGT)) { |
| wpa_printf(MSG_DEBUG, "STA " MACSTR " went to sleep", |
| MAC2STR(sta->addr)); |
| sta->pwrmgt = 1; |
| } |
| } |
| |
| if (type == WLAN_FC_TYPE_CTRL && stype == WLAN_FC_STYPE_PSPOLL) |
| sta->pspoll = 1; |
| } |
| |
| |
| static int rx_duplicate(struct wlantest *wt, const struct ieee80211_hdr *hdr, |
| size_t len, struct wlantest_sta *sta, int to_ap) |
| { |
| u16 fc; |
| int tid = 16; |
| le16 *seq_ctrl; |
| |
| if (sta == NULL) |
| return 0; |
| |
| fc = le_to_host16(hdr->frame_control); |
| if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_DATA && |
| (WLAN_FC_GET_STYPE(fc) & 0x08) && len >= 26) { |
| const u8 *qos = ((const u8 *) hdr) + 24; |
| tid = qos[0] & 0x0f; |
| } |
| |
| if (to_ap) |
| seq_ctrl = &sta->seq_ctrl_to_ap[tid]; |
| else |
| seq_ctrl = &sta->seq_ctrl_to_sta[tid]; |
| |
| if ((fc & WLAN_FC_RETRY) && hdr->seq_ctrl == *seq_ctrl) { |
| u16 s = le_to_host16(hdr->seq_ctrl); |
| wpa_printf(MSG_MSGDUMP, "Ignore duplicated frame (seq=%u " |
| "frag=%u A1=" MACSTR " A2=" MACSTR ")", |
| WLAN_GET_SEQ_SEQ(s), WLAN_GET_SEQ_FRAG(s), |
| MAC2STR(hdr->addr1), MAC2STR(hdr->addr2)); |
| return 1; |
| } |
| |
| *seq_ctrl = hdr->seq_ctrl; |
| |
| return 0; |
| } |
| |
| |
| static void rx_ack(struct wlantest *wt, const struct ieee80211_hdr *hdr) |
| { |
| struct ieee80211_hdr *last = (struct ieee80211_hdr *) wt->last_hdr; |
| u16 fc; |
| |
| if (wt->last_len < 24 || (last->addr1[0] & 0x01) || |
| os_memcmp(hdr->addr1, last->addr2, ETH_ALEN) != 0) { |
| wpa_printf(MSG_MSGDUMP, "Unknown Ack frame (previous frame " |
| "not seen)"); |
| return; |
| } |
| |
| /* Ack to the previous frame */ |
| fc = le_to_host16(last->frame_control); |
| if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT) |
| rx_mgmt_ack(wt, last); |
| } |
| |
| |
| static void rx_frame(struct wlantest *wt, const u8 *data, size_t len) |
| { |
| const struct ieee80211_hdr *hdr; |
| u16 fc; |
| struct wlantest_sta *sta; |
| int to_ap; |
| |
| wpa_hexdump(MSG_EXCESSIVE, "RX frame", data, len); |
| if (len < 2) |
| return; |
| |
| hdr = (const struct ieee80211_hdr *) data; |
| fc = le_to_host16(hdr->frame_control); |
| if (fc & WLAN_FC_PVER) { |
| wpa_printf(MSG_DEBUG, "Drop RX frame with unexpected pver=%d", |
| fc & WLAN_FC_PVER); |
| return; |
| } |
| |
| sta = rx_get_sta(wt, hdr, len, &to_ap); |
| |
| switch (WLAN_FC_GET_TYPE(fc)) { |
| case WLAN_FC_TYPE_MGMT: |
| if (len < 24) |
| break; |
| if (rx_duplicate(wt, hdr, len, sta, to_ap)) |
| break; |
| rx_update_ps(wt, hdr, len, sta, to_ap); |
| rx_mgmt(wt, data, len); |
| break; |
| case WLAN_FC_TYPE_CTRL: |
| if (len < 10) |
| break; |
| wt->rx_ctrl++; |
| rx_update_ps(wt, hdr, len, sta, to_ap); |
| if (WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_ACK) |
| rx_ack(wt, hdr); |
| break; |
| case WLAN_FC_TYPE_DATA: |
| if (len < 24) |
| break; |
| if (rx_duplicate(wt, hdr, len, sta, to_ap)) |
| break; |
| rx_update_ps(wt, hdr, len, sta, to_ap); |
| rx_data(wt, data, len); |
| break; |
| default: |
| wpa_printf(MSG_DEBUG, "Drop RX frame with unexpected type %d", |
| WLAN_FC_GET_TYPE(fc)); |
| break; |
| } |
| |
| os_memcpy(wt->last_hdr, data, len > sizeof(wt->last_hdr) ? |
| sizeof(wt->last_hdr) : len); |
| wt->last_len = len; |
| } |
| |
| |
| static void tx_status(struct wlantest *wt, const u8 *data, size_t len, int ack) |
| { |
| wpa_printf(MSG_DEBUG, "TX status: ack=%d", ack); |
| wpa_hexdump(MSG_EXCESSIVE, "TX status frame", data, len); |
| } |
| |
| |
| static int check_fcs(const u8 *frame, size_t frame_len, const u8 *fcs) |
| { |
| if (WPA_GET_LE32(fcs) != crc32(frame, frame_len)) |
| return -1; |
| return 0; |
| } |
| |
| |
| void wlantest_process(struct wlantest *wt, const u8 *data, size_t len) |
| { |
| struct ieee80211_radiotap_iterator iter; |
| int ret; |
| int rxflags = 0, txflags = 0, failed = 0, fcs = 0; |
| const u8 *frame, *fcspos; |
| size_t frame_len; |
| |
| wpa_hexdump(MSG_EXCESSIVE, "Process data", data, len); |
| |
| if (ieee80211_radiotap_iterator_init(&iter, (void *) data, len)) { |
| wpa_printf(MSG_INFO, "Invalid radiotap frame"); |
| return; |
| } |
| |
| for (;;) { |
| ret = ieee80211_radiotap_iterator_next(&iter); |
| wpa_printf(MSG_EXCESSIVE, "radiotap iter: %d " |
| "this_arg_index=%d", ret, iter.this_arg_index); |
| if (ret == -ENOENT) |
| break; |
| if (ret) { |
| wpa_printf(MSG_INFO, "Invalid radiotap header: %d", |
| ret); |
| return; |
| } |
| switch (iter.this_arg_index) { |
| case IEEE80211_RADIOTAP_FLAGS: |
| if (*iter.this_arg & IEEE80211_RADIOTAP_F_FCS) |
| fcs = 1; |
| break; |
| case IEEE80211_RADIOTAP_RX_FLAGS: |
| rxflags = 1; |
| break; |
| case IEEE80211_RADIOTAP_TX_FLAGS: |
| txflags = 1; |
| failed = le_to_host16((*(u16 *) iter.this_arg)) & |
| IEEE80211_RADIOTAP_F_TX_FAIL; |
| break; |
| |
| } |
| } |
| |
| if (iter.max_length == 8) { |
| wpa_printf(MSG_DEBUG, "Skip frame inserted by wlantest"); |
| return; |
| } |
| frame = data + iter.max_length; |
| frame_len = len - iter.max_length; |
| |
| if (fcs && frame_len >= 4) { |
| frame_len -= 4; |
| fcspos = frame + frame_len; |
| if (check_fcs(frame, frame_len, fcspos) < 0) { |
| wpa_printf(MSG_EXCESSIVE, "Drop RX frame with invalid " |
| "FCS"); |
| wt->fcs_error++; |
| return; |
| } |
| } |
| |
| if (rxflags && txflags) |
| return; |
| if (!txflags) |
| rx_frame(wt, frame, frame_len); |
| else { |
| tx_status(wt, frame, frame_len, !failed); |
| /* Process as RX frame to support local monitor interface */ |
| rx_frame(wt, frame, frame_len); |
| } |
| } |
| |
| |
| void wlantest_process_prism(struct wlantest *wt, const u8 *data, size_t len) |
| { |
| int fcs = 0; |
| const u8 *frame, *fcspos; |
| size_t frame_len; |
| u32 hdrlen; |
| |
| wpa_hexdump(MSG_EXCESSIVE, "Process data", data, len); |
| |
| if (len < 8) |
| return; |
| hdrlen = WPA_GET_LE32(data + 4); |
| |
| if (len < hdrlen) { |
| wpa_printf(MSG_INFO, "Too short frame to include prism " |
| "header"); |
| return; |
| } |
| |
| frame = data + hdrlen; |
| frame_len = len - hdrlen; |
| fcs = 1; |
| |
| if (fcs && frame_len >= 4) { |
| frame_len -= 4; |
| fcspos = frame + frame_len; |
| if (check_fcs(frame, frame_len, fcspos) < 0) { |
| wpa_printf(MSG_EXCESSIVE, "Drop RX frame with invalid " |
| "FCS"); |
| wt->fcs_error++; |
| return; |
| } |
| } |
| |
| rx_frame(wt, frame, frame_len); |
| } |
| |
| |
| void wlantest_process_80211(struct wlantest *wt, const u8 *data, size_t len) |
| { |
| wpa_hexdump(MSG_EXCESSIVE, "Process data", data, len); |
| rx_frame(wt, data, len); |
| } |