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