1#!/usr/bin/python 2# Copyright 2015 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import collections 7import pyshark 8import os 9 10class PacketCapture(object): 11 """ Class to manage the packet capture file access from a chaos test. """ 12 13 LOAD_TIMEOUT = 2 14 15 def __init__(self, file_name): 16 self._file_name = file_name 17 18 def get_output(self, display_filter=None, summaries=True, decryption=None): 19 """ 20 Gets the packets from a trace file as Pyshark packet objects for 21 further analysis. 22 23 @param display_filer: Tshark filter to be used for extracting the 24 relevant packets. 25 @param summaries: Flag to indicate whether to extract only the summaries 26 of packet or not. 27 @param decryption: Decryption key to be used on the trace file. 28 @returns List of pyshark packet objects. 29 30 """ 31 capture = pyshark.FileCapture(self._file_name, 32 display_filter=display_filter, 33 only_summaries=summaries, 34 decryption_key=decryption, 35 encryption_type='wpa-pwd') 36 capture.load_packets(timeout=self.LOAD_TIMEOUT) 37 return capture 38 39 def get_packet_number(self, index, summary): 40 """ 41 Gets the packet that appears index |index| in the capture file. 42 43 @param index: Extract this index from the capture file. 44 @param summary: Flag to indicate whether to extract only the summary 45 of the packet or not. 46 47 @returns pyshark packet object or None. 48 49 """ 50 display_filter = "frame.number == %d" % index 51 capture = pyshark.FileCapture(self._file_name, 52 display_filter=display_filter, 53 only_summaries=summary) 54 capture.load_packets(timeout=self.LOAD_TIMEOUT) 55 if not capture: 56 return None 57 return capture[0] 58 59 def get_packet_after(self, packet): 60 """ 61 Gets the packet that appears next in the capture file. 62 63 @param packet: Reference packet -- the packet after this one will 64 be retrieved. 65 66 @returns pyshark packet object or None. 67 68 """ 69 return self.get_packet_number(int(packet.number) + 1, summary=False) 70 71 def count_packets_with_display_filter(self, display_filter): 72 """ 73 Counts the number of packets which match the provided display filter. 74 75 @param display_filer: Tshark filter to be used for extracting the 76 relevant packets. 77 @returns Number of packets which match the filter. 78 79 """ 80 output = self.get_output(display_filter=display_filter) 81 return len(output) 82 83 def count_packets_from(self, mac_addresses): 84 """ 85 Counts the number of packets sent from a given entity using MAC address. 86 87 @param mac_address: Mac address of the entity. 88 @returns Number of packets which matched the MAC address filter. 89 90 """ 91 filter = ' or '.join(['wlan.ta==%s' % addr for addr in mac_addresses]) 92 return self.count_packets_with_display_filter(filter) 93 94 def count_packets_to(self, mac_addresses): 95 """ 96 Counts the number of packets sent to a given entity using MAC address. 97 98 @param mac_address: Mac address of the entity. 99 @returns Number of packets which matched the MAC address filter. 100 101 """ 102 filter = ' or '.join(['wlan.ra==%s' % addr for addr in mac_addresses]) 103 return self.count_packets_with_display_filter(filter) 104 105 def count_packets_from_or_to(self, mac_addresses): 106 """ 107 Counts the number of packets sent to/from a given entity using MAC 108 address. 109 110 @param mac_address: Mac address of the entity. 111 @returns Number of packets which matched the MAC address filter. 112 113 """ 114 filter = ' or '.join(['wlan.addr==%s' % addr for addr in mac_addresses]) 115 return self.count_packets_with_display_filter(filter) 116 117 def count_beacons_from(self, mac_addresses): 118 """ 119 Counts the number of beacon packets sent from a AP using MAC address. 120 121 @param mac_address: Mac address of the AP. 122 @returns Number of packets which matched the MAC address filter. 123 124 """ 125 filter = ' or '.join(['wlan.ta==%s' % addr for addr in mac_addresses]) 126 filter = '(%s) and wlan.fc.type_subtype == 0x0008' % (filter) 127 return self.count_packets_with_display_filter(filter) 128 129 def get_filtered_packets(self, ap, dut, summaries, decryption): 130 """ 131 Gets the packets sent to/from the DUT from a trace file as Pyshark 132 packet objects for further analysis. 133 134 @param summaries: Flag to indicate whether to extract only the summaries 135 of packet or not. 136 @param dut: Mac address of the DUT. 137 @param ap: Mac address of the AP. 138 @param decryption: Decryption key to be used on the trace file. 139 @returns List of pyshark packet objects. 140 141 """ 142 filter = 'wlan.addr==%s' % dut 143 packets = self.get_output(display_filter=filter, summaries=summaries, 144 decryption=decryption) 145 return packets 146 147 148class WifiStateMachineAnalyzer(object): 149 """ Class to analyze the Wifi Protocol exhcange from a chaos test. """ 150 151 STATE_INIT = "INIT" 152 STATE_PROBE_REQ = "PROBE_REQ" 153 STATE_PROBE_RESP = "PROBE_RESP" 154 STATE_AUTH_REQ = "AUTH_REQ" 155 STATE_AUTH_RESP = "AUTH_RESP" 156 STATE_ASSOC_REQ = "ASSOC_REQ" 157 STATE_ASSOC_RESP = "ASSOC_RESP" 158 STATE_KEY_MESSAGE_1 = "KEY_MESSAGE_1" 159 STATE_KEY_MESSAGE_2 = "KEY_MESSAGE_2" 160 STATE_KEY_MESSAGE_3 = "KEY_MESSAGE_3" 161 STATE_KEY_MESSAGE_4 = "KEY_MESSAGE_4" 162 STATE_DHCP_DISCOVER = "DHCP_DISCOVER" 163 STATE_DHCP_OFFER = "DHCP_OFFER" 164 STATE_DHCP_REQ = "DHCP_REQ" 165 STATE_DHCP_REQ_ACK = "DHCP_REQ_ACK" 166 STATE_END = "END" 167 168 169 PACKET_MATCH_WLAN_FRAME_TYPE = "wlan.fc_type_subtype" 170 PACKET_MATCH_WLAN_FRAME_RETRY_FLAG = "wlan.fc_retry" 171 PACKET_MATCH_WLAN_MANAGEMENT_REASON_CODE = "wlan_mgt.fixed_reason_code" 172 PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE = "wlan_mgt.fixed_status_code" 173 PACKET_MATCH_WLAN_TRANSMITTER = "wlan.ta" 174 PACKET_MATCH_LLC_TYPE = "llc.type" 175 PACKET_MATCH_EAP_TYPE = "eapol.type" 176 PACKET_MATCH_EAP_KEY_INFO_INSTALL = "eapol.keydes_key_info_install" 177 PACKET_MATCH_EAP_KEY_INFO_ACK = "eapol.keydes_key_info_key_ack" 178 PACKET_MATCH_EAP_KEY_INFO_MIC = "eapol.keydes_key_info_key_mic" 179 PACKET_MATCH_EAP_KEY_INFO_SECURE = "eapol.keydes_key_info_secure" 180 PACKET_MATCH_IP_PROTOCOL_TYPE = "ip.proto" 181 PACKET_MATCH_DHCP_MESSAGE_TYPE = "bootp.option_dhcp" 182 PACKET_MATCH_RADIOTAP_DATA_RATE = "radiotap.datarate" 183 184 WLAN_PROBE_REQ_FRAME_TYPE = '0x04' 185 WLAN_PROBE_RESP_FRAME_TYPE = '0x05' 186 WLAN_AUTH_REQ_FRAME_TYPE = '0x0b' 187 WLAN_AUTH_RESP_FRAME_TYPE = '0x0b' 188 WLAN_ASSOC_REQ_FRAME_TYPE = '0x00' 189 WLAN_ASSOC_RESP_FRAME_TYPE = '0x01' 190 WLAN_ACK_FRAME_TYPE = '0x1d' 191 WLAN_DEAUTH_REQ_FRAME_TYPE = '0x0c' 192 WLAN_DISASSOC_REQ_FRAME_TYPE = '0x0a' 193 WLAN_QOS_DATA_FRAME_TYPE = '0x28' 194 WLAN_MANAGEMENT_STATUS_CODE_SUCCESS = '0x0000' 195 WLAN_BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff' 196 WLAN_FRAME_CONTROL_TYPE_MANAGEMENT = '0' 197 198 WLAN_FRAME_RETRY = '1' 199 200 LLC_AUTH_TYPE = '0x888e' 201 202 EAP_KEY_TYPE = '0x03' 203 204 IP_UDP_PROTOCOL_TYPE = '17' 205 206 DHCP_DISCOVER_MESSAGE_TYPE = '1' 207 DHCP_OFFER_MESSAGE_TYPE = '2' 208 DHCP_REQUEST_MESSAGE_TYPE = '3' 209 DHCP_ACK_MESSAGE_TYPE = '5' 210 211 DIR_TO_DUT = 0 212 DIR_FROM_DUT = 1 213 DIR_DUT_TO_AP = 2 214 DIR_AP_TO_DUT = 3 215 DIR_ACK = 4 216 217 # State Info Tuples (Name, Direction, Match fields, Next State) 218 StateInfo = collections.namedtuple( 219 'StateInfo', ['name', 'direction', 'match_fields', 'next_state']) 220 STATE_INFO_INIT = StateInfo("INIT", 0, {}, STATE_PROBE_REQ) 221 STATE_INFO_PROBE_REQ = StateInfo("WLAN PROBE REQUEST", 222 DIR_FROM_DUT, 223 { PACKET_MATCH_WLAN_FRAME_TYPE: 224 WLAN_PROBE_REQ_FRAME_TYPE }, 225 STATE_PROBE_RESP) 226 STATE_INFO_PROBE_RESP = StateInfo("WLAN PROBE RESPONSE", 227 DIR_AP_TO_DUT, 228 { PACKET_MATCH_WLAN_FRAME_TYPE: 229 WLAN_PROBE_RESP_FRAME_TYPE }, 230 STATE_AUTH_REQ) 231 STATE_INFO_AUTH_REQ = StateInfo("WLAN AUTH REQUEST", 232 DIR_DUT_TO_AP, 233 { PACKET_MATCH_WLAN_FRAME_TYPE: 234 WLAN_AUTH_REQ_FRAME_TYPE }, 235 STATE_AUTH_RESP) 236 STATE_INFO_AUTH_RESP = StateInfo( 237 "WLAN AUTH RESPONSE", 238 DIR_AP_TO_DUT, 239 { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_AUTH_REQ_FRAME_TYPE, 240 PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE: 241 WLAN_MANAGEMENT_STATUS_CODE_SUCCESS }, 242 STATE_ASSOC_REQ) 243 STATE_INFO_ASSOC_REQ = StateInfo("WLAN ASSOC REQUEST", 244 DIR_DUT_TO_AP, 245 { PACKET_MATCH_WLAN_FRAME_TYPE: 246 WLAN_ASSOC_REQ_FRAME_TYPE }, 247 STATE_ASSOC_RESP) 248 STATE_INFO_ASSOC_RESP = StateInfo( 249 "WLAN ASSOC RESPONSE", 250 DIR_AP_TO_DUT, 251 { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_ASSOC_RESP_FRAME_TYPE, 252 PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE: 253 WLAN_MANAGEMENT_STATUS_CODE_SUCCESS }, 254 STATE_KEY_MESSAGE_1) 255 STATE_INFO_KEY_MESSAGE_1 = StateInfo("WPA KEY MESSAGE 1", 256 DIR_AP_TO_DUT, 257 { PACKET_MATCH_LLC_TYPE: 258 LLC_AUTH_TYPE, 259 PACKET_MATCH_EAP_KEY_INFO_INSTALL: 260 '0', 261 PACKET_MATCH_EAP_KEY_INFO_ACK: 262 '1', 263 PACKET_MATCH_EAP_KEY_INFO_MIC: 264 '0', 265 PACKET_MATCH_EAP_KEY_INFO_SECURE: 266 '0' }, 267 STATE_KEY_MESSAGE_2) 268 STATE_INFO_KEY_MESSAGE_2 = StateInfo("WPA KEY MESSAGE 2", 269 DIR_DUT_TO_AP, 270 { PACKET_MATCH_LLC_TYPE: 271 LLC_AUTH_TYPE, 272 PACKET_MATCH_EAP_KEY_INFO_INSTALL: 273 '0', 274 PACKET_MATCH_EAP_KEY_INFO_ACK: 275 '0', 276 PACKET_MATCH_EAP_KEY_INFO_MIC: 277 '1', 278 PACKET_MATCH_EAP_KEY_INFO_SECURE: 279 '0' }, 280 STATE_KEY_MESSAGE_3) 281 STATE_INFO_KEY_MESSAGE_3 = StateInfo("WPA KEY MESSAGE 3", 282 DIR_AP_TO_DUT, 283 { PACKET_MATCH_LLC_TYPE: 284 LLC_AUTH_TYPE, 285 PACKET_MATCH_EAP_KEY_INFO_INSTALL: 286 '1', 287 PACKET_MATCH_EAP_KEY_INFO_ACK: 288 '1', 289 PACKET_MATCH_EAP_KEY_INFO_MIC: 290 '1', 291 PACKET_MATCH_EAP_KEY_INFO_SECURE: 292 '1' }, 293 STATE_KEY_MESSAGE_4) 294 STATE_INFO_KEY_MESSAGE_4 = StateInfo("WPA KEY MESSAGE 4", 295 DIR_DUT_TO_AP, 296 { PACKET_MATCH_LLC_TYPE: 297 LLC_AUTH_TYPE, 298 PACKET_MATCH_EAP_KEY_INFO_INSTALL: 299 '0', 300 PACKET_MATCH_EAP_KEY_INFO_ACK: 301 '0', 302 PACKET_MATCH_EAP_KEY_INFO_MIC: 303 '1', 304 PACKET_MATCH_EAP_KEY_INFO_SECURE: 305 '1' }, 306 STATE_DHCP_DISCOVER) 307 STATE_INFO_DHCP_DISCOVER = StateInfo("DHCP DISCOVER", 308 DIR_DUT_TO_AP, 309 { PACKET_MATCH_IP_PROTOCOL_TYPE: 310 IP_UDP_PROTOCOL_TYPE, 311 PACKET_MATCH_DHCP_MESSAGE_TYPE: 312 DHCP_DISCOVER_MESSAGE_TYPE }, 313 STATE_DHCP_OFFER) 314 STATE_INFO_DHCP_OFFER = StateInfo("DHCP OFFER", 315 DIR_AP_TO_DUT, 316 { PACKET_MATCH_IP_PROTOCOL_TYPE: 317 IP_UDP_PROTOCOL_TYPE, 318 PACKET_MATCH_DHCP_MESSAGE_TYPE: 319 DHCP_OFFER_MESSAGE_TYPE }, 320 STATE_DHCP_REQ) 321 STATE_INFO_DHCP_REQ = StateInfo("DHCP REQUEST", 322 DIR_DUT_TO_AP, 323 { PACKET_MATCH_IP_PROTOCOL_TYPE: 324 IP_UDP_PROTOCOL_TYPE, 325 PACKET_MATCH_DHCP_MESSAGE_TYPE: 326 DHCP_REQUEST_MESSAGE_TYPE }, 327 STATE_DHCP_REQ_ACK) 328 STATE_INFO_DHCP_REQ_ACK = StateInfo("DHCP ACK", 329 DIR_AP_TO_DUT, 330 { PACKET_MATCH_IP_PROTOCOL_TYPE: 331 IP_UDP_PROTOCOL_TYPE, 332 PACKET_MATCH_DHCP_MESSAGE_TYPE: 333 DHCP_ACK_MESSAGE_TYPE }, 334 STATE_END) 335 STATE_INFO_END = StateInfo("END", 0, {}, STATE_END) 336 # Master State Table Map of State Infos 337 STATE_INFO_MAP = {STATE_INIT: STATE_INFO_INIT, 338 STATE_PROBE_REQ: STATE_INFO_PROBE_REQ, 339 STATE_PROBE_RESP: STATE_INFO_PROBE_RESP, 340 STATE_AUTH_REQ: STATE_INFO_AUTH_REQ, 341 STATE_AUTH_RESP: STATE_INFO_AUTH_RESP, 342 STATE_ASSOC_REQ: STATE_INFO_ASSOC_REQ, 343 STATE_ASSOC_RESP: STATE_INFO_ASSOC_RESP, 344 STATE_KEY_MESSAGE_1:STATE_INFO_KEY_MESSAGE_1, 345 STATE_KEY_MESSAGE_2:STATE_INFO_KEY_MESSAGE_2, 346 STATE_KEY_MESSAGE_3:STATE_INFO_KEY_MESSAGE_3, 347 STATE_KEY_MESSAGE_4:STATE_INFO_KEY_MESSAGE_4, 348 STATE_DHCP_DISCOVER:STATE_INFO_DHCP_DISCOVER, 349 STATE_DHCP_OFFER: STATE_INFO_DHCP_OFFER, 350 STATE_DHCP_REQ: STATE_INFO_DHCP_REQ, 351 STATE_DHCP_REQ_ACK: STATE_INFO_DHCP_REQ_ACK, 352 STATE_END: STATE_INFO_END} 353 354 # Packet Details Tuples (User friendly name, Field name) 355 PacketDetail = collections.namedtuple( 356 "PacketDetail", ["friendly_name", "field_name"]) 357 PACKET_DETAIL_REASON_CODE = PacketDetail( 358 "Reason Code", 359 PACKET_MATCH_WLAN_MANAGEMENT_REASON_CODE) 360 PACKET_DETAIL_STATUS_CODE = PacketDetail( 361 "Status Code", 362 PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE) 363 PACKET_DETAIL_SENDER = PacketDetail( 364 "Sender", PACKET_MATCH_WLAN_TRANSMITTER) 365 366 # Error State Info Tuples (Name, Match fields) 367 ErrorStateInfo = collections.namedtuple( 368 'ErrorStateInfo', ['name', 'match_fields', 'details']) 369 ERROR_STATE_INFO_DEAUTH = ErrorStateInfo("WLAN DEAUTH REQUEST", 370 { PACKET_MATCH_WLAN_FRAME_TYPE: 371 WLAN_DEAUTH_REQ_FRAME_TYPE }, 372 [ PACKET_DETAIL_SENDER, 373 PACKET_DETAIL_REASON_CODE ]) 374 ERROR_STATE_INFO_DEASSOC = ErrorStateInfo("WLAN DISASSOC REQUEST", 375 { PACKET_MATCH_WLAN_FRAME_TYPE: 376 WLAN_DISASSOC_REQ_FRAME_TYPE }, 377 [ PACKET_DETAIL_SENDER, 378 PACKET_DETAIL_REASON_CODE ]) 379 # Master State Table Tuple of Error State Infos 380 ERROR_STATE_INFO_TUPLE = (ERROR_STATE_INFO_DEAUTH, ERROR_STATE_INFO_DEASSOC) 381 382 # These warnings actually match successful states, but since the we 383 # check forwards and backwards through the state machine for the successful 384 # version of these packets, they can only match a failure. 385 WARNING_INFO_AUTH_REJ = ErrorStateInfo( 386 "WLAN AUTH REJECTED", 387 { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_AUTH_REQ_FRAME_TYPE }, 388 [ PACKET_DETAIL_STATUS_CODE ]) 389 WARNING_INFO_ASSOC_REJ = ErrorStateInfo( 390 "WLAN ASSOC REJECTED", 391 { PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_ASSOC_RESP_FRAME_TYPE }, 392 [ PACKET_DETAIL_STATUS_CODE ]) 393 394 # Master Table Tuple of warning information. 395 WARNING_INFO_TUPLE = (WARNING_INFO_AUTH_REJ, WARNING_INFO_ASSOC_REJ) 396 397 398 def __init__(self, ap_macs, dut_mac, filtered_packets, capture, logger): 399 self._current_state = self._get_state(self.STATE_INIT) 400 self._reached_states = [] 401 self._skipped_states = [] 402 self._packets = filtered_packets 403 self._capture = capture 404 self._dut_mac = dut_mac 405 self._ap_macs = ap_macs 406 self._log = logger 407 self._acks = [] 408 409 @property 410 def acks(self): 411 return self._acks 412 413 def _get_state(self, state): 414 return self.STATE_INFO_MAP[state] 415 416 def _get_next_state(self, state): 417 return self._get_state(state.next_state) 418 419 def _get_curr_next_state(self): 420 return self._get_next_state(self._current_state) 421 422 def _fetch_packet_field_value(self, packet, field): 423 layer_object = packet 424 for layer in field.split('.'): 425 try: 426 layer_object = getattr(layer_object, layer) 427 except AttributeError: 428 return None 429 return layer_object 430 431 def _match_packet_fields(self, packet, fields): 432 for field, exp_value in fields.items(): 433 value = self._fetch_packet_field_value(packet, field) 434 if exp_value != value: 435 return False 436 return True 437 438 def _fetch_packet_data_rate(self, packet): 439 return self._fetch_packet_field_value(packet, 440 self.PACKET_MATCH_RADIOTAP_DATA_RATE) 441 442 def _does_packet_match_state(self, state, packet): 443 fields = state.match_fields 444 if self._match_packet_fields(packet, fields): 445 if state.direction == self.DIR_TO_DUT: 446 # This should have receiver addr of DUT 447 if packet.wlan.ra == self._dut_mac: 448 return True 449 elif state.direction == self.DIR_FROM_DUT: 450 # This should have transmitter addr of DUT 451 if packet.wlan.ta == self._dut_mac: 452 return True 453 elif state.direction == self.DIR_AP_TO_DUT: 454 # This should have receiver addr of DUT & 455 # transmitter addr of AP's 456 if ((packet.wlan.ra == self._dut_mac) and 457 (packet.wlan.ta in self._ap_macs)): 458 return True 459 elif state.direction == self.DIR_DUT_TO_AP: 460 # This should have transmitter addr of DUT & 461 # receiver addr of AP's 462 if ((packet.wlan.ta == self._dut_mac) and 463 (packet.wlan.ra in self._ap_macs)): 464 return True 465 return False 466 467 def _does_packet_match_error_state(self, state, packet): 468 fields = state.match_fields 469 return self._match_packet_fields(packet, fields) 470 471 def _get_packet_detail(self, details, packet): 472 attributes = [] 473 attributes.append("Packet number: %s" % packet.number) 474 for detail in details: 475 value = self._fetch_packet_field_value(packet, detail.field_name) 476 attributes.append("%s: %s" % (detail.friendly_name, value)) 477 return attributes 478 479 def _does_packet_match_ack_state(self, packet): 480 fields = { self.PACKET_MATCH_WLAN_FRAME_TYPE: self.WLAN_ACK_FRAME_TYPE } 481 return self._match_packet_fields(packet, fields) 482 483 def _does_packet_contain_retry_flag(self, packet): 484 fields = { self.PACKET_MATCH_WLAN_FRAME_RETRY_FLAG: 485 self.WLAN_FRAME_RETRY } 486 return self._match_packet_fields(packet, fields) 487 488 def _check_for_ack(self, state, packet): 489 if (packet.wlan.da == self.WLAN_BROADCAST_ADDRESS and 490 packet.wlan.fc_type == self.WLAN_FRAME_CONTROL_TYPE_MANAGEMENT): 491 # Broadcast management frames are not ACKed. 492 return True 493 next_packet = self._capture.get_packet_after(packet) 494 if not next_packet or not ( 495 (self._does_packet_match_ack_state(next_packet)) and 496 (next_packet.wlan.addr == packet.wlan.ta)): 497 msg = "WARNING! Missing ACK for state: " + \ 498 state.name + "." 499 self._log.log_to_output_file(msg) 500 return False 501 self._acks.append(int(next_packet.number)) 502 return True 503 504 def _check_for_error(self, packet): 505 for error_state in self.ERROR_STATE_INFO_TUPLE: 506 if self._does_packet_match_error_state(error_state, packet): 507 error_attributes = self._get_packet_detail(error_state.details, 508 packet) 509 msg = "ERROR! State Machine encountered error due to " + \ 510 error_state.name + ", " + \ 511 ", ".join(error_attributes) + "." 512 self._log.log_to_output_file(msg) 513 return True 514 return False 515 516 def _check_for_warning(self, packet): 517 for warning in self.WARNING_INFO_TUPLE: 518 if self._does_packet_match_error_state(warning, packet): 519 error_attributes = self._get_packet_detail(warning.details, 520 packet) 521 msg = "WARNING! " + warning.name + " found, " + \ 522 ", ".join(error_attributes) + "." 523 self._log.log_to_output_file(msg) 524 return True 525 return False 526 527 def _check_for_repeated_state(self, packet): 528 for state in self._reached_states: 529 if self._does_packet_match_state(state, packet): 530 msg = "WARNING! Repeated State: " + \ 531 state.name + ", Packet number: " + \ 532 str(packet.number) 533 if self._does_packet_contain_retry_flag(packet): 534 msg += " due to retransmission." 535 else: 536 msg += "." 537 self._log.log_to_output_file(msg) 538 539 def _is_from_previous_state(self, packet): 540 for state in self._reached_states + self._skipped_states: 541 if self._does_packet_match_state(state, packet): 542 return True 543 return False 544 545 def _step(self, reached_state, packet): 546 # We missed a few packets in between 547 if self._current_state != reached_state: 548 msg = "WARNING! Missed states: " 549 skipped_state = self._current_state 550 while skipped_state != reached_state: 551 msg += skipped_state.name + ", " 552 self._skipped_states.append(skipped_state) 553 skipped_state = self._get_next_state(skipped_state) 554 msg = msg[:-2] 555 msg += "." 556 self._log.log_to_output_file(msg) 557 msg = "Found state: " + reached_state.name 558 if packet: 559 msg += ", Packet number: " + str(packet.number) + \ 560 ", Data rate: " + str(self._fetch_packet_data_rate(packet))+\ 561 "Mbps." 562 else: 563 msg += "." 564 self._log.log_to_output_file(msg) 565 # Ignore the Init state in the reached states 566 if packet: 567 self._reached_states.append(reached_state) 568 self._current_state = self._get_next_state(reached_state) 569 570 def _step_init(self): 571 #self.log_to_output_file("Starting Analysis") 572 self._current_state = self._get_curr_next_state() 573 574 def analyze(self): 575 """ Starts the analysis of the Wifi Protocol Exchange. """ 576 577 # Start the state machine iteration 578 self._step_init() 579 packet_iterator = iter(self._packets) 580 for packet in packet_iterator: 581 self._check_for_repeated_state(packet) 582 # Try to look ahead in the state machine to account for occasional 583 # packet capture misses. 584 next_state = self._current_state 585 while next_state != self.STATE_INFO_END: 586 if self._does_packet_match_state(next_state, packet): 587 self._step(next_state, packet) 588 self._check_for_ack(next_state, packet) 589 break 590 next_state = self._get_next_state(next_state) 591 if self._current_state == self.STATE_INFO_END: 592 self._log.log_to_output_file("State Machine completed!") 593 return True 594 if self._check_for_error(packet): 595 return False 596 if not self._is_from_previous_state(packet): 597 self._check_for_warning(packet) 598 msg = "ERROR! State Machine halted at " + self._current_state.name + \ 599 " state." 600 self._log.log_to_output_file(msg) 601 return False 602 603 604class ChaosCaptureAnalyzer(object): 605 """ Class to analyze the packet capture from a chaos test . """ 606 607 def __init__(self, ap_bssids, ap_ssid, dut_mac, logger): 608 self._ap_bssids = ap_bssids 609 self._ap_ssid = ap_ssid 610 self._dut_mac = dut_mac 611 self._log = logger 612 613 def _validate_ap_presence(self, capture, bssids, ssid): 614 beacon_count = capture.count_beacons_from(bssids) 615 if not beacon_count: 616 packet_count = capture.count_packets_from(bssids) 617 if not packet_count: 618 self._log.log_to_output_file( 619 "No packets at all from AP BSSIDs %r!" % bssids) 620 else: 621 self._log.log_to_output_file( 622 "No beacons from AP BSSIDs %r but %d packets!" % 623 (bssids, packet_count)) 624 return False 625 self._log.log_to_output_file("AP BSSIDs: %s, SSID: %s." % 626 (bssids, ssid)) 627 self._log.log_to_output_file("AP beacon count: %d." % beacon_count) 628 return True 629 630 def _validate_dut_presence(self, capture, dut_mac): 631 tx_count = capture.count_packets_from([dut_mac]) 632 if not tx_count: 633 self._log.log_to_output_file( 634 "No packets Tx at all from DUT MAC %r!" % dut_mac) 635 return False 636 rx_count = capture.count_packets_to([dut_mac]) 637 self._log.log_to_output_file("DUT MAC: %s." % dut_mac) 638 self._log.log_to_output_file( 639 "DUT packet count Tx: %d, Rx: %d." % (tx_count, rx_count)) 640 return True 641 642 def _ack_interleave(self, packets, capture, acks): 643 """Generator that interleaves packets with their associated ACKs.""" 644 for packet in packets: 645 packet_number = int(packet.no) 646 while acks and acks[0] < packet_number: 647 # ACK packet does not appear in the filtered capture. 648 yield capture.get_packet_number(acks.pop(0), summary=True) 649 if acks and acks[0] == packet_number: 650 # ACK packet also appears in the capture. 651 acks.pop(0) 652 yield packet 653 654 def analyze(self, trace): 655 """ 656 Starts the analysis of the Chaos capture. 657 658 @param trace: Packet capture file path to analyze. 659 660 """ 661 basename = os.path.basename(trace) 662 self._log.log_start_section("Packet Capture File: %s" % basename) 663 capture = PacketCapture(trace) 664 bssids = self._ap_bssids 665 ssid = self._ap_ssid 666 if not self._validate_ap_presence(capture, bssids, ssid): 667 return 668 dut_mac = self._dut_mac 669 if not self._validate_dut_presence(capture, dut_mac): 670 return 671 decryption = 'chromeos:%s' % ssid 672 self._log.log_start_section("WLAN Protocol Verification") 673 filtered_packets = capture.get_filtered_packets( 674 bssids, dut_mac, False, decryption) 675 wifi_state_machine = WifiStateMachineAnalyzer( 676 bssids, dut_mac, filtered_packets, capture, self._log) 677 wifi_state_machine.analyze() 678 self._log.log_start_section("Filtered Packet Capture Summary") 679 filtered_packets = capture.get_filtered_packets( 680 bssids, dut_mac, True, decryption) 681 for packet in self._ack_interleave( 682 filtered_packets, capture, wifi_state_machine.acks): 683 self._log.log_to_output_file("%s" % (packet)) 684