blob: c1b6c4f285f6064487f58f03f22ed1c3314b4bad [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 android.net.util;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.NetworkStackConstants.ARP_HWTYPE_ETHER;
import static com.android.net.module.util.NetworkStackConstants.ARP_PAYLOAD_LEN;
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
import static com.android.net.module.util.NetworkStackConstants.DHCP4_CLIENT_PORT;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_ARP;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MIN_LENGTH;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_MTU;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV4_DST_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_FLAGS_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_FRAGMENT_MASK;
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV4_IHL_MASK;
import static com.android.net.module.util.NetworkStackConstants.IPV4_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_SRC_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
import static com.android.net.module.util.NetworkStackConstants.IPV6_PROTOCOL_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV6_SRC_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
import android.net.MacAddress;
import android.net.dhcp.DhcpPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.StringJoiner;
/**
* Critical connectivity packet summarizing class.
*
* Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
*
* @hide
*/
public class ConnectivityPacketSummary {
private static final String TAG = ConnectivityPacketSummary.class.getSimpleName();
private final byte[] mHwAddr;
private final byte[] mBytes;
private final int mLength;
private final ByteBuffer mPacket;
private final String mSummary;
/**
* Create a string summary of a received packet.
* @param hwaddr MacAddress of the interface sending/receiving the packet.
* @param buffer The packet bytes. Length is assumed to be the buffer length.
* @return A summary of the packet.
*/
public static String summarize(MacAddress hwaddr, byte[] buffer) {
return summarize(hwaddr, buffer, buffer.length);
}
// Methods called herein perform some but by no means all error checking.
// They may throw runtime exceptions on malformed packets.
/**
* Create a string summary of a received packet.
* @param macAddr MacAddress of the interface sending/receiving the packet.
* @param buffer The packet bytes.
* @param length Length of the packet.
* @return A summary of the packet.
*/
public static String summarize(MacAddress macAddr, byte[] buffer, int length) {
if ((macAddr == null) || (buffer == null)) return null;
length = Math.min(length, buffer.length);
return (new ConnectivityPacketSummary(macAddr, buffer, length)).toString();
}
private ConnectivityPacketSummary(MacAddress macAddr, byte[] buffer, int length) {
mHwAddr = macAddr.toByteArray();
mBytes = buffer;
mLength = Math.min(length, mBytes.length);
mPacket = ByteBuffer.wrap(mBytes, 0, mLength);
mPacket.order(ByteOrder.BIG_ENDIAN);
final StringJoiner sj = new StringJoiner(" ");
// TODO: support other link-layers, or even no link-layer header.
parseEther(sj);
mSummary = sj.toString();
}
public String toString() {
return mSummary;
}
private void parseEther(StringJoiner sj) {
if (mPacket.remaining() < ETHER_HEADER_LEN) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
mPacket.position(ETHER_SRC_ADDR_OFFSET);
final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX");
sj.add(getMacAddressString(srcMac));
mPacket.position(ETHER_DST_ADDR_OFFSET);
final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN);
sj.add(">").add(getMacAddressString(dstMac));
mPacket.position(ETHER_TYPE_OFFSET);
final int etherType = asUint(mPacket.getShort());
switch (etherType) {
case ETHER_TYPE_ARP:
sj.add("arp");
parseARP(sj);
break;
case ETHER_TYPE_IPV4:
sj.add("ipv4");
parseIPv4(sj);
break;
case ETHER_TYPE_IPV6:
sj.add("ipv6");
parseIPv6(sj);
break;
default:
// Unknown ether type.
sj.add("ethtype").add(asString(etherType));
break;
}
}
private void parseARP(StringJoiner sj) {
if (mPacket.remaining() < ARP_PAYLOAD_LEN) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER ||
asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 ||
asUint(mPacket.get()) != ETHER_ADDR_LEN ||
asUint(mPacket.get()) != IPV4_ADDR_LEN) {
sj.add("unexpected header");
return;
}
final int opCode = asUint(mPacket.getShort());
final String senderHwAddr = getMacAddressString(mPacket);
final String senderIPv4 = getIPv4AddressString(mPacket);
getMacAddressString(mPacket); // target hardware address, unused
final String targetIPv4 = getIPv4AddressString(mPacket);
if (opCode == ARP_REQUEST) {
sj.add("who-has").add(targetIPv4);
} else if (opCode == ARP_REPLY) {
sj.add("reply").add(senderIPv4).add(senderHwAddr);
} else {
sj.add("unknown opcode").add(asString(opCode));
}
}
private void parseIPv4(StringJoiner sj) {
if (!mPacket.hasRemaining()) {
sj.add("runt");
return;
}
final int startOfIpLayer = mPacket.position();
final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4;
if (mPacket.remaining() < ipv4HeaderLength ||
mPacket.remaining() < IPV4_HEADER_MIN_LEN) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength;
mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET);
final int flagsAndFragment = asUint(mPacket.getShort());
final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0;
mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET);
final int protocol = asUint(mPacket.get());
mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET);
final String srcAddr = getIPv4AddressString(mPacket);
mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET);
final String dstAddr = getIPv4AddressString(mPacket);
sj.add(srcAddr).add(">").add(dstAddr);
mPacket.position(startOfTransportLayer);
if (protocol == IPPROTO_UDP) {
sj.add("udp");
if (isFragment) sj.add("fragment");
else parseUDP(sj);
} else {
sj.add("proto").add(asString(protocol));
if (isFragment) sj.add("fragment");
}
}
private void parseIPv6(StringJoiner sj) {
if (mPacket.remaining() < IPV6_HEADER_LEN) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
final int startOfIpLayer = mPacket.position();
mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET);
final int protocol = asUint(mPacket.get());
mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET);
final String srcAddr = getIPv6AddressString(mPacket);
final String dstAddr = getIPv6AddressString(mPacket);
sj.add(srcAddr).add(">").add(dstAddr);
mPacket.position(startOfIpLayer + IPV6_HEADER_LEN);
if (protocol == IPPROTO_ICMPV6) {
sj.add("icmp6");
parseICMPv6(sj);
} else {
sj.add("proto").add(asString(protocol));
}
}
private void parseICMPv6(StringJoiner sj) {
if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
final int icmp6Type = asUint(mPacket.get());
final int icmp6Code = asUint(mPacket.get());
mPacket.getShort(); // checksum, unused
switch (icmp6Type) {
case ICMPV6_ROUTER_SOLICITATION:
sj.add("rs");
parseICMPv6RouterSolicitation(sj);
break;
case ICMPV6_ROUTER_ADVERTISEMENT:
sj.add("ra");
parseICMPv6RouterAdvertisement(sj);
break;
case ICMPV6_NEIGHBOR_SOLICITATION:
sj.add("ns");
parseICMPv6NeighborMessage(sj);
break;
case ICMPV6_NEIGHBOR_ADVERTISEMENT:
sj.add("na");
parseICMPv6NeighborMessage(sj);
break;
default:
sj.add("type").add(asString(icmp6Type));
sj.add("code").add(asString(icmp6Code));
break;
}
}
private void parseICMPv6RouterSolicitation(StringJoiner sj) {
final int RESERVED = 4;
if (mPacket.remaining() < RESERVED) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
mPacket.position(mPacket.position() + RESERVED);
parseICMPv6NeighborDiscoveryOptions(sj);
}
private void parseICMPv6RouterAdvertisement(StringJoiner sj) {
final int FLAGS_AND_TIMERS = 3 * 4;
if (mPacket.remaining() < FLAGS_AND_TIMERS) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
mPacket.position(mPacket.position() + FLAGS_AND_TIMERS);
parseICMPv6NeighborDiscoveryOptions(sj);
}
private void parseICMPv6NeighborMessage(StringJoiner sj) {
final int RESERVED = 4;
final int minReq = RESERVED + IPV6_ADDR_LEN;
if (mPacket.remaining() < minReq) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
mPacket.position(mPacket.position() + RESERVED);
sj.add(getIPv6AddressString(mPacket));
parseICMPv6NeighborDiscoveryOptions(sj);
}
private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) {
// All ND options are TLV, where T is one byte and L is one byte equal
// to the length of T + L + V in units of 8 octets.
while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) {
final int ndType = asUint(mPacket.get());
final int ndLength = asUint(mPacket.get());
final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2;
if (ndBytes < 0 || ndBytes > mPacket.remaining()) {
sj.add("<malformed>");
break;
}
final int position = mPacket.position();
switch (ndType) {
case ICMPV6_ND_OPTION_SLLA:
sj.add("slla");
sj.add(getMacAddressString(mPacket));
break;
case ICMPV6_ND_OPTION_TLLA:
sj.add("tlla");
sj.add(getMacAddressString(mPacket));
break;
case ICMPV6_ND_OPTION_MTU:
sj.add("mtu");
final short reserved = mPacket.getShort();
sj.add(asString(mPacket.getInt()));
break;
default:
// Skip.
break;
}
mPacket.position(position + ndBytes);
}
}
private void parseUDP(StringJoiner sj) {
if (mPacket.remaining() < UDP_HEADER_LEN) {
sj.add("runt:").add(asString(mPacket.remaining()));
return;
}
final int previous = mPacket.position();
final int srcPort = asUint(mPacket.getShort());
final int dstPort = asUint(mPacket.getShort());
sj.add(asString(srcPort)).add(">").add(asString(dstPort));
mPacket.position(previous + UDP_HEADER_LEN);
if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) {
sj.add("dhcp4");
parseDHCPv4(sj);
}
}
private void parseDHCPv4(StringJoiner sj) {
final DhcpPacket dhcpPacket;
try {
dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2);
sj.add(dhcpPacket.toString());
} catch (DhcpPacket.ParseException e) {
sj.add("parse error: " + e);
}
}
private static String getIPv4AddressString(ByteBuffer ipv4) {
return getIpAddressString(ipv4, IPV4_ADDR_LEN);
}
private static String getIPv6AddressString(ByteBuffer ipv6) {
return getIpAddressString(ipv6, IPV6_ADDR_LEN);
}
private static String getIpAddressString(ByteBuffer ip, int byteLength) {
if (ip == null || ip.remaining() < byteLength) return "invalid";
byte[] bytes = new byte[byteLength];
ip.get(bytes, 0, byteLength);
try {
InetAddress addr = InetAddress.getByAddress(bytes);
return addr.getHostAddress();
} catch (UnknownHostException uhe) {
return "unknown";
}
}
private static String getMacAddressString(ByteBuffer mac) {
if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid";
byte[] bytes = new byte[ETHER_ADDR_LEN];
mac.get(bytes, 0, bytes.length);
Object[] printableBytes = new Object[bytes.length];
int i = 0;
for (byte b : bytes) printableBytes[i++] = new Byte(b);
final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
return String.format(MAC48_FORMAT, printableBytes);
}
/**
* Convenience method to convert an int to a String.
*/
public static String asString(int i) {
return Integer.toString(i);
}
/**
* Convenience method to read a byte as an unsigned int.
*/
public static int asUint(byte b) {
return (b & 0xff);
}
/**
* Convenience method to read a short as an unsigned int.
*/
public static int asUint(short s) {
return (s & 0xffff);
}
}