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