blob: c48ca8e2e40af7d627986e8b828b41b89d160cad [file] [log] [blame]
/*
* 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<Integer> 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 "<unspecified>";
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 "<unspecified>";
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";
}
}
}