hostap/wlantest/process.c
Jouni Malinen 44f6d554ee wlantest: Process TX status frames as RX frames too
This is needed to allow capture files from the mac80211 cooked monitor
mode interface to be processed properly. Without this, the locally
generated frames may not get processed.

Signed-hostap: Jouni Malinen <j@w1.fi>
2012-12-16 12:35:07 +02:00

384 lines
8.8 KiB
C

/*
* 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);
}