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