Merge "Revert "Add a test BroadCastReceiver to receive carrier Config""
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index e0b75c5..2fc4142 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -127,7 +127,6 @@
"//frameworks/libs/net/common/testutils:__subpackages__",
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/NetworkStack:__subpackages__",
- "//frameworks/base/services/core",
],
libs: [
"androidx.annotation_annotation",
@@ -260,24 +259,15 @@
],
lint: { strict_updatability_linting: true },
}
-filegroup {
- name: "net-utils-services-common-srcs",
+
+java_library {
+ name: "net-utils-services-common",
srcs: [
"device/android/net/NetworkFactory.java",
"device/android/net/NetworkFactoryImpl.java",
"device/android/net/NetworkFactoryLegacyImpl.java",
"device/android/net/NetworkFactoryShim.java",
],
- visibility: [
- "//frameworks/base/services/net",
- ],
-}
-
-java_library {
- name: "net-utils-services-common",
- srcs: [
- ":net-utils-services-common-srcs",
- ],
sdk_version: "module_current",
min_sdk_version: "30",
static_libs: [
@@ -292,6 +282,7 @@
"//apex_available:platform",
"com.android.bluetooth",
"com.android.tethering",
+ "com.android.wifi",
],
visibility: [
// TODO: remove after NetworkStatsService moves to the module.
diff --git a/staticlibs/device/com/android/net/module/util/BpfMap.java b/staticlibs/device/com/android/net/module/util/BpfMap.java
index d4ce83b..f7019a5 100644
--- a/staticlibs/device/com/android/net/module/util/BpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/BpfMap.java
@@ -18,7 +18,9 @@
import static android.system.OsConstants.EEXIST;
import static android.system.OsConstants.ENOENT;
+import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
+import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -26,10 +28,12 @@
import com.android.net.module.util.Struct;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.NoSuchElementException;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
/**
* BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
@@ -39,7 +43,7 @@
* @param <K> the key of the map.
* @param <V> the value of the map.
*/
-public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V>, AutoCloseable {
+public class BpfMap<K extends Struct, V extends Struct> implements IBpfMap<K, V> {
static {
System.loadLibrary(JniUtil.getJniLibraryName(BpfMap.class.getPackage()));
}
@@ -57,12 +61,33 @@
private static final int BPF_NOEXIST = 1;
private static final int BPF_EXIST = 2;
- private final int mMapFd;
+ private final ParcelFileDescriptor mMapFd;
private final Class<K> mKeyClass;
private final Class<V> mValueClass;
private final int mKeySize;
private final int mValueSize;
+ private static ConcurrentHashMap<Pair<String, Integer>, ParcelFileDescriptor> sFdCache =
+ new ConcurrentHashMap<>();
+
+ private static ParcelFileDescriptor cachedBpfFdGet(String path, int mode)
+ throws ErrnoException, NullPointerException {
+ Pair<String, Integer> key = Pair.create(path, mode);
+ // unlocked fetch is safe: map is concurrent read capable, and only inserted into
+ ParcelFileDescriptor fd = sFdCache.get(key);
+ if (fd != null) return fd;
+ // ok, no cached fd present, need to grab a lock
+ synchronized (BpfMap.class) {
+ // need to redo the check
+ fd = sFdCache.get(key);
+ if (fd != null) return fd;
+ // okay, we really haven't opened this before...
+ fd = ParcelFileDescriptor.adoptFd(nativeBpfFdGet(path, mode));
+ sFdCache.put(key, fd);
+ return fd;
+ }
+ }
+
/**
* Create a BpfMap map wrapper with "path" of filesystem.
*
@@ -72,8 +97,7 @@
*/
public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
final Class<V> value) throws ErrnoException, NullPointerException {
- mMapFd = bpfFdGet(path, flag);
-
+ mMapFd = cachedBpfFdGet(path, flag);
mKeyClass = key;
mValueClass = value;
mKeySize = Struct.getSize(key);
@@ -85,10 +109,11 @@
* The derived class implements an internal mocked map. It need to implement all functions
* which are related with the native BPF map because the BPF map handler is not initialized.
* See BpfCoordinatorTest#TestBpfMap.
+ * TODO: remove once TestBpfMap derive from IBpfMap.
*/
@VisibleForTesting
protected BpfMap(final Class<K> key, final Class<V> value) {
- mMapFd = -1;
+ mMapFd = null; // unused
mKeyClass = key;
mValueClass = value;
mKeySize = Struct.getSize(key);
@@ -101,7 +126,7 @@
*/
@Override
public void updateEntry(K key, V value) throws ErrnoException {
- writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_ANY);
+ nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(), BPF_ANY);
}
/**
@@ -112,7 +137,8 @@
public void insertEntry(K key, V value)
throws ErrnoException, IllegalStateException {
try {
- writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
+ nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+ BPF_NOEXIST);
} catch (ErrnoException e) {
if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists");
@@ -128,7 +154,8 @@
public void replaceEntry(K key, V value)
throws ErrnoException, NoSuchElementException {
try {
- writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
+ nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+ BPF_EXIST);
} catch (ErrnoException e) {
if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found");
@@ -146,13 +173,15 @@
public boolean insertOrReplaceEntry(K key, V value)
throws ErrnoException {
try {
- writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
+ nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+ BPF_NOEXIST);
return true; /* insert succeeded */
} catch (ErrnoException e) {
if (e.errno != EEXIST) throw e;
}
try {
- writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
+ nativeWriteToMapEntry(mMapFd.getFd(), key.writeToBytes(), value.writeToBytes(),
+ BPF_EXIST);
return false; /* replace succeeded */
} catch (ErrnoException e) {
if (e.errno != ENOENT) throw e;
@@ -169,7 +198,7 @@
/** Remove existing key from eBpf map. Return false if map was not modified. */
@Override
public boolean deleteEntry(K key) throws ErrnoException {
- return deleteMapEntry(mMapFd, key.writeToBytes());
+ return nativeDeleteMapEntry(mMapFd.getFd(), key.writeToBytes());
}
/** Returns {@code true} if this map contains no elements. */
@@ -202,7 +231,7 @@
private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException {
byte[] nextKey = new byte[mKeySize];
- if (getNextMapKey(mMapFd, key, nextKey)) return nextKey;
+ if (nativeGetNextMapKey(mMapFd.getFd(), key, nextKey)) return nextKey;
return null;
}
@@ -237,7 +266,7 @@
private byte[] getRawValue(final byte[] key) throws ErrnoException {
byte[] value = new byte[mValueSize];
- if (findMapEntry(mMapFd, key, value)) return value;
+ if (nativeFindMapEntry(mMapFd.getFd(), key, value)) return value;
return null;
}
@@ -261,9 +290,13 @@
}
}
+ /* Empty implementation to implement AutoCloseable, so we can use BpfMaps
+ * with try with resources, but due to persistent FD cache, there is no actual
+ * need to close anything. File descriptors will actually be closed when we
+ * unlock the BpfMap class and destroy the ParcelFileDescriptor objects.
+ */
@Override
- public void close() throws ErrnoException {
- closeMap(mMapFd);
+ public void close() throws IOException {
}
/**
@@ -281,19 +314,25 @@
}
}
- private static native int closeMap(int fd) throws ErrnoException;
+ private static native int nativeBpfFdGet(String path, int mode)
+ throws ErrnoException, NullPointerException;
- private native int bpfFdGet(String path, int mode) throws ErrnoException, NullPointerException;
+ // Note: the following methods appear to not require the object by virtue of taking the
+ // fd as an int argument, but the hidden reference to this is actually what prevents
+ // the object from being garbage collected (and thus potentially maps closed) prior
+ // to the native code actually running (with a possibly already closed fd).
- private native void writeToMapEntry(int fd, byte[] key, byte[] value, int flags)
+ private native void nativeWriteToMapEntry(int fd, byte[] key, byte[] value, int flags)
throws ErrnoException;
- private native boolean deleteMapEntry(int fd, byte[] key) throws ErrnoException;
+ private native boolean nativeDeleteMapEntry(int fd, byte[] key) throws ErrnoException;
// If key is found, the operation returns true and the nextKey would reference to the next
// element. If key is not found, the operation returns true and the nextKey would reference to
// the first element. If key is the last element, false is returned.
- private native boolean getNextMapKey(int fd, byte[] key, byte[] nextKey) throws ErrnoException;
+ private native boolean nativeGetNextMapKey(int fd, byte[] key, byte[] nextKey)
+ throws ErrnoException;
- private native boolean findMapEntry(int fd, byte[] key, byte[] value) throws ErrnoException;
+ private native boolean nativeFindMapEntry(int fd, byte[] key, byte[] value)
+ throws ErrnoException;
}
diff --git a/staticlibs/device/com/android/net/module/util/FdEventsReader.java b/staticlibs/device/com/android/net/module/util/FdEventsReader.java
index 71ae13d..4825992 100644
--- a/staticlibs/device/com/android/net/module/util/FdEventsReader.java
+++ b/staticlibs/device/com/android/net/module/util/FdEventsReader.java
@@ -69,6 +69,7 @@
* @param <BufferType> the type of the buffer used to read data.
*/
public abstract class FdEventsReader<BufferType> {
+ private static final String TAG = FdEventsReader.class.getSimpleName();
private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
private static final int UNREGISTER_THIS_FD = 0;
@@ -167,6 +168,18 @@
protected void handlePacket(@NonNull BufferType recvbuf, int length) {}
/**
+ * Called by the subclasses of FdEventsReader, decide whether it should stop reading packet or
+ * just ignore the specific error other than EAGAIN or EINTR.
+ *
+ * @return {@code true} if this FdEventsReader should stop reading from the socket.
+ * {@code false} if it should continue.
+ */
+ protected boolean handleReadError(@NonNull ErrnoException e) {
+ logError("readPacket error: ", e);
+ return true; // by default, stop reading on any error.
+ }
+
+ /**
* Called by the main loop to log errors. In some cases |e| may be null.
*/
protected void logError(@NonNull String msg, @Nullable Exception e) {}
@@ -211,7 +224,7 @@
return true;
}
- private boolean isRunning() {
+ protected boolean isRunning() {
return (mFd != null) && mFd.valid();
}
@@ -234,8 +247,10 @@
} else if (e.errno == OsConstants.EINTR) {
continue;
} else {
- if (isRunning()) logError("readPacket error: ", e);
- break;
+ if (!isRunning()) break;
+ final boolean shouldStop = handleReadError(e);
+ if (shouldStop) break;
+ continue;
}
} catch (Exception e) {
if (isRunning()) logError("readPacket error: ", e);
@@ -246,7 +261,7 @@
handlePacket(mBuffer, bytesRead);
} catch (Exception e) {
logError("handlePacket error: ", e);
- Log.wtf(FdEventsReader.class.getSimpleName(), "Error handling packet", e);
+ Log.wtf(TAG, "Error handling packet", e);
}
}
diff --git a/staticlibs/device/com/android/net/module/util/IBpfMap.java b/staticlibs/device/com/android/net/module/util/IBpfMap.java
index d43b22c..dce369a 100644
--- a/staticlibs/device/com/android/net/module/util/IBpfMap.java
+++ b/staticlibs/device/com/android/net/module/util/IBpfMap.java
@@ -20,7 +20,6 @@
import androidx.annotation.NonNull;
import java.util.NoSuchElementException;
-import java.util.function.BiConsumer;
/**
* The interface of BpfMap. This could be used to inject for testing.
@@ -29,7 +28,7 @@
* @param <K> the key of the map.
* @param <V> the value of the map.
*/
-public interface IBpfMap<K extends Struct, V extends Struct> {
+public interface IBpfMap<K extends Struct, V extends Struct> extends AutoCloseable {
/** Update an existing or create a new key -> value entry in an eBbpf map. */
void updateEntry(K key, V value) throws ErrnoException;
diff --git a/staticlibs/device/com/android/net/module/util/Ipv6Utils.java b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
index fe7c89b..d538221 100644
--- a/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
+++ b/staticlibs/device/com/android/net/module/util/Ipv6Utils.java
@@ -20,6 +20,7 @@
import static com.android.net.module.util.IpUtils.icmpv6Checksum;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
@@ -45,29 +46,23 @@
*/
public class Ipv6Utils {
/**
- * Build a generic ICMPv6 packet(e.g., packet used in the neighbor discovery protocol).
+ * Build a generic ICMPv6 packet without Ethernet header.
*/
- public static ByteBuffer buildIcmpv6Packet(final MacAddress srcMac, final MacAddress dstMac,
- final Inet6Address srcIp, final Inet6Address dstIp, short type, short code,
- final ByteBuffer... options) {
- final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+ public static ByteBuffer buildIcmpv6Packet(final Inet6Address srcIp, final Inet6Address dstIp,
+ short type, short code, final ByteBuffer... options) {
final int ipv6HeaderLen = Struct.getSize(Ipv6Header.class);
final int icmpv6HeaderLen = Struct.getSize(Icmpv6Header.class);
int payloadLen = 0;
for (ByteBuffer option: options) {
payloadLen += option.limit();
}
- final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv6HeaderLen
- + icmpv6HeaderLen + payloadLen);
- final EthernetHeader ethHeader =
- new EthernetHeader(dstMac, srcMac, (short) ETHER_TYPE_IPV6);
+ final ByteBuffer packet = ByteBuffer.allocate(ipv6HeaderLen + icmpv6HeaderLen + payloadLen);
final Ipv6Header ipv6Header =
new Ipv6Header((int) 0x60000000 /* version, traffic class, flowlabel */,
icmpv6HeaderLen + payloadLen /* payload length */,
(byte) IPPROTO_ICMPV6 /* next header */, (byte) 0xff /* hop limit */, srcIp, dstIp);
final Icmpv6Header icmpv6Header = new Icmpv6Header(type, code, (short) 0 /* checksum */);
- ethHeader.writeToByteBuffer(packet);
ipv6Header.writeToByteBuffer(packet);
icmpv6Header.writeToByteBuffer(packet);
for (ByteBuffer option : options) {
@@ -79,11 +74,31 @@
packet.flip();
// Populate the ICMPv6 checksum field.
- packet.putShort(etherHeaderLen + ipv6HeaderLen + 2, icmpv6Checksum(packet,
- etherHeaderLen /* ipOffset */,
- (int) (etherHeaderLen + ipv6HeaderLen) /* transportOffset */,
+ packet.putShort(ipv6HeaderLen + 2, icmpv6Checksum(packet, 0 /* ipOffset */,
+ ipv6HeaderLen /* transportOffset */,
(short) (icmpv6HeaderLen + payloadLen) /* transportLen */));
return packet;
+
+ }
+
+ /**
+ * Build a generic ICMPv6 packet(e.g., packet used in the neighbor discovery protocol).
+ */
+ public static ByteBuffer buildIcmpv6Packet(final MacAddress srcMac, final MacAddress dstMac,
+ final Inet6Address srcIp, final Inet6Address dstIp, short type, short code,
+ final ByteBuffer... options) {
+ final ByteBuffer payload = buildIcmpv6Packet(srcIp, dstIp, type, code, options);
+
+ final int etherHeaderLen = Struct.getSize(EthernetHeader.class);
+ final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + payload.limit());
+ final EthernetHeader ethHeader =
+ new EthernetHeader(dstMac, srcMac, (short) ETHER_TYPE_IPV6);
+
+ ethHeader.writeToByteBuffer(packet);
+ packet.put(payload);
+ packet.flip();
+
+ return packet;
}
/**
@@ -159,4 +174,14 @@
return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
(byte) ICMPV6_ECHO_REQUEST_TYPE /* type */, (byte) 0 /* code */, payload);
}
+
+ /**
+ * Build an ICMPv6 Echo Reply packet without ethernet header.
+ */
+ public static ByteBuffer buildEchoReplyPacket(final Inet6Address srcIp,
+ final Inet6Address dstIp) {
+ final ByteBuffer payload = ByteBuffer.allocate(4); // ID and Sequence number may be zero.
+ return buildIcmpv6Packet(srcIp, dstIp, (byte) ICMPV6_ECHO_REPLY_TYPE /* type */,
+ (byte) 0 /* code */, payload);
+ }
}
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
index c908528..33e5bfa 100644
--- a/staticlibs/device/com/android/net/module/util/PacketBuilder.java
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
@@ -25,6 +26,8 @@
import static com.android.net.module.util.IpUtils.udpChecksum;
import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_HEADER_LEN;
+import static com.android.net.module.util.NetworkStackConstants.IPV6_LEN_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
@@ -35,11 +38,13 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
import java.io.IOException;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
@@ -49,7 +54,7 @@
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 2 header (EthernetHeader) | (optional)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Layer 3 header (Ipv4Header) |
+ * | Layer 3 header (Ipv4Header, Ipv6Header) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 4 header (TcpHeader, UdpHeader) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -74,11 +79,14 @@
* sendPacket(buf);
*/
public class PacketBuilder {
+ private static final int INVALID_OFFSET = -1;
+
private final ByteBuffer mBuffer;
- private int mIpv4HeaderOffset = -1;
- private int mTcpHeaderOffset = -1;
- private int mUdpHeaderOffset = -1;
+ private int mIpv4HeaderOffset = INVALID_OFFSET;
+ private int mIpv6HeaderOffset = INVALID_OFFSET;
+ private int mTcpHeaderOffset = INVALID_OFFSET;
+ private int mUdpHeaderOffset = INVALID_OFFSET;
public PacketBuilder(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
@@ -93,9 +101,9 @@
*/
public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
IOException {
- final EthernetHeader ethv4Header = new EthernetHeader(dstMac, srcMac, etherType);
+ final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, etherType);
try {
- ethv4Header.writeToByteBuffer(mBuffer);
+ ethHeader.writeToByteBuffer(mBuffer);
} catch (IllegalArgumentException | BufferOverflowException e) {
throw new IOException("Error writing to buffer: ", e);
}
@@ -130,6 +138,31 @@
}
/**
+ * Write an IPv6 header.
+ * The IP header length is calculated and written back in #finalizePacket.
+ *
+ * @param vtf version, traffic class and flow label
+ * @param nextHeader the transport layer protocol
+ * @param hopLimit hop limit
+ * @param srcIp source IP address
+ * @param dstIp destination IP address
+ */
+ public void writeIpv6Header(int vtf, byte nextHeader, short hopLimit,
+ @NonNull final Inet6Address srcIp, @NonNull final Inet6Address dstIp)
+ throws IOException {
+ mIpv6HeaderOffset = mBuffer.position();
+ final Ipv6Header ipv6Header = new Ipv6Header(vtf,
+ (short) 0 /* payloadLength, calculate in #finalizePacket */, nextHeader,
+ hopLimit, srcIp, dstIp);
+
+ try {
+ ipv6Header.writeToByteBuffer(mBuffer);
+ } catch (IllegalArgumentException | BufferOverflowException e) {
+ throw new IOException("Error writing to buffer: ", e);
+ }
+ }
+
+ /**
* Write a TCP header.
* The TCP header checksum is calculated and written back in #finalizePacket.
*
@@ -186,32 +219,43 @@
*/
@NonNull
public ByteBuffer finalizePacket() throws IOException {
- if (mIpv4HeaderOffset < 0) {
- // TODO: add support for IPv6
- throw new IOException("Packet is missing IPv4 header");
+ // [1] Finalize IPv4 or IPv6 header.
+ int ipHeaderOffset = INVALID_OFFSET;
+ if (mIpv4HeaderOffset != INVALID_OFFSET) {
+ ipHeaderOffset = mIpv4HeaderOffset;
+
+ // Populate the IPv4 totalLength field.
+ mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
+ (short) (mBuffer.position() - mIpv4HeaderOffset));
+
+ // Populate the IPv4 header checksum field.
+ mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+ ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
+ } else if (mIpv6HeaderOffset != INVALID_OFFSET) {
+ ipHeaderOffset = mIpv6HeaderOffset;
+
+ // Populate the IPv6 payloadLength field.
+ // The payload length doesn't include IPv6 header length. See rfc8200 section 3.
+ mBuffer.putShort(mIpv6HeaderOffset + IPV6_LEN_OFFSET,
+ (short) (mBuffer.position() - mIpv6HeaderOffset - IPV6_HEADER_LEN));
+ } else {
+ throw new IOException("Packet is missing neither IPv4 nor IPv6 header");
}
- // Populate the IPv4 totalLength field.
- mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
- (short) (mBuffer.position() - mIpv4HeaderOffset));
-
- // Populate the IPv4 header checksum field.
- mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
- ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
-
- if (mTcpHeaderOffset > 0) {
+ // [2] Finalize TCP or UDP header.
+ if (mTcpHeaderOffset != INVALID_OFFSET) {
// Populate the TCP header checksum field.
mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
- mIpv4HeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
+ ipHeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
mBuffer.position() - mTcpHeaderOffset /* transportLen */));
- } else if (mUdpHeaderOffset > 0) {
+ } else if (mUdpHeaderOffset != INVALID_OFFSET) {
// Populate the UDP header length field.
mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
(short) (mBuffer.position() - mUdpHeaderOffset));
// Populate the UDP header checksum field.
mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
- mIpv4HeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
+ ipHeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
} else {
throw new IOException("Packet is missing neither TCP nor UDP header");
}
@@ -225,15 +269,15 @@
*
* @param hasEther has ethernet header. Set this flag to indicate that the packet has an
* ethernet header.
- * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} currently supported.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
* @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
* currently supported.
* @param payloadLen length of the payload.
*/
@NonNull
public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) {
- if (l3proto != IPPROTO_IP) {
- // TODO: add support for IPv6
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
}
@@ -247,7 +291,8 @@
int packetLen = 0;
if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
- packetLen += Struct.getSize(Ipv4Header.class);
+ packetLen += (l3proto == IPPROTO_IP) ? Struct.getSize(Ipv4Header.class)
+ : Struct.getSize(Ipv6Header.class);
packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
: Struct.getSize(UdpHeader.class);
packetLen += payloadLen;
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
index a216752..9e1e26e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java
@@ -51,8 +51,8 @@
return null;
}
- int payloadLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
- payloadLength -= StructNlMsgHdr.STRUCT_SIZE;
+ final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
+ final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE;
if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) {
// Malformed message or runt buffer. Pretend the buffer was consumed.
byteBuffer.position(byteBuffer.limit());
@@ -68,15 +68,22 @@
// Netlink family messages. The netlink family is required. Note that the reason for using
// if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are
// not constant.
+ final NetlinkMessage parsed;
if (nlFamily == OsConstants.NETLINK_ROUTE) {
- return parseRtMessage(nlmsghdr, byteBuffer);
+ parsed = parseRtMessage(nlmsghdr, byteBuffer);
} else if (nlFamily == OsConstants.NETLINK_INET_DIAG) {
- return parseInetDiagMessage(nlmsghdr, byteBuffer);
+ parsed = parseInetDiagMessage(nlmsghdr, byteBuffer);
} else if (nlFamily == OsConstants.NETLINK_NETFILTER) {
- return parseNfMessage(nlmsghdr, byteBuffer);
+ parsed = parseNfMessage(nlmsghdr, byteBuffer);
+ } else {
+ parsed = null;
}
- return null;
+ // Advance to the end of the message, regardless of whether the parsing code consumed
+ // all of it or not.
+ byteBuffer.position(startPosition + messageLength);
+
+ return parsed;
}
@NonNull
diff --git a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
new file mode 100644
index 0000000..0b2de7a
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 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 com.android.net.module.util;
+
+import android.util.SparseIntArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Keeps track of the counters under different uid, fire exception if the counter
+ * exceeded the specified maximum value.
+ *
+ * @hide
+ */
+public class PerUidCounter {
+ private final int mMaxCountPerUid;
+
+ // Map from UID to count that UID has filed.
+ @VisibleForTesting
+ @GuardedBy("mUidToCount")
+ final SparseIntArray mUidToCount = new SparseIntArray();
+
+ /**
+ * Constructor
+ *
+ * @param maxCountPerUid the maximum count per uid allowed
+ */
+ public PerUidCounter(final int maxCountPerUid) {
+ if (maxCountPerUid <= 0) {
+ throw new IllegalArgumentException("Maximum counter value must be positive");
+ }
+ mMaxCountPerUid = maxCountPerUid;
+ }
+
+ /**
+ * Increments the count of the given uid. Throws an exception if the number
+ * of the counter for the uid exceeds the value of maxCounterPerUid which is the value
+ * passed into the constructor. see: {@link #PerUidCounter(int)}.
+ *
+ * @throws IllegalStateException if the number of counter for the uid exceed
+ * the allowed number.
+ *
+ * @param uid the uid that the counter was made under
+ */
+ public void incrementCountOrThrow(final int uid) {
+ incrementCountOrThrow(uid, 1 /* numToIncrement */);
+ }
+
+ public synchronized void incrementCountOrThrow(final int uid, final int numToIncrement) {
+ if (numToIncrement <= 0) {
+ throw new IllegalArgumentException("Increment count must be positive");
+ }
+ final long newCount = ((long) mUidToCount.get(uid, 0)) + numToIncrement;
+ if (newCount > mMaxCountPerUid) {
+ throw new IllegalStateException("Uid " + uid + " exceeded its allowed limit");
+ }
+ // Since the count cannot be greater than Integer.MAX_VALUE here since mMaxCountPerUid
+ // is an integer, it is safe to cast to int.
+ mUidToCount.put(uid, (int) newCount);
+ }
+
+ /**
+ * Decrements the count of the given uid. Throws an exception if the number
+ * of the counter goes below zero.
+ *
+ * @throws IllegalStateException if the number of counter for the uid goes below
+ * zero.
+ *
+ * @param uid the uid that the count was made under
+ */
+ public void decrementCountOrThrow(final int uid) {
+ decrementCountOrThrow(uid, 1 /* numToDecrement */);
+ }
+
+ public synchronized void decrementCountOrThrow(final int uid, final int numToDecrement) {
+ if (numToDecrement <= 0) {
+ throw new IllegalArgumentException("Decrement count must be positive");
+ }
+ final int newCount = mUidToCount.get(uid, 0) - numToDecrement;
+ if (newCount < 0) {
+ throw new IllegalStateException("BUG: too small count " + newCount + " for UID " + uid);
+ } else if (newCount == 0) {
+ mUidToCount.delete(uid);
+ } else {
+ mUidToCount.put(uid, newCount);
+ }
+ }
+}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
index bdffc0f..a7720ea 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
@@ -49,16 +49,20 @@
protected:
// flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
- int map_fd = mapRetrieve(pathname, flags);
- if (map_fd >= 0) mMapFd.reset(map_fd);
+ mMapFd.reset(mapRetrieve(pathname, flags));
+ if (mMapFd < 0) abort();
+ if (isAtLeastKernelVersion(4, 14, 0)) {
+ if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
+ if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort();
+ }
}
public:
explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {}
BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) {
- int map_fd = createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags);
- if (map_fd >= 0) mMapFd.reset(map_fd);
+ mMapFd.reset(createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags));
+ if (mMapFd < 0) abort();
}
base::Result<Key> getFirstKey() const {
@@ -100,7 +104,26 @@
}
// Function that tries to get map from a pinned path.
- base::Result<void> init(const char* path);
+ [[clang::reinitializes]] base::Result<void> init(const char* path);
+
+#ifdef TEST_BPF_MAP
+ // due to Android SELinux limitations which prevent map creation by anyone besides the bpfloader
+ // this should only ever be used by test code, it is equivalent to:
+ // .reset(createMap(type, keysize, valuesize, max_entries, map_flags)
+ // TODO: derive map_flags from BpfMap vs BpfMapRO
+ [[clang::reinitializes]] base::Result<void> resetMap(bpf_map_type map_type,
+ uint32_t max_entries,
+ uint32_t map_flags = 0) {
+ int map_fd = createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags);
+ if (map_fd < 0) {
+ auto err = ErrnoErrorf("Unable to create map.");
+ mMapFd.reset();
+ return err;
+ };
+ mMapFd.reset(map_fd);
+ return {};
+ }
+#endif
// Iterate through the map and handle each key retrieved based on the filter
// without modification of map content.
@@ -135,14 +158,23 @@
// Move assignment operator
BpfMap<Key, Value>& operator=(BpfMap<Key, Value>&& other) noexcept {
- mMapFd = std::move(other.mMapFd);
- other.reset(-1);
+ if (this != &other) {
+ mMapFd = std::move(other.mMapFd);
+ other.reset();
+ }
return *this;
}
void reset(base::unique_fd fd) = delete;
- void reset(int fd) { mMapFd.reset(fd); }
+ [[clang::reinitializes]] void reset(int fd = -1) {
+ mMapFd.reset(fd);
+ if ((fd >= 0) && isAtLeastKernelVersion(4, 14, 0)) {
+ if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
+ if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort();
+ if (bpfGetFdMapFlags(mMapFd) != 0) abort(); // TODO: fix for BpfMapRO
+ }
+ }
bool isValid() const { return mMapFd != -1; }
@@ -179,10 +211,18 @@
template <class Key, class Value>
base::Result<void> BpfMap<Key, Value>::init(const char* path) {
- mMapFd = base::unique_fd(mapRetrieveRW(path));
+ mMapFd.reset(mapRetrieveRW(path));
if (mMapFd == -1) {
return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
}
+ if (isAtLeastKernelVersion(4, 14, 0)) {
+ // Normally we should return an error here instead of calling abort,
+ // but this cannot happen at runtime without a massive code bug (K/V type mismatch)
+ // and as such it's better to just blow the system up and let the developer fix it.
+ // Crashes are much more likely to be noticed than logs and missing functionality.
+ if (bpfGetFdKeySize(mMapFd) != sizeof(Key)) abort();
+ if (bpfGetFdValueSize(mMapFd) != sizeof(Value)) abort();
+ }
return {};
}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
index 8f1b9a2..4429164 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
@@ -60,6 +60,9 @@
// This is a temporary hack for network stats map swap on devices running
// 4.9 kernels. The kernel code of socket release on pf_key socket will
// explicitly call synchronize_rcu() which is exactly what we need.
+ //
+ // Linux 4.14/4.19/5.4/5.10/5.15 (and 5.18) still have this same behaviour.
+ // see net/key/af_key.c: pfkey_release() -> synchronize_rcu()
int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
if (pfSocket < 0) {
@@ -92,22 +95,22 @@
#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
-static inline unsigned kernelVersion() {
+static inline unsigned uncachedKernelVersion() {
struct utsname buf;
- int ret = uname(&buf);
- if (ret) return 0;
+ if (uname(&buf)) return 0;
- unsigned kver_major;
- unsigned kver_minor;
- unsigned kver_sub;
- char unused;
- ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &unused);
- // Check the device kernel version
- if (ret < 3) return 0;
-
+ unsigned kver_major = 0;
+ unsigned kver_minor = 0;
+ unsigned kver_sub = 0;
+ (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub);
return KVER(kver_major, kver_minor, kver_sub);
}
+static inline unsigned kernelVersion() {
+ static unsigned kver = uncachedKernelVersion();
+ return kver;
+}
+
static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
return kernelVersion() >= KVER(major, minor, sub);
}
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index ac9f9bc..4b035b9 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -19,6 +19,17 @@
* *
******************************************************************************/
+// The actual versions of the bpfloader that shipped in various Android releases
+
+// Android P/Q/R: BpfLoader was initially part of netd,
+// this was later split out into a standalone binary, but was unversioned.
+
+// Android S / 12 (api level 31) - added 'tethering' mainline eBPF support
+#define BPFLOADER_S_VERSION 2u
+
+// Android T / 13 Beta 3 (api level 33) - added support for 'netd_shared'
+#define BPFLOADER_T_BETA3_VERSION 13u
+
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
* process the resulting .o file.
@@ -177,6 +188,8 @@
static unsigned long long (*bpf_get_current_pid_tgid)(void) = (void*) BPF_FUNC_get_current_pid_tgid;
static unsigned long long (*bpf_get_current_uid_gid)(void) = (void*) BPF_FUNC_get_current_uid_gid;
static unsigned long long (*bpf_get_smp_processor_id)(void) = (void*) BPF_FUNC_get_smp_processor_id;
+static long (*bpf_get_stackid)(void* ctx, void* map, uint64_t flags) = (void*) BPF_FUNC_get_stackid;
+static long (*bpf_get_current_comm)(void* buf, uint32_t buf_size) = (void*) BPF_FUNC_get_current_comm;
#define DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
opt) \
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index abf83da..4b29c44 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -150,6 +150,36 @@
});
}
+// requires 4.14+ kernel
+
+#define DEFINE_BPF_GET_FD_INFO(NAME, FIELD) \
+inline int bpfGetFd ## NAME(const BPF_FD_TYPE map_fd) { \
+ struct bpf_map_info map_info = {}; \
+ union bpf_attr attr = { .info = { \
+ .bpf_fd = BPF_FD_TO_U32(map_fd), \
+ .info_len = sizeof(map_info), \
+ .info = ptr_to_u64(&map_info), \
+ }}; \
+ int rv = bpf(BPF_OBJ_GET_INFO_BY_FD, attr); \
+ if (rv) return rv; \
+ if (attr.info.info_len < offsetof(bpf_map_info, FIELD) + sizeof(map_info.FIELD)) { \
+ errno = EOPNOTSUPP; \
+ return -1; \
+ }; \
+ return map_info.FIELD; \
+}
+
+// All 6 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
+// while BPF_OBJ_GET_INFO_BY_FD is not implemented at all in v4.9 (even ACK 4.9-Q)
+DEFINE_BPF_GET_FD_INFO(MapType, type) // int bpfGetFdMapType(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD_INFO(MapId, id) // int bpfGetFdMapId(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD_INFO(KeySize, key_size) // int bpfGetFdKeySize(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD_INFO(ValueSize, value_size) // int bpfGetFdValueSize(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD_INFO(MaxEntries, max_entries) // int bpfGetFdMaxEntries(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD_INFO(MapFlags, map_flags) // int bpfGetFdMapFlags(const BPF_FD_TYPE map_fd)
+
+#undef DEFINE_BPF_GET_FD_INFO
+
} // namespace bpf
} // namespace android
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
index e25e17d..2e88fc8 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -27,27 +27,18 @@
namespace android {
-static jint com_android_net_module_util_BpfMap_closeMap(JNIEnv *env, jobject clazz,
- jint fd) {
- int ret = close(fd);
-
- if (ret) jniThrowErrnoException(env, "closeMap", errno);
-
- return ret;
-}
-
-static jint com_android_net_module_util_BpfMap_bpfFdGet(JNIEnv *env, jobject clazz,
+static jint com_android_net_module_util_BpfMap_nativeBpfFdGet(JNIEnv *env, jclass clazz,
jstring path, jint mode) {
ScopedUtfChars pathname(env, path);
jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
- if (fd < 0) jniThrowErrnoException(env, "bpfFdGet", errno);
+ if (fd < 0) jniThrowErrnoException(env, "nativeBpfFdGet", errno);
return fd;
}
-static void com_android_net_module_util_BpfMap_writeToMapEntry(JNIEnv *env, jobject clazz,
+static void com_android_net_module_util_BpfMap_nativeWriteToMapEntry(JNIEnv *env, jobject self,
jint fd, jbyteArray key, jbyteArray value, jint flags) {
ScopedByteArrayRO keyRO(env, key);
ScopedByteArrayRO valueRO(env, value);
@@ -55,7 +46,7 @@
int ret = bpf::writeToMapEntry(static_cast<int>(fd), keyRO.get(), valueRO.get(),
static_cast<int>(flags));
- if (ret) jniThrowErrnoException(env, "writeToMapEntry", errno);
+ if (ret) jniThrowErrnoException(env, "nativeWriteToMapEntry", errno);
}
static jboolean throwIfNotEnoent(JNIEnv *env, const char* functionName, int ret, int err) {
@@ -65,7 +56,7 @@
return false;
}
-static jboolean com_android_net_module_util_BpfMap_deleteMapEntry(JNIEnv *env, jobject clazz,
+static jboolean com_android_net_module_util_BpfMap_nativeDeleteMapEntry(JNIEnv *env, jobject self,
jint fd, jbyteArray key) {
ScopedByteArrayRO keyRO(env, key);
@@ -73,10 +64,10 @@
// to ENOENT.
int ret = bpf::deleteMapEntry(static_cast<int>(fd), keyRO.get());
- return throwIfNotEnoent(env, "deleteMapEntry", ret, errno);
+ return throwIfNotEnoent(env, "nativeDeleteMapEntry", ret, errno);
}
-static jboolean com_android_net_module_util_BpfMap_getNextMapKey(JNIEnv *env, jobject clazz,
+static jboolean com_android_net_module_util_BpfMap_nativeGetNextMapKey(JNIEnv *env, jobject self,
jint fd, jbyteArray key, jbyteArray nextKey) {
// If key is found, the operation returns zero and sets the next key pointer to the key of the
// next element. If key is not found, the operation returns zero and sets the next key pointer
@@ -92,10 +83,10 @@
ret = bpf::getNextMapKey(static_cast<int>(fd), keyRO.get(), nextKeyRW.get());
}
- return throwIfNotEnoent(env, "getNextMapKey", ret, errno);
+ return throwIfNotEnoent(env, "nativeGetNextMapKey", ret, errno);
}
-static jboolean com_android_net_module_util_BpfMap_findMapEntry(JNIEnv *env, jobject clazz,
+static jboolean com_android_net_module_util_BpfMap_nativeFindMapEntry(JNIEnv *env, jobject self,
jint fd, jbyteArray key, jbyteArray value) {
ScopedByteArrayRO keyRO(env, key);
ScopedByteArrayRW valueRW(env, value);
@@ -104,7 +95,7 @@
// "value". If no element is found, the operation returns -1 and sets errno to ENOENT.
int ret = bpf::findMapEntry(static_cast<int>(fd), keyRO.get(), valueRW.get());
- return throwIfNotEnoent(env, "findMapEntry", ret, errno);
+ return throwIfNotEnoent(env, "nativeFindMapEntry", ret, errno);
}
/*
@@ -112,18 +103,16 @@
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- { "closeMap", "(I)I",
- (void*) com_android_net_module_util_BpfMap_closeMap },
- { "bpfFdGet", "(Ljava/lang/String;I)I",
- (void*) com_android_net_module_util_BpfMap_bpfFdGet },
- { "writeToMapEntry", "(I[B[BI)V",
- (void*) com_android_net_module_util_BpfMap_writeToMapEntry },
- { "deleteMapEntry", "(I[B)Z",
- (void*) com_android_net_module_util_BpfMap_deleteMapEntry },
- { "getNextMapKey", "(I[B[B)Z",
- (void*) com_android_net_module_util_BpfMap_getNextMapKey },
- { "findMapEntry", "(I[B[B)Z",
- (void*) com_android_net_module_util_BpfMap_findMapEntry },
+ { "nativeBpfFdGet", "(Ljava/lang/String;I)I",
+ (void*) com_android_net_module_util_BpfMap_nativeBpfFdGet },
+ { "nativeWriteToMapEntry", "(I[B[BI)V",
+ (void*) com_android_net_module_util_BpfMap_nativeWriteToMapEntry },
+ { "nativeDeleteMapEntry", "(I[B)Z",
+ (void*) com_android_net_module_util_BpfMap_nativeDeleteMapEntry },
+ { "nativeGetNextMapKey", "(I[B[B)Z",
+ (void*) com_android_net_module_util_BpfMap_nativeGetNextMapKey },
+ { "nativeFindMapEntry", "(I[B[B)Z",
+ (void*) com_android_net_module_util_BpfMap_nativeFindMapEntry },
};
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
index 073a46f..cb06afb 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -34,7 +34,7 @@
int error = isEthernet(interface.c_str(), result);
if (error) {
throwIOException(
- env, "com_android_net_module_util_TcUtils_isEthernet error: ", error);
+ env, "com_android_net_module_util_TcUtils_isEthernet error: ", -error);
}
// result is not touched when error is returned; leave false.
return result;
@@ -50,7 +50,7 @@
if (error) {
throwIOException(
env,
- "com_android_net_module_util_TcUtils_tcFilterAddDevBpf error: ", error);
+ "com_android_net_module_util_TcUtils_tcFilterAddDevBpf error: ", -error);
}
}
@@ -68,7 +68,7 @@
throwIOException(env,
"com_android_net_module_util_TcUtils_"
"tcFilterAddDevIngressPolice error: ",
- error);
+ -error);
}
}
@@ -80,7 +80,7 @@
if (error) {
throwIOException(
env,
- "com_android_net_module_util_TcUtils_tcFilterDelDev error: ", error);
+ "com_android_net_module_util_TcUtils_tcFilterDelDev error: ", -error);
}
}
@@ -92,7 +92,7 @@
if (error) {
throwIOException(
env,
- "com_android_net_module_util_TcUtils_tcQdiscAddDevClsact error: ", error);
+ "com_android_net_module_util_TcUtils_tcQdiscAddDevClsact error: ", -error);
}
}
diff --git a/staticlibs/native/tcutils/kernelversion.h b/staticlibs/native/tcutils/kernelversion.h
index 3be1ad2..9aab31d 100644
--- a/staticlibs/native/tcutils/kernelversion.h
+++ b/staticlibs/native/tcutils/kernelversion.h
@@ -32,25 +32,22 @@
namespace android {
-static inline unsigned kernelVersion() {
+static inline unsigned uncachedKernelVersion() {
struct utsname buf;
- int ret = uname(&buf);
- if (ret)
- return 0;
+ if (uname(&buf)) return 0;
- unsigned kver_major;
- unsigned kver_minor;
- unsigned kver_sub;
- char discard;
- ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub,
- &discard);
- // Check the device kernel version
- if (ret < 3)
- return 0;
-
+ unsigned kver_major = 0;
+ unsigned kver_minor = 0;
+ unsigned kver_sub = 0;
+ (void)sscanf(buf.release, "%u.%u.%u", &kver_major, &kver_minor, &kver_sub);
return KVER(kver_major, kver_minor, kver_sub);
}
+static unsigned kernelVersion() {
+ static unsigned kver = uncachedKernelVersion();
+ return kver;
+}
+
static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor,
unsigned sub) {
return kernelVersion() >= KVER(major, minor, sub);
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 798fa3a..cfa9bbb 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -53,6 +53,14 @@
min_sdk_version: "29",
}
+// TODO: delete this one when AOSP is no longer auto-merge to git_sc-mainline-prod.
+cc_library_static {
+ name: "netd_aidl_interface-lateststable-cpp",
+ whole_static_libs: [
+ "netd_aidl_interface-V9-cpp",
+ ],
+}
+
cc_defaults {
name: "netd_aidl_interface_lateststable_cpp_static",
static_libs: ["netd_aidl_interface-V9-cpp"],
diff --git a/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
index f75fa76..9e37d11 100644
--- a/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
+++ b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
@@ -21,6 +21,7 @@
#include <vector>
#include <android-base/macros.h>
+#include <fmt/format.h>
#include <gtest/gtest.h>
#include "netdutils/InternetAddresses.h"
@@ -438,6 +439,192 @@
}
}
+TEST(IPPrefixTest, containsPrefix) {
+ const struct {
+ const char* prefix;
+ const char* otherPrefix;
+ const bool expected;
+ std::string asParameters() const {
+ return fmt::format("prefix={}, other={}, expect={}", prefix, otherPrefix, expected);
+ }
+ } testExpectations[] = {
+ {"192.0.0.0/8", "192.0.0.0/8", true},
+ {"192.1.0.0/16", "192.1.0.0/16", true},
+ {"192.1.2.0/24", "192.1.2.0/24", true},
+ {"192.1.2.3/32", "192.1.2.3/32", true},
+ {"0.0.0.0/0", "192.0.0.0/8", true},
+ {"0.0.0.0/0", "192.1.0.0/16", true},
+ {"0.0.0.0/0", "192.1.2.0/24", true},
+ {"0.0.0.0/0", "192.1.2.3/32", true},
+ {"192.0.0.0/8", "192.1.0.0/16", true},
+ {"192.0.0.0/8", "192.1.2.0/24", true},
+ {"192.0.0.0/8", "192.1.2.5/32", true},
+ {"192.1.0.0/16", "192.1.2.0/24", true},
+ {"192.1.0.0/16", "192.1.3.6/32", true},
+ {"192.5.6.0/24", "192.5.6.7/32", true},
+ {"192.1.2.3/32", "192.1.2.0/24", false},
+ {"192.1.2.3/32", "192.1.0.0/16", false},
+ {"192.1.2.3/32", "192.0.0.0/8", false},
+ {"192.1.2.3/32", "0.0.0.0/0", false},
+ {"192.1.2.0/24", "192.1.0.0/16", false},
+ {"192.1.2.0/24", "192.0.0.0/8", false},
+ {"192.1.2.0/24", "0.0.0.0/0", false},
+ {"192.9.0.0/16", "192.0.0.0/8", false},
+ {"192.9.0.0/16", "0.0.0.0/0", false},
+ {"192.0.0.0/8", "0.0.0.0/0", false},
+ {"192.0.0.0/8", "191.0.0.0/8", false},
+ {"191.0.0.0/8", "192.0.0.0/8", false},
+ {"192.8.0.0/16", "192.7.0.0/16", false},
+ {"192.7.0.0/16", "192.8.0.0/16", false},
+ {"192.8.6.0/24", "192.7.5.0/24", false},
+ {"192.7.5.0/24", "192.8.6.0/24", false},
+ {"192.8.6.100/32", "192.8.6.200/32", false},
+ {"192.8.6.200/32", "192.8.6.100/32", false},
+ {"192.0.0.0/8", "192.0.0.0/12", true},
+ {"192.0.0.0/12", "192.0.0.0/8", false},
+ {"2001::/16", "2001::/16", true},
+ {"2001:db8::/32", "2001:db8::/32", true},
+ {"2001:db8:cafe::/48", "2001:db8:cafe::/48", true},
+ {"2001:db8:cafe:d00d::/64", "2001:db8:cafe:d00d::/64", true},
+ {"2001:db8:cafe:d00d:fec0::/80", "2001:db8:cafe:d00d:fec0::/80", true},
+ {"2001:db8:cafe:d00d:fec0:de::/96", "2001:db8:cafe:d00d:fec0:de::/96", true},
+ {"2001:db8:cafe:d00d:fec0:de:ac::/112", "2001:db8:cafe:d00d:fec0:de:ac::/112", true},
+ {"2001:db8::cafe:0:1/128", "2001:db8::cafe:0:1/128", true},
+ {"2001::/16", "2001:db8::/32", true},
+ {"2001::/16", "2001:db8:cafe::/48", true},
+ {"2001::/16", "2001:db8:cafe:d00d::/64", true},
+ {"2001::/16", "2001:db8:cafe:d00d:fec0::/80", true},
+ {"2001::/16", "2001:db8:cafe:d00d:fec0:de::/96", true},
+ {"2001::/16", "2001:db8:cafe:d00d:fec0:de:ac::/112", true},
+ {"2001::/16", "2001:db8:cafe:d00d:fec0:de:ac:dd/128", true},
+ {"::/0", "2001::/16", true},
+ {"::/0", "2001:db8::/32", true},
+ {"::/0", "2001:db8:cafe::/48", true},
+ {"::/0", "2001:db8:cafe:d00d::/64", true},
+ {"::/0", "2001:db8:cafe:d00d:fec0::/80", true},
+ {"::/0", "2001:db8:cafe:d00d:fec0:de::/96", true},
+ {"::/0", "2001:db8:cafe:d00d:fec0:de:ac::/112", true},
+ {"::/0", "2001:db8:cafe:d00d:fec0:de:ac:dd/128", true},
+ {"2001:db8::dd/128", "2001::/16", false},
+ {"2001:db8::dd/128", "2001:db8::/32", false},
+ {"2001:db8::dd/128", "2001:db8:cafe::/48", false},
+ {"2001:db8::dd/128", "2001:db8:cafe:d00d::/64", false},
+ {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0::/80", false},
+ {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0:de::/96", false},
+ {"2001:db8::dd/128", "2001:db8:cafe:d00d:fec0:de:ac::/112", false},
+ {"2001:db7::/32", "2001:db8::/32", false},
+ {"2001:db8::/32", "2001:db7::/32", false},
+ {"2001:db8:caff::/48", "2001:db8:cafe::/48", false},
+ {"2001:db8:cafe::/48", "2001:db8:caff::/48", false},
+ {"2001:db8:cafe:a00d::/64", "2001:db8:cafe:d00d::/64", false},
+ {"2001:db8:cafe:d00d::/64", "2001:db8:cafe:a00d::/64", false},
+ {"2001:db8:cafe:d00d:fec1::/80", "2001:db8:cafe:d00d:fec0::/80", false},
+ {"2001:db8:cafe:d00d:fec0::/80", "2001:db8:cafe:d00d:fec1::/80", false},
+ {"2001:db8:cafe:d00d:fec0:dd::/96", "2001:db8:cafe:d00d:fec0:ae::/96", false},
+ {"2001:db8:cafe:d00d:fec0:ae::/96", "2001:db8:cafe:d00d:fec0:dd::/96", false},
+ {"2001:db8:cafe:d00d:fec0:de:aa::/112", "2001:db8:cafe:d00d:fec0:de:ac::/112", false},
+ {"2001:db8:cafe:d00d:fec0:de:ac::/112", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+ {"2001:db8::cafe:0:123/128", "2001:db8::cafe:0:456/128", false},
+ {"2001:db8::cafe:0:456/128", "2001:db8::cafe:0:123/128", false},
+ {"2001:db8::/32", "2001:db8::/64", true},
+ {"2001:db8::/64", "2001:db8::/32", false},
+ {"::/0", "0.0.0.0/0", false},
+ {"::/0", "1.0.0.0/8", false},
+ {"::/0", "1.2.0.0/16", false},
+ {"::/0", "1.2.3.0/24", false},
+ {"::/0", "1.2.3.4/32", false},
+ {"2001::/16", "1.2.3.4/32", false},
+ {"2001::db8::/32", "1.2.3.4/32", false},
+ {"2001:db8:cafe::/48", "1.2.3.4/32", false},
+ {"2001:db8:cafe:d00d::/64", "1.2.3.4/32", false},
+ {"2001:db8:cafe:d00d:fec0::/80", "1.2.3.4/32", false},
+ {"2001:db8:cafe:d00d:fec0:ae::/96", "1.2.3.4/32", false},
+ {"2001:db8:cafe:d00d:fec0:de:aa::/112", "1.2.3.4/32", false},
+ {"0.0.0.0/0", "::/0", false},
+ {"0.0.0.0/0", "2001::/16", false},
+ {"0.0.0.0/0", "2001::db8::/32", false},
+ {"0.0.0.0/0", "2001:db8:cafe::/48", false},
+ {"0.0.0.0/0", "2001:db8:cafe:d00d::/64", false},
+ {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0::/80", false},
+ {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0:ae::/96", false},
+ {"0.0.0.0/0", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+ {"1.2.3.4/32", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+ {"1.2.3.0/24", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+ {"1.2.0.0/16", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+ {"1.0.0.0/8", "2001:db8:cafe:d00d:fec0:de:aa::/112", false},
+ };
+
+ for (const auto& expectation : testExpectations) {
+ SCOPED_TRACE(expectation.asParameters());
+ IPPrefix a = IPPrefix::forString(expectation.prefix);
+ IPPrefix b = IPPrefix::forString(expectation.otherPrefix);
+ EXPECT_EQ(expectation.expected, a.contains(b));
+ }
+}
+
+TEST(IPPrefixTest, containsAddress) {
+ const struct {
+ const char* prefix;
+ const char* address;
+ const bool expected;
+ std::string asParameters() const {
+ return fmt::format("prefix={}, address={}, expect={}", prefix, address, expected);
+ }
+ } testExpectations[] = {
+ {"0.0.0.0/0", "255.255.255.255", true},
+ {"0.0.0.0/0", "1.2.3.4", true},
+ {"0.0.0.0/0", "1.2.3.0", true},
+ {"0.0.0.0/0", "1.2.0.0", true},
+ {"0.0.0.0/0", "1.0.0.0", true},
+ {"0.0.0.0/0", "0.0.0.0", true},
+ {"0.0.0.0/0", "2001:4868:4860::8888", false},
+ {"0.0.0.0/0", "::/0", false},
+ {"192.0.2.0/23", "192.0.2.0", true},
+ {"192.0.2.0/23", "192.0.2.43", true},
+ {"192.0.2.0/23", "192.0.3.21", true},
+ {"192.0.2.0/23", "192.0.0.21", false},
+ {"192.0.2.0/23", "8.8.8.8", false},
+ {"192.0.2.0/23", "2001:4868:4860::8888", false},
+ {"192.0.2.0/23", "::/0", false},
+ {"1.2.3.4/32", "1.2.3.4", true},
+ {"1.2.3.4/32", "1.2.3.5", false},
+ {"10.0.0.0/8", "10.2.0.0", true},
+ {"10.0.0.0/8", "10.2.3.5", true},
+ {"10.0.0.0/8", "10.0.0.0", true},
+ {"10.0.0.0/8", "10.255.255.254", true},
+ {"10.0.0.0/8", "11.0.0.0", false},
+ {"::/0", "2001:db8:f000::ace:d00c", true},
+ {"::/0", "2002:db8:f00::ace:d00d", true},
+ {"::/0", "2001:db7:f00::ace:d00e", true},
+ {"::/0", "2001:db8:f01::bad:d00d", true},
+ {"::/0", "::", true},
+ {"::/0", "0.0.0.0", false},
+ {"::/0", "1.2.3.4", false},
+ {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00c", true},
+ {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00d", true},
+ {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::ace:d00e", false},
+ {"2001:db8:f00::ace:d00d/127", "2001:db8:f00::bad:d00d", false},
+ {"2001:db8:f00::ace:d00d/127", "2001:4868:4860::8888", false},
+ {"2001:db8:f00::ace:d00d/127", "8.8.8.8", false},
+ {"2001:db8:f00::ace:d00d/127", "0.0.0.0", false},
+ {"2001:db8:f00::ace:d00d/128", "2001:db8:f00::ace:d00d", true},
+ {"2001:db8:f00::ace:d00d/128", "2001:db8:f00::ace:d00c", false},
+ {"2001::/16", "2001::", true},
+ {"2001::/16", "2001:db8:f00::ace:d00d", true},
+ {"2001::/16", "2001:db8:f00::bad:d00d", true},
+ {"2001::/16", "2001::abc", true},
+ {"2001::/16", "2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff", true},
+ {"2001::/16", "2000::", false},
+ };
+
+ for (const auto& expectation : testExpectations) {
+ SCOPED_TRACE(expectation.asParameters());
+ IPPrefix a = IPPrefix::forString(expectation.prefix);
+ IPAddress b = IPAddress::forString(expectation.address);
+ EXPECT_EQ(expectation.expected, a.contains(b));
+ }
+}
+
TEST(IPPrefixTest, GamutOfOperators) {
const std::vector<OperatorExpectation<IPPrefix>> kExpectations{
{EQ, IPPrefix(), IPPrefix()},
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
index d5cbe2b..d10cec7 100644
--- a/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
+++ b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
@@ -221,6 +221,12 @@
in_addr addr4() const noexcept { return mData.ip.v4; }
in6_addr addr6() const noexcept { return mData.ip.v6; }
constexpr int length() const noexcept { return mData.cidrlen; }
+ bool contains(const IPPrefix& other) {
+ return length() <= other.length() && IPPrefix(other.ip(), length()).ip() == ip();
+ }
+ bool contains(const IPAddress& other) {
+ return IPPrefix(other, length()).ip() == ip();
+ }
bool isUninitialized() const noexcept;
std::string toString() const noexcept;
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
index 03a1ec9..4866a48 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
@@ -131,6 +131,21 @@
assertEquals(0, icmpv6.code);
}
+ @Test
+ public void testBuildEchoReplyPacket() {
+ final ByteBuffer b = Ipv6Utils.buildEchoReplyPacket(LINK_LOCAL, ALL_NODES);
+
+ Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+ assertEquals(255, ipv6.hopLimit);
+ assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+ assertEquals(LINK_LOCAL, ipv6.srcIp);
+ assertEquals(ALL_NODES, ipv6.dstIp);
+
+ Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+ assertEquals(NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE, icmpv6.type);
+ assertEquals(0, icmpv6.code);
+ }
+
private void assertPioEquals(PrefixInformationOption pio, String prefix, byte flags,
long valid, long preferred) {
assertEquals(NetworkStackConstants.ICMPV6_ND_OPTION_PIO, pio.type);
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
index 8f9a1f9..e40cd6b 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -17,10 +17,12 @@
package com.android.net.module.util;
import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_IPV6;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
@@ -41,6 +43,7 @@
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.TcpHeader;
import com.android.net.module.util.structs.UdpHeader;
@@ -49,6 +52,7 @@
import java.io.IOException;
import java.net.Inet4Address;
+import java.net.Inet6Address;
import java.nio.ByteBuffer;
@RunWith(AndroidJUnit4.class)
@@ -56,8 +60,10 @@
public class PacketBuilderTest {
private static final MacAddress SRC_MAC = MacAddress.fromString("11:22:33:44:55:66");
private static final MacAddress DST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
- private static final Inet4Address IPV4_SRC_ADDR = addr("192.0.2.1");
- private static final Inet4Address IPV4_DST_ADDR = addr("198.51.100.1");
+ private static final Inet4Address IPV4_SRC_ADDR = addr4("192.0.2.1");
+ private static final Inet4Address IPV4_DST_ADDR = addr4("198.51.100.1");
+ private static final Inet6Address IPV6_SRC_ADDR = addr6("2001:db8::1");
+ private static final Inet6Address IPV6_DST_ADDR = addr6("2001:db8::2");
private static final short SRC_PORT = 9876;
private static final short DST_PORT = 433;
private static final short SEQ_NO = 13579;
@@ -68,6 +74,9 @@
private static final byte TIME_TO_LIVE = (byte) 0x40;
private static final short WINDOW = (short) 0x2000;
private static final short URGENT_POINTER = 0;
+ // version=6, traffic class=0x80, flowlabel=0x515ca;
+ private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x680515ca;
+ private static final short HOP_LIMIT = 0x40;
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[] {
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
});
@@ -256,15 +265,239 @@
(byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
};
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv6') /
+ // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433))
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv6') /
+ // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433) /
+ // b'\xde\xad\xbe\xef')
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv6') /
+ // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0) /
+ // b'\xde\xad\xbe\xef')
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0xd9, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR =
+ new byte[] {
+ // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+ // type='IPv6') /
+ // scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0))
+ // Ether header
+ (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+ (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+ (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+ (byte) 0x86, (byte) 0xdd,
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0x76, (byte) 0xa7, (byte) 0x00, (byte) 0x00
+ };
+
+ private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0) /
+ // b'\xde\xad\xbe\xef')
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x18, (byte) 0x06, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0xd9, (byte) 0x05, (byte) 0x00, (byte) 0x00,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
+ private static final byte[] TEST_PACKET_IPV6HDR_TCPHDR =
+ new byte[] {
+ // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+ // flags='A', window=8192, urgptr=0))
+ // IPv6 header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x14, (byte) 0x06, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // TCP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+ (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+ (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+ (byte) 0x76, (byte) 0xa7, (byte) 0x00, (byte) 0x00
+ };
+
+ private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR =
+ new byte[] {
+ // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433))
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x08, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x08, (byte) 0x7c, (byte) 0x24
+ };
+
+ private static final byte[] TEST_PACKET_IPV6HDR_UDPHDR_DATA =
+ new byte[] {
+ // packet = (scapy.IPv6(src="2001:db8::1", dst="2001:db8::2", tc=0x80,
+ // fl=0x515ca, hlim=0x40) /
+ // scapy.UDP(sport=9876, dport=433) /
+ // b'\xde\xad\xbe\xef')
+ // IP header
+ (byte) 0x68, (byte) 0x05, (byte) 0x15, (byte) 0xca,
+ (byte) 0x00, (byte) 0x0c, (byte) 0x11, (byte) 0x40,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
+ (byte) 0x20, (byte) 0x01, (byte) 0x0d, (byte) 0xb8,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x02,
+ // UDP header
+ (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+ (byte) 0x00, (byte) 0x0c, (byte) 0xde, (byte) 0x7e,
+ // Data
+ (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+ };
+
/**
- * Build an IPv4 packet which has ether header, IPv4 header, TCP/UDP header and data.
+ * Build a packet which has ether header, IP header, TCP/UDP header and data.
* The ethernet header and data are optional. Note that both source mac address and
* destination mac address are required for ethernet header.
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 2 header (EthernetHeader) | (optional)
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- * | Layer 3 header (Ipv4Header) |
+ * | Layer 3 header (Ipv4Header, Ipv6Header) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Layer 4 header (TcpHeader, UdpHeader) |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -273,33 +506,55 @@
*
* @param srcMac source MAC address. used by L2 ether header.
* @param dstMac destination MAC address. used by L2 ether header.
- * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
* @param payload the payload.
*/
@NonNull
- private ByteBuffer buildIpv4Packet(@Nullable final MacAddress srcMac,
- @Nullable final MacAddress dstMac, final int l4proto,
+ private ByteBuffer buildPacket(@Nullable final MacAddress srcMac,
+ @Nullable final MacAddress dstMac, final int l3proto, final int l4proto,
@Nullable final ByteBuffer payload)
throws Exception {
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
+ }
+
if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
fail("Unsupported layer 4 protocol " + l4proto);
}
final boolean hasEther = (srcMac != null && dstMac != null);
final int payloadLen = (payload == null) ? 0 : payload.limit();
- final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, l4proto,
+ final ByteBuffer buffer = PacketBuilder.allocate(hasEther, l3proto, l4proto,
payloadLen);
final PacketBuilder packetBuilder = new PacketBuilder(buffer);
- if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
- packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
- TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+ // [1] Build ether header.
+ if (hasEther) {
+ final int etherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+ packetBuilder.writeL2Header(srcMac, dstMac, (short) etherType);
+ }
+
+ // [2] Build IP header.
+ if (l3proto == IPPROTO_IP) {
+ packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+ TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+ } else if (l3proto == IPPROTO_IPV6) {
+ packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL,
+ (byte) l4proto, HOP_LIMIT, IPV6_SRC_ADDR, IPV6_DST_ADDR);
+ }
+
+ // [3] Build TCP or UDP header.
if (l4proto == IPPROTO_TCP) {
packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
TCPHDR_ACK, WINDOW, URGENT_POINTER);
} else if (l4proto == IPPROTO_UDP) {
packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT);
}
+
+ // [4] Build payload.
if (payload != null) {
buffer.put(payload);
// in case data might be reused by caller, restore the position and
@@ -313,19 +568,27 @@
/**
* Check ethernet header.
*
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
* @param actual the packet to check.
*/
- private void checkEtherHeader(final ByteBuffer actual) {
+ private void checkEtherHeader(final int l3proto, final ByteBuffer actual) {
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
+ }
+
final EthernetHeader eth = Struct.parse(EthernetHeader.class, actual);
assertEquals(SRC_MAC, eth.srcMac);
assertEquals(DST_MAC, eth.dstMac);
- assertEquals(ETHER_TYPE_IPV4, eth.etherType);
+ final int expectedEtherType = (l3proto == IPPROTO_IP) ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
+ assertEquals(expectedEtherType, eth.etherType);
}
/**
* Check IPv4 header.
*
- * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
* @param hasData true if the packet has data payload; false otherwise.
* @param actual the packet to check.
*/
@@ -359,19 +622,64 @@
}
/**
- * Check TCPv4 packet.
+ * Check IPv6 header.
*
- * @param hasEther true if the packet has ether header; false otherwise.
+ * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+ * currently supported.
* @param hasData true if the packet has data payload; false otherwise.
* @param actual the packet to check.
*/
- private void checkTcpv4Packet(final boolean hasEther, final boolean hasData,
+ private void checkIpv6Header(final int l4proto, final boolean hasData,
final ByteBuffer actual) {
- if (hasEther) {
- checkEtherHeader(actual);
+ if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+ fail("Unsupported layer 4 protocol " + l4proto);
}
- checkIpv4Header(IPPROTO_TCP, hasData, actual);
+ final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, actual);
+
+ assertEquals(VERSION_TRAFFICCLASS_FLOWLABEL, ipv6Header.vtf);
+ assertEquals(HOP_LIMIT, ipv6Header.hopLimit);
+ assertEquals(IPV6_SRC_ADDR, ipv6Header.srcIp);
+ assertEquals(IPV6_DST_ADDR, ipv6Header.dstIp);
+
+ final int dataLength = hasData ? DATA.limit() : 0;
+ if (l4proto == IPPROTO_TCP) {
+ assertEquals(TCP_HEADER_MIN_LEN + dataLength, ipv6Header.payloadLength);
+ assertEquals((byte) IPPROTO_TCP, ipv6Header.nextHeader);
+ } else if (l4proto == IPPROTO_UDP) {
+ assertEquals(UDP_HEADER_LEN + dataLength, ipv6Header.payloadLength);
+ assertEquals((byte) IPPROTO_UDP, ipv6Header.nextHeader);
+ }
+ }
+
+ /**
+ * Check TCP packet.
+ *
+ * @param hasEther true if the packet has ether header; false otherwise.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
+ * @param hasData true if the packet has data payload; false otherwise.
+ * @param actual the packet to check.
+ */
+ private void checkTcpPacket(final boolean hasEther, final int l3proto, final boolean hasData,
+ final ByteBuffer actual) {
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
+ }
+
+ // [1] Check ether header.
+ if (hasEther) {
+ checkEtherHeader(l3proto, actual);
+ }
+
+ // [2] Check IP header.
+ if (l3proto == IPPROTO_IP) {
+ checkIpv4Header(IPPROTO_TCP, hasData, actual);
+ } else if (l3proto == IPPROTO_IPV6) {
+ checkIpv6Header(IPPROTO_TCP, hasData, actual);
+ }
+
+ // [3] Check TCP header.
final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, actual);
assertEquals(SRC_PORT, tcpHeader.srcPort);
assertEquals(DST_PORT, tcpHeader.dstPort);
@@ -380,35 +688,59 @@
assertEquals((short) 0x5010 /* offset=5(*4bytes), control bits=ACK */,
tcpHeader.dataOffsetAndControlBits);
assertEquals(WINDOW, tcpHeader.window);
- assertEquals(hasData ? (short) 0x4844 : (short) 0xe5e5, tcpHeader.checksum);
assertEquals(URGENT_POINTER, tcpHeader.urgentPointer);
+ if (l3proto == IPPROTO_IP) {
+ assertEquals(hasData ? (short) 0x4844 : (short) 0xe5e5, tcpHeader.checksum);
+ } else if (l3proto == IPPROTO_IPV6) {
+ assertEquals(hasData ? (short) 0xd905 : (short) 0x76a7, tcpHeader.checksum);
+ }
+ // [4] Check payload.
if (hasData) {
assertEquals(0xdeadbeef, actual.getInt());
}
}
/**
- * Check UDPv4 packet.
+ * Check UDP packet.
*
* @param hasEther true if the packet has ether header; false otherwise.
+ * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} and {@code IPPROTO_IPV6}
+ * currently supported.
* @param hasData true if the packet has data payload; false otherwise.
* @param actual the packet to check.
*/
- private void checkUdpv4Packet(final boolean hasEther, final boolean hasData,
+ private void checkUdpPacket(final boolean hasEther, final int l3proto, final boolean hasData,
final ByteBuffer actual) {
- if (hasEther) {
- checkEtherHeader(actual);
+ if (l3proto != IPPROTO_IP && l3proto != IPPROTO_IPV6) {
+ fail("Unsupported layer 3 protocol " + l3proto);
}
- checkIpv4Header(IPPROTO_UDP, hasData, actual);
+ // [1] Check ether header.
+ if (hasEther) {
+ checkEtherHeader(l3proto, actual);
+ }
+
+ // [2] Check IP header.
+ if (l3proto == IPPROTO_IP) {
+ checkIpv4Header(IPPROTO_UDP, hasData, actual);
+ } else if (l3proto == IPPROTO_IPV6) {
+ checkIpv6Header(IPPROTO_UDP, hasData, actual);
+ }
+
+ // [3] Check UDP header.
final UdpHeader udpHeader = Struct.parse(UdpHeader.class, actual);
assertEquals(SRC_PORT, udpHeader.srcPort);
assertEquals(DST_PORT, udpHeader.dstPort);
final int dataLength = hasData ? DATA.limit() : 0;
assertEquals(UDP_HEADER_LEN + dataLength, udpHeader.length);
- assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum);
+ if (l3proto == IPPROTO_IP) {
+ assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum);
+ } else if (l3proto == IPPROTO_IPV6) {
+ assertEquals(hasData ? (short) 0xde7e : (short) 0x7c24, udpHeader.checksum);
+ }
+ // [4] Check payload.
if (hasData) {
assertEquals(0xdeadbeef, actual.getInt());
}
@@ -416,66 +748,133 @@
@Test
public void testBuildPacketEtherIPv4Tcp() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, null /* data */);
- checkTcpv4Packet(true /* hasEther */, false /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP,
+ null /* data */);
+ checkTcpPacket(true /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR, packet.array());
}
@Test
public void testBuildPacketEtherIPv4TcpData() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, DATA);
- checkTcpv4Packet(true /* hasEther */, true /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_TCP, DATA);
+ checkTcpPacket(true /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA,
packet.array());
}
@Test
public void testBuildPacketIPv4Tcp() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
- IPPROTO_TCP, null /* data */);
- checkTcpv4Packet(false /* hasEther */, false /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_TCP, null /* data */);
+ checkTcpPacket(false /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR, packet.array());
}
@Test
public void testBuildPacketIPv4TcpData() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
- IPPROTO_TCP, DATA);
- checkTcpv4Packet(false /* hasEther */, true /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_TCP, DATA);
+ checkTcpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR_DATA, packet.array());
}
@Test
public void testBuildPacketEtherIPv4Udp() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_UDP, null /* data */);
- checkUdpv4Packet(true /* hasEther */, false /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP,
+ null /* data */);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR, packet.array());
}
@Test
public void testBuildPacketEtherIPv4UdpData() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_UDP, DATA);
- checkUdpv4Packet(true /* hasEther */, true /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IP, IPPROTO_UDP, DATA);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA, packet.array());
}
@Test
public void testBuildPacketIPv4Udp() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
- IPPROTO_UDP, null /*data*/);
- checkUdpv4Packet(false /* hasEther */, false /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_UDP, null /*data*/);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IP, false /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR, packet.array());
}
@Test
public void testBuildPacketIPv4UdpData() throws Exception {
- final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
- IPPROTO_UDP, DATA);
- checkUdpv4Packet(false /* hasEther */, true /* hasData */, packet);
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IP, IPPROTO_UDP, DATA);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IP, true /* hasData */, packet);
assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR_DATA, packet.array());
}
@Test
+ public void testBuildPacketEtherIPv6TcpData() throws Exception {
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_TCP, DATA);
+ checkTcpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR_DATA,
+ packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv6Tcp() throws Exception {
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_TCP,
+ null /*data*/);
+ checkTcpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_TCPHDR,
+ packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv6TcpData() throws Exception {
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, IPPROTO_IPV6,
+ IPPROTO_TCP, DATA);
+ checkTcpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV6HDR_TCPHDR_DATA, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv6Tcp() throws Exception {
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */, IPPROTO_IPV6,
+ IPPROTO_TCP, null /*data*/);
+ checkTcpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV6HDR_TCPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv6Udp() throws Exception {
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ null /* data */);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketEtherIPv6UdpData() throws Exception {
+ final ByteBuffer packet = buildPacket(SRC_MAC, DST_MAC, IPPROTO_IPV6, IPPROTO_UDP,
+ DATA);
+ checkUdpPacket(true /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_ETHERHDR_IPV6HDR_UDPHDR_DATA, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv6Udp() throws Exception {
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IPV6, IPPROTO_UDP, null /*data*/);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, false /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR, packet.array());
+ }
+
+ @Test
+ public void testBuildPacketIPv6UdpData() throws Exception {
+ final ByteBuffer packet = buildPacket(null /* srcMac */, null /* dstMac */,
+ IPPROTO_IPV6, IPPROTO_UDP, DATA);
+ checkUdpPacket(false /* hasEther */, IPPROTO_IPV6, true /* hasData */, packet);
+ assertArrayEquals(TEST_PACKET_IPV6HDR_UDPHDR_DATA, packet.array());
+ }
+
+ @Test
public void testFinalizePacketWithoutIpv4Header() throws Exception {
final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
IPPROTO_TCP, 0 /* payloadLen */);
@@ -526,7 +925,11 @@
assertThrows(IOException.class, () -> packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT));
}
- private static Inet4Address addr(String addr) {
+ private static Inet4Address addr4(String addr) {
return (Inet4Address) InetAddresses.parseNumericAddress(addr);
}
+
+ private static Inet6Address addr6(String addr) {
+ return (Inet6Address) InetAddresses.parseNumericAddress(addr);
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt
new file mode 100644
index 0000000..0f2d52a
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PerUidCounterTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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 com.android.net.module.util
+
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class PerUidCounterTest {
+ private val UID_A = 1000
+ private val UID_B = 1001
+
+ @Test
+ fun testCounterMaximum() {
+ assertFailsWith<IllegalArgumentException> {
+ PerUidCounter(-1)
+ }
+ assertFailsWith<IllegalArgumentException> {
+ PerUidCounter(0)
+ }
+
+ val largeMaxCounter = PerUidCounter(Integer.MAX_VALUE)
+ largeMaxCounter.incrementCountOrThrow(UID_A, Integer.MAX_VALUE)
+ assertFailsWith<IllegalStateException> {
+ largeMaxCounter.incrementCountOrThrow(UID_A)
+ }
+ }
+
+ @Test
+ fun testIncrementCountOrThrow() {
+ val counter = PerUidCounter(3)
+
+ // Verify the increment count cannot be zero.
+ assertFailsWith<IllegalArgumentException> {
+ counter.incrementCountOrThrow(UID_A, 0)
+ }
+
+ // Verify the counters work independently.
+ counter.incrementCountOrThrow(UID_A)
+ counter.incrementCountOrThrow(UID_B, 2)
+ counter.incrementCountOrThrow(UID_B)
+ counter.incrementCountOrThrow(UID_A)
+ counter.incrementCountOrThrow(UID_A)
+ assertFailsWith<IllegalStateException> {
+ counter.incrementCountOrThrow(UID_A)
+ }
+ assertFailsWith<IllegalStateException> {
+ counter.incrementCountOrThrow(UID_B)
+ }
+
+ // Verify exception can be triggered again.
+ assertFailsWith<IllegalStateException> {
+ counter.incrementCountOrThrow(UID_A)
+ }
+ assertFailsWith<IllegalStateException> {
+ counter.incrementCountOrThrow(UID_A, 3)
+ }
+ }
+
+ @Test
+ fun testDecrementCountOrThrow() {
+ val counter = PerUidCounter(3)
+
+ // Verify the decrement count cannot be zero.
+ assertFailsWith<IllegalArgumentException> {
+ counter.decrementCountOrThrow(UID_A, 0)
+ }
+
+ // Verify the count cannot go below zero.
+ assertFailsWith<IllegalStateException> {
+ counter.decrementCountOrThrow(UID_A)
+ }
+ assertFailsWith<IllegalStateException> {
+ counter.decrementCountOrThrow(UID_A, 5)
+ }
+ assertFailsWith<IllegalStateException> {
+ counter.decrementCountOrThrow(UID_A, Integer.MAX_VALUE)
+ }
+
+ // Verify the counters work independently.
+ counter.incrementCountOrThrow(UID_A)
+ counter.incrementCountOrThrow(UID_B)
+ assertFailsWith<IllegalStateException> {
+ counter.decrementCountOrThrow(UID_A, 3)
+ }
+ counter.decrementCountOrThrow(UID_A)
+ assertFailsWith<IllegalStateException> {
+ counter.decrementCountOrThrow(UID_A)
+ }
+ }
+}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java
index 392314f..55cfd50 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkRouteMessageTest.java
@@ -63,15 +63,7 @@
return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
}
- @Test
- public void testParseRtmRouteAddress() {
- final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX);
- byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
- final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
- assertNotNull(msg);
- assertTrue(msg instanceof RtNetlinkRouteMessage);
- final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
-
+ private void assertRtmRouteMessage(final RtNetlinkRouteMessage routeMsg) {
final StructNlMsgHdr hdr = routeMsg.getHeader();
assertNotNull(hdr);
assertEquals(136, hdr.nlmsg_len);
@@ -97,6 +89,18 @@
assertEquals((Inet6Address) routeMsg.getGateway(), TEST_IPV6_LINK_LOCAL_GATEWAY);
}
+ @Test
+ public void testParseRtmRouteMessage() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkRouteMessage);
+ final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
+ assertRtmRouteMessage(routeMsg);
+ }
+
private static final String RTM_NEWROUTE_PACK_HEX =
"4C000000180000060000000000000000" // struct nlmsghr
+ "0A400000FC02000100000000" // struct rtmsg
@@ -143,7 +147,7 @@
+ "08000400DF020000"; // RTA_OIF
@Test
- public void testParseRtmRouteAddress_IPv4MappedIPv6Gateway() {
+ public void testParseRtmRouteMessage_IPv4MappedIPv6Gateway() {
final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_GATEWAY_HEX);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
@@ -160,7 +164,7 @@
+ "08000400DF020000"; // RTA_OIF
@Test
- public void testParseRtmRouteAddress_IPv4MappedIPv6Destination() {
+ public void testParseRtmRouteMessage_IPv4MappedIPv6Destination() {
final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_IPV4_MAPPED_IPV6_DST_HEX);
byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
@@ -169,6 +173,32 @@
assertNull(msg);
}
+ // An example of the full RTM_NEWADDR message.
+ private static final String RTM_NEWADDR_HEX =
+ "48000000140000000000000000000000" // struct nlmsghr
+ + "0A4080FD1E000000" // struct ifaddrmsg
+ + "14000100FE800000000000002C415CFFFE096665" // IFA_ADDRESS
+ + "14000600100E0000201C00002A70000045700000" // IFA_CACHEINFO
+ + "0800080080000000"; // IFA_FLAGS
+
+ @Test
+ public void testParseMultipleRtmMessagesInOneByteBuffer() {
+ final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX + RTM_NEWADDR_HEX);
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
+
+ // Try to parse the RTM_NEWROUTE message.
+ NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkRouteMessage);
+ final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
+ assertRtmRouteMessage(routeMsg);
+
+ // Try to parse the RTM_NEWADDR message.
+ msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+ assertNotNull(msg);
+ assertTrue(msg instanceof RtNetlinkAddressMessage);
+ }
+
@Test
public void testToString() {
final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWROUTE_HEX);
diff --git a/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
new file mode 100644
index 0000000..6f603d5
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/testutils/DeviceInfoUtilsTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 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 com.android.testutils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public final class DeviceInfoUtilsTest {
+ /**
+ * Verifies that version string compare logic returns expected result for various cases.
+ * Note that only major and minor number are compared.
+ */
+ @Test
+ public void testMajorMinorVersionCompare() {
+ assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8.1", "4.8"));
+ assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("4.9", "4.8.1"));
+ assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5.0", "4.8"));
+ assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5", "4.8"));
+ assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("5", "5.0"));
+ assertEquals(1, DeviceInfoUtils.compareMajorMinorVersion("5-beta1", "4.8"));
+ assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8.0.0", "4.8"));
+ assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8-RC1", "4.8"));
+ assertEquals(0, DeviceInfoUtils.compareMajorMinorVersion("4.8", "4.8"));
+ assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("3.10", "4.8.0"));
+ assertEquals(-1, DeviceInfoUtils.compareMajorMinorVersion("4.7.10.10", "4.8"));
+ }
+
+ @Test
+ public void testGetMajorMinorSubminorVersion() throws Exception {
+ final DeviceInfoUtils.KVersion expected = new DeviceInfoUtils.KVersion(4, 19, 220);
+ assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220"));
+ assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion("4.19.220.50"));
+ assertEquals(expected, DeviceInfoUtils.getMajorMinorSubminorVersion(
+ "4.19.220-g500ede0aed22-ab8272303"));
+
+ final DeviceInfoUtils.KVersion expected2 = new DeviceInfoUtils.KVersion(5, 17, 0);
+ assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17"));
+ assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17."));
+ assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion("5.17.beta"));
+ assertEquals(expected2, DeviceInfoUtils.getMajorMinorSubminorVersion(
+ "5.17-rc6-g52099515ca00-ab8032400"));
+
+ final DeviceInfoUtils.KVersion invalid = new DeviceInfoUtils.KVersion(0, 0, 0);
+ assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion(""));
+ assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4"));
+ assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4."));
+ assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("4-beta"));
+ assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("1.x.1"));
+ assertEquals(invalid, DeviceInfoUtils.getMajorMinorSubminorVersion("x.1.1"));
+ }
+
+ @Test
+ public void testVersion() throws Exception {
+ final DeviceInfoUtils.KVersion v1 = new DeviceInfoUtils.KVersion(4, 8, 1);
+ final DeviceInfoUtils.KVersion v2 = new DeviceInfoUtils.KVersion(4, 8, 1);
+ final DeviceInfoUtils.KVersion v3 = new DeviceInfoUtils.KVersion(4, 8, 2);
+ final DeviceInfoUtils.KVersion v4 = new DeviceInfoUtils.KVersion(4, 9, 1);
+ final DeviceInfoUtils.KVersion v5 = new DeviceInfoUtils.KVersion(5, 8, 1);
+
+ assertEquals(v1, v2);
+ assertNotEquals(v1, v3);
+ assertNotEquals(v1, v4);
+ assertNotEquals(v1, v5);
+
+ assertEquals(0, v1.compareTo(v2));
+ assertEquals(-1, v1.compareTo(v3));
+ assertEquals(1, v3.compareTo(v1));
+ assertEquals(-1, v1.compareTo(v4));
+ assertEquals(1, v4.compareTo(v1));
+ assertEquals(-1, v1.compareTo(v5));
+ assertEquals(1, v5.compareTo(v1));
+
+ assertTrue(v2.isInRange(v1, v5));
+ assertTrue(v3.isInRange(v1, v5));
+ assertTrue(v4.isInRange(v1, v5));
+ assertFalse(v5.isInRange(v1, v5));
+ assertFalse(v1.isInRange(v3, v5));
+ assertFalse(v5.isInRange(v2, v4));
+
+ assertTrue(v2.isAtLeast(v1));
+ assertTrue(v3.isAtLeast(v1));
+ assertTrue(v4.isAtLeast(v1));
+ assertTrue(v5.isAtLeast(v1));
+ assertFalse(v1.isAtLeast(v3));
+ assertFalse(v1.isAtLeast(v4));
+ assertFalse(v1.isAtLeast(v5));
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
index 8b58e71..6a073ea 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
@@ -17,16 +17,21 @@
package com.android.testutils
import android.os.Build
+import androidx.test.InstrumentationRegistry
import com.android.modules.utils.build.SdkLevel
import kotlin.test.fail
import org.junit.Assume.assumeTrue
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
+import java.util.regex.Pattern
// TODO: Remove it when Build.VERSION_CODES.SC_V2 is available
const val SC_V2 = 32
+private val MAX_TARGET_SDK_ANNOTATION_RE = Pattern.compile("MaxTargetSdk([0-9]+)$")
+private val targetSdk = InstrumentationRegistry.getContext().applicationInfo.targetSdkVersion
+
/**
* Returns true if the development SDK version of the device is in the provided range.
*
@@ -67,6 +72,14 @@
}
}
+private fun getMaxTargetSdk(description: Description): Int? {
+ return description.annotations.firstNotNullOfOrNull {
+ MAX_TARGET_SDK_ANNOTATION_RE.matcher(it::class.simpleName).let { m ->
+ if (m.find()) m.group(1).toIntOrNull() else null
+ }
+ }
+}
+
/**
* A test rule to ignore tests based on the development SDK level.
*
@@ -108,11 +121,17 @@
val ignoreAfter = description.getAnnotation(IgnoreAfter::class.java)
val ignoreUpTo = description.getAnnotation(IgnoreUpTo::class.java)
- val message = "Skipping test for build ${Build.VERSION.CODENAME} " +
+ val devSdkMessage = "Skipping test for build ${Build.VERSION.CODENAME} " +
"with SDK ${Build.VERSION.SDK_INT}"
- assumeTrue(message, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
- assumeTrue(message, isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value))
+ assumeTrue(devSdkMessage, isDevSdkInRange(ignoreClassUpTo, ignoreClassAfter))
+ assumeTrue(devSdkMessage, isDevSdkInRange(ignoreUpTo?.value, ignoreAfter?.value))
+
+ val maxTargetSdk = getMaxTargetSdk(description)
+ if (maxTargetSdk != null) {
+ assumeTrue("Skipping test, target SDK $targetSdk greater than $maxTargetSdk",
+ targetSdk <= maxTargetSdk)
+ }
base.evaluate()
}
}
-}
\ No newline at end of file
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
new file mode 100644
index 0000000..1925b55
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DeviceInfoUtils.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 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 com.android.testutils;
+
+import android.text.TextUtils;
+import android.util.Pair;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utilities for device information.
+ */
+public class DeviceInfoUtils {
+ /**
+ * Class for a three-part kernel version number.
+ */
+ public static class KVersion {
+ public final int major;
+ public final int minor;
+ public final int sub;
+
+ public KVersion(int major, int minor, int sub) {
+ this.major = major;
+ this.minor = minor;
+ this.sub = sub;
+ }
+
+ /**
+ * Compares with other version numerically.
+ *
+ * @param other the other version to compare
+ * @return the value 0 if this == other;
+ * a value less than 0 if this < other and
+ * a value greater than 0 if this > other.
+ */
+ public int compareTo(final KVersion other) {
+ int res = Integer.compare(this.major, other.major);
+ if (res == 0) {
+ res = Integer.compare(this.minor, other.minor);
+ }
+ if (res == 0) {
+ res = Integer.compare(this.sub, other.sub);
+ }
+ return res;
+ }
+
+ /**
+ * At least satisfied with the given version.
+ *
+ * @param from the start version to compare
+ * @return return true if this version is at least satisfied with the given version.
+ * otherwise, return false.
+ */
+ public boolean isAtLeast(final KVersion from) {
+ return compareTo(from) >= 0;
+ }
+
+ /**
+ * Falls within the given range [from, to).
+ *
+ * @param from the start version to compare
+ * @param to the end version to compare
+ * @return return true if this version falls within the given range.
+ * otherwise, return false.
+ */
+ public boolean isInRange(final KVersion from, final KVersion to) {
+ return isAtLeast(from) && !isAtLeast(to);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof KVersion)) return false;
+ KVersion that = (KVersion) o;
+ return this.major == that.major
+ && this.minor == that.minor
+ && this.sub == that.sub;
+ }
+ };
+
+ /**
+ * Get a two-part kernel version number (major and minor) from a given string.
+ *
+ * TODO: use class KVersion.
+ */
+ private static Pair<Integer, Integer> getMajorMinorVersion(String version) {
+ // Only gets major and minor number of the version string.
+ final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
+ final Matcher m = versionPattern.matcher(version);
+ if (m.matches()) {
+ final int major = Integer.parseInt(m.group(1));
+ final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
+ return new Pair<>(major, minor);
+ } else {
+ return new Pair<>(0, 0);
+ }
+ }
+
+ /**
+ * Compares two version strings numerically. Compare only major and minor number of the
+ * version string. The version comparison uses #Integer.compare. Possible version
+ * 5, 5.10, 5-beta1, 4.8-RC1, 4.7.10.10 and so on.
+ *
+ * @param s1 the first version string to compare
+ * @param s2 the second version string to compare
+ * @return the value 0 if s1 == s2;
+ * a value less than 0 if s1 < s2 and
+ * a value greater than 0 if s1 > s2.
+ *
+ * TODO: use class KVersion.
+ */
+ public static int compareMajorMinorVersion(final String s1, final String s2) {
+ final Pair<Integer, Integer> v1 = getMajorMinorVersion(s1);
+ final Pair<Integer, Integer> v2 = getMajorMinorVersion(s2);
+
+ if (v1.first == v2.first) {
+ return Integer.compare(v1.second, v2.second);
+ } else {
+ return Integer.compare(v1.first, v2.first);
+ }
+ }
+
+ /**
+ * Get a three-part kernel version number (major, minor and subminor) from a given string.
+ * Any version string must at least have major and minor number. If the subminor number can't
+ * be parsed from string. Assign zero as subminor number. Invalid version is treated as
+ * version 0.0.0.
+ */
+ public static KVersion getMajorMinorSubminorVersion(final String version) {
+ // The kernel version is a three-part version number (major, minor and subminor). Get
+ // the three-part version numbers and discard the remaining stuff if any.
+ // For example:
+ // 4.19.220-g500ede0aed22-ab8272303 --> 4.19.220
+ // 5.17-rc6-g52099515ca00-ab8032400 --> 5.17.0
+ final Pattern versionPattern = Pattern.compile("^(\\d+)\\.(\\d+)(\\.(\\d+))?.*");
+ final Matcher m = versionPattern.matcher(version);
+ if (m.matches()) {
+ final int major = Integer.parseInt(m.group(1));
+ final int minor = Integer.parseInt(m.group(2));
+ final int sub = TextUtils.isEmpty(m.group(4)) ? 0 : Integer.parseInt(m.group(4));
+ return new KVersion(major, minor, sub);
+ } else {
+ return new KVersion(0, 0, 0);
+ }
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
index 40731ea..bdea6c7 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
@@ -23,6 +23,7 @@
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
+import android.net.LinkProperties
import android.net.TestNetworkInterface
import android.net.TestNetworkManager
import android.os.Binder
@@ -32,6 +33,7 @@
/**
* Create a test network based on a TUN interface with a LinkAddress.
+ * TODO: remove this function after fixing all the callers.
*
* This method will block until the test network is available. Requires
* [android.Manifest.permission.CHANGE_NETWORK_STATE] and
@@ -42,11 +44,13 @@
interfaceAddr: LinkAddress,
setupTimeoutMs: Long = 10_000L
): TestNetworkTracker {
- return initTestNetwork(context, listOf(interfaceAddr), setupTimeoutMs)
+ val lp = LinkProperties()
+ lp.addLinkAddress(interfaceAddr)
+ return initTestNetwork(context, lp, setupTimeoutMs)
}
/**
- * Create a test network based on a TUN interface with giving LinkAddress list.
+ * Create a test network based on a TUN interface
*
* This method will block until the test network is available. Requires
* [android.Manifest.permission.CHANGE_NETWORK_STATE] and
@@ -54,13 +58,14 @@
*/
fun initTestNetwork(
context: Context,
- linkAddrs: List<LinkAddress>,
+ lp: LinkProperties,
setupTimeoutMs: Long = 10_000L
): TestNetworkTracker {
val tnm = context.getSystemService(TestNetworkManager::class.java)
- val iface = if (isAtLeastS()) tnm.createTunInterface(linkAddrs)
- else tnm.createTunInterface(linkAddrs.toTypedArray())
- return TestNetworkTracker(context, iface, tnm, setupTimeoutMs)
+ val iface = if (isAtLeastS()) tnm.createTunInterface(lp.getLinkAddresses())
+ else tnm.createTunInterface(lp.getLinkAddresses().toTypedArray())
+ lp.setInterfaceName(iface.interfaceName)
+ return TestNetworkTracker(context, iface, tnm, lp, setupTimeoutMs)
}
/**
@@ -72,6 +77,7 @@
val context: Context,
val iface: TestNetworkInterface,
val tnm: TestNetworkManager,
+ val lp: LinkProperties,
setupTimeoutMs: Long
) {
private val cm = context.getSystemService(ConnectivityManager::class.java)
@@ -98,7 +104,7 @@
cm.requestNetwork(networkRequest, networkCallback)
try {
- tnm.setupTestNetwork(iface.interfaceName, binder)
+ tnm.setupTestNetwork(lp, true /* isMetered */, binder)
network = networkFuture.get(setupTimeoutMs, TimeUnit.MILLISECONDS)
} catch (e: Throwable) {
teardown()
@@ -112,4 +118,4 @@
cm.unregisterNetworkCallback(networkCallback)
tnm.teardownTestNetwork(network)
}
-}
\ No newline at end of file
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt
new file mode 100644
index 0000000..be0103d
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/filters/CtsNetTestCasesMaxTargetSdk31.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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 com.android.testutils.filters
+
+/**
+ * Only run this test in the CtsNetTestCasesMaxTargetSdk31 suite.
+ */
+annotation class CtsNetTestCasesMaxTargetSdk31(val reason: String)