Merge "Parse DnsRecord by factory method"
diff --git a/staticlibs/device/com/android/net/module/util/FdEventsReader.java b/staticlibs/device/com/android/net/module/util/FdEventsReader.java
index 4825992..f88883b 100644
--- a/staticlibs/device/com/android/net/module/util/FdEventsReader.java
+++ b/staticlibs/device/com/android/net/module/util/FdEventsReader.java
@@ -180,6 +180,17 @@
}
/**
+ * Called by the subclasses of FdEventsReader, decide whether it should stop reading from the
+ * socket or process the packet and continue to read upon receiving a zero-length packet.
+ *
+ * @return {@code true} if this FdEventsReader should process the zero-length packet.
+ * {@code false} if it should stop reading from the socket.
+ */
+ protected boolean shouldProcessZeroLengthPacket() {
+ return false; // by default, stop reading upon receiving zero-length packet.
+ }
+
+ /**
* Called by the main loop to log errors. In some cases |e| may be null.
*/
protected void logError(@NonNull String msg, @Nullable Exception e) {}
@@ -235,7 +246,7 @@
try {
bytesRead = readPacket(mFd, mBuffer);
- if (bytesRead < 1) {
+ if (bytesRead == 0 && !shouldProcessZeroLengthPacket()) {
if (isRunning()) logError("Socket closed, exiting", null);
break;
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index e69a844..f8b4716 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -39,6 +39,7 @@
import android.net.util.SocketUtils;
import android.os.Process;
+import android.os.SystemClock;
import android.system.ErrnoException;
import android.util.Log;
import android.util.Range;
@@ -461,11 +462,15 @@
*/
public static void destroyLiveTcpSockets(Set<Range<Integer>> ranges, Set<Integer> exemptUids)
throws SocketException, InterruptedIOException, ErrnoException {
+ final long startTimeMs = SystemClock.elapsedRealtime();
destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER,
(diagMsg) -> !exemptUids.contains(diagMsg.inetDiagMsg.idiag_uid)
&& containsUid(diagMsg, ranges)
&& !isLoopback(diagMsg)
&& !isAdbSocket(diagMsg));
+ final long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
+ Log.d(TAG, "Destroyed live tcp sockets for uids=" + ranges + " exemptUids=" + exemptUids
+ + " in " + durationMs + "ms");
}
/**
@@ -479,10 +484,13 @@
*/
public static void destroyLiveTcpSocketsByOwnerUids(Set<Integer> ownerUids)
throws SocketException, InterruptedIOException, ErrnoException {
+ final long startTimeMs = SystemClock.elapsedRealtime();
destroySockets(IPPROTO_TCP, TCP_ALIVE_STATE_FILTER,
(diagMsg) -> ownerUids.contains(diagMsg.inetDiagMsg.idiag_uid)
&& !isLoopback(diagMsg)
&& !isAdbSocket(diagMsg));
+ final long durationMs = SystemClock.elapsedRealtime() - startTimeMs;
+ Log.d(TAG, "Destroyed live tcp sockets for uids=" + ownerUids + " in " + durationMs + "ms");
}
@Override
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index 308ea24..0f7bd80 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -22,6 +22,7 @@
import static android.system.OsConstants.EPROTO;
import static android.system.OsConstants.ETIMEDOUT;
import static android.system.OsConstants.NETLINK_INET_DIAG;
+import static android.system.OsConstants.NETLINK_ROUTE;
import static android.system.OsConstants.SOCK_CLOEXEC;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOL_SOCKET;
@@ -41,9 +42,11 @@
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InterruptedIOException;
+import java.net.Inet6Address;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Objects;
/**
* Utilities for netlink related class that may not be able to fit into a specific class.
@@ -93,6 +96,15 @@
if (nlmsghdr == null || nlmsghdr.nlmsg_type != NetlinkConstants.NLMSG_ERROR) {
return null;
}
+
+ final int messageLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len);
+ final int payloadLength = messageLength - StructNlMsgHdr.STRUCT_SIZE;
+ if (payloadLength < 0 || payloadLength > bytes.remaining()) {
+ // Malformed message or runt buffer. Pretend the buffer was consumed.
+ bytes.position(bytes.limit());
+ return null;
+ }
+
return NetlinkErrorMessage.parse(nlmsghdr, bytes);
}
@@ -159,6 +171,31 @@
}
/**
+ * Send an RTM_NEWADDR message to kernel to add or update an IPv6 address.
+ *
+ * @param ifIndex interface index.
+ * @param ip IPv6 address to be added.
+ * @param prefixlen IPv6 address prefix length.
+ * @param flags IPv6 address flags.
+ * @param scope IPv6 address scope.
+ * @param preferred The preferred lifetime of IPv6 address.
+ * @param valid The valid lifetime of IPv6 address.
+ */
+ public static boolean sendRtmNewAddressRequest(int ifIndex, @NonNull final Inet6Address ip,
+ short prefixlen, int flags, byte scope, long preferred, long valid) {
+ Objects.requireNonNull(ip, "IPv6 address to be added should not be null.");
+ final byte[] msg = RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqNo*/, ip,
+ prefixlen, flags, scope, ifIndex, preferred, valid);
+ try {
+ NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+ return true;
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Fail to send RTM_NEWADDR to add " + ip.getHostAddress(), e);
+ return false;
+ }
+ }
+
+ /**
* Create netlink socket with the given netlink protocol type.
*
* @return fd the fileDescriptor of the socket.
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
index 1705f1c..9acac69 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
@@ -51,6 +51,7 @@
public static final short RTA_DST = 1;
public static final short RTA_OIF = 4;
public static final short RTA_GATEWAY = 5;
+ public static final short RTA_CACHEINFO = 12;
private int mIfindex;
@NonNull
@@ -59,6 +60,8 @@
private IpPrefix mDestination;
@Nullable
private InetAddress mGateway;
+ @Nullable
+ private StructRtaCacheInfo mRtaCacheInfo;
private RtNetlinkRouteMessage(StructNlMsgHdr header) {
super(header);
@@ -66,6 +69,7 @@
mDestination = null;
mGateway = null;
mIfindex = 0;
+ mRtaCacheInfo = null;
}
public int getInterfaceIndex() {
@@ -87,6 +91,11 @@
return mGateway;
}
+ @Nullable
+ public StructRtaCacheInfo getRtaCacheInfo() {
+ return mRtaCacheInfo;
+ }
+
/**
* Check whether the address families of destination and gateway match rtm_family in
* StructRtmsg.
@@ -158,6 +167,13 @@
routeMsg.mIfindex = nlAttr.getValueAsInt(0 /* 0 isn't a valid ifindex */);
}
+ // RTA_CACHEINFO
+ byteBuffer.position(baseOffset);
+ nlAttr = StructNlAttr.findNextAttrOfType(RTA_CACHEINFO, byteBuffer);
+ if (nlAttr != null) {
+ routeMsg.mRtaCacheInfo = StructRtaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
+ }
+
return routeMsg;
}
@@ -180,6 +196,11 @@
final StructNlAttr ifindex = new StructNlAttr(RTA_OIF, mIfindex);
ifindex.pack(byteBuffer);
}
+ if (mRtaCacheInfo != null) {
+ final StructNlAttr cacheInfo = new StructNlAttr(RTA_CACHEINFO,
+ mRtaCacheInfo.writeToBytes());
+ cacheInfo.pack(byteBuffer);
+ }
}
@Override
@@ -189,7 +210,8 @@
+ "Rtmsg{" + mRtmsg.toString() + "}, "
+ "destination{" + mDestination.getAddress().getHostAddress() + "}, "
+ "gateway{" + (mGateway == null ? "" : mGateway.getHostAddress()) + "}, "
- + "ifindex{" + mIfindex + "} "
+ + "ifindex{" + mIfindex + "}, "
+ + "rta_cacheinfo{" + (mRtaCacheInfo == null ? "" : mRtaCacheInfo.toString()) + "} "
+ "}";
}
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java
new file mode 100644
index 0000000..fef1f9e
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructRtaCacheInfo.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2023 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.netlink;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.nio.ByteBuffer;
+
+/**
+ * struct rta_cacheinfo
+ *
+ * see also:
+ *
+ * include/uapi/linux/rtnetlink.h
+ *
+ * @hide
+ */
+public class StructRtaCacheInfo extends Struct {
+ // Already aligned.
+ public static final int STRUCT_SIZE = 32;
+
+ @Field(order = 0, type = Type.U32)
+ public final long clntref;
+ @Field(order = 1, type = Type.U32)
+ public final long lastuse;
+ @Field(order = 2, type = Type.S32)
+ public final int expires;
+ @Field(order = 3, type = Type.U32)
+ public final long error;
+ @Field(order = 4, type = Type.U32)
+ public final long used;
+ @Field(order = 5, type = Type.U32)
+ public final long id;
+ @Field(order = 6, type = Type.U32)
+ public final long ts;
+ @Field(order = 7, type = Type.U32)
+ public final long tsage;
+
+ StructRtaCacheInfo(long clntref, long lastuse, int expires, long error, long used, long id,
+ long ts, long tsage) {
+ this.clntref = clntref;
+ this.lastuse = lastuse;
+ this.expires = expires;
+ this.error = error;
+ this.used = used;
+ this.id = id;
+ this.ts = ts;
+ this.tsage = tsage;
+ }
+
+ /**
+ * Parse an rta_cacheinfo struct from a {@link ByteBuffer}.
+ *
+ * @param byteBuffer The buffer from which to parse the rta_cacheinfo.
+ * @return the parsed rta_cacheinfo struct, or {@code null} if the rta_cacheinfo struct
+ * could not be parsed successfully (for example, if it was truncated).
+ */
+ @Nullable
+ public static StructRtaCacheInfo parse(@NonNull final ByteBuffer byteBuffer) {
+ if (byteBuffer.remaining() < STRUCT_SIZE) return null;
+
+ // The ByteOrder must already have been set to native order.
+ return Struct.parse(StructRtaCacheInfo.class, byteBuffer);
+ }
+
+ /**
+ * Write a rta_cacheinfo struct to {@link ByteBuffer}.
+ */
+ public void pack(@NonNull final ByteBuffer byteBuffer) {
+ // The ByteOrder must already have been set to native order.
+ this.writeToByteBuffer(byteBuffer);
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
index cd974e6..f0e4409 100644
--- a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
@@ -18,6 +18,8 @@
import static com.android.net.module.util.NetworkStackConstants.DHCP6_OPTION_IAPREFIX;
+import androidx.annotation.VisibleForTesting;
+
import com.android.net.module.util.Struct;
import com.android.net.module.util.Struct.Field;
import com.android.net.module.util.Struct.Type;
@@ -62,13 +64,14 @@
public final long preferred;
@Field(order = 3, type = Type.U32)
public final long valid;
- @Field(order = 4, type = Type.U8)
- public final short prefixLen;
+ @Field(order = 4, type = Type.S8)
+ public final byte prefixLen;
@Field(order = 5, type = Type.ByteArray, arraysize = 16)
public final byte[] prefix;
- IaPrefixOption(final short code, final short length, final long preferred,
- final long valid, final short prefixLen, final byte[] prefix) {
+ @VisibleForTesting
+ public IaPrefixOption(final short code, final short length, final long preferred,
+ final long valid, final byte prefixLen, final byte[] prefix) {
this.code = code;
this.length = length;
this.preferred = preferred;
@@ -81,7 +84,7 @@
* Build an IA_PD prefix option with given specific parameters.
*/
public static ByteBuffer build(final short length, final long preferred, final long valid,
- final short prefixLen, final byte[] prefix) {
+ final byte prefixLen, final byte[] prefix) {
final IaPrefixOption option = new IaPrefixOption((byte) DHCP6_OPTION_IAPREFIX,
length /* 25 + IAPrefix options length */, preferred, valid, prefixLen, prefix);
return ByteBuffer.wrap(option.writeToBytes(ByteOrder.BIG_ENDIAN));
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 2ecbc93..0dcdf1e 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -253,7 +253,7 @@
// Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE.
// NAME_NORMAL and NAME_COMPRESSION are used for checking name compression,
// refer to rfc 1035 section 4.1.4.
- private static final int MAXNAMESIZE = 255;
+ public static final int MAXNAMESIZE = 255;
public static final int NAME_NORMAL = 0;
public static final int NAME_COMPRESSION = 0xC0;
diff --git a/staticlibs/native/bpf_headers/Android.bp b/staticlibs/native/bpf_headers/Android.bp
index 31adef9..41184ea 100644
--- a/staticlibs/native/bpf_headers/Android.bp
+++ b/staticlibs/native/bpf_headers/Android.bp
@@ -33,8 +33,10 @@
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
- "com.android.tethering",
"com.android.art.debug",
+ "com.android.os.statsd",
+ "com.android.resolv",
+ "com.android.tethering",
],
}
diff --git a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
index d23afae..6c0841c 100644
--- a/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
+++ b/staticlibs/native/bpf_headers/BpfRingbufTest.cpp
@@ -25,6 +25,7 @@
#include "BpfSyscallWrappers.h"
#include "bpf/BpfRingbuf.h"
#include "bpf/BpfUtils.h"
+#include "bpf/KernelUtils.h"
#define TEST_RINGBUF_MAGIC_NUM 12345
@@ -49,10 +50,6 @@
GTEST_SKIP() << "BPF ring buffers not supported below 5.8";
}
- if (sizeof(unsigned long) != 8) {
- GTEST_SKIP() << "BPF ring buffers not supported on 32 bit arch";
- }
-
errno = 0;
mProgram.reset(retrieveProgram(mProgPath.c_str()));
EXPECT_EQ(errno, 0);
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index cac1e43..dd1504c 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -24,6 +24,8 @@
#include "bpf/BpfUtils.h"
+#include <atomic>
+
namespace android {
namespace bpf {
@@ -80,8 +82,28 @@
android::base::unique_fd mRingFd;
void* mDataPos = nullptr;
- unsigned long* mConsumerPos = nullptr;
- unsigned long* mProducerPos = nullptr;
+ // The kernel uses an "unsigned long" type for both consumer and producer position.
+ // Unsigned long is a 4 byte value on a 32-bit kernel, and an 8 byte value on a 64-bit kernel.
+ // To support 32-bit kernels, producer pos is capped at 4 bytes (despite it being 8 bytes on
+ // 64-bit kernels) and all comparisons of consumer and producer pos only compare the low-order 4
+ // bytes (an inequality comparison is performed to support overflow).
+ // This solution is bitness agnostic. The consumer only increments the 8 byte consumer pos, which,
+ // in a little-endian architecture, is safe since the entire page is mapped into memory and a
+ // 32-bit kernel will just ignore the high-order bits.
+ std::atomic_uint64_t* mConsumerPos = nullptr;
+ std::atomic_uint32_t* mProducerPos = nullptr;
+
+ // In order to guarantee atomic access in a 32 bit userspace environment, atomic_uint64_t is used
+ // in addition to std::atomic<T>::is_always_lock_free that guarantees that read / write operations
+ // are indeed atomic.
+ // Since std::atomic does not support wrapping preallocated memory, an additional static assert on
+ // the size of the atomic and the underlying type is added to ensure a reinterpret_cast from type
+ // to its atomic version is safe (is_always_lock_free being true should provide additional
+ // confidence).
+ static_assert(std::atomic_uint64_t::is_always_lock_free);
+ static_assert(std::atomic_uint32_t::is_always_lock_free);
+ static_assert(sizeof(std::atomic_uint64_t) == sizeof(uint64_t));
+ static_assert(sizeof(std::atomic_uint32_t) == sizeof(uint32_t));
};
// This is a class wrapper for eBPF ring buffers. An eBPF ring buffer is a
@@ -121,34 +143,8 @@
BpfRingbuf() : BpfRingbufBase(sizeof(Value)) {}
};
-#define ACCESS_ONCE(x) (*(volatile typeof(x)*)&(x))
-
-#if defined(__i386__) || defined(__x86_64__)
-#define smp_sync() asm volatile("" ::: "memory")
-#elif defined(__aarch64__)
-#define smp_sync() asm volatile("dmb ish" ::: "memory")
-#else
-#define smp_sync() __sync_synchronize()
-#endif
-
-#define smp_store_release(p, v) \
- do { \
- smp_sync(); \
- ACCESS_ONCE(*(p)) = (v); \
- } while (0)
-
-#define smp_load_acquire(p) \
- ({ \
- auto ___p = ACCESS_ONCE(*(p)); \
- smp_sync(); \
- ___p; \
- })
inline base::Result<void> BpfRingbufBase::Init(const char* path) {
- if (sizeof(unsigned long) != 8) {
- return android::base::Error()
- << "BpfRingbuf does not support 32 bit architectures";
- }
mRingFd.reset(mapRetrieveRW(path));
if (!mRingFd.ok()) {
return android::base::ErrnoError()
@@ -184,7 +180,7 @@
return android::base::ErrnoError()
<< "failed to mmap ringbuf consumer pages";
}
- mConsumerPos = reinterpret_cast<unsigned long*>(ptr);
+ mConsumerPos = reinterpret_cast<decltype(mConsumerPos)>(ptr);
}
{
@@ -194,7 +190,7 @@
return android::base::ErrnoError()
<< "failed to mmap ringbuf producer page";
}
- mProducerPos = reinterpret_cast<unsigned long*>(ptr);
+ mProducerPos = reinterpret_cast<decltype(mProducerPos)>(ptr);
}
mDataPos = pointerAddBytes<void*>(mProducerPos, getpagesize());
@@ -204,14 +200,19 @@
inline base::Result<int> BpfRingbufBase::ConsumeAll(
const std::function<void(const void*)>& callback) {
int64_t count = 0;
- unsigned long cons_pos = smp_load_acquire(mConsumerPos);
- unsigned long prod_pos = smp_load_acquire(mProducerPos);
- while (cons_pos < prod_pos) {
+ uint32_t prod_pos = mProducerPos->load(std::memory_order_acquire);
+ // Only userspace writes to mConsumerPos, so no need to use std::memory_order_acquire
+ uint64_t cons_pos = mConsumerPos->load(std::memory_order_relaxed);
+ while ((cons_pos & 0xFFFFFFFF) != prod_pos) {
// Find the start of the entry for this read (wrapping is done here).
void* start_ptr = pointerAddBytes<void*>(mDataPos, cons_pos & mPosMask);
// The entry has an 8 byte header containing the sample length.
- uint32_t length = smp_load_acquire(reinterpret_cast<uint32_t*>(start_ptr));
+ // struct bpf_ringbuf_hdr {
+ // u32 len;
+ // u32 pg_off;
+ // };
+ uint32_t length = *reinterpret_cast<volatile uint32_t*>(start_ptr);
// If the sample isn't committed, we're caught up with the producer.
if (length & BPF_RINGBUF_BUSY_BIT) return count;
@@ -220,7 +221,7 @@
if ((length & BPF_RINGBUF_DISCARD_BIT) == 0) {
if (length != mValueSize) {
- smp_store_release(mConsumerPos, cons_pos);
+ mConsumerPos->store(cons_pos, std::memory_order_release);
errno = EMSGSIZE;
return android::base::ErrnoError()
<< "BPF ring buffer message has unexpected size (want "
@@ -230,7 +231,7 @@
count++;
}
- smp_store_release(mConsumerPos, cons_pos);
+ mConsumerPos->store(cons_pos, std::memory_order_release);
}
return count;
@@ -252,10 +253,5 @@
});
}
-#undef ACCESS_ONCE
-#undef smp_sync
-#undef smp_store_release
-#undef smp_load_acquire
-
} // namespace bpf
} // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
index 99c7a91..9dd5822 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
@@ -28,7 +28,7 @@
#include <log/log.h>
-#include "KernelVersion.h"
+#include "KernelUtils.h"
namespace android {
namespace bpf {
diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
new file mode 100644
index 0000000..59257b8
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/KernelUtils.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/personality.h>
+#include <sys/utsname.h>
+
+namespace android {
+namespace bpf {
+
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+
+static inline unsigned uncachedKernelVersion() {
+ struct utsname buf;
+ if (uname(&buf)) 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 __unused bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+ return kernelVersion() >= KVER(major, minor, sub);
+}
+
+// Figure out the bitness of userspace.
+// Trivial and known at compile time.
+static constexpr bool isUserspace32bit() {
+ return sizeof(void*) == 4;
+}
+
+static constexpr bool isUserspace64bit() {
+ return sizeof(void*) == 8;
+}
+
+#if defined(__LP64__)
+static_assert(isUserspace64bit(), "huh? LP64 must have 64-bit userspace");
+#elif defined(__ILP32__)
+static_assert(isUserspace32bit(), "huh? ILP32 must have 32-bit userspace");
+#else
+#error "huh? must be either LP64 (64-bit userspace) or ILP32 (32-bit userspace)"
+#endif
+
+static_assert(isUserspace32bit() || isUserspace64bit(), "must be either 32 or 64 bit");
+
+// Figure out the bitness of the kernel.
+static inline bool isKernel64Bit() {
+ // a 64-bit userspace requires a 64-bit kernel
+ if (isUserspace64bit()) return true;
+
+ static bool init = false;
+ static bool cache = false;
+ if (init) return cache;
+
+ // Retrieve current personality - on Linux this system call *cannot* fail.
+ int p = personality(0xffffffff);
+ // But if it does just assume kernel and userspace (which is 32-bit) match...
+ if (p == -1) return false;
+
+ // This will effectively mask out the bottom 8 bits, and switch to 'native'
+ // personality, and then return the previous personality of this thread
+ // (likely PER_LINUX or PER_LINUX32) with any extra options unmodified.
+ int q = personality((p & ~PER_MASK) | PER_LINUX);
+ // Per man page this theoretically could error out with EINVAL,
+ // but kernel code analysis suggests setting PER_LINUX cannot fail.
+ // Either way, assume kernel and userspace (which is 32-bit) match...
+ if (q != p) return false;
+
+ struct utsname u;
+ (void)uname(&u); // only possible failure is EFAULT, but u is on stack.
+
+ // Switch back to previous personality.
+ // Theoretically could fail with EINVAL on arm64 with no 32-bit support,
+ // but then we wouldn't have fetched 'p' from the kernel in the first place.
+ // Either way there's nothing meaningful we can do in case of error.
+ // Since PER_LINUX32 vs PER_LINUX only affects uname.machine it doesn't
+ // really hurt us either. We're really just switching back to be 'clean'.
+ (void)personality(p);
+
+ // Possible values of utsname.machine observed on x86_64 desktop (arm via qemu):
+ // x86_64 i686 aarch64 armv7l
+ // additionally observed on arm device:
+ // armv8l
+ // presumably also might just be possible:
+ // i386 i486 i586
+ // and there might be other weird arm32 cases.
+ // We note that the 64 is present in both 64-bit archs,
+ // and in general is likely to be present in only 64-bit archs.
+ cache = !!strstr(u.machine, "64");
+ init = true;
+ return cache;
+}
+
+static inline __unused bool isKernel32Bit() {
+ return !isKernel64Bit();
+}
+
+static constexpr bool isArm() {
+#if defined(__arm__)
+ static_assert(isUserspace32bit(), "huh? arm must be 32 bit");
+ return true;
+#elif defined(__aarch64__)
+ static_assert(isUserspace64bit(), "aarch64 must be LP64 - no support for ILP32");
+ return true;
+#else
+ return false;
+#endif
+}
+
+static constexpr bool isX86() {
+#if defined(__i386__)
+ static_assert(isUserspace32bit(), "huh? i386 must be 32 bit");
+ return true;
+#elif defined(__x86_64__)
+ static_assert(isUserspace64bit(), "x86_64 must be LP64 - no support for ILP32 (x32)");
+ return true;
+#else
+ return false;
+#endif
+}
+
+static constexpr bool isRiscV() {
+#if defined(__riscv)
+ static_assert(isUserspace64bit(), "riscv must be 64 bit");
+ return true;
+#else
+ return false;
+#endif
+}
+
+static_assert(isArm() || isX86() || isRiscV(), "Unknown architecture");
+
+static __unused const char * describeArch() {
+ // ordered so as to make it easier to compile time optimize,
+ // only thing not known at compile time is isKernel64Bit()
+ if (isUserspace64bit()) {
+ if (isArm()) return "64-on-aarch64";
+ if (isX86()) return "64-on-x86-64";
+ if (isRiscV()) return "64-on-riscv64";
+ } else if (isKernel64Bit()) {
+ if (isArm()) return "32-on-aarch64";
+ if (isX86()) return "32-on-x86-64";
+ } else {
+ if (isArm()) return "32-on-arm32";
+ if (isX86()) return "32-on-x86-32";
+ }
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h b/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h
deleted file mode 100644
index 29a36e6..0000000
--- a/staticlibs/native/bpf_headers/include/bpf/KernelVersion.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <stdio.h>
-#include <sys/utsname.h>
-
-namespace android {
-namespace bpf {
-
-#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
-
-static inline unsigned uncachedKernelVersion() {
- struct utsname buf;
- if (uname(&buf)) 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);
-}
-
-} // namespace bpf
-} // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index 70c0f89..67ac0e4 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -84,6 +84,16 @@
size_t _size_of_bpf_prog_def SECTION("size_of_bpf_prog_def") = sizeof(struct bpf_prog_def); \
char _license[] SECTION("license") = (NAME)
+/* This macro disables loading BTF map debug information on Android <=U *and* all user builds.
+ *
+ * Note: Bpfloader v0.39+ honours 'btf_user_min_bpfloader_ver' on user builds,
+ * and 'btf_min_bpfloader_ver' on non-user builds.
+ * Older BTF capable versions unconditionally honour 'btf_min_bpfloader_ver'
+ */
+#define DISABLE_BTF_ON_USER_BUILDS() \
+ unsigned _btf_min_bpfloader_ver SECTION("btf_min_bpfloader_ver") = 39u; \
+ unsigned _btf_user_min_bpfloader_ver SECTION("btf_user_min_bpfloader_ver") = 0xFFFFFFFFu
+
/* flag the resulting bpf .o file as critical to system functionality,
* loading all kernel version appropriate programs in it must succeed
* for bpfloader success
@@ -99,6 +109,30 @@
#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
#define KVER_INF 0xFFFFFFFFu
+/*
+ * BPFFS (ie. /sys/fs/bpf) labelling is as follows:
+ * subdirectory selinux context mainline usecase / usable by
+ * / fs_bpf no [*] core operating system (ie. platform)
+ * /loader fs_bpf_loader no, U+ (as yet unused)
+ * /net_private fs_bpf_net_private yes, T+ network_stack
+ * /net_shared fs_bpf_net_shared yes, T+ network_stack & system_server
+ * /netd_readonly fs_bpf_netd_readonly yes, T+ network_stack & system_server & r/o to netd
+ * /netd_shared fs_bpf_netd_shared yes, T+ network_stack & system_server & netd [**]
+ * /tethering fs_bpf_tethering yes, S+ network_stack
+ * /vendor fs_bpf_vendor no, T+ vendor
+ *
+ * [*] initial support for bpf was added back in P,
+ * but things worked differently back then with no bpfloader,
+ * and instead netd doing stuff by hand,
+ * bpfloader with pinning into /sys/fs/bpf was (I believe) added in Q
+ * (and was definitely there in R).
+ *
+ * [**] additionally bpf programs are accessible to netutils_wrapper
+ * for use by iptables xt_bpf extensions.
+ *
+ * See cs/p:aosp-master%20-file:prebuilts/%20file:genfs_contexts%20"genfscon%20bpf"
+ */
+
/* generic functions */
/*
@@ -199,14 +233,22 @@
selinux, pindir, share, KVER(5, 8, 0), KVER_INF, \
min_loader, max_loader, ignore_eng, ignore_user, \
ignore_userdebug); \
+ \
+ _Static_assert((size_bytes) >= 4096, "min 4 kiB ringbuffer size"); \
+ _Static_assert((size_bytes) <= 0x10000000, "max 256 MiB ringbuffer size"); \
+ _Static_assert(((size_bytes) & ((size_bytes) - 1)) == 0, \
+ "ring buffer size must be a power of two"); \
+ \
static inline __always_inline __unused int bpf_##the_map##_output( \
const ValueType* v) { \
return bpf_ringbuf_output_unsafe(&the_map, v, sizeof(*v), 0); \
} \
+ \
static inline __always_inline __unused \
ValueType* bpf_##the_map##_reserve() { \
return bpf_ringbuf_reserve_unsafe(&the_map, sizeof(ValueType), 0); \
} \
+ \
static inline __always_inline __unused void bpf_##the_map##_submit( \
const ValueType* v) { \
bpf_ringbuf_submit_unsafe(v, 0); \
@@ -238,6 +280,8 @@
KVER_NONE, KVER_INF, min_loader, max_loader, \
ignore_eng, ignore_user, ignore_userdebug); \
BPF_MAP_ASSERT_OK(BPF_MAP_TYPE_##TYPE, (num_entries), (md)); \
+ _Static_assert(sizeof(KeyType) < 1024, "aosp/2370288 requires < 1024 byte keys"); \
+ _Static_assert(sizeof(ValueType) < 65536, "aosp/2370288 requires < 65536 byte values"); \
BPF_ANNOTATE_KV_PAIR(the_map, KeyType, ValueType); \
\
static inline __always_inline __unused ValueType* bpf_##the_map##_lookup_elem( \
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
index 65540e0..e7428b6 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -179,6 +179,14 @@
bool ignore_on_eng:1;
bool ignore_on_user:1;
bool ignore_on_userdebug:1;
+ // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in
+ // older bpfloader versions, and zero in programs compiled before 0.38.
+ // These are tests on the kernel architecture, ie. they ignore userspace bit-ness.
+ bool ignore_on_arm32:1;
+ bool ignore_on_aarch64:1;
+ bool ignore_on_x86_32:1;
+ bool ignore_on_x86_64:1;
+ bool ignore_on_riscv64:1;
char pad0[2]; // manually pad up to 4 byte alignment, may be used for extensions in the future
@@ -208,6 +216,14 @@
bool ignore_on_eng:1;
bool ignore_on_user:1;
bool ignore_on_userdebug:1;
+ // The following 5 ignore_on_* fields were added in version 0.38 (U). These are ignored in
+ // older bpfloader versions, and zero in programs compiled before 0.38.
+ // These are tests on the kernel architecture, ie. they ignore userspace bit-ness.
+ bool ignore_on_arm32:1;
+ bool ignore_on_aarch64:1;
+ bool ignore_on_x86_32:1;
+ bool ignore_on_x86_64:1;
+ bool ignore_on_riscv64:1;
char pad0[2]; // manually pad up to 4 byte alignment, may be used for extensions in the future
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
index 125d631..b3efc21 100644
--- a/staticlibs/native/bpf_syscall_wrappers/Android.bp
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -33,6 +33,8 @@
"//apex_available:platform",
"com.android.art.debug",
"com.android.mediaprovider",
+ "com.android.os.statsd",
+ "com.android.resolv",
"com.android.tethering",
],
}
diff --git a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
index 8502961..ba16d53 100644
--- a/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
+++ b/staticlibs/native/bpf_syscall_wrappers/include/BpfSyscallWrappers.h
@@ -28,11 +28,13 @@
#define BPF_FD_TO_U32(x) static_cast<__u32>((x).get())
#endif
-#define ptr_to_u64(x) ((uint64_t)(uintptr_t)(x))
-
namespace android {
namespace bpf {
+inline uint64_t ptr_to_u64(const void * const x) {
+ return (uint64_t)(uintptr_t)x;
+}
+
/* Note: bpf_attr is a union which might have a much larger size then the anonymous struct portion
* of it that we are using. The kernel's bpf() system call will perform a strict check to ensure
* all unused portions are zero. It will fail with E2BIG if we don't fully zero bpf_attr.
@@ -53,6 +55,23 @@
});
}
+// Note:
+// 'map_type' must be one of BPF_MAP_TYPE_{ARRAY,HASH}_OF_MAPS
+// 'value_size' must be sizeof(u32), ie. 4
+// 'inner_map_fd' is basically a template specifying {map_type, key_size, value_size, max_entries, map_flags}
+// of the inner map type (and possibly only key_size/value_size actually matter?).
+inline int createOuterMap(bpf_map_type map_type, uint32_t key_size, uint32_t value_size,
+ uint32_t max_entries, uint32_t map_flags, const BPF_FD_TYPE inner_map_fd) {
+ return bpf(BPF_MAP_CREATE, {
+ .map_type = map_type,
+ .key_size = key_size,
+ .value_size = value_size,
+ .max_entries = max_entries,
+ .map_flags = map_flags,
+ .inner_map_fd = BPF_FD_TO_U32(inner_map_fd),
+ });
+}
+
inline int writeToMapEntry(const BPF_FD_TYPE map_fd, const void* key, const void* value,
uint64_t flags) {
return bpf(BPF_MAP_UPDATE_ELEM, {
@@ -168,38 +187,38 @@
// over time), so we need to check that the field we're interested in is actually
// supported/returned by the running kernel. We do this by checking it is fully
// within the bounds of the struct size as reported by the 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 = {}; \
+#define DEFINE_BPF_GET_FD(TYPE, NAME, FIELD) \
+inline int bpfGetFd ## NAME(const BPF_FD_TYPE fd) { \
+ struct bpf_ ## TYPE ## _info 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), \
+ .bpf_fd = BPF_FD_TO_U32(fd), \
+ .info_len = sizeof(info), \
+ .info = ptr_to_u64(&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)) { \
+ if (attr.info.info_len < offsetof(bpf_ ## TYPE ## _info, FIELD) + sizeof(info.FIELD)) { \
errno = EOPNOTSUPP; \
return -1; \
}; \
- return map_info.FIELD; \
+ return info.FIELD; \
}
-// All 6 of these fields are already present in Linux v4.14 (even ACK 4.14-P)
+// All 7 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)
+DEFINE_BPF_GET_FD(map, MapType, type) // int bpfGetFdMapType(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, MapId, id) // int bpfGetFdMapId(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, KeySize, key_size) // int bpfGetFdKeySize(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, ValueSize, value_size) // int bpfGetFdValueSize(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, MaxEntries, max_entries) // int bpfGetFdMaxEntries(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(map, MapFlags, map_flags) // int bpfGetFdMapFlags(const BPF_FD_TYPE map_fd)
+DEFINE_BPF_GET_FD(prog, ProgId, id) // int bpfGetFdProgId(const BPF_FD_TYPE prog_fd)
-#undef DEFINE_BPF_GET_FD_INFO
+#undef DEFINE_BPF_GET_FD
} // namespace bpf
} // namespace android
-#undef ptr_to_u64
#undef BPF_FD_TO_U32
#undef BPF_FD_TYPE
#undef BPF_FD_JUST_USE_INT
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 2146d17..f93d6e1 100644
--- a/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_BpfMap.cpp
@@ -25,7 +25,7 @@
#define BPF_FD_JUST_USE_INT
#include "BpfSyscallWrappers.h"
-#include "bpf/KernelVersion.h"
+#include "bpf/KernelUtils.h"
namespace android {
diff --git a/staticlibs/native/nettestutils/Android.bp b/staticlibs/native/nettestutils/Android.bp
index 42df8e0..df3bb42 100644
--- a/staticlibs/native/nettestutils/Android.bp
+++ b/staticlibs/native/nettestutils/Android.bp
@@ -21,9 +21,15 @@
export_include_dirs: ["include"],
srcs: ["DumpService.cpp"],
+ // Don't depend on libbinder, because some users of this library may not want to link to it.
+ // CtsNativeNetPlatformTestCases is one such user. See r.android.com/2599405 .
+ header_libs: [
+ "libbinder_headers",
+ ],
+
shared_libs: [
- "libbinder",
"libutils",
+ "libbinder_ndk",
],
cflags: [
"-Werror",
diff --git a/staticlibs/native/nettestutils/DumpService.cpp b/staticlibs/native/nettestutils/DumpService.cpp
index ba3d77e..40c3b9a 100644
--- a/staticlibs/native/nettestutils/DumpService.cpp
+++ b/staticlibs/native/nettestutils/DumpService.cpp
@@ -17,10 +17,12 @@
#include "nettestutils/DumpService.h"
#include <android-base/file.h>
+#include <android/binder_status.h>
#include <sstream>
#include <thread>
+// Version for code using libbinder (e.g., AIDL interfaces with the C++ backend).
android::status_t dumpService(const android::sp<android::IBinder>& binder,
const std::vector<std::string>& args,
std::vector<std::string>& outputLines) {
@@ -55,3 +57,36 @@
return android::OK;
}
+
+// Version for code using libbinder_ndk (e.g., AIDL interfaces with the NDK backend)..
+android::status_t dumpService(const ndk::SpAIBinder& binder,
+ const char** args,
+ uint32_t num_args,
+ std::vector<std::string>& outputLines) {
+ if (!outputLines.empty()) return -EUCLEAN;
+
+ android::base::unique_fd localFd, remoteFd;
+ if (!Pipe(&localFd, &remoteFd)) return -errno;
+
+ android::status_t ret;
+ // dump() blocks until another thread has consumed all its output.
+ std::thread dumpThread =
+ std::thread([&ret, binder, remoteFd{std::move(remoteFd)}, args, num_args]() {
+ ret = AIBinder_dump(binder.get(), remoteFd, args, num_args);
+ });
+
+ std::string dumpContent;
+ if (!android::base::ReadFdToString(localFd.get(), &dumpContent)) {
+ return -errno;
+ }
+ dumpThread.join();
+ if (ret != android::OK) return ret;
+
+ std::stringstream dumpStream(dumpContent);
+ std::string line;
+ while (std::getline(dumpStream, line)) {
+ outputLines.push_back(std::move(line));
+ }
+
+ return android::OK;
+}
diff --git a/staticlibs/native/nettestutils/include/nettestutils/DumpService.h b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
index 2a72181..323d752 100644
--- a/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
+++ b/staticlibs/native/nettestutils/include/nettestutils/DumpService.h
@@ -15,9 +15,15 @@
*/
#include <binder/Binder.h>
+#include <android/binder_auto_utils.h>
#include <vector>
android::status_t dumpService(const android::sp<android::IBinder>& binder,
const std::vector<std::string>& args,
std::vector<std::string>& outputLines);
+
+android::status_t dumpService(const ndk::SpAIBinder& binder,
+ const char** args,
+ uint32_t num_args,
+ std::vector<std::string>& outputLines);
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
index 37a7ec8..c82390f 100644
--- a/staticlibs/native/tcutils/tcutils.cpp
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -19,7 +19,7 @@
#include "tcutils/tcutils.h"
#include "logging.h"
-#include "bpf/KernelVersion.h"
+#include "bpf/KernelUtils.h"
#include "scopeguard.h"
#include <arpa/inet.h>
diff --git a/staticlibs/native/tcutils/tests/tcutils_test.cpp b/staticlibs/native/tcutils/tests/tcutils_test.cpp
index 53835d7..7732247 100644
--- a/staticlibs/native/tcutils/tests/tcutils_test.cpp
+++ b/staticlibs/native/tcutils/tests/tcutils_test.cpp
@@ -18,7 +18,7 @@
#include <gtest/gtest.h>
-#include "bpf/KernelVersion.h"
+#include "bpf/KernelUtils.h"
#include <tcutils/tcutils.h>
#include <BpfSyscallWrappers.h>
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 55cfd50..9881653 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
@@ -87,6 +87,8 @@
assertEquals(routeMsg.getDestination(), TEST_IPV6_GLOBAL_PREFIX);
assertEquals(735, routeMsg.getInterfaceIndex());
assertEquals((Inet6Address) routeMsg.getGateway(), TEST_IPV6_LINK_LOCAL_GATEWAY);
+
+ assertNotNull(routeMsg.getRtaCacheInfo());
}
@Test
@@ -106,7 +108,9 @@
+ "0A400000FC02000100000000" // struct rtmsg
+ "1400010020010DB8000100000000000000000000" // RTA_DST
+ "14000500FE800000000000000000000000000001" // RTA_GATEWAY
- + "08000400DF020000"; // RTA_OIF
+ + "08000400DF020000" // RTA_OIF
+ + "24000C0000000000000000005EEA000000000000" // RTA_CACHEINFO
+ + "00000000000000000000000000000000";
@Test
public void testPackRtmNewRoute() {
@@ -117,7 +121,7 @@
assertTrue(msg instanceof RtNetlinkRouteMessage);
final RtNetlinkRouteMessage routeMsg = (RtNetlinkRouteMessage) msg;
- final ByteBuffer packBuffer = ByteBuffer.allocate(76);
+ final ByteBuffer packBuffer = ByteBuffer.allocate(112);
packBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing.
routeMsg.pack(packBuffer);
assertEquals(RTM_NEWROUTE_PACK_HEX, HexDump.toHexString(packBuffer.array()));
@@ -216,7 +220,9 @@
+ "scope: 0, type: 1, flags: 0}, "
+ "destination{2001:db8:1::}, "
+ "gateway{fe80::1}, "
- + "ifindex{735} "
+ + "ifindex{735}, "
+ + "rta_cacheinfo{clntref: 0, lastuse: 0, expires: 59998, error: 0, used: 0, "
+ + "id: 0, ts: 0, tsage: 0} "
+ "}";
assertEquals(expected, routeMsg.toString());
}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
index 30e0daf..0f6fa48 100644
--- a/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/testutils/HandlerUtilsTest.kt
@@ -18,8 +18,10 @@
import android.os.Handler
import android.os.HandlerThread
+import com.android.testutils.FunctionalUtils.ThrowingSupplier
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import kotlin.test.assertNull
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -69,13 +71,18 @@
repeat(ATTEMPTS) { attempt ->
var x = -10
- visibleOnHandlerThread(handler) { x = attempt }
+ var y = -11
+ y = visibleOnHandlerThread(handler, ThrowingSupplier<Int> { x = attempt; attempt })
assertEquals(attempt, x)
+ assertEquals(attempt, y)
handler.post { assertEquals(attempt, x) }
}
assertFailsWith<IllegalArgumentException> {
visibleOnHandlerThread(handler) { throw IllegalArgumentException() }
}
+
+ // Null values may be returned by the supplier
+ assertNull(visibleOnHandlerThread(handler, ThrowingSupplier<Nothing?> { null }))
}
}
diff --git a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
index 4ed881a..e838bdc 100644
--- a/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
+++ b/staticlibs/tests/unit/src/com/android/testutils/TestableNetworkCallbackTest.kt
@@ -354,6 +354,13 @@
}
@Test
+ fun testExpectClass() {
+ val net = Network(1)
+ mCallback.onAvailable(net)
+ assertFails { mCallback.expect(LOST, net) }
+ }
+
+ @Test
fun testPoll() {
assertNull(mCallback.poll(SHORT_TIMEOUT_MS))
TNCInterpreter.interpretTestSpec(initial = mCallback, lineShift = 1,
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 3382156..be4ccfe 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -83,5 +83,5 @@
],
libs: ["tradefed"],
test_suites: ["ats", "device-tests", "general-tests", "cts", "mts-networking"],
- data: [":ConnectivityChecker"],
+ data: [":ConnectivityTestPreparer"],
}
diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp
index 58f22db..f7118cf 100644
--- a/staticlibs/testutils/app/connectivitychecker/Android.bp
+++ b/staticlibs/testutils/app/connectivitychecker/Android.bp
@@ -17,7 +17,7 @@
}
android_test_helper_app {
- name: "ConnectivityChecker",
+ name: "ConnectivityTestPreparer",
srcs: ["src/**/*.kt"],
sdk_version: "system_current",
// Allow running the test on any device with SDK Q+, even when built from a branch that uses
diff --git a/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml b/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml
index 8e5958c..015b41f 100644
--- a/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml
+++ b/staticlibs/testutils/app/connectivitychecker/AndroidManifest.xml
@@ -15,7 +15,7 @@
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.testutils.connectivitychecker">
+ package="com.android.testutils.connectivitypreparer">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -27,6 +27,6 @@
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.testutils.connectivitychecker"
- android:label="Connectivity checker target preparer" />
+ android:targetPackage="com.android.testutils.connectivitypreparer"
+ android:label="Connectivity test target preparer" />
</manifest>
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
similarity index 92%
rename from staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt
rename to staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index f72938d..6bcb8fc 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitychecker/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.testutils.connectivitychecker
+package com.android.testutils.connectivitypreparer
import android.content.pm.PackageManager.FEATURE_TELEPHONY
import android.content.pm.PackageManager.FEATURE_WIFI
@@ -40,7 +40,7 @@
val pm by lazy { context.packageManager }
@Test
- fun testCheckDeviceSetup() {
+ fun testCheckConnectivity() {
checkWifiSetup()
checkTelephonySetup()
}
@@ -57,10 +57,11 @@
val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
"testing, you can use atest X -- " +
- "--test-arg com.android.testutils.ConnectivityCheckTargetPreparer:disable:true"
+ "--test-arg com.android.testutils.ConnectivityTestTargetPreparer" +
+ ":ignore-connectivity-check:true"
// Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
- fail("The device has no SIM card inserted. " + commonError)
+ fail("The device has no SIM card inserted. $commonError")
} else if (tm.simState != TelephonyManager.SIM_STATE_READY) {
fail("The device is not setup with a usable SIM card. Sim state was ${tm.simState}. " +
commonError)
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
index aa252a5..f00ca11 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/HandlerUtils.kt
@@ -23,6 +23,7 @@
import android.os.HandlerThread
import android.util.Log
import com.android.testutils.FunctionalUtils.ThrowingRunnable
+import com.android.testutils.FunctionalUtils.ThrowingSupplier
import java.lang.Exception
import java.util.concurrent.Executor
import kotlin.test.fail
@@ -55,7 +56,8 @@
}
/**
- * Executes a block of code, making its side effects visible on the caller and the handler thread
+ * Executes a block of code that returns a value, making its side effects visible on the caller and
+ * the handler thread.
*
* After this function returns, the side effects of the passed block of code are guaranteed to be
* observed both on the thread running the handler and on the thread running this method.
@@ -63,15 +65,15 @@
* until it's executed, so keep in mind this method will block, (including, if the handler isn't
* running, blocking forever).
*/
-fun visibleOnHandlerThread(handler: Handler, r: ThrowingRunnable) {
+fun <T> visibleOnHandlerThread(handler: Handler, supplier: ThrowingSupplier<T>): T {
val cv = ConditionVariable()
- var e: Exception? = null
+ var rv: Result<T> = Result.failure(RuntimeException("Not run"))
handler.post {
try {
- r.run()
+ rv = Result.success(supplier.get())
} catch (exception: Exception) {
Log.e(TAG, "visibleOnHandlerThread caught exception", exception)
- e = exception
+ rv = Result.failure(exception)
}
cv.open()
}
@@ -79,5 +81,10 @@
// and this thread also has seen the change (since cv.open() happens-before cv.block()
// returns).
cv.block()
- e?.let { throw it }
+ return rv.getOrThrow()
+}
+
+/** Overload of visibleOnHandlerThread but executes a block of code that does not return a value. */
+inline fun visibleOnHandlerThread(handler: Handler, r: ThrowingRunnable){
+ visibleOnHandlerThread(handler, ThrowingSupplier<Unit> { r.run() })
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
index 85c6493..0a2b5d4 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
@@ -151,7 +151,8 @@
private void processPacket() {
final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf);
if (len < 1) {
- throw new IllegalStateException("Unexpected buffer length: " + len);
+ // Usually happens when socket read is being interrupted, e.g. stopping PacketForwarder.
+ return;
}
final int version = mBuf[0] >>> 4;
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
index b028045..498b1a3 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
@@ -20,11 +20,12 @@
import android.system.ErrnoException
import android.system.Os
+import android.system.OsConstants
import com.android.net.module.util.IpUtils
import com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH
import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH
import java.io.FileDescriptor
-import java.io.IOException
+import java.io.InterruptedIOException
import java.net.InetAddress
import java.nio.ByteBuffer
@@ -32,8 +33,15 @@
return try {
Os.read(fd, buf, 0, buf.size)
} catch (e: ErrnoException) {
- -1
- } catch (e: IOException) {
+ // Ignore normal use cases such as the EAGAIN error indicates that the read operation
+ // cannot be completed immediately, or the EINTR error indicates that the read
+ // operation was interrupted by a signal.
+ if (e.errno == OsConstants.EAGAIN || e.errno == OsConstants.EINTR) {
+ -1
+ } else {
+ throw e
+ }
+ } catch (e: InterruptedIOException) {
-1
}
}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 0e73112..4b6aea2 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -236,7 +236,11 @@
errorMsg: String? = null,
test: (T) -> Boolean = { true }
) = expect<CallbackEntry>(network, timeoutMs, errorMsg) {
- test(it as? T ?: fail("Expected callback ${type.simpleName}, got $it"))
+ if (type.isInstance(it)) {
+ test(it as T) // Cast can't fail since type.isInstance(it) and type: KClass<T>
+ } else {
+ fail("Expected callback ${type.simpleName}, got $it")
+ }
} as T
@JvmOverloads
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt
deleted file mode 100644
index ccc4064..0000000
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityCheckTargetPreparer.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2021 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 com.android.ddmlib.testrunner.TestResult
-import com.android.tradefed.invoker.TestInformation
-import com.android.tradefed.result.CollectingTestListener
-import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner
-import com.android.tradefed.targetprep.BaseTargetPreparer
-import com.android.tradefed.targetprep.TargetSetupError
-import com.android.tradefed.targetprep.suite.SuiteApkInstaller
-
-private const val CONNECTIVITY_CHECKER_APK = "ConnectivityChecker.apk"
-private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitychecker"
-// As per the <instrumentation> defined in the checker manifest
-private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
-
-/**
- * A target preparer that verifies that the device was setup correctly for connectivity tests.
- *
- * For quick and dirty local testing, can be disabled by running tests with
- * "atest -- --test-arg com.android.testutils.ConnectivityCheckTargetPreparer:disable:true".
- */
-class ConnectivityCheckTargetPreparer : BaseTargetPreparer() {
- val installer = SuiteApkInstaller()
-
- override fun setUp(testInformation: TestInformation) {
- if (isDisabled) return
- installer.setCleanApk(true)
- installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
- installer.setShouldGrantPermission(true)
- installer.setUp(testInformation)
-
- val runner = DefaultRemoteAndroidTestRunner(
- CONNECTIVITY_PKG_NAME,
- CONNECTIVITY_CHECK_RUNNER_NAME,
- testInformation.device.iDevice)
- runner.runOptions = "--no-hidden-api-checks"
-
- val receiver = CollectingTestListener()
- if (!testInformation.device.runInstrumentationTests(runner, receiver)) {
- throw TargetSetupError("Device state check failed to complete",
- testInformation.device.deviceDescriptor)
- }
-
- val runResult = receiver.currentRunResults
- if (runResult.isRunFailure) {
- throw TargetSetupError("Failed to check device state before the test: " +
- runResult.runFailureMessage, testInformation.device.deviceDescriptor)
- }
-
- if (!runResult.hasFailedTests()) return
- val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
- if (TestResult.TestStatus.FAILURE != testResult.status) null
- else "$testDescription: ${testResult.stackTrace}"
- }.joinToString("\n")
-
- throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
- testInformation.device.deviceDescriptor)
- }
-
- override fun tearDown(testInformation: TestInformation?, e: Throwable?) {
- if (isTearDownDisabled) return
- installer.tearDown(testInformation, e)
- }
-}
\ No newline at end of file
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
new file mode 100644
index 0000000..3fc74aa
--- /dev/null
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 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 com.android.ddmlib.testrunner.TestResult
+import com.android.tradefed.config.Option
+import com.android.tradefed.invoker.TestInformation
+import com.android.tradefed.result.CollectingTestListener
+import com.android.tradefed.result.ddmlib.DefaultRemoteAndroidTestRunner
+import com.android.tradefed.targetprep.BaseTargetPreparer
+import com.android.tradefed.targetprep.TargetSetupError
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller
+
+private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
+private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
+private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
+// As per the <instrumentation> defined in the checker manifest
+private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
+private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check"
+
+/**
+ * A target preparer that sets up and verifies a device for connectivity tests.
+ *
+ * For quick and dirty local testing, the connectivity check can be disabled by running tests with
+ * "atest -- \
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true".
+ */
+open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
+ private val installer = SuiteApkInstaller()
+
+ @Option(name = IGNORE_CONN_CHECK_OPTION,
+ description = "Disables the check for mobile data and wifi")
+ private var ignoreConnectivityCheck = false
+
+ override fun setUp(testInformation: TestInformation) {
+ if (isDisabled) return
+ disableGmsUpdate(testInformation)
+ runPreparerApk(testInformation)
+ }
+
+ private fun runPreparerApk(testInformation: TestInformation) {
+ installer.setCleanApk(true)
+ installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
+ installer.setShouldGrantPermission(true)
+ installer.setUp(testInformation)
+
+ val runner = DefaultRemoteAndroidTestRunner(
+ CONNECTIVITY_PKG_NAME,
+ CONNECTIVITY_CHECK_RUNNER_NAME,
+ testInformation.device.iDevice)
+ runner.runOptions = "--no-hidden-api-checks"
+
+ val receiver = CollectingTestListener()
+ if (!testInformation.device.runInstrumentationTests(runner, receiver)) {
+ throw TargetSetupError("Device state check failed to complete",
+ testInformation.device.deviceDescriptor)
+ }
+
+ val runResult = receiver.currentRunResults
+ if (runResult.isRunFailure) {
+ throw TargetSetupError("Failed to check device state before the test: " +
+ runResult.runFailureMessage, testInformation.device.deviceDescriptor)
+ }
+
+ val ignoredTestClasses = mutableSetOf<String>()
+ if (ignoreConnectivityCheck) {
+ ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS)
+ }
+
+ val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
+ if (TestResult.TestStatus.FAILURE != testResult.status ||
+ ignoredTestClasses.contains(testDescription.className)) {
+ null
+ } else {
+ "$testDescription: ${testResult.stackTrace}"
+ }
+ }.joinToString("\n")
+ if (errorMsg.isBlank()) return
+
+ throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
+ testInformation.device.deviceDescriptor)
+ }
+
+ private fun disableGmsUpdate(testInformation: TestInformation) {
+ // This will be a no-op on devices without root (su) or not using gservices, but that's OK.
+ testInformation.device.executeShellCommand("su 0 am broadcast " +
+ "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
+ "-e finsky.play_services_auto_update_enabled false")
+ }
+
+ private fun clearGmsUpdateOverride(testInformation: TestInformation) {
+ testInformation.device.executeShellCommand("su 0 am broadcast " +
+ "-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
+ "--esn finsky.play_services_auto_update_enabled")
+ }
+
+ override fun tearDown(testInformation: TestInformation, e: Throwable?) {
+ if (isTearDownDisabled) return
+ installer.tearDown(testInformation, e)
+ clearGmsUpdateOverride(testInformation)
+ }
+}
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt
new file mode 100644
index 0000000..fe312a0
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/NetworkStackModuleTest.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2023 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
+
+/**
+ * Indicates that the test covers functionality that was rolled out in a NetworkStack module update.
+ */
+annotation class NetworkStackModuleTest