]>
Commit | Line | Data |
---|---|---|
a149fcc7 JM |
1 | /* |
2 | * Received frame processing | |
3 | * Copyright (c) 2010, Jouni Malinen <j@w1.fi> | |
4 | * | |
0f3d578e JM |
5 | * This software may be distributed under the terms of the BSD license. |
6 | * See README for more details. | |
a149fcc7 JM |
7 | */ |
8 | ||
9 | #include "utils/includes.h" | |
10 | ||
11 | #include "utils/common.h" | |
12 | #include "utils/radiotap.h" | |
13 | #include "utils/radiotap_iter.h" | |
14 | #include "common/ieee802_11_defs.h" | |
15 | #include "wlantest.h" | |
16 | ||
17 | ||
7e7a57ae JM |
18 | static struct wlantest_sta * rx_get_sta(struct wlantest *wt, |
19 | const struct ieee80211_hdr *hdr, | |
20 | size_t len, int *to_ap) | |
a912dd16 JM |
21 | { |
22 | u16 fc; | |
a912dd16 JM |
23 | const u8 *sta_addr, *bssid; |
24 | struct wlantest_bss *bss; | |
a912dd16 | 25 | |
7e7a57ae | 26 | *to_ap = 0; |
a912dd16 | 27 | if (hdr->addr1[0] & 0x01) |
7e7a57ae | 28 | return NULL; /* Ignore group addressed frames */ |
a912dd16 JM |
29 | |
30 | fc = le_to_host16(hdr->frame_control); | |
7e7a57ae JM |
31 | switch (WLAN_FC_GET_TYPE(fc)) { |
32 | case WLAN_FC_TYPE_MGMT: | |
33 | if (len < 24) | |
34 | return NULL; | |
a912dd16 JM |
35 | bssid = hdr->addr3; |
36 | if (os_memcmp(bssid, hdr->addr2, ETH_ALEN) == 0) { | |
37 | sta_addr = hdr->addr1; | |
7e7a57ae | 38 | *to_ap = 0; |
a912dd16 JM |
39 | } else { |
40 | if (os_memcmp(bssid, hdr->addr1, ETH_ALEN) != 0) | |
7e7a57ae | 41 | return NULL; /* Unsupported STA-to-STA frame */ |
a912dd16 | 42 | sta_addr = hdr->addr2; |
7e7a57ae | 43 | *to_ap = 1; |
a912dd16 | 44 | } |
7e7a57ae JM |
45 | break; |
46 | case WLAN_FC_TYPE_DATA: | |
47 | if (len < 24) | |
48 | return NULL; | |
a912dd16 JM |
49 | switch (fc & (WLAN_FC_TODS | WLAN_FC_FROMDS)) { |
50 | case 0: | |
7e7a57ae | 51 | return NULL; /* IBSS not supported */ |
a912dd16 JM |
52 | case WLAN_FC_FROMDS: |
53 | sta_addr = hdr->addr1; | |
54 | bssid = hdr->addr2; | |
7e7a57ae | 55 | *to_ap = 0; |
a912dd16 JM |
56 | break; |
57 | case WLAN_FC_TODS: | |
58 | sta_addr = hdr->addr2; | |
59 | bssid = hdr->addr1; | |
7e7a57ae | 60 | *to_ap = 1; |
a912dd16 JM |
61 | break; |
62 | case WLAN_FC_TODS | WLAN_FC_FROMDS: | |
7e7a57ae | 63 | return NULL; /* WDS not supported */ |
a912dd16 | 64 | default: |
7e7a57ae | 65 | return NULL; |
a912dd16 | 66 | } |
7e7a57ae JM |
67 | break; |
68 | case WLAN_FC_TYPE_CTRL: | |
69 | if (WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_PSPOLL && | |
70 | len >= 16) { | |
71 | sta_addr = hdr->addr2; | |
72 | bssid = hdr->addr1; | |
73 | *to_ap = 1; | |
74 | } else | |
75 | return NULL; | |
76 | break; | |
dfaeda04 JM |
77 | default: |
78 | return NULL; | |
a912dd16 JM |
79 | } |
80 | ||
81 | bss = bss_find(wt, bssid); | |
82 | if (bss == NULL) | |
dfaeda04 | 83 | return NULL; |
7e7a57ae JM |
84 | return sta_find(bss, sta_addr); |
85 | } | |
86 | ||
87 | ||
88 | static void rx_update_ps(struct wlantest *wt, const struct ieee80211_hdr *hdr, | |
89 | size_t len, struct wlantest_sta *sta, int to_ap) | |
90 | { | |
91 | u16 fc, type, stype; | |
92 | ||
93 | if (sta == NULL) | |
94 | return; | |
95 | ||
96 | fc = le_to_host16(hdr->frame_control); | |
97 | type = WLAN_FC_GET_TYPE(fc); | |
98 | stype = WLAN_FC_GET_STYPE(fc); | |
99 | ||
100 | if (!to_ap) { | |
101 | if (sta->pwrmgt && !sta->pspoll) { | |
102 | u16 seq_ctrl = le_to_host16(hdr->seq_ctrl); | |
e4d99217 JM |
103 | add_note(wt, MSG_DEBUG, "AP " MACSTR " sent a frame " |
104 | "(%u:%u) to a sleeping STA " MACSTR | |
105 | " (seq=%u)", | |
106 | MAC2STR(sta->bss->bssid), | |
107 | type, stype, MAC2STR(sta->addr), | |
108 | WLAN_GET_SEQ_SEQ(seq_ctrl)); | |
7e7a57ae JM |
109 | } else |
110 | sta->pspoll = 0; | |
111 | return; | |
112 | } | |
113 | ||
114 | sta->pspoll = 0; | |
115 | ||
116 | if (type == WLAN_FC_TYPE_DATA || type == WLAN_FC_TYPE_MGMT || | |
117 | (type == WLAN_FC_TYPE_CTRL && stype == WLAN_FC_STYPE_PSPOLL)) { | |
118 | /* | |
119 | * In theory, the PS state changes only at the end of the frame | |
120 | * exchange that is ACKed by the AP. However, most cases are | |
121 | * handled with this simpler implementation that does not | |
122 | * maintain state through the frame exchange. | |
123 | */ | |
124 | if (sta->pwrmgt && !(fc & WLAN_FC_PWRMGT)) { | |
e4d99217 JM |
125 | add_note(wt, MSG_DEBUG, "STA " MACSTR " woke up from " |
126 | "sleep", MAC2STR(sta->addr)); | |
7e7a57ae JM |
127 | sta->pwrmgt = 0; |
128 | } else if (!sta->pwrmgt && (fc & WLAN_FC_PWRMGT)) { | |
e4d99217 JM |
129 | add_note(wt, MSG_DEBUG, "STA " MACSTR " went to sleep", |
130 | MAC2STR(sta->addr)); | |
7e7a57ae JM |
131 | sta->pwrmgt = 1; |
132 | } | |
133 | } | |
134 | ||
135 | if (type == WLAN_FC_TYPE_CTRL && stype == WLAN_FC_STYPE_PSPOLL) | |
136 | sta->pspoll = 1; | |
137 | } | |
138 | ||
139 | ||
140 | static int rx_duplicate(struct wlantest *wt, const struct ieee80211_hdr *hdr, | |
141 | size_t len, struct wlantest_sta *sta, int to_ap) | |
142 | { | |
143 | u16 fc; | |
144 | int tid = 16; | |
145 | le16 *seq_ctrl; | |
146 | ||
a912dd16 JM |
147 | if (sta == NULL) |
148 | return 0; | |
149 | ||
7e7a57ae JM |
150 | fc = le_to_host16(hdr->frame_control); |
151 | if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_DATA && | |
152 | (WLAN_FC_GET_STYPE(fc) & 0x08) && len >= 26) { | |
153 | const u8 *qos = ((const u8 *) hdr) + 24; | |
154 | tid = qos[0] & 0x0f; | |
155 | } | |
156 | ||
a912dd16 JM |
157 | if (to_ap) |
158 | seq_ctrl = &sta->seq_ctrl_to_ap[tid]; | |
159 | else | |
160 | seq_ctrl = &sta->seq_ctrl_to_sta[tid]; | |
161 | ||
162 | if ((fc & WLAN_FC_RETRY) && hdr->seq_ctrl == *seq_ctrl) { | |
163 | u16 s = le_to_host16(hdr->seq_ctrl); | |
e4d99217 JM |
164 | add_note(wt, MSG_MSGDUMP, "Ignore duplicated frame (seq=%u " |
165 | "frag=%u A1=" MACSTR " A2=" MACSTR ")", | |
166 | WLAN_GET_SEQ_SEQ(s), WLAN_GET_SEQ_FRAG(s), | |
167 | MAC2STR(hdr->addr1), MAC2STR(hdr->addr2)); | |
a912dd16 JM |
168 | return 1; |
169 | } | |
170 | ||
171 | *seq_ctrl = hdr->seq_ctrl; | |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
176 | ||
fb8f5fc6 JM |
177 | static void rx_ack(struct wlantest *wt, const struct ieee80211_hdr *hdr) |
178 | { | |
179 | struct ieee80211_hdr *last = (struct ieee80211_hdr *) wt->last_hdr; | |
180 | u16 fc; | |
181 | ||
182 | if (wt->last_len < 24 || (last->addr1[0] & 0x01) || | |
183 | os_memcmp(hdr->addr1, last->addr2, ETH_ALEN) != 0) { | |
e4d99217 JM |
184 | add_note(wt, MSG_MSGDUMP, "Unknown Ack frame (previous frame " |
185 | "not seen)"); | |
fb8f5fc6 JM |
186 | return; |
187 | } | |
188 | ||
189 | /* Ack to the previous frame */ | |
190 | fc = le_to_host16(last->frame_control); | |
191 | if (WLAN_FC_GET_TYPE(fc) == WLAN_FC_TYPE_MGMT) | |
192 | rx_mgmt_ack(wt, last); | |
193 | } | |
194 | ||
195 | ||
a149fcc7 JM |
196 | static void rx_frame(struct wlantest *wt, const u8 *data, size_t len) |
197 | { | |
198 | const struct ieee80211_hdr *hdr; | |
199 | u16 fc; | |
7e7a57ae JM |
200 | struct wlantest_sta *sta; |
201 | int to_ap; | |
a149fcc7 JM |
202 | |
203 | wpa_hexdump(MSG_EXCESSIVE, "RX frame", data, len); | |
204 | if (len < 2) | |
205 | return; | |
206 | ||
207 | hdr = (const struct ieee80211_hdr *) data; | |
208 | fc = le_to_host16(hdr->frame_control); | |
209 | if (fc & WLAN_FC_PVER) { | |
210 | wpa_printf(MSG_DEBUG, "Drop RX frame with unexpected pver=%d", | |
211 | fc & WLAN_FC_PVER); | |
212 | return; | |
213 | } | |
214 | ||
7e7a57ae JM |
215 | sta = rx_get_sta(wt, hdr, len, &to_ap); |
216 | ||
a149fcc7 JM |
217 | switch (WLAN_FC_GET_TYPE(fc)) { |
218 | case WLAN_FC_TYPE_MGMT: | |
a912dd16 JM |
219 | if (len < 24) |
220 | break; | |
7e7a57ae | 221 | if (rx_duplicate(wt, hdr, len, sta, to_ap)) |
a912dd16 | 222 | break; |
7e7a57ae | 223 | rx_update_ps(wt, hdr, len, sta, to_ap); |
a149fcc7 JM |
224 | rx_mgmt(wt, data, len); |
225 | break; | |
226 | case WLAN_FC_TYPE_CTRL: | |
227 | if (len < 10) | |
a912dd16 | 228 | break; |
a149fcc7 | 229 | wt->rx_ctrl++; |
7e7a57ae | 230 | rx_update_ps(wt, hdr, len, sta, to_ap); |
fb8f5fc6 JM |
231 | if (WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_ACK) |
232 | rx_ack(wt, hdr); | |
a149fcc7 JM |
233 | break; |
234 | case WLAN_FC_TYPE_DATA: | |
a912dd16 JM |
235 | if (len < 24) |
236 | break; | |
7e7a57ae | 237 | if (rx_duplicate(wt, hdr, len, sta, to_ap)) |
a912dd16 | 238 | break; |
7e7a57ae | 239 | rx_update_ps(wt, hdr, len, sta, to_ap); |
a149fcc7 JM |
240 | rx_data(wt, data, len); |
241 | break; | |
242 | default: | |
243 | wpa_printf(MSG_DEBUG, "Drop RX frame with unexpected type %d", | |
244 | WLAN_FC_GET_TYPE(fc)); | |
245 | break; | |
246 | } | |
fb8f5fc6 JM |
247 | |
248 | os_memcpy(wt->last_hdr, data, len > sizeof(wt->last_hdr) ? | |
249 | sizeof(wt->last_hdr) : len); | |
250 | wt->last_len = len; | |
a149fcc7 JM |
251 | } |
252 | ||
253 | ||
254 | static void tx_status(struct wlantest *wt, const u8 *data, size_t len, int ack) | |
255 | { | |
256 | wpa_printf(MSG_DEBUG, "TX status: ack=%d", ack); | |
257 | wpa_hexdump(MSG_EXCESSIVE, "TX status frame", data, len); | |
258 | } | |
259 | ||
260 | ||
261 | static int check_fcs(const u8 *frame, size_t frame_len, const u8 *fcs) | |
262 | { | |
263 | if (WPA_GET_LE32(fcs) != crc32(frame, frame_len)) | |
264 | return -1; | |
265 | return 0; | |
266 | } | |
267 | ||
268 | ||
269 | void wlantest_process(struct wlantest *wt, const u8 *data, size_t len) | |
270 | { | |
271 | struct ieee80211_radiotap_iterator iter; | |
272 | int ret; | |
273 | int rxflags = 0, txflags = 0, failed = 0, fcs = 0; | |
274 | const u8 *frame, *fcspos; | |
275 | size_t frame_len; | |
276 | ||
277 | wpa_hexdump(MSG_EXCESSIVE, "Process data", data, len); | |
278 | ||
279 | if (ieee80211_radiotap_iterator_init(&iter, (void *) data, len)) { | |
e4d99217 | 280 | add_note(wt, MSG_INFO, "Invalid radiotap frame"); |
a149fcc7 JM |
281 | return; |
282 | } | |
283 | ||
284 | for (;;) { | |
285 | ret = ieee80211_radiotap_iterator_next(&iter); | |
286 | wpa_printf(MSG_EXCESSIVE, "radiotap iter: %d " | |
287 | "this_arg_index=%d", ret, iter.this_arg_index); | |
288 | if (ret == -ENOENT) | |
289 | break; | |
290 | if (ret) { | |
e4d99217 JM |
291 | add_note(wt, MSG_INFO, "Invalid radiotap header: %d", |
292 | ret); | |
a149fcc7 JM |
293 | return; |
294 | } | |
295 | switch (iter.this_arg_index) { | |
296 | case IEEE80211_RADIOTAP_FLAGS: | |
297 | if (*iter.this_arg & IEEE80211_RADIOTAP_F_FCS) | |
298 | fcs = 1; | |
299 | break; | |
300 | case IEEE80211_RADIOTAP_RX_FLAGS: | |
301 | rxflags = 1; | |
302 | break; | |
303 | case IEEE80211_RADIOTAP_TX_FLAGS: | |
304 | txflags = 1; | |
305 | failed = le_to_host16((*(u16 *) iter.this_arg)) & | |
306 | IEEE80211_RADIOTAP_F_TX_FAIL; | |
307 | break; | |
308 | ||
309 | } | |
310 | } | |
311 | ||
62a0255c | 312 | if (iter.max_length == 8) { |
e4d99217 | 313 | add_note(wt, MSG_DEBUG, "Skip frame inserted by wlantest"); |
62a0255c JM |
314 | return; |
315 | } | |
a149fcc7 JM |
316 | frame = data + iter.max_length; |
317 | frame_len = len - iter.max_length; | |
318 | ||
319 | if (fcs && frame_len >= 4) { | |
320 | frame_len -= 4; | |
321 | fcspos = frame + frame_len; | |
322 | if (check_fcs(frame, frame_len, fcspos) < 0) { | |
e4d99217 JM |
323 | add_note(wt, MSG_EXCESSIVE, "Drop RX frame with " |
324 | "invalid FCS"); | |
a149fcc7 JM |
325 | wt->fcs_error++; |
326 | return; | |
327 | } | |
328 | } | |
329 | ||
330 | if (rxflags && txflags) | |
331 | return; | |
332 | if (!txflags) | |
333 | rx_frame(wt, frame, frame_len); | |
44f6d554 | 334 | else { |
e4d99217 JM |
335 | add_note(wt, MSG_EXCESSIVE, "TX status - process as RX of " |
336 | "local frame"); | |
a149fcc7 | 337 | tx_status(wt, frame, frame_len, !failed); |
44f6d554 JM |
338 | /* Process as RX frame to support local monitor interface */ |
339 | rx_frame(wt, frame, frame_len); | |
340 | } | |
a149fcc7 | 341 | } |
350132be JM |
342 | |
343 | ||
344 | void wlantest_process_prism(struct wlantest *wt, const u8 *data, size_t len) | |
345 | { | |
346 | int fcs = 0; | |
347 | const u8 *frame, *fcspos; | |
348 | size_t frame_len; | |
349 | u32 hdrlen; | |
350 | ||
351 | wpa_hexdump(MSG_EXCESSIVE, "Process data", data, len); | |
352 | ||
353 | if (len < 8) | |
354 | return; | |
355 | hdrlen = WPA_GET_LE32(data + 4); | |
356 | ||
357 | if (len < hdrlen) { | |
358 | wpa_printf(MSG_INFO, "Too short frame to include prism " | |
359 | "header"); | |
360 | return; | |
361 | } | |
362 | ||
363 | frame = data + hdrlen; | |
364 | frame_len = len - hdrlen; | |
365 | fcs = 1; | |
366 | ||
367 | if (fcs && frame_len >= 4) { | |
368 | frame_len -= 4; | |
369 | fcspos = frame + frame_len; | |
370 | if (check_fcs(frame, frame_len, fcspos) < 0) { | |
e4d99217 JM |
371 | add_note(wt, MSG_EXCESSIVE, "Drop RX frame with " |
372 | "invalid FCS"); | |
350132be JM |
373 | wt->fcs_error++; |
374 | return; | |
375 | } | |
376 | } | |
377 | ||
378 | rx_frame(wt, frame, frame_len); | |
379 | } | |
0f3e4f2a JM |
380 | |
381 | ||
382 | void wlantest_process_80211(struct wlantest *wt, const u8 *data, size_t len) | |
383 | { | |
384 | wpa_hexdump(MSG_EXCESSIVE, "Process data", data, len); | |
25315176 JM |
385 | |
386 | if (wt->assume_fcs && len >= 4) { | |
387 | const u8 *fcspos; | |
388 | ||
389 | len -= 4; | |
390 | fcspos = data + len; | |
391 | if (check_fcs(data, len, fcspos) < 0) { | |
e4d99217 JM |
392 | add_note(wt, MSG_EXCESSIVE, "Drop RX frame with " |
393 | "invalid FCS"); | |
25315176 JM |
394 | wt->fcs_error++; |
395 | return; | |
396 | } | |
397 | } | |
398 | ||
0f3e4f2a JM |
399 | rx_frame(wt, data, len); |
400 | } |