public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
/**
+ * Attaches a socket filter that accepts L2-L4 signaling traffic required for IP connectivity.
+ *
+ * This includes: all ARP, ICMPv6 RS/RA/NS/NA messages, and DHCPv4 exchanges.
+ *
+ * @param fd the socket's {@link FileDescriptor}.
+ * @param packetType the hardware address type, one of ARPHRD_*.
+ */
+ public native static void attachControlPacketFilter(FileDescriptor fd, int packetType)
+ throws SocketException;
+
+ /**
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
* @param fd the socket's {@link FileDescriptor}.
* @param ifIndex the interface index.
namespace android {
+static const uint32_t kEtherTypeOffset = offsetof(ether_header, ether_type);
+static const uint32_t kEtherHeaderLen = sizeof(ether_header);
+static const uint32_t kIPv4Protocol = kEtherHeaderLen + offsetof(iphdr, protocol);
+static const uint32_t kIPv4FlagsOffset = kEtherHeaderLen + offsetof(iphdr, frag_off);
+static const uint32_t kIPv6NextHeader = kEtherHeaderLen + offsetof(ip6_hdr, ip6_nxt);
+static const uint32_t kIPv6PayloadStart = kEtherHeaderLen + sizeof(ip6_hdr);
+static const uint32_t kICMPv6TypeOffset = kIPv6PayloadStart + offsetof(icmp6_hdr, icmp6_type);
+static const uint32_t kUDPSrcPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, source);
+static const uint32_t kUDPDstPortIndirectOffset = kEtherHeaderLen + offsetof(udphdr, dest);
static const uint16_t kDhcpClientPort = 68;
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
{
- uint32_t ip_offset = sizeof(ether_header);
- uint32_t proto_offset = ip_offset + offsetof(iphdr, protocol);
- uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off);
- uint32_t dport_indirect_offset = ip_offset + offsetof(udphdr, dest);
struct sock_filter filter_code[] = {
// Check the protocol is UDP.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, proto_offset),
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6),
// Check this is not a fragment.
- BPF_STMT(BPF_LD | BPF_H | BPF_ABS, flags_offset),
- BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x1fff, 4, 0),
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0),
// Get the IP header length.
- BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, ip_offset),
+ BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
// Check the destination port.
- BPF_STMT(BPF_LD | BPF_H | BPF_IND, dport_indirect_offset),
+ BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1),
// Accept or reject.
return;
}
- uint32_t ipv6_offset = sizeof(ether_header);
- uint32_t ipv6_next_header_offset = ipv6_offset + offsetof(ip6_hdr, ip6_nxt);
- uint32_t icmp6_offset = ipv6_offset + sizeof(ip6_hdr);
- uint32_t icmp6_type_offset = icmp6_offset + offsetof(icmp6_hdr, icmp6_type);
struct sock_filter filter_code[] = {
// Check IPv6 Next Header is ICMPv6.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, ipv6_next_header_offset),
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
// Check ICMPv6 type is Router Advertisement.
- BPF_STMT(BPF_LD | BPF_B | BPF_ABS, icmp6_type_offset),
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
// Accept or reject.
}
}
+// TODO: Move all this filter code into libnetutils.
+static void android_net_utils_attachControlPacketFilter(
+ JNIEnv *env, jobject clazz, jobject javaFd, jint hardwareAddressType) {
+ if (hardwareAddressType != ARPHRD_ETHER) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "attachControlPacketFilter only supports ARPHRD_ETHER");
+ return;
+ }
+
+ // Capture all:
+ // - ARPs
+ // - DHCPv4 packets
+ // - Router Advertisements & Solicitations
+ // - Neighbor Advertisements & Solicitations
+ //
+ // tcpdump:
+ // arp or
+ // '(ip and udp port 68)' or
+ // '(icmp6 and ip6[40] >= 133 and ip6[40] <= 136)'
+ struct sock_filter filter_code[] = {
+ // Load the link layer next payload field.
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kEtherTypeOffset),
+
+ // Accept all ARP.
+ // TODO: Figure out how to better filter ARPs on noisy networks.
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_ARP, 16, 0),
+
+ // If IPv4:
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IP, 0, 9),
+
+ // Check the protocol is UDP.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv4Protocol),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 14),
+
+ // Check this is not a fragment.
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 12, 0),
+
+ // Get the IP header length.
+ BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, kEtherHeaderLen),
+
+ // Check the source port.
+ BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPSrcPortIndirectOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 8, 0),
+
+ // Check the destination port.
+ BPF_STMT(BPF_LD | BPF_H | BPF_IND, kUDPDstPortIndirectOffset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 6, 7),
+
+ // IPv6 ...
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ETHERTYPE_IPV6, 0, 6),
+ // ... check IPv6 Next Header is ICMPv6 (ignore fragments), ...
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kIPv6NextHeader),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 4),
+ // ... and check the ICMPv6 type is one of RS/RA/NS/NA.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, kICMPv6TypeOffset),
+ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, ND_ROUTER_SOLICIT, 0, 2),
+ BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, ND_NEIGHBOR_ADVERT, 1, 0),
+
+ // Accept or reject.
+ BPF_STMT(BPF_RET | BPF_K, 0xffff),
+ BPF_STMT(BPF_RET | BPF_K, 0)
+ };
+ struct sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+ }
+}
+
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
jint ifIndex)
{
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
+ { "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
};
* Defines basic data and operations needed to build and use packets for the
* DHCP protocol. Subclasses create the specific packets used at each
* stage of the negotiation.
+ *
+ * @hide
*/
-abstract class DhcpPacket {
+public abstract class DhcpPacket {
protected static final String TAG = "DhcpPacket";
// dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
--- /dev/null
+/*
+ * 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.ip;
+
+import static android.system.OsConstants.*;
+
+import android.net.NetworkUtils;
+import android.net.util.BlockingSocketReader;
+import android.net.util.ConnectivityPacketSummary;
+import android.os.Handler;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.PacketSocketAddress;
+import android.util.Log;
+import android.util.LocalLog;
+
+import libcore.io.IoBridge;
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+
+
+/**
+ * Critical connectivity packet tracking daemon.
+ *
+ * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets.
+ *
+ * This class's constructor, start() and stop() methods must only be called
+ * from the same thread on which the passed in |log| is accessed.
+ *
+ * Log lines include a hexdump of the packet, which can be decoded via:
+ *
+ * echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /'
+ * | text2pcap - -
+ * | tcpdump -n -vv -e -r -
+ *
+ * @hide
+ */
+public class ConnectivityPacketTracker {
+ private static final String TAG = ConnectivityPacketTracker.class.getSimpleName();
+ private static final boolean DBG = false;
+ private static final String MARK_START = "--- START ---";
+ private static final String MARK_STOP = "--- STOP ---";
+
+ private final String mTag;
+ private final Handler mHandler;
+ private final LocalLog mLog;
+ private final BlockingSocketReader mPacketListener;
+
+ public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) {
+ final String ifname;
+ final int ifindex;
+ final byte[] hwaddr;
+ final int mtu;
+
+ try {
+ ifname = netif.getName();
+ ifindex = netif.getIndex();
+ hwaddr = netif.getHardwareAddress();
+ mtu = netif.getMTU();
+ } catch (NullPointerException|SocketException e) {
+ throw new IllegalArgumentException("bad network interface", e);
+ }
+
+ mTag = TAG + "." + ifname;
+ mHandler = new Handler();
+ mLog = log;
+ mPacketListener = new PacketListener(ifindex, hwaddr, mtu);
+ }
+
+ public void start() {
+ mLog.log(MARK_START);
+ mPacketListener.start();
+ }
+
+ public void stop() {
+ mPacketListener.stop();
+ mLog.log(MARK_STOP);
+ }
+
+ private final class PacketListener extends BlockingSocketReader {
+ private final int mIfIndex;
+ private final byte mHwAddr[];
+
+ PacketListener(int ifindex, byte[] hwaddr, int mtu) {
+ super(mtu);
+ mIfIndex = ifindex;
+ mHwAddr = hwaddr;
+ }
+
+ @Override
+ protected FileDescriptor createSocket() {
+ FileDescriptor s = null;
+ try {
+ // TODO: Evaluate switching to SOCK_DGRAM and changing the
+ // BlockingSocketReader's read() to recvfrom(), so that this
+ // might work on non-ethernet-like links (via SLL).
+ s = Os.socket(AF_PACKET, SOCK_RAW, 0);
+ NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER);
+ Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex));
+ } catch (ErrnoException | IOException e) {
+ logError("Failed to create packet tracking socket: ", e);
+ closeSocket(s);
+ return null;
+ }
+ return s;
+ }
+
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ final String summary = ConnectivityPacketSummary.summarize(
+ mHwAddr, recvbuf, length);
+ if (summary == null) return;
+
+ if (DBG) Log.d(mTag, summary);
+ addLogEntry(summary +
+ "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]");
+ }
+
+ @Override
+ protected void logError(String msg, Exception e) {
+ Log.e(mTag, msg, e);
+ addLogEntry(msg + e);
+ }
+
+ private void addLogEntry(String entry) {
+ mHandler.post(() -> mLog.log(entry));
+ }
+ }
+}
private static final int EVENT_DHCPACTION_TIMEOUT = 10;
private static final int MAX_LOG_RECORDS = 500;
+ private static final int MAX_PACKET_RECORDS = 100;
private static final boolean NO_CALLBACKS = false;
private static final boolean SEND_CALLBACKS = true;
private final WakeupMessage mDhcpActionTimeoutAlarm;
private final AvoidBadWifiTracker mAvoidBadWifiTracker;
private final LocalLog mLocalLog;
+ private final LocalLog mConnectivityPacketLog;
private final MessageHandlingLogger mMsgStateLogger;
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
mNwService = nwService;
mLocalLog = new LocalLog(MAX_LOG_RECORDS);
+ mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
mMsgStateLogger = new MessageHandlingLogger();
mNetlinkTracker = new NetlinkTracker(
}
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
- pw.println("APF dump:");
+ pw.println(mTag + " APF dump:");
pw.increaseIndent();
// Thread-unsafe access to mApfFilter but just used for debugging.
ApfFilter apfFilter = mApfFilter;
pw.increaseIndent();
mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
pw.decreaseIndent();
+
+ pw.println();
+ pw.println(mTag + " connectivity packet log:");
+ pw.increaseIndent();
+ mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args);
+ pw.decreaseIndent();
}
}
class RunningState extends State {
+ private ConnectivityPacketTracker mPacketTracker;
private boolean mDhcpActionInFlight;
@Override
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
}
+ mPacketTracker = createPacketTracker();
+ if (mPacketTracker != null) mPacketTracker.start();
+
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
transitionTo(mStoppingState);
mDhcpClient.doQuit();
}
+ if (mPacketTracker != null) {
+ mPacketTracker.stop();
+ mPacketTracker = null;
+ }
+
if (mApfFilter != null) {
mApfFilter.shutdown();
mApfFilter = null;
resetLinkProperties();
}
+ private ConnectivityPacketTracker createPacketTracker() {
+ try {
+ return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+
private void ensureDhcpAction() {
if (!mDhcpActionInFlight) {
mCallback.onPreDhcpAction();
--- /dev/null
+/*
+ * 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 android.annotation.Nullable;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+
+
+/**
+ * A thread that reads from a socket and passes the received packets to a
+ * subclass's handlePacket() method. The packet receive buffer is recycled
+ * on every read call, so subclasses should make any copies they would like
+ * inside their handlePacket() implementation.
+ *
+ * All public methods may be called from any thread.
+ *
+ * @hide
+ */
+public abstract class BlockingSocketReader {
+ public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024;
+
+ private final byte[] mPacket;
+ private final Thread mThread;
+ private volatile FileDescriptor mSocket;
+ private volatile boolean mRunning;
+ private volatile long mPacketsReceived;
+
+ // Make it slightly easier for subclasses to properly close a socket
+ // without having to know this incantation.
+ public static final void closeSocket(@Nullable FileDescriptor fd) {
+ try {
+ IoBridge.closeAndSignalBlockedThreads(fd);
+ } catch (IOException ignored) {}
+ }
+
+ protected BlockingSocketReader() {
+ this(DEFAULT_RECV_BUF_SIZE);
+ }
+
+ protected BlockingSocketReader(int recvbufsize) {
+ if (recvbufsize < DEFAULT_RECV_BUF_SIZE) {
+ recvbufsize = DEFAULT_RECV_BUF_SIZE;
+ }
+ mPacket = new byte[recvbufsize];
+ mThread = new Thread(() -> { mainLoop(); });
+ }
+
+ public final boolean start() {
+ if (mSocket != null) return false;
+
+ try {
+ mSocket = createSocket();
+ } catch (Exception e) {
+ logError("Failed to create socket: ", e);
+ return false;
+ }
+
+ if (mSocket == null) return false;
+
+ mRunning = true;
+ mThread.start();
+ return true;
+ }
+
+ public final void stop() {
+ mRunning = false;
+ closeSocket(mSocket);
+ mSocket = null;
+ }
+
+ public final boolean isRunning() { return mRunning; }
+
+ public final long numPacketsReceived() { return mPacketsReceived; }
+
+ /**
+ * Subclasses MUST create the listening socket here, including setting
+ * all desired socket options, interface or address/port binding, etc.
+ */
+ protected abstract FileDescriptor createSocket();
+
+ /**
+ * Called by the main loop for every packet. Any desired copies of
+ * |recvbuf| should be made in here, and the underlying byte array is
+ * reused across all reads.
+ */
+ protected void handlePacket(byte[] recvbuf, int length) {}
+
+ /**
+ * Called by the main loop to log errors. In some cases |e| may be null.
+ */
+ protected void logError(String msg, Exception e) {}
+
+ /**
+ * Called by the main loop just prior to exiting.
+ */
+ protected void onExit() {}
+
+ private final void mainLoop() {
+ while (isRunning()) {
+ final int bytesRead;
+
+ try {
+ // Blocking read.
+ // TODO: See if this can be converted to recvfrom.
+ bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length);
+ if (bytesRead < 1) {
+ if (isRunning()) logError("Socket closed, exiting", null);
+ break;
+ }
+ mPacketsReceived++;
+ } catch (ErrnoException e) {
+ if (e.errno != OsConstants.EINTR) {
+ if (isRunning()) logError("read error: ", e);
+ break;
+ }
+ continue;
+ } catch (IOException ioe) {
+ if (isRunning()) logError("read error: ", ioe);
+ continue;
+ }
+
+ try {
+ handlePacket(mPacket, bytesRead);
+ } catch (Exception e) {
+ logError("Unexpected exception: ", e);
+ break;
+ }
+ }
+
+ stop();
+ onExit();
+ }
+}
--- /dev/null
+/*
+ * 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 android.net.dhcp.DhcpPacket;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.StringJoiner;
+
+import static android.system.OsConstants.*;
+import static android.net.util.NetworkConstants.*;
+
+
+/**
+ * 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;
+
+ public static String summarize(byte[] 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.
+ public static String summarize(byte[] hwaddr, byte[] buffer, int length) {
+ if ((hwaddr == null) || (hwaddr.length != ETHER_ADDR_LEN)) return null;
+ if (buffer == null) return null;
+ length = Math.min(length, buffer.length);
+ return (new ConnectivityPacketSummary(hwaddr, buffer, length)).toString();
+ }
+
+ private ConnectivityPacketSummary(byte[] hwaddr, byte[] buffer, int length) {
+ mHwAddr = hwaddr;
+ 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 (mPacket.remaining() < ndBytes) 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);
+ Byte[] printableBytes = new Byte[bytes.length];
+ int i = 0;
+ for (byte b : bytes) printableBytes[i++] = b;
+
+ final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x";
+ return String.format(MAC48_FORMAT, printableBytes);
+ }
+}
--- /dev/null
+/*
+ * 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 java.nio.ByteBuffer;
+
+
+/**
+ * Networking protocol constants.
+ *
+ * Includes:
+ * - constants that describe packet layout
+ * - various helper functions
+ *
+ * @hide
+ */
+public final class NetworkConstants {
+ private NetworkConstants() { throw new RuntimeException("no instance permitted"); }
+
+ /**
+ * Ethernet constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc894
+ * - https://tools.ietf.org/html/rfc7042
+ * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+ * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
+ */
+ public static final int ETHER_DST_ADDR_OFFSET = 0;
+ public static final int ETHER_SRC_ADDR_OFFSET = 6;
+ public static final int ETHER_ADDR_LEN = 6;
+
+ public static final int ETHER_TYPE_OFFSET = 12;
+ public static final int ETHER_TYPE_LENGTH = 2;
+ public static final int ETHER_TYPE_ARP = 0x0806;
+ public static final int ETHER_TYPE_IPV4 = 0x0800;
+ public static final int ETHER_TYPE_IPV6 = 0x86dd;
+
+ public static final int ETHER_HEADER_LEN = 14;
+
+ private static final byte FF = asByte(0xff);
+ public static final byte[] ETHER_ADDR_BROADCAST = {
+ FF, FF, FF, FF, FF, FF
+ };
+
+ /**
+ * ARP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc826
+ * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml
+ */
+ public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4.
+ public static final int ARP_REQUEST = 1;
+ public static final int ARP_REPLY = 2;
+ public static final int ARP_HWTYPE_RESERVED_LO = 0;
+ public static final int ARP_HWTYPE_ETHER = 1;
+ public static final int ARP_HWTYPE_RESERVED_HI = 0xffff;
+
+ /**
+ * IPv4 constants.
+ *
+ * See als:
+ * - https://tools.ietf.org/html/rfc791
+ */
+ public static final int IPV4_HEADER_MIN_LEN = 20;
+ public static final int IPV4_IHL_MASK = 0xf;
+ public static final int IPV4_FLAGS_OFFSET = 6;
+ public static final int IPV4_FRAGMENT_MASK = 0x1fff;
+ public static final int IPV4_PROTOCOL_OFFSET = 9;
+ public static final int IPV4_SRC_ADDR_OFFSET = 12;
+ public static final int IPV4_DST_ADDR_OFFSET = 16;
+ public static final int IPV4_ADDR_LEN = 4;
+
+ /**
+ * IPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2460
+ */
+ public static final int IPV6_HEADER_LEN = 40;
+ public static final int IPV6_PROTOCOL_OFFSET = 6;
+ public static final int IPV6_SRC_ADDR_OFFSET = 8;
+ public static final int IPV6_DST_ADDR_OFFSET = 24;
+ public static final int IPV6_ADDR_LEN = 16;
+
+ /**
+ * ICMPv6 constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc4443
+ * - https://tools.ietf.org/html/rfc4861
+ */
+ public static final int ICMPV6_HEADER_MIN_LEN = 4;
+ public static final int ICMPV6_ROUTER_SOLICITATION = 133;
+ public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134;
+ public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135;
+ public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136;
+
+ public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8;
+ public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8;
+ public static final int ICMPV6_ND_OPTION_SLLA = 1;
+ public static final int ICMPV6_ND_OPTION_TLLA = 2;
+ public static final int ICMPV6_ND_OPTION_MTU = 5;
+
+ /**
+ * UDP constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc768
+ */
+ public static final int UDP_HEADER_LEN = 8;
+
+ /**
+ * DHCP(v4) constants.
+ *
+ * See also:
+ * - https://tools.ietf.org/html/rfc2131
+ */
+ public static final int DHCP4_SERVER_PORT = 67;
+ public static final int DHCP4_CLIENT_PORT = 68;
+
+ /**
+ * Utility functions.
+ */
+ public static byte asByte(int i) { return (byte) i; }
+
+ public static String asString(int i) { return Integer.toString(i); }
+
+ public static int asUint(byte b) { return (b & 0xff); }
+ public static int asUint(short s) { return (s & 0xffff); }
+}
--- /dev/null
+/*
+ * 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.*;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Tests for BlockingSocketReader.
+ *
+ * @hide
+ */
+public class BlockingSocketReaderTest extends TestCase {
+ static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress();
+ static final StructTimeval TIMEO = StructTimeval.fromMillis(500);
+
+ protected CountDownLatch mLatch;
+ protected FileDescriptor mLocalSocket;
+ protected InetSocketAddress mLocalSockName;
+ protected byte[] mLastRecvBuf;
+ protected boolean mExited;
+ protected BlockingSocketReader mReceiver;
+
+ @Override
+ public void setUp() {
+ resetLatch();
+ mLocalSocket = null;
+ mLocalSockName = null;
+ mLastRecvBuf = null;
+ mExited = false;
+
+ mReceiver = new BlockingSocketReader() {
+ @Override
+ protected FileDescriptor createSocket() {
+ FileDescriptor s = null;
+ try {
+ s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
+ Os.bind(s, LOOPBACK6, 0);
+ mLocalSockName = (InetSocketAddress) Os.getsockname(s);
+ Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO);
+ } catch (ErrnoException|SocketException e) {
+ closeSocket(s);
+ fail();
+ return null;
+ }
+
+ mLocalSocket = s;
+ return s;
+ }
+
+ @Override
+ protected void handlePacket(byte[] recvbuf, int length) {
+ mLastRecvBuf = Arrays.copyOf(recvbuf, length);
+ mLatch.countDown();
+ }
+
+ @Override
+ protected void onExit() {
+ mExited = true;
+ mLatch.countDown();
+ }
+ };
+ }
+
+ @Override
+ public void tearDown() {
+ if (mReceiver != null) mReceiver.stop();
+ mReceiver = null;
+ }
+
+ void resetLatch() { mLatch = new CountDownLatch(1); }
+
+ void waitForActivity() throws Exception {
+ assertTrue(mLatch.await(500, TimeUnit.MILLISECONDS));
+ resetLatch();
+ }
+
+ void sendPacket(byte[] contents) throws Exception {
+ final DatagramSocket sender = new DatagramSocket();
+ sender.connect(mLocalSockName);
+ sender.send(new DatagramPacket(contents, contents.length));
+ sender.close();
+ }
+
+ public void testBasicWorking() throws Exception {
+ assertTrue(mReceiver.start());
+ assertTrue(mLocalSockName != null);
+ assertEquals(LOOPBACK6, mLocalSockName.getAddress());
+ assertTrue(0 < mLocalSockName.getPort());
+ assertTrue(mLocalSocket != null);
+ assertFalse(mExited);
+
+ final byte[] one = "one 1".getBytes("UTF-8");
+ sendPacket(one);
+ waitForActivity();
+ assertEquals(1, mReceiver.numPacketsReceived());
+ assertTrue(Arrays.equals(one, mLastRecvBuf));
+ assertFalse(mExited);
+
+ final byte[] two = "two 2".getBytes("UTF-8");
+ sendPacket(two);
+ waitForActivity();
+ assertEquals(2, mReceiver.numPacketsReceived());
+ assertTrue(Arrays.equals(two, mLastRecvBuf));
+ assertFalse(mExited);
+
+ mReceiver.stop();
+ waitForActivity();
+ assertEquals(2, mReceiver.numPacketsReceived());
+ assertTrue(Arrays.equals(two, mLastRecvBuf));
+ assertTrue(mExited);
+ }
+}
--- /dev/null
+/*
+ * 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.net.util.NetworkConstants.*;
+
+import libcore.util.HexEncoding;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Tests for ConnectivityPacketSummary.
+ *
+ * @hide
+ */
+public class ConnectivityPacketSummaryTest extends TestCase {
+ private static final byte[] MYHWADDR = {
+ asByte(0x80), asByte(0x7a), asByte(0xbf), asByte(0x6f), asByte(0x48), asByte(0xf3)
+ };
+
+ private String getSummary(String hexBytes) {
+ hexBytes = hexBytes.replaceAll("\\s+", "");
+ final byte[] bytes = HexEncoding.decode(hexBytes.toCharArray(), false);
+ return ConnectivityPacketSummary.summarize(MYHWADDR, bytes);
+ }
+
+ public void testParseICMPv6DADProbe() {
+ final String packet =
+ // Ethernet
+ "3333FF6F48F3 807ABF6F48F3 86DD" +
+ // IPv6
+ "600000000018 3A FF" +
+ "00000000000000000000000000000000" +
+ "FF0200000000000000000001FF6F48F3" +
+ // ICMPv6
+ "87 00 A8E7" +
+ "00000000" +
+ "FE80000000000000827ABFFFFE6F48F3";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > 33:33:ff:6f:48:f3 ipv6" +
+ " :: > ff02::1:ff6f:48f3 icmp6" +
+ " ns fe80::827a:bfff:fe6f:48f3";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseICMPv6RS() {
+ final String packet =
+ // Ethernet
+ "333300000002 807ABF6F48F3 86DD" +
+ // IPv6
+ "600000000010 3A FF" +
+ "FE80000000000000827ABFFFFE6F48F3" +
+ "FF020000000000000000000000000002" +
+ // ICMPv6 RS
+ "85 00 6973" +
+ "00000000" +
+ "01 01 807ABF6F48F3";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > 33:33:00:00:00:02 ipv6" +
+ " fe80::827a:bfff:fe6f:48f3 > ff02::2 icmp6" +
+ " rs slla 80:7a:bf:6f:48:f3";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseICMPv6RA() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 100E7E263FC1 86DD" +
+ // IPv6
+ "600000000068 3A FF" +
+ "FE80000000000000FA000004FD000001" +
+ "FE80000000000000827ABFFFFE6F48F3" +
+ // ICMPv6 RA
+ "86 00 8141" +
+ "40 00 0E10" +
+ "00000000" +
+ "00000000" +
+ "01 01 00005E000265" +
+ "05 01 0000000005DC" +
+ "19 05 000000000E10" +
+ " 20014860486000000000000000008844" +
+ " 20014860486000000000000000008888" +
+ "03 04 40 C0" +
+ " 00278D00" +
+ " 00093A80" +
+ " 00000000" +
+ " 2401FA000004FD000000000000000000";
+
+ final String expected =
+ "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
+ " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" +
+ " ra slla 00:00:5e:00:02:65 mtu 1500";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseICMPv6NS() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 100E7E263FC1 86DD" +
+ // IPv6
+ "6C0000000020 3A FF" +
+ "FE80000000000000FA000004FD000001" +
+ "FF0200000000000000000001FF01C146" +
+ // ICMPv6 NS
+ "87 00 8AD4" +
+ "00000000" +
+ "2401FA000004FD0015EA6A5C7B01C146" +
+ "01 01 00005E000265";
+
+ final String expected =
+ "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" +
+ " fe80::fa00:4:fd00:1 > ff02::1:ff01:c146 icmp6" +
+ " ns 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 slla 00:00:5e:00:02:65";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseICMPv6NA() {
+ final String packet =
+ // Ethernet
+ "00005E000265 807ABF6F48F3 86DD" +
+ "600000000020 3A FF" +
+ "2401FA000004FD0015EA6A5C7B01C146" +
+ "FE80000000000000FA000004FD000001" +
+ "88 00 E8126" +
+ "0000000" +
+ "2401FA000004FD0015EA6A5C7B01C146" +
+ "02 01 807ABF6F48F3";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > 00:00:5e:00:02:65 ipv6" +
+ " 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 > fe80::fa00:4:fd00:1 icmp6" +
+ " na 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 tlla 80:7a:bf:6f:48:f3";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseARPRequest() {
+ final String packet =
+ // Ethernet
+ "FFFFFFFFFFFF 807ABF6F48F3 0806" +
+ // ARP
+ "0001 0800 06 04" +
+ // Request
+ "0001" +
+ "807ABF6F48F3 64706ADB" +
+ "000000000000 64706FFD";
+
+ final String expected =
+ "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff arp" +
+ " who-has 100.112.111.253";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseARPReply() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 288A1CA8DFC1 0806" +
+ // ARP
+ "0001 0800 06 04" +
+ // Reply
+ "0002" +
+ "288A1CA8DFC1 64706FFD"+
+ "807ABF6F48F3 64706ADB" +
+ // Ethernet padding to packet min size.
+ "0000000000000000000000000000";
+
+ final String expected =
+ "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 arp" +
+ " reply 100.112.111.253 28:8a:1c:a8:df:c1";
+
+ assertEquals(expected, getSummary(packet));
+ }
+
+ public void testParseDHCPv4Discover() {
+ final String packet =
+ // Ethernet
+ "FFFFFFFFFFFF 807ABF6F48F3 0800" +
+ // IPv4
+ "451001580000400040113986" +
+ "00000000" +
+ "FFFFFFFF" +
+ // UDP
+ "0044 0043" +
+ "0144 5559" +
+ // DHCPv4
+ "01 01 06 00" +
+ "79F7ACA4" +
+ "0000 0000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 01" +
+ "3D 07 01807ABF6F48F3" +
+ "39 02 05DC" +
+ "3C 12 616E64726F69642D646863702D372E312E32" +
+ "0C 18 616E64726F69642D36623030366333313333393835343139" +
+ "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
+ "FF" +
+ "00";
+
+ final String expectedPrefix =
+ "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
+ " 0.0.0.0 > 255.255.255.255 udp" +
+ " 68 > 67 dhcp4" +
+ " 80:7a:bf:6f:48:f3 DISCOVER";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+
+ public void testParseDHCPv4Offer() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 288A1CA8DFC1 0800" +
+ // IPv4
+ "4500013D4D2C0000401188CB" +
+ "64706FFD" +
+ "64706ADB" +
+ // UDP
+ "0043 0044" +
+ "0129 371D" +
+ // DHCPv4
+ "02 01 06 01" +
+ "79F7ACA4" +
+ "0000 0000" +
+ "00000000" +
+ "64706ADB" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 02" +
+ "36 04 AC188A0B" +
+ "33 04 00000708" +
+ "01 04 FFFFF000" +
+ "03 04 64706FFE" +
+ "06 08 08080808" +
+ " 08080404" +
+ "FF0001076165313A363636FF";
+
+ final String expectedPrefix =
+ "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
+ " 100.112.111.253 > 100.112.106.219 udp" +
+ " 67 > 68 dhcp4" +
+ " 80:7a:bf:6f:48:f3 OFFER";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+
+ public void testParseDHCPv4Request() {
+ final String packet =
+ // Ethernet
+ "FFFFFFFFFFFF 807ABF6F48F3 0800" +
+ // IPv4
+ "45100164000040004011397A" +
+ "00000000" +
+ "FFFFFFFF" +
+ // UDP
+ "0044 0043" +
+ "0150 E5C7" +
+ // DHCPv4
+ "01 01 06 00" +
+ "79F7ACA4" +
+ "0001 0000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 03" +
+ "3D 07 01807ABF6F48F3" +
+ "32 04 64706ADB" +
+ "36 04 AC188A0B" +
+ "39 02 05DC" +
+ "3C 12 616E64726F69642D646863702D372E312E32" +
+ "0C 18 616E64726F69642D36623030366333313333393835343139" +
+ "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" +
+ "FF" +
+ "00";
+
+ final String expectedPrefix =
+ "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" +
+ " 0.0.0.0 > 255.255.255.255 udp" +
+ " 68 > 67 dhcp4" +
+ " 80:7a:bf:6f:48:f3 REQUEST";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+
+ public void testParseDHCPv4Ack() {
+ final String packet =
+ // Ethernet
+ "807ABF6F48F3 288A1CA8DFC1 0800" +
+ // IPv4
+ "4500013D4D3B0000401188BC" +
+ "64706FFD" +
+ "64706ADB" +
+ // UDP
+ "0043 0044" +
+ "0129 341C" +
+ // DHCPv4
+ "02 01 06 01" +
+ "79F7ACA4" +
+ "0001 0000" +
+ "00000000" +
+ "64706ADB" +
+ "00000000" +
+ "00000000" +
+ "807ABF6F48F300000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "0000000000000000000000000000000000000000000000000000000000000000" +
+ "63 82 53 63" +
+ "35 01 05" +
+ "36 04 AC188A0B" +
+ "33 04 00000708" +
+ "01 04 FFFFF000" +
+ "03 04 64706FFE" +
+ "06 08 08080808" +
+ " 08080404" +
+ "FF0001076165313A363636FF";
+
+ final String expectedPrefix =
+ "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" +
+ " 100.112.111.253 > 100.112.106.219 udp" +
+ " 67 > 68 dhcp4" +
+ " 80:7a:bf:6f:48:f3 ACK";
+
+ assertTrue(getSummary(packet).startsWith(expectedPrefix));
+ }
+}