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