Merge "Have IpManager track L2-L4 signaling traffic required for IP connectivity." into nyc-mr2-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
c75ab85cd2
@ -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}.
|
||||
|
@ -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 },
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
148
services/net/java/android/net/ip/ConnectivityPacketTracker.java
Normal file
148
services/net/java/android/net/ip/ConnectivityPacketTracker.java
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
155
services/net/java/android/net/util/BlockingSocketReader.java
Normal file
155
services/net/java/android/net/util/BlockingSocketReader.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
146
services/net/java/android/net/util/NetworkConstants.java
Normal file
146
services/net/java/android/net/util/NetworkConstants.java
Normal 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); }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user