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 * 8 9PYSHARK_LOAD_TIMEOUT = 2 10FRAME_FIELD_RADIOTAP_DATARATE = 'radiotap.datarate' 11FRAME_FIELD_RADIOTAP_MCS_INDEX = 'radiotap.mcs_index' 12FRAME_FIELD_WLAN_FRAME_TYPE = 'wlan.fc_type_subtype' 13FRAME_FIELD_WLAN_SOURCE_ADDR = 'wlan.sa' 14FRAME_FIELD_WLAN_MGMT_SSID = 'wlan_mgt.ssid' 15RADIOTAP_KNOWN_BAD_FCS_REJECTOR = ( 16 'not radiotap.flags.badfcs or radiotap.flags.badfcs==0') 17RADIOTAP_LOW_SIGNAL_REJECTOR = ('radiotap.dbm_antsignal > -85') 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, reject_bad_fcs=True, 122 reject_low_signal=False): 123 """ 124 Get a parsed representation of the contents of a pcap file. 125 If the RF shielding in the wificell or other chambers is imperfect, 126 we'll see packets from the external environment in the packet capture 127 and tests that assert if the packet capture has certain properties 128 (i.e. only packets of a certain kind) will fail. A good way to reject 129 these packets ("leakage from the outside world") is to look at signal 130 strength. The DUT is usually either next to the AP or <5ft from the AP 131 in these chambers. A signal strength of < -85 dBm in an incoming packet 132 should imply it is leakage. The reject_low_signal option is turned off by 133 default and external packets are part of the capture by default. 134 Be careful to not turn on this option in an attenuated setup, where the 135 DUT/AP packets will also have a low signal (i.e. network_WiFi_AttenPerf). 136 137 @param local_pcap_path: string path to a local pcap file on the host. 138 @param display_filter: string filter to apply to captured frames. 139 @param reject_bad_fcs: bool, for frames with bad Frame Check Sequence. 140 @param reject_low_signal: bool, for packets with signal < -85 dBm. These 141 are likely from the external environment and show 142 up due to poor shielding in the RF chamber. 143 144 @return list of Frame structs. 145 146 """ 147 if reject_bad_fcs is True: 148 display_filter = '(%s) and (%s)' % (RADIOTAP_KNOWN_BAD_FCS_REJECTOR, 149 display_filter) 150 151 if reject_low_signal is True: 152 display_filter = '(%s) and (%s)' % (RADIOTAP_LOW_SIGNAL_REJECTOR, 153 display_filter) 154 155 logging.debug('Capture: %s, Filter: %s', local_pcap_path, display_filter) 156 capture_frames = _open_capture(local_pcap_path, display_filter) 157 frames = [] 158 logging.info('Parsing frames') 159 160 for frame in capture_frames: 161 rate = _fetch_frame_field_value(frame, FRAME_FIELD_RADIOTAP_DATARATE) 162 if rate: 163 rate = atof(rate) 164 else: 165 logging.debug('Capture frame missing rate: %s', frame) 166 167 frametime = frame.sniff_time 168 169 mcs_index = _fetch_frame_field_value( 170 frame, FRAME_FIELD_RADIOTAP_MCS_INDEX) 171 if mcs_index: 172 mcs_index = int(mcs_index) 173 174 source_addr = _fetch_frame_field_value( 175 frame, FRAME_FIELD_WLAN_SOURCE_ADDR) 176 177 # Get the SSID for any probe requests 178 frame_type = _fetch_frame_field_value( 179 frame, FRAME_FIELD_WLAN_FRAME_TYPE) 180 if (frame_type in [WLAN_BEACON_FRAME_TYPE, WLAN_PROBE_REQ_FRAME_TYPE]): 181 ssid = _fetch_frame_field_value(frame, FRAME_FIELD_WLAN_MGMT_SSID) 182 # Since the SSID name is a variable length field, there seems to be 183 # a bug in the pyshark parsing, it returns 'SSID: ' instead of '' 184 # for broadcast SSID's. 185 if ssid == PYSHARK_BROADCAST_SSID: 186 ssid = BROADCAST_SSID 187 else: 188 ssid = None 189 190 frames.append(Frame(frametime, rate, mcs_index, ssid, source_addr)) 191 192 return frames 193 194 195def get_probe_ssids(local_pcap_path, probe_sender=None): 196 """ 197 Get the SSIDs that were named in 802.11 probe requests frames. 198 199 Parse a pcap, returning all the SSIDs named in 802.11 probe 200 request frames. If |probe_sender| is specified, only probes 201 from that MAC address will be considered. 202 203 @param pcap_path: string path to a local pcap file on the host. 204 @param remote_host: Host object (if the file is remote). 205 @param probe_sender: MAC address of the device sending probes. 206 207 @return: A frozenset of the SSIDs that were probed. 208 209 """ 210 if probe_sender: 211 display_filter = '%s and wlan.addr==%s' % ( 212 WLAN_PROBE_REQ_ACCEPTOR, probe_sender) 213 else: 214 display_filter = WLAN_PROBE_REQ_ACCEPTOR 215 216 frames = get_frames(local_pcap_path, display_filter, reject_bad_fcs=True) 217 218 return frozenset( 219 [frame.ssid for frame in frames if frame.ssid is not None]) 220