• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6
7from locale import *
8from autotest_lib.client.common_lib import error
9
10PYSHARK_LOAD_TIMEOUT = 2
11FRAME_FIELD_RADIOTAP_DATARATE = 'radiotap.datarate'
12FRAME_FIELD_RADIOTAP_MCS_INDEX = 'radiotap.mcs_index'
13FRAME_FIELD_WLAN_FRAME_TYPE = 'wlan.fc_type_subtype'
14FRAME_FIELD_WLAN_SOURCE_ADDR = 'wlan.sa'
15FRAME_FIELD_WLAN_MGMT_SSID = 'wlan_mgt.ssid'
16RADIOTAP_KNOWN_BAD_FCS_REJECTOR = (
17    'not radiotap.flags.badfcs or radiotap.flags.badfcs==0')
18WLAN_BEACON_FRAME_TYPE = '0x08'
19WLAN_BEACON_ACCEPTOR = 'wlan.fc.type_subtype==0x08'
20WLAN_PROBE_REQ_FRAME_TYPE = '0x04'
21WLAN_PROBE_REQ_ACCEPTOR = 'wlan.fc.type_subtype==0x04'
22PYSHARK_BROADCAST_SSID = 'SSID: '
23BROADCAST_SSID = ''
24
25setlocale(LC_ALL, '')
26
27class Frame(object):
28    """A frame from a packet capture."""
29    TIME_FORMAT = "%H:%M:%S.%f"
30
31
32    def __init__(self, frametime, bit_rate, mcs_index, ssid, source_addr):
33        self._datetime = frametime
34        self._bit_rate = bit_rate
35        self._mcs_index = mcs_index
36        self._ssid = ssid
37        self._source_addr = source_addr
38
39
40    @property
41    def time_datetime(self):
42        """The time of the frame, as a |datetime| object."""
43        return self._datetime
44
45
46    @property
47    def bit_rate(self):
48        """The bitrate used to transmit the frame, as an int."""
49        return self._bit_rate
50
51
52    @property
53    def mcs_index(self):
54        """
55        The MCS index used to transmit the frame, as an int.
56
57        The value may be None, if the frame was not transmitted
58        using 802.11n modes.
59        """
60        return self._mcs_index
61
62
63    @property
64    def ssid(self):
65        """
66        The SSID of the frame, as a string.
67
68        The value may be None, if the frame does not have an SSID.
69        """
70        return self._ssid
71
72
73    @property
74    def source_addr(self):
75        """The source address of the frame, as a string."""
76        return self._source_addr
77
78
79    @property
80    def time_string(self):
81        """The time of the frame, in local time, as a string."""
82        return self._datetime.strftime(self.TIME_FORMAT)
83
84
85def _fetch_frame_field_value(frame, field):
86    """
87    Retrieve the value of |field| within the |frame|.
88
89    @param frame: Pyshark packet object corresponding to a captured frame.
90    @param field: Field for which the value needs to be extracted from |frame|.
91
92    @return Value extracted from the frame if the field exists, else None.
93
94    """
95    layer_object = frame
96    for layer in field.split('.'):
97        try:
98            layer_object = getattr(layer_object, layer)
99        except AttributeError:
100            return None
101    return layer_object
102
103
104def _open_capture(pcap_path, display_filter):
105    """
106    Get pyshark packet object parsed contents of a pcap file.
107
108    @param pcap_path: string path to pcap file.
109    @param display_filter: string filter to apply to captured frames.
110
111    @return list of Pyshark packet objects.
112
113    """
114    import pyshark
115    capture = pyshark.FileCapture(
116        input_file=pcap_path, display_filter=display_filter)
117    capture.load_packets(timeout=PYSHARK_LOAD_TIMEOUT)
118    return capture
119
120
121def get_frames(local_pcap_path, display_filter, bad_fcs):
122    """
123    Get a parsed representation of the contents of a pcap file.
124
125    @param local_pcap_path: string path to a local pcap file on the host.
126    @param diplay_filter: string filter to apply to captured frames.
127    @param bad_fcs: string 'include' or 'discard'
128
129    @return list of Frame structs.
130
131    """
132    if bad_fcs == 'include':
133        display_filter = display_filter
134    elif bad_fcs == 'discard':
135        display_filter = '(%s) and (%s)' % (RADIOTAP_KNOWN_BAD_FCS_REJECTOR,
136                                            display_filter)
137    else:
138        raise error.TestError('Invalid value for bad_fcs arg: %s.' % bad_fcs)
139
140    logging.debug('Capture: %s, Filter: %s', local_pcap_path, display_filter)
141    capture_frames = _open_capture(local_pcap_path, display_filter)
142    frames = []
143    logging.info('Parsing frames')
144
145    for frame in capture_frames:
146        rate = _fetch_frame_field_value(frame, FRAME_FIELD_RADIOTAP_DATARATE)
147        if rate:
148            rate = atof(rate)
149        else:
150            logging.debug('Found bad capture frame: %s', frame)
151            continue
152
153        frametime = frame.sniff_time
154
155        mcs_index = _fetch_frame_field_value(
156            frame, FRAME_FIELD_RADIOTAP_MCS_INDEX)
157        if mcs_index:
158            mcs_index = int(mcs_index)
159
160        source_addr = _fetch_frame_field_value(
161            frame, FRAME_FIELD_WLAN_SOURCE_ADDR)
162
163        # Get the SSID for any probe requests
164        frame_type = _fetch_frame_field_value(
165            frame, FRAME_FIELD_WLAN_FRAME_TYPE)
166        if (frame_type in [WLAN_BEACON_FRAME_TYPE, WLAN_PROBE_REQ_FRAME_TYPE]):
167            ssid = _fetch_frame_field_value(frame, FRAME_FIELD_WLAN_MGMT_SSID)
168            # Since the SSID name is a variable length field, there seems to be
169            # a bug in the pyshark parsing, it returns 'SSID: ' instead of ''
170            # for broadcast SSID's.
171            if ssid == PYSHARK_BROADCAST_SSID:
172                ssid = BROADCAST_SSID
173        else:
174            ssid = None
175
176        frames.append(Frame(frametime, rate, mcs_index, ssid, source_addr))
177
178    return frames
179
180
181def get_probe_ssids(local_pcap_path, probe_sender=None):
182    """
183    Get the SSIDs that were named in 802.11 probe requests frames.
184
185    Parse a pcap, returning all the SSIDs named in 802.11 probe
186    request frames. If |probe_sender| is specified, only probes
187    from that MAC address will be considered.
188
189    @param pcap_path: string path to a local pcap file on the host.
190    @param remote_host: Host object (if the file is remote).
191    @param probe_sender: MAC address of the device sending probes.
192
193    @return: A frozenset of the SSIDs that were probed.
194
195    """
196    if probe_sender:
197        diplay_filter = '%s and wlan.addr==%s' % (
198                WLAN_PROBE_REQ_ACCEPTOR, probe_sender)
199    else:
200        diplay_filter = WLAN_PROBE_REQ_ACCEPTOR
201
202    frames = get_frames(local_pcap_path, diplay_filter, bad_fcs='discard')
203
204    return frozenset(
205            [frame.ssid for frame in frames if frame.ssid is not None])
206