/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.util; import android.util.Log; import com.android.server.wifi.WifiLoggerHal; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.HashSet; import java.util.Set; /** * This class parses the raw bytes of a network frame, and stores the parsed information in its * public fields. */ public class FrameParser { /** * Note: When adding constants derived from network protocol specifications, please encode * these constants the same way as the relevant specification, for ease of comparison. */ private static final String TAG = "FrameParser"; /* These fields hold the information parsed from this frame. */ public String mMostSpecificProtocolString = "N/A"; public String mTypeString = "N/A"; public String mResultString = "N/A"; /** * Parses the contents of a given network frame. * * @param frameType The type of the frame, as defined in * {@link com.android.server.wifi.WifiLoggerHal}. * @param frameBytes The raw bytes of the frame to be parsed. */ public FrameParser(byte frameType, byte[] frameBytes) { try { ByteBuffer frameBuffer = ByteBuffer.wrap(frameBytes); frameBuffer.order(ByteOrder.BIG_ENDIAN); if (frameType == WifiLoggerHal.FRAME_TYPE_ETHERNET_II) { parseEthernetFrame(frameBuffer); } else if (frameType == WifiLoggerHal.FRAME_TYPE_80211_MGMT) { parseManagementFrame(frameBuffer); } } catch (BufferUnderflowException | IllegalArgumentException e) { Log.e(TAG, "Dissection aborted mid-frame: " + e); } } /** * Read one byte into a form that can easily be compared against, or output as, an integer * in the range (0, 255). */ private static short getUnsignedByte(ByteBuffer data) { return (short) (data.get() & 0x00ff); } /** * Read two bytes into a form that can easily be compared against, or output as, an integer * in the range (0, 65535). */ private static int getUnsignedShort(ByteBuffer data) { return (data.getShort() & 0xffff); } private static final int ETHERNET_SRC_MAC_ADDR_LEN = 6; private static final int ETHERNET_DST_MAC_ADDR_LEN = 6; private static final short ETHERTYPE_IP_V4 = (short) 0x0800; private static final short ETHERTYPE_ARP = (short) 0x0806; private static final short ETHERTYPE_IP_V6 = (short) 0x86dd; private static final short ETHERTYPE_EAPOL = (short) 0x888e; private void parseEthernetFrame(ByteBuffer data) { mMostSpecificProtocolString = "Ethernet"; data.position(data.position() + ETHERNET_SRC_MAC_ADDR_LEN + ETHERNET_DST_MAC_ADDR_LEN); short etherType = data.getShort(); switch (etherType) { case ETHERTYPE_IP_V4: parseIpv4Packet(data); return; case ETHERTYPE_ARP: parseArpPacket(data); return; case ETHERTYPE_IP_V6: parseIpv6Packet(data); return; case ETHERTYPE_EAPOL: parseEapolPacket(data); return; default: return; } } private static final byte IP_V4_VERSION_BYTE_MASK = (byte) 0b11110000; private static final byte IP_V4_IHL_BYTE_MASK = (byte) 0b00001111; private static final byte IP_V4_ADDR_LEN = 4; private static final byte IP_V4_DSCP_AND_ECN_LEN = 1; private static final byte IP_V4_TOTAL_LEN_LEN = 2; private static final byte IP_V4_ID_LEN = 2; private static final byte IP_V4_FLAGS_AND_FRAG_OFFSET_LEN = 2; private static final byte IP_V4_TTL_LEN = 1; private static final byte IP_V4_HEADER_CHECKSUM_LEN = 2; private static final byte IP_V4_SRC_ADDR_LEN = 4; private static final byte IP_V4_DST_ADDR_LEN = 4; private static final byte IP_PROTO_ICMP = 1; private static final byte IP_PROTO_TCP = 6; private static final byte IP_PROTO_UDP = 17; private static final byte BYTES_PER_QUAD = 4; private void parseIpv4Packet(ByteBuffer data) { mMostSpecificProtocolString = "IPv4"; data.mark(); byte versionAndHeaderLen = data.get(); int version = (versionAndHeaderLen & IP_V4_VERSION_BYTE_MASK) >> 4; if (version != 4) { Log.e(TAG, "IPv4 header: Unrecognized protocol version " + version); return; } data.position(data.position() + IP_V4_DSCP_AND_ECN_LEN + IP_V4_TOTAL_LEN_LEN + IP_V4_ID_LEN + IP_V4_FLAGS_AND_FRAG_OFFSET_LEN + IP_V4_TTL_LEN); short protocolNumber = getUnsignedByte(data); data.position(data.position() + IP_V4_HEADER_CHECKSUM_LEN + IP_V4_SRC_ADDR_LEN + IP_V4_DST_ADDR_LEN); int headerLen = (versionAndHeaderLen & IP_V4_IHL_BYTE_MASK) * BYTES_PER_QUAD; data.reset(); // back to start of IPv4 header data.position(data.position() + headerLen); switch (protocolNumber) { case IP_PROTO_ICMP: parseIcmpPacket(data); break; case IP_PROTO_TCP: parseTcpPacket(data); break; case IP_PROTO_UDP: parseUdpPacket(data); break; default: break; } } private static final byte TCP_SRC_PORT_LEN = 2; private static final int HTTPS_PORT = 443; private static final Set HTTP_PORTS = new HashSet<>(); static { HTTP_PORTS.add(80); HTTP_PORTS.add(3128); HTTP_PORTS.add(3132); HTTP_PORTS.add(5985); HTTP_PORTS.add(8080); HTTP_PORTS.add(8088); HTTP_PORTS.add(11371); HTTP_PORTS.add(1900); HTTP_PORTS.add(2869); HTTP_PORTS.add(2710); } private void parseTcpPacket(ByteBuffer data) { mMostSpecificProtocolString = "TCP"; data.position(data.position() + TCP_SRC_PORT_LEN); int dstPort = getUnsignedShort(data); if (dstPort == HTTPS_PORT) { mTypeString = "HTTPS"; } else if (HTTP_PORTS.contains(dstPort)) { mTypeString = "HTTP"; } } private static final byte UDP_PORT_BOOTPS = 67; private static final byte UDP_PORT_BOOTPC = 68; private static final byte UDP_PORT_NTP = 123; private static final byte UDP_CHECKSUM_LEN = 2; private void parseUdpPacket(ByteBuffer data) { mMostSpecificProtocolString = "UDP"; int srcPort = getUnsignedShort(data); int dstPort = getUnsignedShort(data); int length = getUnsignedShort(data); data.position(data.position() + UDP_CHECKSUM_LEN); if ((srcPort == UDP_PORT_BOOTPC && dstPort == UDP_PORT_BOOTPS) || (srcPort == UDP_PORT_BOOTPS && dstPort == UDP_PORT_BOOTPC)) { parseDhcpPacket(data); return; } if (srcPort == UDP_PORT_NTP || dstPort == UDP_PORT_NTP) { mMostSpecificProtocolString = "NTP"; return; } } private static final byte BOOTP_OPCODE_LEN = 1; private static final byte BOOTP_HWTYPE_LEN = 1; private static final byte BOOTP_HWADDR_LEN_LEN = 1; private static final byte BOOTP_HOPCOUNT_LEN = 1; private static final byte BOOTP_TRANSACTION_ID_LEN = 4; private static final byte BOOTP_ELAPSED_SECONDS_LEN = 2; private static final byte BOOTP_FLAGS_LEN = 2; private static final byte BOOTP_CLIENT_HWADDR_LEN = 16; private static final byte BOOTP_SERVER_HOSTNAME_LEN = 64; private static final short BOOTP_BOOT_FILENAME_LEN = 128; private static final byte BOOTP_MAGIC_COOKIE_LEN = 4; private static final short DHCP_OPTION_TAG_PAD = 0; private static final short DHCP_OPTION_TAG_MESSAGE_TYPE = 53; private static final short DHCP_OPTION_TAG_END = 255; private void parseDhcpPacket(ByteBuffer data) { mMostSpecificProtocolString = "DHCP"; data.position(data.position() + BOOTP_OPCODE_LEN + BOOTP_HWTYPE_LEN + BOOTP_HWADDR_LEN_LEN + BOOTP_HOPCOUNT_LEN + BOOTP_TRANSACTION_ID_LEN + BOOTP_ELAPSED_SECONDS_LEN + BOOTP_FLAGS_LEN + IP_V4_ADDR_LEN * 4 + BOOTP_CLIENT_HWADDR_LEN + BOOTP_SERVER_HOSTNAME_LEN + BOOTP_BOOT_FILENAME_LEN + BOOTP_MAGIC_COOKIE_LEN); while (data.remaining() > 0) { short dhcpOptionTag = getUnsignedByte(data); if (dhcpOptionTag == DHCP_OPTION_TAG_PAD) { continue; } if (dhcpOptionTag == DHCP_OPTION_TAG_END) { break; } short dhcpOptionLen = getUnsignedByte(data); switch (dhcpOptionTag) { case DHCP_OPTION_TAG_MESSAGE_TYPE: if (dhcpOptionLen != 1) { Log.e(TAG, "DHCP option len: " + dhcpOptionLen + " (expected |1|)"); return; } mTypeString = decodeDhcpMessageType(getUnsignedByte(data)); return; default: data.position(data.position() + dhcpOptionLen); } } } private static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1; private static final byte DHCP_MESSAGE_TYPE_OFFER = 2; private static final byte DHCP_MESSAGE_TYPE_REQUEST = 3; private static final byte DHCP_MESSAGE_TYPE_DECLINE = 4; private static final byte DHCP_MESSAGE_TYPE_ACK = 5; private static final byte DHCP_MESSAGE_TYPE_NAK = 6; private static final byte DHCP_MESSAGE_TYPE_RELEASE = 7; private static final byte DHCP_MESSAGE_TYPE_INFORM = 8; private static String decodeDhcpMessageType(short messageType) { switch (messageType) { case DHCP_MESSAGE_TYPE_DISCOVER: return "Discover"; case DHCP_MESSAGE_TYPE_OFFER: return "Offer"; case DHCP_MESSAGE_TYPE_REQUEST: return "Request"; case DHCP_MESSAGE_TYPE_DECLINE: return "Decline"; case DHCP_MESSAGE_TYPE_ACK: return "Ack"; case DHCP_MESSAGE_TYPE_NAK: return "Nak"; case DHCP_MESSAGE_TYPE_RELEASE: return "Release"; case DHCP_MESSAGE_TYPE_INFORM: return "Inform"; default: return "Unknown type " + messageType; } } private static final byte ICMP_TYPE_ECHO_REPLY = 0; private static final byte ICMP_TYPE_DEST_UNREACHABLE = 3; private static final byte ICMP_TYPE_REDIRECT = 5; private static final byte ICMP_TYPE_ECHO_REQUEST = 8; private void parseIcmpPacket(ByteBuffer data) { mMostSpecificProtocolString = "ICMP"; short messageType = getUnsignedByte(data); switch (messageType) { case ICMP_TYPE_ECHO_REPLY: mTypeString = "Echo Reply"; return; case ICMP_TYPE_DEST_UNREACHABLE: mTypeString = "Destination Unreachable"; return; case ICMP_TYPE_REDIRECT: mTypeString = "Redirect"; return; case ICMP_TYPE_ECHO_REQUEST: mTypeString = "Echo Request"; return; default: mTypeString = "Type " + messageType; return; } } private static final byte ARP_HWTYPE_LEN = 2; private static final byte ARP_PROTOTYPE_LEN = 2; private static final byte ARP_HWADDR_LEN_LEN = 1; private static final byte ARP_PROTOADDR_LEN_LEN = 1; private static final byte ARP_OPCODE_REQUEST = 1; private static final byte ARP_OPCODE_REPLY = 2; private void parseArpPacket(ByteBuffer data) { mMostSpecificProtocolString = "ARP"; data.position(data.position() + ARP_HWTYPE_LEN + ARP_PROTOTYPE_LEN + ARP_HWADDR_LEN_LEN + ARP_PROTOADDR_LEN_LEN); int opCode = getUnsignedShort(data); switch (opCode) { case ARP_OPCODE_REQUEST: mTypeString = "Request"; break; case ARP_OPCODE_REPLY: mTypeString = "Reply"; break; default: mTypeString = "Operation " + opCode; } } private static final byte IP_V6_PAYLOAD_LENGTH_LEN = 2; private static final byte IP_V6_HOP_LIMIT_LEN = 1; private static final byte IP_V6_ADDR_LEN = 16; private static final byte IP_V6_HEADER_TYPE_HOP_BY_HOP_OPTION = 0; private static final byte IP_V6_HEADER_TYPE_ICMP_V6 = 58; private static final byte BYTES_PER_OCT = 8; private void parseIpv6Packet(ByteBuffer data) { mMostSpecificProtocolString = "IPv6"; int versionClassAndLabel = data.getInt(); int version = (versionClassAndLabel & 0xf0000000) >> 28; if (version != 6) { Log.e(TAG, "IPv6 header: invalid IP version " + version); return; } data.position(data.position() + IP_V6_PAYLOAD_LENGTH_LEN); short nextHeaderType = getUnsignedByte(data); data.position(data.position() + IP_V6_HOP_LIMIT_LEN + IP_V6_ADDR_LEN * 2); while (nextHeaderType == IP_V6_HEADER_TYPE_HOP_BY_HOP_OPTION) { int thisHeaderLen; data.mark(); nextHeaderType = getUnsignedByte(data); thisHeaderLen = (getUnsignedByte(data) + 1) * BYTES_PER_OCT; data.reset(); // back to start of this header data.position(data.position() + thisHeaderLen); } switch (nextHeaderType) { case IP_V6_HEADER_TYPE_ICMP_V6: parseIcmpV6Packet(data); return; default: mTypeString = "Option/Protocol " + nextHeaderType; return; } } private static final short ICMP_V6_TYPE_ECHO_REQUEST = 128; private static final short ICMP_V6_TYPE_ECHO_REPLY = 129; private static final short ICMP_V6_TYPE_ROUTER_SOLICITATION = 133; private static final short ICMP_V6_TYPE_ROUTER_ADVERTISEMENT = 134; private static final short ICMP_V6_TYPE_NEIGHBOR_SOLICITATION = 135; private static final short ICMP_V6_TYPE_NEIGHBOR_ADVERTISEMENT = 136; private static final short ICMP_V6_TYPE_MULTICAST_LISTENER_DISCOVERY = 143; private void parseIcmpV6Packet(ByteBuffer data) { mMostSpecificProtocolString = "ICMPv6"; short icmpV6Type = getUnsignedByte(data); switch (icmpV6Type) { case ICMP_V6_TYPE_ECHO_REQUEST: mTypeString = "Echo Request"; return; case ICMP_V6_TYPE_ECHO_REPLY: mTypeString = "Echo Reply"; return; case ICMP_V6_TYPE_ROUTER_SOLICITATION: mTypeString = "Router Solicitation"; return; case ICMP_V6_TYPE_ROUTER_ADVERTISEMENT: mTypeString = "Router Advertisement"; return; case ICMP_V6_TYPE_NEIGHBOR_SOLICITATION: mTypeString = "Neighbor Solicitation"; return; case ICMP_V6_TYPE_NEIGHBOR_ADVERTISEMENT: mTypeString = "Neighbor Advertisement"; return; case ICMP_V6_TYPE_MULTICAST_LISTENER_DISCOVERY: mTypeString = "MLDv2 report"; return; default: mTypeString = "Type " + icmpV6Type; return; } } private static final byte EAPOL_TYPE_KEY = 3; private static final byte EAPOL_KEY_DESCRIPTOR_RSN_KEY = 2; private static final byte EAPOL_LENGTH_LEN = 2; private static final short WPA_KEY_INFO_FLAG_PAIRWISE = (short) 1 << 3; // bit 4 private static final short WPA_KEY_INFO_FLAG_INSTALL = (short) 1 << 6; // bit 7 private static final short WPA_KEY_INFO_FLAG_MIC = (short) 1 << 8; // bit 9 private static final byte WPA_KEYLEN_LEN = 2; private static final byte WPA_REPLAY_COUNTER_LEN = 8; private static final byte WPA_KEY_NONCE_LEN = 32; private static final byte WPA_KEY_IV_LEN = 16; private static final byte WPA_KEY_RECEIVE_SEQUENCE_COUNTER_LEN = 8; private static final byte WPA_KEY_IDENTIFIER_LEN = 8; private static final byte WPA_KEY_MIC_LEN = 16; private void parseEapolPacket(ByteBuffer data) { mMostSpecificProtocolString = "EAPOL"; short eapolVersion = getUnsignedByte(data); if (eapolVersion < 1 || eapolVersion > 2) { Log.e(TAG, "Unrecognized EAPOL version " + eapolVersion); return; } short eapolType = getUnsignedByte(data); if (eapolType != EAPOL_TYPE_KEY) { Log.e(TAG, "Unrecognized EAPOL type " + eapolType); return; } data.position(data.position() + EAPOL_LENGTH_LEN); short eapolKeyDescriptorType = getUnsignedByte(data); if (eapolKeyDescriptorType != EAPOL_KEY_DESCRIPTOR_RSN_KEY) { Log.e(TAG, "Unrecognized key descriptor " + eapolKeyDescriptorType); return; } short wpaKeyInfo = data.getShort(); if ((wpaKeyInfo & WPA_KEY_INFO_FLAG_PAIRWISE) == 0) { mTypeString = "Group Key"; } else { mTypeString = "Pairwise Key"; } // See goo.gl/tu8AQC for details. if ((wpaKeyInfo & WPA_KEY_INFO_FLAG_MIC) == 0) { mTypeString += " message 1/4"; return; } if ((wpaKeyInfo & WPA_KEY_INFO_FLAG_INSTALL) != 0) { mTypeString += " message 3/4"; return; } data.position(data.position() + WPA_KEYLEN_LEN + WPA_REPLAY_COUNTER_LEN + WPA_KEY_NONCE_LEN + WPA_KEY_IV_LEN + WPA_KEY_RECEIVE_SEQUENCE_COUNTER_LEN + WPA_KEY_IDENTIFIER_LEN + WPA_KEY_MIC_LEN); int wpaKeyDataLen = getUnsignedShort(data); if (wpaKeyDataLen > 0) { mTypeString += " message 2/4"; } else { mTypeString += " message 4/4"; } } private static final byte IEEE_80211_FRAME_TYPE_MGMT = 0b00; // Per 802.11-2016 Table 9-1 private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_REQ = 0b0000; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_RESP = 0b0001; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_REQ = 0b0010; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_RESP = 0b0011; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_REQ = 0b0100; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_RESP = 0b0101; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_TIMING_AD = 0b0110; // 0b0111 reserved private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_BEACON = 0b1000; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ATIM = 0b1001; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DISASSOC = 0b1010; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_AUTH = 0b1011; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DEAUTH = 0b1100; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION = 0b1101; private static final byte IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION_NO_ACK = 0b1110; // 0b1111 reserved private static final byte IEEE_80211_FRAME_FLAG_ORDER = (byte) (1 << 7); // bit 8 private static final byte IEEE_80211_DURATION_LEN = 2; private static final byte IEEE_80211_ADDR1_LEN = 6; private static final byte IEEE_80211_ADDR2_LEN = 6; private static final byte IEEE_80211_ADDR3_LEN = 6; private static final byte IEEE_80211_SEQUENCE_CONTROL_LEN = 2; private static final byte IEEE_80211_HT_CONTROL_LEN = 4; private static byte parseIeee80211FrameCtrlVersion(byte b) { return (byte) (b & 0b00000011); } private static byte parseIeee80211FrameCtrlType(byte b) { return (byte) ((b & 0b00001100) >> 2); } private static byte parseIeee80211FrameCtrlSubtype(byte b) { return (byte) ((b & 0b11110000) >> 4); } private void parseManagementFrame(ByteBuffer data) { // 802.11-2012 Sec 8.3.3.1 data.order(ByteOrder.LITTLE_ENDIAN); mMostSpecificProtocolString = "802.11 Mgmt"; byte frameControlVersionTypeSubtype = data.get(); byte ieee80211Version = parseIeee80211FrameCtrlVersion(frameControlVersionTypeSubtype); if (ieee80211Version != 0) { Log.e(TAG, "Unrecognized 802.11 version " + ieee80211Version); return; } byte ieee80211FrameType = parseIeee80211FrameCtrlType(frameControlVersionTypeSubtype); if (ieee80211FrameType != IEEE_80211_FRAME_TYPE_MGMT) { Log.e(TAG, "Unexpected frame type " + ieee80211FrameType); return; } byte frameControlFlags = data.get(); data.position(data.position() + IEEE_80211_DURATION_LEN + IEEE_80211_ADDR1_LEN + IEEE_80211_ADDR2_LEN + IEEE_80211_ADDR3_LEN + IEEE_80211_SEQUENCE_CONTROL_LEN); if ((frameControlFlags & IEEE_80211_FRAME_FLAG_ORDER) != 0) { // Per 802.11-2012 Sec 8.2.4.1.10. data.position(data.position() + IEEE_80211_HT_CONTROL_LEN); } byte ieee80211FrameSubtype = parseIeee80211FrameCtrlSubtype(frameControlVersionTypeSubtype); switch (ieee80211FrameSubtype) { case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_REQ: mTypeString = "Association Request"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ASSOC_RESP: mTypeString = "Association Response"; parseAssociationResponse(data); return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_REQ: mTypeString = "Reassociation Request"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_REASSOC_RESP: mTypeString = "Reassociation Response"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_REQ: mTypeString = "Probe Request"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_PROBE_RESP: mTypeString = "Probe Response"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_TIMING_AD: mTypeString = "Timing Advertisement"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_BEACON: mTypeString = "Beacon"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ATIM: mTypeString = "ATIM"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DISASSOC: mTypeString = "Disassociation"; parseDisassociationFrame(data); return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_AUTH: mTypeString = "Authentication"; parseAuthenticationFrame(data); return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_DEAUTH: mTypeString = "Deauthentication"; parseDeauthenticationFrame(data); return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION: mTypeString = "Action"; return; case IEEE_80211_FRAME_TYPE_MGMT_SUBTYPE_ACTION_NO_ACK: mTypeString = "Action No Ack"; return; case 0b0111: case 0b1111: mTypeString = "Reserved"; return; default: mTypeString = "Unexpected subtype " + ieee80211FrameSubtype; return; } } // Per 802.11-2012 Secs 8.3.3.6 and 8.4.1. private static final byte IEEE_80211_CAPABILITY_INFO_LEN = 2; private void parseAssociationResponse(ByteBuffer data) { data.position(data.position() + IEEE_80211_CAPABILITY_INFO_LEN); short resultCode = data.getShort(); mResultString = String.format( "%d: %s", resultCode, decodeIeee80211StatusCode(resultCode)); } // Per 802.11-2016 Sec 9.3.3.5 private void parseDisassociationFrame(ByteBuffer data) { short reasonCode = data.getShort(); mResultString = String.format("%d: %s", reasonCode, decodeIeee80211ReasonCode(reasonCode)); } // Per 802.11-2012 Secs 8.3.3.11 and 8.4.1. private static final short IEEE_80211_AUTH_ALG_OPEN = 0; private static final short IEEE_80211_AUTH_ALG_SHARED_KEY = 1; private static final short IEEE_80211_AUTH_ALG_FAST_BSS_TRANSITION = 2; private static final short IEEE_80211_AUTH_ALG_SIMUL_AUTH_OF_EQUALS = 3; private void parseAuthenticationFrame(ByteBuffer data) { short algorithm = data.getShort(); short sequenceNum = data.getShort(); boolean hasResultCode = false; switch (algorithm) { case IEEE_80211_AUTH_ALG_OPEN: case IEEE_80211_AUTH_ALG_SHARED_KEY: if (sequenceNum == 2) { hasResultCode = true; } break; case IEEE_80211_AUTH_ALG_FAST_BSS_TRANSITION: if (sequenceNum == 2 || sequenceNum == 4) { hasResultCode = true; } break; case IEEE_80211_AUTH_ALG_SIMUL_AUTH_OF_EQUALS: hasResultCode = true; break; default: // Ignore unknown algorithm -- don't know which frames would have result codes. } if (hasResultCode) { short resultCode = data.getShort(); mResultString = String.format( "%d: %s", resultCode, decodeIeee80211StatusCode(resultCode)); } } // Per 802.11-2016 Sec 9.3.3.13 private void parseDeauthenticationFrame(ByteBuffer data) { short reasonCode = data.getShort(); mResultString = String.format("%d: %s", reasonCode, decodeIeee80211ReasonCode(reasonCode)); } // Per 802.11-2016 Table 9-45 private String decodeIeee80211ReasonCode(short reasonCode) { switch (reasonCode) { case 0: return "Reserved"; case 1: return "Unspecified reason"; case 2: return "Previous authentication no longer valid"; case 3: return "Deauthenticated because sending STA is leaving (or has left) IBSS or ESS"; case 4: return "Disassociated due to inactivity"; case 5: return "Disassociated because AP is unable to handle all currently associated STAs"; case 6: return "Class 2 frame received from nonauthenticated STA"; case 7: return "Class 3 frame received from nonassociated STA"; case 8: return "Disassociated because sending STA is leaving (or has left) BSS"; case 9: return "STA requesting (re)association is not authenticated with responding STA"; case 10: return "Disassociated because the information in the Power Capability element is " + "unacceptable"; case 11: return "Disassociated because the information in the Supported Channels element " + "is unacceptable"; case 12: return "Disassociated due to BSS transition management"; case 13: return "Invalid element, i.e., an element defined in this standard for which the " + "content does not meet the specifications in Clause 9"; case 14: return "Message integrity code (MIC) failure"; case 15: return "4-way handshake timeout"; case 16: return "Group key handshake timeout"; case 17: return "Element in 4-way handshake different from (Re)Association Request/Probe " + "Response/Beacon frame"; case 18: return "Invalid group cipher"; case 19: return "Invalid pairwise cipher"; case 20: return "Invalid AKMP"; case 21: return "Unsupported RSNE version"; case 22: return "Invalid RSNE capabilities"; case 23: return "IEEE 802.1X authentication failed"; case 24: return "Cipher suite rejected because of the security policy"; case 25: return "TDLS direct-link teardown due to TDLS peer STA unreachable via the TDLS " + "direct link"; case 26: return "TDLS direct-link teardown for unspecified reason"; case 27: return "Disassociated because session terminated by SSP request"; case 28: return "Disassociated because of lack of SSP roaming agreement"; case 29: return "Requested service rejected because of SSP cipher suite or AKM requirement"; case 30: return "Requested service not authorized in this location"; case 31: return "TS deleted because QoS AP lacks sufficient bandwidth for this QoS STA due" + " to a change in BSS service characteristics or operational mode (e.g.," + " an HT BSS change from 40 MHz channel to 20 MHz channel)"; case 32: return "Disassociated for unspecified, QoS-related reason"; case 33: return "Disassociated because QoS AP lacks sufficient bandwidth for this QoS STA"; case 34: return "Disassociated because excessive number of frames need to be acknowledged," + " but are not acknowledged due to AP transmissions and/or poor channel " + "conditions"; case 35: return "Disassociated because STA is transmitting outside the limits of its TXOPs"; case 36: return "Requesting STA is leaving the BSS (or resetting)"; case 37: return "Requesting STA is no longer using the stream or session"; case 38: return "Requesting STA received frames using a mechanism for which a setup has " + "not been completed"; case 39: return "Requested from peer STA due to timeout"; case 40: case 41: case 42: case 43: case 44: return ""; case 45: return "Peer STA does not support the requested cipher suite"; case 46: return "In a DLS Teardown frame: The teardown was initiated by the DLS peer. In a" + " Disassociation frame: Disassociated because authorized access limit " + "reached"; case 47: return "In a DLS Teardown frame: The teardown was initiated by the AP. In a " + "Disassociation frame: Disassociated due to external service " + "requirements"; case 48: return "Invalid FT Action frame count"; case 49: return "Invalid pairwise master key identifier (PMKID)"; case 50: return "Invalid MDE"; case 51: return "Invalid FTE"; case 52: return "Mesh peering canceled for unknown reasons"; case 53: return "The mesh STA has reached the supported maximum number of peer mesh STAs"; case 54: return "The received information violates the Mesh Configuration policy " + "configured in the mesh STA profile"; case 55: return "The mesh STA has received a Mesh Peering Close frame requesting to close " + "the mesh peering."; case 56: return "The mesh STA has resent dot11MeshMaxRetries Mesh Peering Open frames, " + "without receiving a Mesh Peering Confirm frame."; case 57: return "The confirmTimer for the mesh peering instance times out."; case 58: return "The mesh STA fails to unwrap the GTK or the values in the wrapped " + "contents do not match"; case 59: return "The mesh STA receives inconsistent information about the mesh parameters " + "between mesh peering Management frames"; case 60: return "The mesh STA fails the authenticated mesh peering exchange because due to" + " failure in selecting either the pairwise ciphersuite or group " + "ciphersuite"; case 61: return "The mesh STA does not have proxy information for this external " + "destination."; case 62: return "The mesh STA does not have forwarding information for this destination."; case 63: return "The mesh STA determines that the link to the next hop of an active path " + "in its forwarding information is no longer usable."; case 64: return "The Deauthentication frame was sent because the MAC address of the STA " + "already exists in the mesh BSS. See 11.3.6."; case 65: return "The mesh STA performs channel switch to meet regulatory requirements."; case 66: return "The mesh STA performs channel switching with unspecified reason."; default: return "Reserved"; } } // Per 802.11-2012 Table 8-37. private String decodeIeee80211StatusCode(short statusCode) { switch (statusCode) { case 0: return "Success"; case 1: return "Unspecified failure"; case 2: return "TDLS wakeup schedule rejected; alternative provided"; case 3: return "TDLS wakeup schedule rejected"; case 4: return "Reserved"; case 5: return "Security disabled"; case 6: return "Unacceptable lifetime"; case 7: return "Not in same BSS"; case 8: case 9: return "Reserved"; case 10: return "Capabilities mismatch"; case 11: return "Reassociation denied; could not confirm association exists"; case 12: return "Association denied for reasons outside standard"; case 13: return "Unsupported authentication algorithm"; case 14: return "Authentication sequence number of of sequence"; case 15: return "Authentication challenge failure"; case 16: return "Authentication timeout"; case 17: return "Association denied; too many STAs"; case 18: return "Association denied; must support BSSBasicRateSet"; case 19: return "Association denied; must support short preamble"; case 20: return "Association denied; must support PBCC"; case 21: return "Association denied; must support channel agility"; case 22: return "Association rejected; must support spectrum management"; case 23: return "Association rejected; unacceptable power capability"; case 24: return "Association rejected; unacceptable supported channels"; case 25: return "Association denied; must support short slot time"; case 26: return "Association denied; must support DSSS-OFDM"; case 27: return "Association denied; must support HT"; case 28: return "R0 keyholder unreachable (802.11r)"; case 29: return "Association denied; must support PCO transition time"; case 30: return "Refused temporarily"; case 31: return "Robust management frame policy violation"; case 32: return "Unspecified QoS failure"; case 33: return "Association denied; insufficient bandwidth for QoS"; case 34: return "Association denied; poor channel"; case 35: return "Association denied; must support QoS"; case 36: return "Reserved"; case 37: return "Declined"; case 38: return "Invalid parameters"; case 39: return "TS cannot be honored; changes suggested"; case 40: return "Invalid element"; case 41: return "Invalid group cipher"; case 42: return "Invalid pairwise cipher"; case 43: return "Invalid auth/key mgmt proto (AKMP)"; case 44: return "Unsupported RSNE version"; case 45: return "Invalid RSNE capabilities"; case 46: return "Cipher suite rejected by policy"; case 47: return "TS cannot be honored now; try again later"; case 48: return "Direct link rejected by policy"; case 49: return "Destination STA not in BSS"; case 50: return "Destination STA not configured for QoS"; case 51: return "Association denied; listen interval too large"; case 52: return "Invalid fast transition action frame count"; case 53: return "Invalid PMKID"; case 54: return "Invalid MDE"; case 55: return "Invalid FTE"; case 56: return "Unsupported TCLAS"; case 57: return "Requested TCLAS exceeds resources"; case 58: return "TS cannot be honored; try another BSS"; case 59: return "GAS Advertisement not supported"; case 60: return "No outstanding GAS request"; case 61: return "No query response from GAS server"; case 62: return "GAS query timeout"; case 63: return "GAS response too large"; case 64: return "Home network does not support request"; case 65: return "Advertisement server unreachable"; case 66: return "Reserved"; case 67: return "Rejected for SSP permissions"; case 68: return "Authentication required"; case 69: case 70: case 71: return "Reserved"; case 72: return "Invalid RSNE contents"; case 73: return "U-APSD coexistence unsupported"; case 74: return "Requested U-APSD coex mode unsupported"; case 75: return "Requested parameter unsupported with U-APSD coex"; case 76: return "Auth rejected; anti-clogging token required"; case 77: return "Auth rejected; offered group is not supported"; case 78: return "Cannot find alternative TBTT"; case 79: return "Transmission failure"; case 80: return "Requested TCLAS not supported"; case 81: return "TCLAS resources exhausted"; case 82: return "Rejected with suggested BSS transition"; case 83: return "Reserved"; case 84: case 85: case 86: case 87: case 88: case 89: case 90: case 91: return ""; case 92: return "Refused due to external reason"; case 93: return "Refused; AP out of memory"; case 94: return "Refused; emergency services not supported"; case 95: return "GAS query response outstanding"; case 96: case 97: case 98: case 99: return "Reserved"; case 100: return "Failed; reservation conflict"; case 101: return "Failed; exceeded MAF limit"; case 102: return "Failed; exceeded MCCA track limit"; default: return "Reserved"; } } }