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;
|
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.
|
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
|
||||||
* @param fd the socket's {@link FileDescriptor}.
|
* @param fd the socket's {@link FileDescriptor}.
|
||||||
|
@ -47,28 +47,33 @@ int ifc_disable(const char *ifname);
|
|||||||
|
|
||||||
namespace android {
|
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 const uint16_t kDhcpClientPort = 68;
|
||||||
|
|
||||||
static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
|
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[] = {
|
struct sock_filter filter_code[] = {
|
||||||
// Check the protocol is UDP.
|
// 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),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6),
|
||||||
|
|
||||||
// Check this is not a fragment.
|
// Check this is not a fragment.
|
||||||
BPF_STMT(BPF_LD | BPF_H | BPF_ABS, flags_offset),
|
BPF_STMT(BPF_LD | BPF_H | BPF_ABS, kIPv4FlagsOffset),
|
||||||
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x1fff, 4, 0),
|
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, IP_OFFMASK, 4, 0),
|
||||||
|
|
||||||
// Get the IP header length.
|
// 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.
|
// 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),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1),
|
||||||
|
|
||||||
// Accept or reject.
|
// Accept or reject.
|
||||||
@ -96,17 +101,13 @@ static void android_net_utils_attachRaFilter(JNIEnv *env, jobject clazz, jobject
|
|||||||
return;
|
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[] = {
|
struct sock_filter filter_code[] = {
|
||||||
// Check IPv6 Next Header is ICMPv6.
|
// 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),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_ICMPV6, 0, 3),
|
||||||
|
|
||||||
// Check ICMPv6 type is Router Advertisement.
|
// 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),
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ND_ROUTER_ADVERT, 0, 1),
|
||||||
|
|
||||||
// Accept or reject.
|
// 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,
|
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
|
||||||
jint ifIndex)
|
jint ifIndex)
|
||||||
{
|
{
|
||||||
@ -266,6 +342,7 @@ static const JNINativeMethod gNetworkUtilMethods[] = {
|
|||||||
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
|
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
|
||||||
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
|
||||||
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
|
{ "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 },
|
{ "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
|
* Defines basic data and operations needed to build and use packets for the
|
||||||
* DHCP protocol. Subclasses create the specific packets used at each
|
* DHCP protocol. Subclasses create the specific packets used at each
|
||||||
* stage of the negotiation.
|
* stage of the negotiation.
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
*/
|
*/
|
||||||
abstract class DhcpPacket {
|
public abstract class DhcpPacket {
|
||||||
protected static final String TAG = "DhcpPacket";
|
protected static final String TAG = "DhcpPacket";
|
||||||
|
|
||||||
// dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the
|
// 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 EVENT_DHCPACTION_TIMEOUT = 10;
|
||||||
|
|
||||||
private static final int MAX_LOG_RECORDS = 500;
|
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 NO_CALLBACKS = false;
|
||||||
private static final boolean SEND_CALLBACKS = true;
|
private static final boolean SEND_CALLBACKS = true;
|
||||||
@ -399,6 +400,7 @@ public class IpManager extends StateMachine {
|
|||||||
private final WakeupMessage mDhcpActionTimeoutAlarm;
|
private final WakeupMessage mDhcpActionTimeoutAlarm;
|
||||||
private final AvoidBadWifiTracker mAvoidBadWifiTracker;
|
private final AvoidBadWifiTracker mAvoidBadWifiTracker;
|
||||||
private final LocalLog mLocalLog;
|
private final LocalLog mLocalLog;
|
||||||
|
private final LocalLog mConnectivityPacketLog;
|
||||||
private final MessageHandlingLogger mMsgStateLogger;
|
private final MessageHandlingLogger mMsgStateLogger;
|
||||||
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
|
private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
|
||||||
|
|
||||||
@ -439,6 +441,7 @@ public class IpManager extends StateMachine {
|
|||||||
mNwService = nwService;
|
mNwService = nwService;
|
||||||
|
|
||||||
mLocalLog = new LocalLog(MAX_LOG_RECORDS);
|
mLocalLog = new LocalLog(MAX_LOG_RECORDS);
|
||||||
|
mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS);
|
||||||
mMsgStateLogger = new MessageHandlingLogger();
|
mMsgStateLogger = new MessageHandlingLogger();
|
||||||
|
|
||||||
mNetlinkTracker = new NetlinkTracker(
|
mNetlinkTracker = new NetlinkTracker(
|
||||||
@ -609,7 +612,7 @@ public class IpManager extends StateMachine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
|
||||||
pw.println("APF dump:");
|
pw.println(mTag + " APF dump:");
|
||||||
pw.increaseIndent();
|
pw.increaseIndent();
|
||||||
// Thread-unsafe access to mApfFilter but just used for debugging.
|
// Thread-unsafe access to mApfFilter but just used for debugging.
|
||||||
ApfFilter apfFilter = mApfFilter;
|
ApfFilter apfFilter = mApfFilter;
|
||||||
@ -625,6 +628,12 @@ public class IpManager extends StateMachine {
|
|||||||
pw.increaseIndent();
|
pw.increaseIndent();
|
||||||
mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
|
mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
|
||||||
pw.decreaseIndent();
|
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 {
|
class RunningState extends State {
|
||||||
|
private ConnectivityPacketTracker mPacketTracker;
|
||||||
private boolean mDhcpActionInFlight;
|
private boolean mDhcpActionInFlight;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1232,6 +1242,9 @@ public class IpManager extends StateMachine {
|
|||||||
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
|
mCallback.setFallbackMulticastFilter(mMulticastFiltering);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mPacketTracker = createPacketTracker();
|
||||||
|
if (mPacketTracker != null) mPacketTracker.start();
|
||||||
|
|
||||||
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
|
if (mConfiguration.mEnableIPv6 && !startIPv6()) {
|
||||||
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
|
doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6);
|
||||||
transitionTo(mStoppingState);
|
transitionTo(mStoppingState);
|
||||||
@ -1266,6 +1279,11 @@ public class IpManager extends StateMachine {
|
|||||||
mDhcpClient.doQuit();
|
mDhcpClient.doQuit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mPacketTracker != null) {
|
||||||
|
mPacketTracker.stop();
|
||||||
|
mPacketTracker = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (mApfFilter != null) {
|
if (mApfFilter != null) {
|
||||||
mApfFilter.shutdown();
|
mApfFilter.shutdown();
|
||||||
mApfFilter = null;
|
mApfFilter = null;
|
||||||
@ -1274,6 +1292,14 @@ public class IpManager extends StateMachine {
|
|||||||
resetLinkProperties();
|
resetLinkProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConnectivityPacketTracker createPacketTracker() {
|
||||||
|
try {
|
||||||
|
return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureDhcpAction() {
|
private void ensureDhcpAction() {
|
||||||
if (!mDhcpActionInFlight) {
|
if (!mDhcpActionInFlight) {
|
||||||
mCallback.onPreDhcpAction();
|
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