• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Codelab: Using wifi Autotests to learn 802.11 basics
2
3The Autotest infrastructure provides packet capture functionality which we can
4use to intercept and view WiFi packets that are sent between the
5Device-Under-Test (DUT) and router during a test. In this codelab we will
6analyze the packet sequence during the connection process to learn the basics
7of 802.11 connection protocols.
8
9## Setup
10
11### Prerequisites
12
13* Access to a wifi test setup (local or in test lab). Read the
14  [wificell documentation] for some background.
15* Understanding of Autotest and the [test_that] command.
16
17### Configuration Considerations
18
19This codelab can be completed from either a personal testing setup or a
20dedicated setup in our testing lab, but there are a few special considerations
21in each case. For instance, some of the commands in this lab will use the
22variable `${DUT_HOSTNAME}`, and the value of this variable is dependent on the
23testing setup that you use. Further considerations are included below in the
24instructions for each option.
25
26#### Using the wifi testing labs
27
28Our testing lab setups are operated through the skylab infrastructure. If you
29don't have the skylab tool installed on your machine, follow the instructions
30in the [skylab tools guide].
31
32Once you have the skylab tool, you'll need to run the login command and follow
33its instructions to get started.
34
35```bash
36skylab login
37```
38
39For this codelab, you will need to use a `wificell` test setup. Available DUTs
40can be found on the [skylab portal]. To find a wificell test setup, visit the
41portal and filter for *label-wificell = true* (the filter should already be
42set when you click the link). You'll need to find a setup who's current task
43is *idle* with dut_state *ready*, and then lock it while in use. To lock a DUT
44in the skylab use this command to lease it for the specified number of
45minutes (60 minutes should suffice for this codelab, but if your lease
46expires you can simply lease your DUT again):
47
48```bash
49skylab lease-dut -minutes ${NUM_MINUTES} ${DUT_NAME}
50```
51
52*** note
53**Note:** There are several similar fields on the bot page that can potentially
54be confused. Bots are listed by their *id* field in the skylab search portal,
55which usually takes a form similar to `crossk-chromeos15-row2-rack4-host6`.
56*dut_name* is referred to in this document by the variable `${DUT_NAME}`, and
57is typically the *id* without `crossk`, e.g. `chromeos15-row2-rack4-host6`. The
58hostname for a DUT (`${DUT_HOSTNAME}` in this doc) is not shown on the skylab
59bot page, but it is the *dut_name* with '.cros' appended e.g.
60`chromeos15-row2-rack4-host6.cros`.
61***
62
63Autotest requires a working build of the board type being tested on, so it is
64best to pick a board for which you have already built an image on your machine.
65
66Autotest will automatically determine the hostnames of the router and packet
67capture device but if you want to access them directly, say through ssh,
68you can use the hostnames **${DUT\_NAME}-router.cros** and
69**${DUT\_NAME}-pcap.cros** respectively. You can access each with ssh
70through the root user with password `test0000`.
71
72Lastly, Autotest may have issues with hosts that have the `chameleon` label.
73If you are having [chameleon issues], the current workaround is to set
74*enable_ssh_tunnel_for_chameleon: True* in
75`src/third_party/autotest/files/global_config.ini`.
76
77#### Using a local testing setup
78
79For a local test setup, you'll need a flashed DUT and two flashed Google-made
80wifi routers that run Chrome OS, all running special test images. The
81Google-made routers can be either of the boards `whirlwind` or `gale`,
82and see [network_WiFi_UpdateRouter] for what images they should be running.
83In order for Autotest to determine the hostnames of your router and packet
84capture device, you'll have to designate their IP addresses within your chroot.
85Assign the IP address of your DUT to 'dut', and the IPs of your routers to
86'dut-router' and 'dut-pcap' by adding lines like these to `/etc/hosts`:
87
88```bash
89xxx.xxx.xxx.xxx dut-router
90xxx.xxx.xxx.xxx dut-pcap
91xxx.xxx.xxx.xxx dut
92```
93
94Now, you can use **${DUT\_HOSTNAME}** = '*dut*' and Autotest will use your
95hosts file to find the other devices. The final consideration when using a
96local testing setup is that the designated testbeds are contained in shielding
97boxes which isolate them from other signals, while your local setup is
98probably held in open air. This means that your packet capture device will also
99pick up packets from any other devices broadcasting in your area. This will make
100the packet feed noisier, but you can still find all the packets involved in the
101connection process so its not a dealbreaker for this codelab.
102
103### Let's get started
104
105[network_WiFi_SimpleConnect] is a very simple test that connects and disconnects
106a DUT from a router, so it's ideal for our purposes in this codelab. The test
107itself is held at `server/site_tests/network_WiFi_SimpleConnect/` in the
108Autotest repository. Briefly look through this file to get a sense for what it
109is doing.
110
111Before you make any changes to code, be sure to start a new branch within the
112Autotest repository.
113
114#### 1. Gather pcap data
115
116Our first goal is to initiate packet capture and record all of the frames that
117our pcap device sees throughout the test. Conveniently,
118[network_WiFi_SimpleConnect] already utilizes a pcap device, which is accessed
119at `self.context.capture_host`. Before the testing starts, the test begins
120capturing packets by calling `start_capture()` on the capture device, and after
121the test completes, `stop_capture()` completes the capturing process.
122`stop_capture()` returns a list of filepaths that hold the captured packets, so
123let's store the results of this function in a variable:
124
125```python3
126capture_results = self.context.capture_host.stop_capture()
127```
128
129The pcap file is accessible at `capture_results[0].local_pcap_path`, so let's
130print out a dump of our captured packets. Add these lines after the call to
131`stop_capture()`:
132
133```python3
134packets = open(capture_results[0].local_pcap_path, 'r')
135logging.info(packets.read())
136packets.close()
137```
138
139Now, lets run the test and see what we can learn:
140
141```bash
142test_that --fast -b ${BOARD} ${DUT_HOSTNAME} network_WiFi_SimpleConnect.wifi_check5HT20
143```
144
145That's a lot of garbage. The packets aren't going to be much use to us in their
146current state. In the next section, we'll use Wireshark to translate the packets
147into a readable form that we can study.
148
149#### 2. Use Wireshark to analyze the packets
150
151Pyshark is a wrapper for Wireshark within Python, and we'll be using it in this
152codelab to interperet our captured packets. Learn more at the
153[Pyshark documentation] page.
154
155Delete the lines you just added and replace them with calls to Pyshark that will
156parse and translate the packets, then write the packets to a file:
157
158```python3
159
160import pyshark
161capture = pyshark.FileCapture(
162    input_file=capture_results[0].local_pcap_path)
163capture.load_packets(timeout=2)
164
165packet_file = open('/tmp/pcap', 'w')
166for packet in capture:
167    packet_file.write(str(packet))
168packet_file.close()
169
170```
171
172Run the Autotest again and open `/tmp/pcap`. Look at that, tons of
173human-readable data! Maybe even a little too much? Right now we're getting the
174entirety of every packet, but we only need a few fields. As a final step, we're
175going to parse out the needed fields from each packet so we can digest some
176relevant information about the connection process. Add the following methods to
177the global scope of [network_WiFi_SimpleConnect]:
178
179```python3
180
181def _fetch_frame_field_value(frame, field):
182    layer_object = frame
183    for layer in field.split('.'):
184        try:
185            layer_object = getattr(layer_object, layer)
186        except AttributeError:
187            return None
188    return layer_object
189
190"""
191Parses input frames and stores frames of type listed in filter_types.
192If filter_types is empty, stores all parsed frames.
193"""
194def parse_frames(capture_frames, filter_types):
195    frames = []
196    for frame in capture_frames:
197        frame_type = _fetch_frame_field_value(
198            frame, 'wlan.fc_type_subtype')
199        if filter_types and frame_type not in filter_types:
200            continue
201        frametime = frame.sniff_time
202        source_addr = _fetch_frame_field_value(
203            frame, 'wlan.sa')
204        dest_addr = _fetch_frame_field_value(
205            frame, 'wlan.da')
206        frames.append([frametime, source_addr, dest_addr, frame_type])
207    return frames
208
209```
210
211Using these functions, you can retrieve a timestamp, the source address, the
212destination address, and the frame subtype for every packet that your pcap
213device captured over the course of the test. The keywords within
214`parse_frames()` ('wlan.sa', 'wlan.da', 'wlan.fc_type_subtype'), are special
215Wireshark filters that correspond to the relevant data we are looking for.
216There are over 242000 such filters which you can find in the [wireshark docs].
217
218Now we just need to call `parse_frames()` and upgrade our packet logging logic.
219Replace the file logging logic from above with the following code which parses
220the frames into a much more readable format:
221
222```python3
223
224frameTypesToFilter = {}
225frames = parse_frames(capture, frameTypesToFilter)
226
227packet_file = open('/tmp/pcap', 'w')
228packet_file.write('{:^28s}|{:^19s}|{:^19s}|{:^6s}\n'.format(
229    'Timestamp', 'Source Address', 'Receiver Address', 'Type'))
230packet_file.write('---------------------------------------------------------------------------\n')
231for packet in frames:
232    packet_file.write('{:^28s}|{:^19s}|{:^19s}|{:^6s}\n'.format(
233        str(packet[0]), packet[1], packet[2], packet[3]))
234packet_file.close()
235
236```
237
238This time when we run the test we can very concisely see every single packet
239that our pcap device captured, and we get only the data which is relevant to
240our purposes. Later on we'll populate `frameTypesToFilter` to single out the
241frames that are relevant to the connection/disconnection process, but first
242let's look deeper into the frames themselves.
243
244#### 3. Learn some 802.11 background
245
246Before we start analyzing the packets, we need some background on 802.11 frames.
247The state machine below represents the 802.11 connection/disconnection protocol.
248As you can see, a connection's state is determined by the authentication and
249association status between its devices. The types of packets that a device is
250able to send and receive are dependent on the state of its connections.
251
252![State Machine](assets/wifi-state-machine.gif)
253
254##### Authentication and Association
255
256In order to ensure security, users must be authenticated to a network before
257they are allowed to use the network. The authentication process itself is not
258strictly defined by the 802.11 protocol, but it usually consists of a robust
259cryptographic exchange that allows the network to trust the user. Once a user
260has been authenticated to the network, it is *trusted*, but it is still not
261actually a member of the network until it has been *associated*. Association
262can be thought of as the proccess of actually joining the network, and also
263acts as a sort of *registration* that allows the network to determine which
264access point to use for a given user.
265
266##### Class 1 frames
267
268Class 1 frames can be sent in any state, and they are used to support the basic
269operations of 802.11 connections. Class 1 frames are called *Management Frames*
270and they allow devices to find a network and negotiate their connection status.
271
272**Some class 1 frames:**
273
274* *Beacons* are frames that access points send out on a regular interval to
275broadcast their existence to the world. Devices are only aware of access points
276because they can see the beacon frames they send.
277* Devices respond to beacons with *Probe Requests* which in turn let the
278network know of their existence. The probe request also includes a list of all
279data rates the device supports, which the network can use to check for
280compatibility with those supported by the access point.
281* Access points respond with *Probe Responses* which either confirm or deny
282compatibility.
283* If the two are compatible, they can engage in the authentication/association
284process as explained above with various *Association* and *Authentication*
285frames.
286
287##### Class 2 frames
288
289Class 2 frames can only be sent from a successfully authenticated device, which
290means they can be sent in states 2 and 3. Class 2 frames are called
291*Control Frames*, and their purpose is to allow authenticated devices to
292negotiate the sending of data between them. Request to send (RTS), clear to send
293(CTS), and acknowledge (ACK) are all examples of class 2 frames.
294
295##### Class 3 frames
296
297Class 3 frames can only be sent from an authenticated and associated device,
298meaning they can only be sent while in state 3. Class 3 frames are
299*Data Frames* and they make up all of the actual bulk of wireless
300communication. All frames which are used to send non-meta data between devices
301are data frames.
302
303#### 4. Let's analyze some packets
304
305Now that we have a basic understanding of 802.11 frame classes, we can use our
306captured packets to study the 802.11 connection/disconnection protocol in
307action. Near the bottom of this page is a set of [lookup tables] that outline
308every type of frame in the 802.11 protocol, which you can use to determine what
309kind of packets we picked up.
310
311[Solutions and hints] to the questions below can be found after the
312lookup tables at the bottom of this page, but please do your best to answer
313them yourself before referring to the solutions.
314
315Lets see if we can answer some basic questions about your configuration based
316on the context of the captured packets:
317
3181. What is the MAC address of your router? (you may already know this, but
319   try to infer from the context of the packets)
3201. What is the MAC address of your DUT?
3211. What is the beacon interval (time between beacons) of your router?
3221. What could a receiver address of *ff:ff:ff:ff:ff:ff* indicate?
323
324Now, try to find the frames where the DUT and router negotiate their connection.
325Depending on how noisy your setup is this could be somewhat difficult, but you
326should be able to see the authentication/association process in action by
327looking for some key frame types. (Hint: look for a class 3 frame being sent
328from your DUT to your router, and work back to see the frames that got them
329there). Study the process and compare the flow to the frame class descriptions
330above.
331
332#### 5. Filter the frames and check your results
333
334We can populate `framesToFilter` with frame type codes (i.e. '0x04') to show
335only the frames that are a part of the connection process. Based on what
336you know about the 802.11 state machine, begin filtering for frames
337that you know are relevant. Do not include beacon frames (type 0x08) because
338while they are a part of the connection process, there are so many of them
339that they will clog up the output. After improving the filter, add the
340following code to the bottom of [network_WiFi_SimpleConnect] to produce another
341output file which only shows the frametypes so the testing script can parse
342them.
343
344```python3
345
346output_file = open('/tmp/filtered_pcap', 'w')
347for packet in frames:
348    output_file.write(str(packet[3]) + '\n')
349output_file.close()
350
351```
352
353Now, test your ouput file by running the testing python script:
354
355```bash
356python wifi-basics-codelab-pcap-test.py
357```
358
359This script will check your output to see if you've isolated the correct
360frames and that the entire connection sequence can be seen. If the script
361fails, keep adjusting your filter until it succeeds. After you have passed the
362test, review `/tmp/pcap` again to see the entire process in action. Finally,
363refer back to the questions in [section 4] one last time to see if you've
364gained any new insight into the 802.11 protocol.
365
366## Lookup Tables
367
368### Management Frames (Class 1)
369
370| Subtype Value | Hex Encoding | Subtype Name           |
371|---------------|--------------|------------------------|
372| 0000          | 0x00         | Association Request    |
373| 0001          | 0x01         | Association Response   |
374| 0010          | 0x02         | Reassociation Request  |
375| 0011          | 0x03         | Reassociation Response |
376| 0100          | 0x04         | Probe Request          |
377| 0101          | 0x05         | Probe Response         |
378| 1000          | 0x08         | Beacon                 |
379| 1001          | 0x09         | ATIM                   |
380| 1010          | 0x0a         | Disassociation         |
381| 1011          | 0x0b         | Authentication         |
382| 1100          | 0x0c         | Deauthentication       |
383| 1101          | 0x0d         | Action                 |
384
385### Control Frames (Class 2)
386
387| Subtype Value | Hex Encoding | Subtype Name                 |
388|---------------|--------------|------------------------------|
389| 1000          | 0x18         | Block Acknowedgement Request |
390| 1001          | 0x19         | Block Acknowledgement        |
391| 1010          | 0x1a         | Power Save (PS)-Poll         |
392| 1011          | 0x1b         | RTS                          |
393| 1100          | 0x1c         | CTS                          |
394| 1101          | 0x1d         | Acknowledgement (ACK)        |
395| 1110          | 0x1e         | Contention-Free (CF)-End     |
396| 1111          | 0x1f         | CF-End+CF-ACK                |
397
398### Data Frames (Class 3)
399
400| Subtype Value | Hex Encoding | Subtype Name                               |
401|---------------|--------------|--------------------------------------------|
402| 0000          | 0x20         | Data                                       |
403| 0001          | 0x21         | Data + CF-Ack                              |
404| 0010          | 0x22         | Data + CF-Poll                             |
405| 0011          | 0x23         | Data + CF-Ack+CF-Poll                      |
406| 0100          | 0x24         | Null Data (no data transmitted)            |
407| 0101          | 0x25         | CF-Ack (no data transmitted)               |
408| 0110          | 0x26         | CF-Poll (no data transmitted)              |
409| 0111          | 0x27         | CF-Ack + CF-Poll (no data transmitted)     |
410| 1000          | 0x28         | QoS Data                                   |
411| 1001          | 0x29         | Qos Data + CF-Ack                          |
412| 1010          | 0x2a         | QoS Data + CF-Poll                         |
413| 1011          | 0x2b         | QoS Data + CF-Ack + CF-Poll                |
414| 1100          | 0x2c         | QoS Null (no data transmitted)             |
415| 1101          | 0x2d         | Qos CF-Ack (no data transmitted)           |
416| 1110          | 0x2e         | QoS CF-Poll (no data transmitted)          |
417| 1111          | 0x2f         | QoS CF-Ack + CF-Poll (no data transmitted) |
418
419## Solutions and hints
420
421### Configuration questions
422
4231. Your router should be sending many beacon packets (type 0x08 frames), so
424   look for the source address of the frames of type 0x08.
4251. Your DUT can be recognized as the device which has a "conversation" with
426   your router. I.e. you should be able to see one IP which is the
427   sender/receiver of several different management frames (0x00, 0x01, etc.)
428   with your router.
4291. The beacon interval is the time a device waits between sending beacon
430   frames. You can determine this interval for a device by finding the time
431   that passes between two beacons being transmitted by the
432   device. The beacon interval for your router is most likely 100ms.
4331. A receiver address of *ff:ff:ff:ff:ff:ff* indicates that the frame is
434   being broadcasted to any receiver that can hear it. This is pattern is
435   used for beacon frames because these frames are intended as a sort of 'ping'
436   to all nearby devices.
437
438### Packet filter solution
439
440The testing script is looking for a particular packet sequence that
441shows the DUT and router connecting to each other. The golden connection
442sequence is as follows:
443
4441. Probe Request: 0x04
4451. Probe Response: 0x05
4461. Authentication: 0x0b
4471. Assoc Request: 0x00
4481. Assoc Response: 0x01
4491. Deauthentication: 0x0c
450
451In practice, we have noticed that many of the recorded connection sequences do
452not include an Assoc Request packet, so the script is tolerant of that case.
453
454Finally, the script also verifies that no non-relevant frames were included,
455so any non class 1 frames in the output file will cause failure. (Although,
456only the frames in the sequence above are strictly required.)
457
458[chameleon issues]: https://crbug.com/964549
459[lookup tables]: #lookup-tables
460[network_WiFi_UpdateRouter]: ../server/site_tests/network_WiFi_UpdateRouter/network_WiFi_UpdateRouter.py
461[network_WiFi_SimpleConnect]: ../server/site_tests/network_WiFi_SimpleConnect/network_WiFi_SimpleConnect.py
462[Pyshark documentation]: https://kiminewt.github.io/pyshark/
463[section 4]: #4_let_s-analyze-some-packets
464[skylab portal]: https://chromeos-swarming.appspot.com/botlist?c=id&c=task&c=dut_state&c=label-board&c=label-model&c=label-pool&c=os&c=provisionable-cros-version&c=status&d=asc&f=label-wificell%3ATrue&k=label-wificell&s=id
465[skylab tools guide]: http://go/skylab-cli
466[solutions and hints]: #solutions-and-hints
467[test_that]: ./test-that.md
468[wificell documentation]: ./wificell.md
469[wireshark docs]: https://www.wireshark.org/docs/dfref/
470