Merge "Have IpManager track L2-L4 signaling traffic required for IP connectivity." into nyc-mr2-dev

This commit is contained in:
TreeHugger Robot
2016-12-15 10:03:44 +00:00
committed by Android (Google) Code Review
10 changed files with 1482 additions and 17 deletions

View File

@ -51,6 +51,17 @@ public class NetworkUtils {
*/
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}.

View File

@ -47,28 +47,33 @@ int ifc_disable(const char *ifname);
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.
@ -96,17 +101,13 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject
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.
@ -125,6 +126,81 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject
}
}
// 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)
{
@ -266,6 +342,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
{ "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 },
};

View File

@ -25,8 +25,10 @@ import java.util.List;
* 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

View File

@ -0,0 +1,148 @@
/*
* 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));
}
}
}

View File

@ -374,6 +374,7 @@ public class IpManager extends StateMachine {
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;
@ -399,6 +400,7 @@ public class IpManager extends StateMachine {
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();
@ -439,6 +441,7 @@ public class IpManager extends StateMachine {
mNwService = nwService;
mLocalLog = new LocalLog(MAX_LOG_RECORDS);
mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
mMsgStateLogger = new MessageHandlingLogger();
mNetlinkTracker = new NetlinkTracker(
@ -609,7 +612,7 @@ public class IpManager extends StateMachine {
}
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;
@ -625,6 +628,12 @@ public class IpManager extends StateMachine {
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();
}
@ -1220,6 +1229,7 @@ public class IpManager extends StateMachine {
}
class RunningState extends State {
private ConnectivityPacketTracker mPacketTracker;
private boolean mDhcpActionInFlight;
@Override
@ -1232,6 +1242,9 @@ public class IpManager extends StateMachine {
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
}
mPacketTracker = createPacketTracker();
if (mPacketTracker != null) mPacketTracker.start();
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
transitionTo(mStoppingState);
@ -1266,6 +1279,11 @@ public class IpManager extends StateMachine {
mDhcpClient.doQuit();
}
if (mPacketTracker != null) {
mPacketTracker.stop();
mPacketTracker = null;
}
if (mApfFilter != null) {
mApfFilter.shutdown();
mApfFilter = null;
@ -1274,6 +1292,14 @@ public class IpManager extends StateMachine {
resetLinkProperties();
}
private ConnectivityPacketTracker createPacketTracker() {
try {
return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
} catch (IllegalArgumentException e) {
return null;
}
}
private void ensureDhcpAction() {
if (!mDhcpActionInFlight) {
mCallback.onPreDhcpAction();

View File

@ -0,0 +1,155 @@
/*
* 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();
}
}

View File

@ -0,0 +1,375 @@
/*
* 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);
}
}

View File

@ -0,0 +1,146 @@
/*
* 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); }
}

View File

@ -0,0 +1,148 @@
/*
* 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);
}
}

View File

@ -0,0 +1,377 @@
/*
* 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));
}
}