blob: 23925af14788e9f632a97b653a684ec9cd085c04 [file] [log] [blame]
#!/usr/bin/python
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import collections
import pyshark
import os
class PacketCapture(object):
""" Class to manage the packet capture file access from a chaos test. """
def __init__(self, file_name):
self._file_name = file_name
def get_output(self, display_filter=None, summaries=True, decryption=None):
"""
Gets the packets from a trace file as Pyshark packet objects for
further analysis.
@param display_filer: Tshark filter to be used for extracting the
relevant packets.
@param summaries: Flag to indicate whether to extract only the summaries
of packet or not.
@param decryption: Decryption key to be used on the trace file.
@returns List of pyshark packet objects.
"""
capture = pyshark.FileCapture(self._file_name,
display_filter=display_filter,
only_summaries=summaries,
decryption_key=decryption,
encryption_type='wpa-pwd')
capture.load_packets()
return capture
def get_packet_number(self, index, summary):
"""
Gets the packet that appears index |index| in the capture file.
@param index: Extract this index from the capture file.
@param summary: Flag to indicate whether to extract only the summary
of the packet or not.
@returns pyshark packet object or None.
"""
display_filter = "frame.number == %d" % index
capture = pyshark.FileCapture(self._file_name,
display_filter=display_filter,
only_summaries=summary)
capture.load_packets()
if not capture:
return None
return capture[0]
def get_packet_after(self, packet):
"""
Gets the packet that appears next in the capture file.
@param packet: Reference packet -- the packet after this one will
be retrieved.
@returns pyshark packet object or None.
"""
return self.get_packet_number(int(packet.number) + 1, summary=False)
def count_packets_with_display_filter(self, display_filter):
"""
Counts the number of packets which match the provided display filter.
@param display_filer: Tshark filter to be used for extracting the
relevant packets.
@returns Number of packets which match the filter.
"""
output = self.get_output(display_filter=display_filter)
return len(output)
def count_packets_from(self, mac_addresses):
"""
Counts the number of packets sent from a given entity using MAC address.
@param mac_address: Mac address of the entity.
@returns Number of packets which matched the MAC address filter.
"""
filter = ' or '.join(['wlan.ta==%s' % addr for addr in mac_addresses])
return self.count_packets_with_display_filter(filter)
def count_packets_to(self, mac_addresses):
"""
Counts the number of packets sent to a given entity using MAC address.
@param mac_address: Mac address of the entity.
@returns Number of packets which matched the MAC address filter.
"""
filter = ' or '.join(['wlan.ra==%s' % addr for addr in mac_addresses])
return self.count_packets_with_display_filter(filter)
def count_packets_from_or_to(self, mac_addresses):
"""
Counts the number of packets sent to/from a given entity using MAC
address.
@param mac_address: Mac address of the entity.
@returns Number of packets which matched the MAC address filter.
"""
filter = ' or '.join(['wlan.addr==%s' % addr for addr in mac_addresses])
return self.count_packets_with_display_filter(filter)
def count_beacons_from(self, mac_addresses):
"""
Counts the number of beacon packets sent from a AP using MAC address.
@param mac_address: Mac address of the AP.
@returns Number of packets which matched the MAC address filter.
"""
filter = ' or '.join(['wlan.ta==%s' % addr for addr in mac_addresses])
filter = '(%s) and wlan.fc.type_subtype == 0x0008' % (filter)
return self.count_packets_with_display_filter(filter)
def get_filtered_packets(self, ap, dut, summaries, decryption):
"""
Gets the packets sent to/from the DUT from a trace file as Pyshark
packet objects for further analysis.
@param summaries: Flag to indicate whether to extract only the summaries
of packet or not.
@param dut: Mac address of the DUT.
@param ap: Mac address of the AP.
@param decryption: Decryption key to be used on the trace file.
@returns List of pyshark packet objects.
"""
filter = 'wlan.addr==%s' % dut
packets = self.get_output(display_filter=filter, summaries=summaries,
decryption=decryption)
return packets
class WifiStateMachineAnalyzer(object):
""" Class to analyze the Wifi Protocol exhcange from a chaos test. """
STATE_INIT = "INIT"
STATE_PROBE_REQ = "PROBE_REQ"
STATE_PROBE_RESP = "PROBE_RESP"
STATE_AUTH_REQ = "AUTH_REQ"
STATE_AUTH_RESP = "AUTH_RESP"
STATE_ASSOC_REQ = "ASSOC_REQ"
STATE_ASSOC_RESP = "ASSOC_RESP"
STATE_KEY_MESSAGE_1 = "KEY_MESSAGE_1"
STATE_KEY_MESSAGE_2 = "KEY_MESSAGE_2"
STATE_KEY_MESSAGE_3 = "KEY_MESSAGE_3"
STATE_KEY_MESSAGE_4 = "KEY_MESSAGE_4"
STATE_DHCP_DISCOVER = "DHCP_DISCOVER"
STATE_DHCP_OFFER = "DHCP_OFFER"
STATE_DHCP_REQ = "DHCP_REQ"
STATE_DHCP_REQ_ACK = "DHCP_REQ_ACK"
STATE_END = "END"
PACKET_MATCH_WLAN_FRAME_TYPE = "wlan.fc_type_subtype"
PACKET_MATCH_WLAN_FRAME_RETRY_FLAG = "wlan.fc_retry"
PACKET_MATCH_WLAN_MANAGEMENT_REASON_CODE = "wlan_mgt.fixed_reason_code"
PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE = "wlan_mgt.fixed_status_code"
PACKET_MATCH_WLAN_TRANSMITTER = "wlan.ta"
PACKET_MATCH_LLC_TYPE = "llc.type"
PACKET_MATCH_EAP_TYPE = "eapol.type"
PACKET_MATCH_EAP_KEY_INFO_INSTALL = "eapol.keydes_key_info_install"
PACKET_MATCH_EAP_KEY_INFO_ACK = "eapol.keydes_key_info_key_ack"
PACKET_MATCH_EAP_KEY_INFO_MIC = "eapol.keydes_key_info_key_mic"
PACKET_MATCH_EAP_KEY_INFO_SECURE = "eapol.keydes_key_info_secure"
PACKET_MATCH_IP_PROTOCOL_TYPE = "ip.proto"
PACKET_MATCH_DHCP_MESSAGE_TYPE = "bootp.option_dhcp"
PACKET_MATCH_RADIOTAP_DATA_RATE = "radiotap.datarate"
WLAN_PROBE_REQ_FRAME_TYPE = '0x04'
WLAN_PROBE_RESP_FRAME_TYPE = '0x05'
WLAN_AUTH_REQ_FRAME_TYPE = '0x0b'
WLAN_AUTH_RESP_FRAME_TYPE = '0x0b'
WLAN_ASSOC_REQ_FRAME_TYPE = '0x00'
WLAN_ASSOC_RESP_FRAME_TYPE = '0x01'
WLAN_ACK_FRAME_TYPE = '0x1d'
WLAN_DEAUTH_REQ_FRAME_TYPE = '0x0c'
WLAN_DISASSOC_REQ_FRAME_TYPE = '0x0a'
WLAN_QOS_DATA_FRAME_TYPE = '0x28'
WLAN_MANAGEMENT_STATUS_CODE_SUCCESS = '0x0000'
WLAN_BROADCAST_ADDRESS = 'ff:ff:ff:ff:ff:ff'
WLAN_FRAME_CONTROL_TYPE_MANAGEMENT = '0'
WLAN_FRAME_RETRY = '1'
LLC_AUTH_TYPE = '0x888e'
EAP_KEY_TYPE = '0x03'
IP_UDP_PROTOCOL_TYPE = '17'
DHCP_DISCOVER_MESSAGE_TYPE = '1'
DHCP_OFFER_MESSAGE_TYPE = '2'
DHCP_REQUEST_MESSAGE_TYPE = '3'
DHCP_ACK_MESSAGE_TYPE = '5'
DIR_TO_DUT = 0
DIR_FROM_DUT = 1
DIR_DUT_TO_AP = 2
DIR_AP_TO_DUT = 3
DIR_ACK = 4
# State Info Tuples (Name, Direction, Match fields, Next State)
StateInfo = collections.namedtuple(
'StateInfo', ['name', 'direction', 'match_fields', 'next_state'])
STATE_INFO_INIT = StateInfo("INIT", 0, {}, STATE_PROBE_REQ)
STATE_INFO_PROBE_REQ = StateInfo("WLAN PROBE REQUEST",
DIR_FROM_DUT,
{ PACKET_MATCH_WLAN_FRAME_TYPE:
WLAN_PROBE_REQ_FRAME_TYPE },
STATE_PROBE_RESP)
STATE_INFO_PROBE_RESP = StateInfo("WLAN PROBE RESPONSE",
DIR_AP_TO_DUT,
{ PACKET_MATCH_WLAN_FRAME_TYPE:
WLAN_PROBE_RESP_FRAME_TYPE },
STATE_AUTH_REQ)
STATE_INFO_AUTH_REQ = StateInfo("WLAN AUTH REQUEST",
DIR_DUT_TO_AP,
{ PACKET_MATCH_WLAN_FRAME_TYPE:
WLAN_AUTH_REQ_FRAME_TYPE },
STATE_AUTH_RESP)
STATE_INFO_AUTH_RESP = StateInfo(
"WLAN AUTH RESPONSE",
DIR_AP_TO_DUT,
{ PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_AUTH_REQ_FRAME_TYPE,
PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE:
WLAN_MANAGEMENT_STATUS_CODE_SUCCESS },
STATE_ASSOC_REQ)
STATE_INFO_ASSOC_REQ = StateInfo("WLAN ASSOC REQUEST",
DIR_DUT_TO_AP,
{ PACKET_MATCH_WLAN_FRAME_TYPE:
WLAN_ASSOC_REQ_FRAME_TYPE },
STATE_ASSOC_RESP)
STATE_INFO_ASSOC_RESP = StateInfo(
"WLAN ASSOC RESPONSE",
DIR_AP_TO_DUT,
{ PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_ASSOC_RESP_FRAME_TYPE,
PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE:
WLAN_MANAGEMENT_STATUS_CODE_SUCCESS },
STATE_KEY_MESSAGE_1)
STATE_INFO_KEY_MESSAGE_1 = StateInfo("WPA KEY MESSAGE 1",
DIR_AP_TO_DUT,
{ PACKET_MATCH_LLC_TYPE:
LLC_AUTH_TYPE,
PACKET_MATCH_EAP_KEY_INFO_INSTALL:
'0',
PACKET_MATCH_EAP_KEY_INFO_ACK:
'1',
PACKET_MATCH_EAP_KEY_INFO_MIC:
'0',
PACKET_MATCH_EAP_KEY_INFO_SECURE:
'0' },
STATE_KEY_MESSAGE_2)
STATE_INFO_KEY_MESSAGE_2 = StateInfo("WPA KEY MESSAGE 2",
DIR_DUT_TO_AP,
{ PACKET_MATCH_LLC_TYPE:
LLC_AUTH_TYPE,
PACKET_MATCH_EAP_KEY_INFO_INSTALL:
'0',
PACKET_MATCH_EAP_KEY_INFO_ACK:
'0',
PACKET_MATCH_EAP_KEY_INFO_MIC:
'1',
PACKET_MATCH_EAP_KEY_INFO_SECURE:
'0' },
STATE_KEY_MESSAGE_3)
STATE_INFO_KEY_MESSAGE_3 = StateInfo("WPA KEY MESSAGE 3",
DIR_AP_TO_DUT,
{ PACKET_MATCH_LLC_TYPE:
LLC_AUTH_TYPE,
PACKET_MATCH_EAP_KEY_INFO_INSTALL:
'1',
PACKET_MATCH_EAP_KEY_INFO_ACK:
'1',
PACKET_MATCH_EAP_KEY_INFO_MIC:
'1',
PACKET_MATCH_EAP_KEY_INFO_SECURE:
'1' },
STATE_KEY_MESSAGE_4)
STATE_INFO_KEY_MESSAGE_4 = StateInfo("WPA KEY MESSAGE 4",
DIR_DUT_TO_AP,
{ PACKET_MATCH_LLC_TYPE:
LLC_AUTH_TYPE,
PACKET_MATCH_EAP_KEY_INFO_INSTALL:
'0',
PACKET_MATCH_EAP_KEY_INFO_ACK:
'0',
PACKET_MATCH_EAP_KEY_INFO_MIC:
'1',
PACKET_MATCH_EAP_KEY_INFO_SECURE:
'1' },
STATE_DHCP_DISCOVER)
STATE_INFO_DHCP_DISCOVER = StateInfo("DHCP DISCOVER",
DIR_DUT_TO_AP,
{ PACKET_MATCH_IP_PROTOCOL_TYPE:
IP_UDP_PROTOCOL_TYPE,
PACKET_MATCH_DHCP_MESSAGE_TYPE:
DHCP_DISCOVER_MESSAGE_TYPE },
STATE_DHCP_OFFER)
STATE_INFO_DHCP_OFFER = StateInfo("DHCP OFFER",
DIR_AP_TO_DUT,
{ PACKET_MATCH_IP_PROTOCOL_TYPE:
IP_UDP_PROTOCOL_TYPE,
PACKET_MATCH_DHCP_MESSAGE_TYPE:
DHCP_OFFER_MESSAGE_TYPE },
STATE_DHCP_REQ)
STATE_INFO_DHCP_REQ = StateInfo("DHCP REQUEST",
DIR_DUT_TO_AP,
{ PACKET_MATCH_IP_PROTOCOL_TYPE:
IP_UDP_PROTOCOL_TYPE,
PACKET_MATCH_DHCP_MESSAGE_TYPE:
DHCP_REQUEST_MESSAGE_TYPE },
STATE_DHCP_REQ_ACK)
STATE_INFO_DHCP_REQ_ACK = StateInfo("DHCP ACK",
DIR_AP_TO_DUT,
{ PACKET_MATCH_IP_PROTOCOL_TYPE:
IP_UDP_PROTOCOL_TYPE,
PACKET_MATCH_DHCP_MESSAGE_TYPE:
DHCP_ACK_MESSAGE_TYPE },
STATE_END)
STATE_INFO_END = StateInfo("END", 0, {}, STATE_END)
# Master State Table Map of State Infos
STATE_INFO_MAP = {STATE_INIT: STATE_INFO_INIT,
STATE_PROBE_REQ: STATE_INFO_PROBE_REQ,
STATE_PROBE_RESP: STATE_INFO_PROBE_RESP,
STATE_AUTH_REQ: STATE_INFO_AUTH_REQ,
STATE_AUTH_RESP: STATE_INFO_AUTH_RESP,
STATE_ASSOC_REQ: STATE_INFO_ASSOC_REQ,
STATE_ASSOC_RESP: STATE_INFO_ASSOC_RESP,
STATE_KEY_MESSAGE_1:STATE_INFO_KEY_MESSAGE_1,
STATE_KEY_MESSAGE_2:STATE_INFO_KEY_MESSAGE_2,
STATE_KEY_MESSAGE_3:STATE_INFO_KEY_MESSAGE_3,
STATE_KEY_MESSAGE_4:STATE_INFO_KEY_MESSAGE_4,
STATE_DHCP_DISCOVER:STATE_INFO_DHCP_DISCOVER,
STATE_DHCP_OFFER: STATE_INFO_DHCP_OFFER,
STATE_DHCP_REQ: STATE_INFO_DHCP_REQ,
STATE_DHCP_REQ_ACK: STATE_INFO_DHCP_REQ_ACK,
STATE_END: STATE_INFO_END}
# Packet Details Tuples (User friendly name, Field name)
PacketDetail = collections.namedtuple(
"PacketDetail", ["friendly_name", "field_name"])
PACKET_DETAIL_REASON_CODE = PacketDetail(
"Reason Code",
PACKET_MATCH_WLAN_MANAGEMENT_REASON_CODE)
PACKET_DETAIL_STATUS_CODE = PacketDetail(
"Status Code",
PACKET_MATCH_WLAN_MANAGEMENT_STATUS_CODE)
PACKET_DETAIL_SENDER = PacketDetail(
"Sender", PACKET_MATCH_WLAN_TRANSMITTER)
# Error State Info Tuples (Name, Match fields)
ErrorStateInfo = collections.namedtuple(
'ErrorStateInfo', ['name', 'match_fields', 'details'])
ERROR_STATE_INFO_DEAUTH = ErrorStateInfo("WLAN DEAUTH REQUEST",
{ PACKET_MATCH_WLAN_FRAME_TYPE:
WLAN_DEAUTH_REQ_FRAME_TYPE },
[ PACKET_DETAIL_SENDER,
PACKET_DETAIL_REASON_CODE ])
ERROR_STATE_INFO_DEASSOC = ErrorStateInfo("WLAN DISASSOC REQUEST",
{ PACKET_MATCH_WLAN_FRAME_TYPE:
WLAN_DISASSOC_REQ_FRAME_TYPE },
[ PACKET_DETAIL_SENDER,
PACKET_DETAIL_REASON_CODE ])
# Master State Table Tuple of Error State Infos
ERROR_STATE_INFO_TUPLE = (ERROR_STATE_INFO_DEAUTH, ERROR_STATE_INFO_DEASSOC)
# These warnings actually match successful states, but since the we
# check forwards and backwards through the state machine for the successful
# version of these packets, they can only match a failure.
WARNING_INFO_AUTH_REJ = ErrorStateInfo(
"WLAN AUTH REJECTED",
{ PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_AUTH_REQ_FRAME_TYPE },
[ PACKET_DETAIL_STATUS_CODE ])
WARNING_INFO_ASSOC_REJ = ErrorStateInfo(
"WLAN ASSOC REJECTED",
{ PACKET_MATCH_WLAN_FRAME_TYPE: WLAN_ASSOC_RESP_FRAME_TYPE },
[ PACKET_DETAIL_STATUS_CODE ])
# Master Table Tuple of warning information.
WARNING_INFO_TUPLE = (WARNING_INFO_AUTH_REJ, WARNING_INFO_ASSOC_REJ)
def __init__(self, ap_macs, dut_mac, filtered_packets, capture, logger):
self._current_state = self._get_state(self.STATE_INIT)
self._reached_states = []
self._skipped_states = []
self._packets = filtered_packets
self._capture = capture
self._dut_mac = dut_mac
self._ap_macs = ap_macs
self._log = logger
self._acks = []
@property
def acks(self):
return self._acks
def _get_state(self, state):
return self.STATE_INFO_MAP[state]
def _get_next_state(self, state):
return self._get_state(state.next_state)
def _get_curr_next_state(self):
return self._get_next_state(self._current_state)
def _fetch_packet_field_value(self, packet, field):
layer_object = packet
for layer in field.split('.'):
try:
layer_object = getattr(layer_object, layer)
except AttributeError:
return None
return layer_object
def _match_packet_fields(self, packet, fields):
for field, exp_value in fields.items():
value = self._fetch_packet_field_value(packet, field)
if exp_value != value:
return False
return True
def _fetch_packet_data_rate(self, packet):
return self._fetch_packet_field_value(packet,
self.PACKET_MATCH_RADIOTAP_DATA_RATE)
def _does_packet_match_state(self, state, packet):
fields = state.match_fields
if self._match_packet_fields(packet, fields):
if state.direction == self.DIR_TO_DUT:
# This should have receiver addr of DUT
if packet.wlan.ra == self._dut_mac:
return True
elif state.direction == self.DIR_FROM_DUT:
# This should have transmitter addr of DUT
if packet.wlan.ta == self._dut_mac:
return True
elif state.direction == self.DIR_AP_TO_DUT:
# This should have receiver addr of DUT &
# transmitter addr of AP's
if ((packet.wlan.ra == self._dut_mac) and
(packet.wlan.ta in self._ap_macs)):
return True
elif state.direction == self.DIR_DUT_TO_AP:
# This should have transmitter addr of DUT &
# receiver addr of AP's
if ((packet.wlan.ta == self._dut_mac) and
(packet.wlan.ra in self._ap_macs)):
return True
return False
def _does_packet_match_error_state(self, state, packet):
fields = state.match_fields
return self._match_packet_fields(packet, fields)
def _get_packet_detail(self, details, packet):
attributes = []
attributes.append("Packet number: %s" % packet.number)
for detail in details:
value = self._fetch_packet_field_value(packet, detail.field_name)
attributes.append("%s: %s" % (detail.friendly_name, value))
return attributes
def _does_packet_match_ack_state(self, packet):
fields = { self.PACKET_MATCH_WLAN_FRAME_TYPE: self.WLAN_ACK_FRAME_TYPE }
return self._match_packet_fields(packet, fields)
def _does_packet_contain_retry_flag(self, packet):
fields = { self.PACKET_MATCH_WLAN_FRAME_RETRY_FLAG:
self.WLAN_FRAME_RETRY }
return self._match_packet_fields(packet, fields)
def _check_for_ack(self, state, packet):
if (packet.wlan.da == self.WLAN_BROADCAST_ADDRESS and
packet.wlan.fc_type == self.WLAN_FRAME_CONTROL_TYPE_MANAGEMENT):
# Broadcast management frames are not ACKed.
return True
next_packet = self._capture.get_packet_after(packet)
if not next_packet or not (
(self._does_packet_match_ack_state(next_packet)) and
(next_packet.wlan.addr == packet.wlan.ta)):
msg = "WARNING! Missing ACK for state: " + \
state.name + "."
self._log.log_to_output_file(msg)
return False
self._acks.append(int(next_packet.number))
return True
def _check_for_error(self, packet):
for error_state in self.ERROR_STATE_INFO_TUPLE:
if self._does_packet_match_error_state(error_state, packet):
error_attributes = self._get_packet_detail(error_state.details,
packet)
msg = "ERROR! State Machine encountered error due to " + \
error_state.name + ", " + \
", ".join(error_attributes) + "."
self._log.log_to_output_file(msg)
return True
return False
def _check_for_warning(self, packet):
for warning in self.WARNING_INFO_TUPLE:
if self._does_packet_match_error_state(warning, packet):
error_attributes = self._get_packet_detail(warning.details,
packet)
msg = "WARNING! " + warning.name + " found, " + \
", ".join(error_attributes) + "."
self._log.log_to_output_file(msg)
return True
return False
def _check_for_repeated_state(self, packet):
for state in self._reached_states:
if self._does_packet_match_state(state, packet):
msg = "WARNING! Repeated State: " + \
state.name + ", Packet number: " + \
str(packet.number)
if self._does_packet_contain_retry_flag(packet):
msg += " due to retransmission."
else:
msg += "."
self._log.log_to_output_file(msg)
def _is_from_previous_state(self, packet):
for state in self._reached_states + self._skipped_states:
if self._does_packet_match_state(state, packet):
return True
return False
def _step(self, reached_state, packet):
# We missed a few packets in between
if self._current_state != reached_state:
msg = "WARNING! Missed states: "
skipped_state = self._current_state
while skipped_state != reached_state:
msg += skipped_state.name + ", "
self._skipped_states.append(skipped_state)
skipped_state = self._get_next_state(skipped_state)
msg = msg[:-2]
msg += "."
self._log.log_to_output_file(msg)
msg = "Found state: " + reached_state.name
if packet:
msg += ", Packet number: " + str(packet.number) + \
", Data rate: " + str(self._fetch_packet_data_rate(packet))+\
"Mbps."
else:
msg += "."
self._log.log_to_output_file(msg)
# Ignore the Init state in the reached states
if packet:
self._reached_states.append(reached_state)
self._current_state = self._get_next_state(reached_state)
def _step_init(self):
#self.log_to_output_file("Starting Analysis")
self._current_state = self._get_curr_next_state()
def analyze(self):
""" Starts the analysis of the Wifi Protocol Exchange. """
# Start the state machine iteration
self._step_init()
packet_iterator = iter(self._packets)
for packet in packet_iterator:
self._check_for_repeated_state(packet)
# Try to look ahead in the state machine to account for occasional
# packet capture misses.
next_state = self._current_state
while next_state != self.STATE_INFO_END:
if self._does_packet_match_state(next_state, packet):
self._step(next_state, packet)
self._check_for_ack(next_state, packet)
break
next_state = self._get_next_state(next_state)
if self._current_state == self.STATE_INFO_END:
self._log.log_to_output_file("State Machine completed!")
return True
if self._check_for_error(packet):
return False
if not self._is_from_previous_state(packet):
self._check_for_warning(packet)
msg = "ERROR! State Machine halted at " + self._current_state.name + \
" state."
self._log.log_to_output_file(msg)
return False
class ChaosCaptureAnalyzer(object):
""" Class to analyze the packet capture from a chaos test . """
def __init__(self, ap_bssids, ap_ssid, dut_mac, logger):
self._ap_bssids = ap_bssids
self._ap_ssid = ap_ssid
self._dut_mac = dut_mac
self._log = logger
def _validate_ap_presence(self, capture, bssids, ssid):
beacon_count = capture.count_beacons_from(bssids)
if not beacon_count:
packet_count = capture.count_packets_from(bssids)
if not packet_count:
self._log.log_to_output_file(
"No packets at all from AP BSSIDs %r!" % bssids)
else:
self._log.log_to_output_file(
"No beacons from AP BSSIDs %r but %d packets!" %
(bssids, packet_count))
return False
self._log.log_to_output_file("AP BSSIDs: %s, SSID: %s." %
(bssids, ssid))
self._log.log_to_output_file("AP beacon count: %d." % beacon_count)
return True
def _validate_dut_presence(self, capture, dut_mac):
tx_count = capture.count_packets_from([dut_mac])
if not tx_count:
self._log.log_to_output_file(
"No packets Tx at all from DUT MAC %r!" % dut_mac)
return False
rx_count = capture.count_packets_to([dut_mac])
self._log.log_to_output_file("DUT MAC: %s." % dut_mac)
self._log.log_to_output_file(
"DUT packet count Tx: %d, Rx: %d." % (tx_count, rx_count))
return True
def _ack_interleave(self, packets, capture, acks):
"""Generator that interleaves packets with their associated ACKs."""
for packet in packets:
packet_number = int(packet.no)
while acks and acks[0] < packet_number:
# ACK packet does not appear in the filtered capture.
yield capture.get_packet_number(acks.pop(0), summary=True)
if acks and acks[0] == packet_number:
# ACK packet also appears in the capture.
acks.pop(0)
yield packet
def analyze(self, trace):
"""
Starts the analysis of the Chaos capture.
@param trace: Packet capture file path to analyze.
"""
basename = os.path.basename(trace)
self._log.log_start_section("Packet Capture File: %s" % basename)
capture = PacketCapture(trace)
bssids = self._ap_bssids
ssid = self._ap_ssid
if not self._validate_ap_presence(capture, bssids, ssid):
return
dut_mac = self._dut_mac
if not self._validate_dut_presence(capture, dut_mac):
return
decryption = 'chromeos:%s' % ssid
self._log.log_start_section("WLAN Protocol Verification")
filtered_packets = capture.get_filtered_packets(
bssids, dut_mac, False, decryption)
wifi_state_machine = WifiStateMachineAnalyzer(
bssids, dut_mac, filtered_packets, capture, self._log)
wifi_state_machine.analyze()
self._log.log_start_section("Filtered Packet Capture Summary")
filtered_packets = capture.get_filtered_packets(
bssids, dut_mac, True, decryption)
for packet in self._ack_interleave(
filtered_packets, capture, wifi_state_machine.acks):
self._log.log_to_output_file("%s" % (packet))