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