Merge "Add constants for clat and v6 fragmentation" into main
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index ff65228..d3e68b7 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -35,11 +35,13 @@
   name: "net-utils-device-common",
   srcs: [
       "device/com/android/net/module/util/DeviceConfigUtils.java",
+      "device/com/android/net/module/util/DomainUtils.java",
       "device/com/android/net/module/util/FdEventsReader.java",
       "device/com/android/net/module/util/NetworkMonitorUtils.java",
       "device/com/android/net/module/util/PacketReader.java",
       "device/com/android/net/module/util/SharedLog.java",
       "device/com/android/net/module/util/SocketUtils.java",
+      "device/com/android/net/module/util/FeatureVersions.java",
       // This library is used by system modules, for which the system health impact of Kotlin
       // has not yet been evaluated. Annotations may need jarjar'ing.
       // "src_devicecommon/**/*.kt",
diff --git a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
index dae4eb9..bea227d 100644
--- a/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
+++ b/staticlibs/device/com/android/net/module/util/DeviceConfigUtils.java
@@ -17,6 +17,12 @@
 package com.android.net.module.util;
 
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
+
+import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.MODULE_MASK;
+import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.VERSION_MASK;
 
 import android.content.Context;
 import android.content.Intent;
@@ -54,23 +60,31 @@
     private static final String CONNECTIVITY_RES_PKG_DIR = "/apex/" + TETHERING_MODULE_NAME + "/";
 
     @VisibleForTesting
+    public static final long DEFAULT_PACKAGE_VERSION = 1000;
+
+    @VisibleForTesting
     public static void resetPackageVersionCacheForTest() {
         sPackageVersion = -1;
         sModuleVersion = -1;
+        sNetworkStackModuleVersion = -1;
     }
 
     private static volatile long sPackageVersion = -1;
-    private static long getPackageVersion(@NonNull final Context context)
-            throws PackageManager.NameNotFoundException {
+    private static long getPackageVersion(@NonNull final Context context) {
         // sPackageVersion may be set by another thread just after this check, but querying the
         // package version several times on rare occasions is fine.
         if (sPackageVersion >= 0) {
             return sPackageVersion;
         }
-        final long version = context.getPackageManager().getPackageInfo(
-                context.getPackageName(), 0).getLongVersionCode();
-        sPackageVersion = version;
-        return version;
+        try {
+            final long version = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), 0).getLongVersionCode();
+            sPackageVersion = version;
+            return version;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "Failed to get package info: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
     }
 
     /**
@@ -173,13 +187,8 @@
      */
     public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace,
             @NonNull String name, boolean defaultEnabled) {
-        try {
-            final long packageVersion = getPackageVersion(context);
-            return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Could not find the package name", e);
-            return false;
-        }
+        final long packageVersion = getPackageVersion(context);
+        return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
     }
 
     /**
@@ -205,18 +214,12 @@
             throw new IllegalArgumentException(
                     "This method is only usable by the tethering module");
         }
-        try {
-            final long packageVersion = getTetheringModuleVersion(context);
-            return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Could not find the module name", e);
-            return false;
-        }
+        final long packageVersion = getTetheringModuleVersion(context);
+        return isFeatureEnabled(context, packageVersion, namespace, name, defaultEnabled);
     }
 
     private static boolean isFeatureEnabled(@NonNull Context context, long packageVersion,
-            @NonNull String namespace, String name, boolean defaultEnabled)
-            throws PackageManager.NameNotFoundException {
+            @NonNull String namespace, String name, boolean defaultEnabled) {
         final int propertyVersion = getDeviceConfigPropertyInt(namespace, name,
                 0 /* default value */);
         return (propertyVersion == 0 && defaultEnabled)
@@ -229,14 +232,7 @@
     // If that fails retry by appending "go.tethering" instead
     private static long resolveTetheringModuleVersion(@NonNull Context context)
             throws PackageManager.NameNotFoundException {
-        final String connResourcesPackage = getConnectivityResourcesPackageName(context);
-        final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity");
-        if (pkgPrefixLen < 0) {
-            throw new IllegalStateException(
-                    "Invalid connectivity resources package: " + connResourcesPackage);
-        }
-
-        final String pkgPrefix = connResourcesPackage.substring(0, pkgPrefixLen);
+        final String pkgPrefix = resolvePkgPrefix(context);
         final PackageManager packageManager = context.getPackageManager();
         try {
             return packageManager.getPackageInfo(pkgPrefix + "tethering",
@@ -250,15 +246,106 @@
                 PackageManager.MATCH_APEX).getLongVersionCode();
     }
 
+    private static String resolvePkgPrefix(Context context) {
+        final String connResourcesPackage = getConnectivityResourcesPackageName(context);
+        final int pkgPrefixLen = connResourcesPackage.indexOf("connectivity");
+        if (pkgPrefixLen < 0) {
+            throw new IllegalStateException(
+                    "Invalid connectivity resources package: " + connResourcesPackage);
+        }
+
+        return connResourcesPackage.substring(0, pkgPrefixLen);
+    }
+
     private static volatile long sModuleVersion = -1;
-    private static long getTetheringModuleVersion(@NonNull Context context)
-            throws PackageManager.NameNotFoundException {
+    private static long getTetheringModuleVersion(@NonNull Context context) {
         if (sModuleVersion >= 0) return sModuleVersion;
 
-        sModuleVersion = resolveTetheringModuleVersion(context);
+        try {
+            sModuleVersion = resolveTetheringModuleVersion(context);
+        } catch (PackageManager.NameNotFoundException e) {
+            // It's expected to fail tethering module version resolution on the devices with
+            // flattened apex
+            Log.e(TAG, "Failed to resolve tethering module version: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
         return sModuleVersion;
     }
 
+    private static volatile long sNetworkStackModuleVersion = -1;
+
+    /**
+     * Get networkstack module version.
+     */
+    @VisibleForTesting
+    static long getNetworkStackModuleVersion(@NonNull Context context) {
+        if (sNetworkStackModuleVersion >= 0) return sNetworkStackModuleVersion;
+
+        try {
+            sNetworkStackModuleVersion = resolveNetworkStackModuleVersion(context);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.wtf(TAG, "Failed to resolve networkstack module version: " + e);
+            return DEFAULT_PACKAGE_VERSION;
+        }
+        return sNetworkStackModuleVersion;
+    }
+
+    private static long resolveNetworkStackModuleVersion(@NonNull Context context)
+            throws PackageManager.NameNotFoundException {
+        // TODO(b/293975546): Strictly speaking this is the prefix for connectivity and not
+        //  network stack. In practice, it's the same. Read the prefix from network stack instead.
+        final String pkgPrefix = resolvePkgPrefix(context);
+        final PackageManager packageManager = context.getPackageManager();
+        try {
+            return packageManager.getPackageInfo(pkgPrefix + "networkstack",
+                    PackageManager.MATCH_SYSTEM_ONLY).getLongVersionCode();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.d(TAG, "Device is using go or non-mainline modules");
+            // fall through
+        }
+
+        return packageManager.getPackageInfo(pkgPrefix + "go.networkstack",
+                PackageManager.MATCH_ALL).getLongVersionCode();
+    }
+
+    /**
+     * Check whether one specific feature is supported from the feature Id. The feature Id is
+     * composed by a module package Id and version Id from {@link FeatureVersions}.
+     *
+     * This is useful when a feature required minimal module version supported and cannot function
+     * well with a standalone newer module.
+     * @param context The global context information about an app environment.
+     * @param featureId The feature id that contains required module id and minimal module version
+     * @return true if this feature is supported, or false if not supported.
+     **/
+    public static boolean isFeatureSupported(@NonNull Context context, long featureId) {
+        final long moduleVersion;
+        final long moduleId = featureId & MODULE_MASK;
+        if (moduleId == CONNECTIVITY_MODULE_ID) {
+            moduleVersion = getTetheringModuleVersion(context);
+        } else if (moduleId == NETWORK_STACK_MODULE_ID) {
+            moduleVersion = getNetworkStackModuleVersion(context);
+        } else {
+            throw new IllegalArgumentException("Unknown module " + moduleId);
+        }
+        // Support by default if no module version is available.
+        return moduleVersion == DEFAULT_PACKAGE_VERSION
+                || moduleVersion >= (featureId & VERSION_MASK);
+    }
+
+    /**
+     * Check whether one specific experimental feature in tethering module from {@link DeviceConfig}
+     * is disabled by setting a non-zero value in the property.
+     *
+     * @param name The name of the property to look up.
+     * @return true if this feature is force disabled, or false if not disabled.
+     */
+    public static boolean isTetheringFeatureForceDisabled(String name) {
+        final int propertyVersion = getDeviceConfigPropertyInt(NAMESPACE_TETHERING, name,
+                0 /* default value */);
+        return propertyVersion != 0;
+    }
+
     /**
      * Gets boolean config from resources.
      */
diff --git a/staticlibs/device/com/android/net/module/util/DomainUtils.java b/staticlibs/device/com/android/net/module/util/DomainUtils.java
new file mode 100644
index 0000000..b327fd0
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/DomainUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import java.nio.BufferOverflowException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/**
+ * Utilities for encoding/decoding the domain name or domain search list.
+ *
+ * @hide
+ */
+public final class DomainUtils {
+    private static final String TAG = "DomainUtils";
+    private static final int MAX_OPTION_LEN = 255;
+
+    @NonNull
+    private static String getSubstring(@NonNull final String string, @NonNull final String[] labels,
+            int index) {
+        int beginIndex = 0;
+        for (int i = 0; i < index; i++) {
+            beginIndex += labels[i].length() + 1; // include the dot
+        }
+        return string.substring(beginIndex);
+    }
+
+    /**
+     * Encode the given single domain name to byte array, comply with RFC1035 section-3.1.
+     *
+     * @return null if the given domain string is invalid, otherwise, return a byte array
+     *         wrapping the encoded domain, not including any padded octets, caller should
+     *         pad zero octets at the end if needed.
+     */
+    @Nullable
+    public static byte[] encode(@NonNull final String domain) {
+        if (!DnsRecordParser.isHostName(domain)) return null;
+        return encode(new String[]{ domain }, false /* compression */);
+    }
+
+    /**
+     * Encode the given multiple domain names to byte array, comply with RFC1035 section-3.1
+     * and section 4.1.4 (message compression) if enabled.
+     *
+     * @return Null if encode fails due to BufferOverflowException, otherwise, return a byte
+     *         array wrapping the encoded domains, not including any padded octets, caller
+     *         should pad zero octets at the end if needed. The byte array may be empty if
+     *         the given domain strings are invalid.
+     */
+    @Nullable
+    public static byte[] encode(@NonNull final String[] domains, boolean compression) {
+        try {
+            final ByteBuffer buffer = ByteBuffer.allocate(MAX_OPTION_LEN);
+            final ArrayMap<String, Integer> offsetMap = new ArrayMap<>();
+            for (int i = 0; i < domains.length; i++) {
+                if (!DnsRecordParser.isHostName(domains[i])) {
+                    Log.e(TAG, "Skip invalid domain name " + domains[i]);
+                    continue;
+                }
+                final String[] labels = domains[i].split("\\.");
+                for (int j = 0; j < labels.length; j++) {
+                    if (compression) {
+                        final String suffix = getSubstring(domains[i], labels, j);
+                        if (offsetMap.containsKey(suffix)) {
+                            int offsetOfSuffix = offsetMap.get(suffix);
+                            offsetOfSuffix |= 0xC000;
+                            buffer.putShort((short) offsetOfSuffix);
+                            break; // unnecessary to put the compressed string into map
+                        } else {
+                            offsetMap.put(suffix, buffer.position());
+                        }
+                    }
+                    // encode the domain name string without compression when:
+                    // - compression feature isn't enabled,
+                    // - suffix does not match any string in the map.
+                    final byte[] labelBytes = labels[j].getBytes(StandardCharsets.UTF_8);
+                    buffer.put((byte) labelBytes.length);
+                    buffer.put(labelBytes);
+                    if (j == labels.length - 1) {
+                        // Pad terminate label at the end of last label.
+                        buffer.put((byte) 0);
+                    }
+                }
+            }
+            buffer.flip();
+            final byte[] out = new byte[buffer.limit()];
+            buffer.get(out);
+            return out;
+        } catch (BufferOverflowException e) {
+            Log.e(TAG, "Fail to encode domain name and stop encoding", e);
+            return null;
+        }
+    }
+
+    /**
+     * Decode domain name(s) from the given byteBuffer. Decode follows RFC1035 section 3.1 and
+     * section 4.1.4(message compression).
+     *
+     * @return domain name(s) string array with space separated, or empty string if decode fails.
+     */
+    @NonNull
+    public static ArrayList<String> decode(@NonNull final ByteBuffer buffer, boolean compression) {
+        final ArrayList<String> domainList = new ArrayList<>();
+        while (buffer.remaining() > 0) {
+            try {
+                // TODO: replace the recursion with loop in parseName and don't need to pass in the
+                // maxLabelCount parameter to prevent recursion from overflowing stack.
+                final String domain = DnsRecordParser.parseName(buffer, 0 /* depth */,
+                        15 /* maxLabelCount */, compression);
+                if (!DnsRecordParser.isHostName(domain)) continue;
+                domainList.add(domain);
+            } catch (BufferUnderflowException | DnsPacket.ParseException e) {
+                Log.e(TAG, "Fail to parse domain name and stop parsing", e);
+                break;
+            }
+        }
+        return domainList;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/FeatureVersions.java b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
new file mode 100644
index 0000000..4986a58
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/FeatureVersions.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Class to centralize feature version control that requires a specific module or a specific
+ * module version.
+ * @hide
+ */
+public class FeatureVersions {
+    public static final long MODULE_MASK = 0xFF00_000000000L;
+    public static final long VERSION_MASK = 0x0000_FFFFFFFFFL;
+    public static final long CONNECTIVITY_MODULE_ID = 0x0100_000000000L;
+    public static final long NETWORK_STACK_MODULE_ID = 0x0200_000000000L;
+    // CLAT_ADDRESS_TRANSLATE is a feature of the network stack, which doesn't throw when system
+    // try to add a NAT-T keepalive packet filter with v6 address, introduced in version
+    // M-2023-Sept on July 3rd, 2023.
+    public static final long FEATURE_CLAT_ADDRESS_TRANSLATE =
+            NETWORK_STACK_MODULE_ID + 340900000L;
+}
diff --git a/staticlibs/device/com/android/net/module/util/SharedLog.java b/staticlibs/device/com/android/net/module/util/SharedLog.java
index 17b061e..6b12c80 100644
--- a/staticlibs/device/com/android/net/module/util/SharedLog.java
+++ b/staticlibs/device/com/android/net/module/util/SharedLog.java
@@ -46,6 +46,8 @@
         ERROR,
         MARK,
         WARN,
+        VERBOSE,
+        TERRIBLE,
     }
 
     private final LocalLog mLocalLog;
@@ -159,6 +161,41 @@
         Log.w(mTag, record(Category.WARN, msg));
     }
 
+    /**
+     * Log a verbose message.
+     *
+     * <p>The log entry will be also added to the system log.
+     */
+    public void v(String msg) {
+        Log.v(mTag, record(Category.VERBOSE, msg));
+    }
+
+    /**
+     * Log a terrible failure message.
+     *
+     * <p>The log entry will be also added to the system log and will trigger system reporting
+     * for terrible failures.
+     */
+    public void wtf(String msg) {
+        Log.wtf(mTag, record(Category.TERRIBLE, msg));
+    }
+
+    /**
+     * Log a terrible failure due to an exception, with the exception stacktrace if provided.
+     *
+     * <p>The error and exception message appear in the shared log, but the stacktrace is only
+     * logged in general log output (logcat). The log entry will be also added to the system log
+     * and will trigger system reporting for terrible failures.
+     */
+    public void wtf(@NonNull String msg, @Nullable Throwable exception) {
+        if (exception == null) {
+            e(msg);
+            return;
+        }
+        Log.wtf(mTag, record(Category.TERRIBLE, msg + ": " + exception.getMessage()), exception);
+    }
+
+
     //////
     // Methods that only log an entry (and do NOT emit to the system log).
     //////
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 0f7bd80..33bd36d 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -33,6 +33,7 @@
 import android.net.util.SocketUtils;
 import android.system.ErrnoException;
 import android.system.Os;
+import android.system.OsConstants;
 import android.system.StructTimeval;
 import android.util.Log;
 
@@ -196,6 +197,27 @@
     }
 
     /**
+     * Send an RTM_DELADDR message to kernel to delete an IPv6 address.
+     *
+     * @param ifIndex interface index.
+     * @param ip IPv6 address to be deleted.
+     * @param prefixlen IPv6 address prefix length.
+     */
+    public static boolean sendRtmDelAddressRequest(int ifIndex, final Inet6Address ip,
+            short prefixlen) {
+        Objects.requireNonNull(ip, "IPv6 address to be deleted should not be null.");
+        final byte[] msg = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqNo*/, ip,
+                prefixlen, ifIndex);
+        try {
+            NetlinkUtils.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
+            return true;
+        } catch (ErrnoException e) {
+            Log.e(TAG, "Fail to send RTM_DELADDR to delete " + ip.getHostAddress(), e);
+            return false;
+        }
+    }
+
+    /**
      * Create netlink socket with the given netlink protocol type.
      *
      * @return fd the fileDescriptor of the socket.
@@ -270,5 +292,20 @@
         return Os.write(fd, bytes, offset, count);
     }
 
+    private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
+
+    /**
+     * Convert the system time in clock ticks(clock_t type in times(), not in clock()) to
+     * milliseconds. times() clock_t ticks at the kernel's USER_HZ (100) while clock() clock_t
+     * ticks at CLOCKS_PER_SEC (1000000).
+     *
+     * See the NOTES on https://man7.org/linux/man-pages/man2/times.2.html for the difference
+     * of clock_t used in clock() and times().
+     */
+    public static long ticksToMilliSeconds(int intClockTicks) {
+        final long longClockTicks = intClockTicks & 0xffffffffL;
+        return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
+    }
+
     private NetlinkUtils() {}
 }
diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
index 2829b92..cbe0ab0 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -161,7 +161,7 @@
     }
 
     /**
-     * A convenience method to create an RTM_NEWADDR message.
+     * A convenience method to create a RTM_NEWADDR message.
      */
     public static byte[] newRtmNewAddressMessage(int seqNo, @NonNull final InetAddress ip,
             short prefixlen, int flags, byte scope, int ifIndex, long preferred, long valid) {
@@ -192,6 +192,51 @@
         return bytes;
     }
 
+    /**
+     * A convenience method to create a RTM_DELADDR message.
+     */
+    public static byte[] newRtmDelAddressMessage(int seqNo, @NonNull final InetAddress ip,
+            short prefixlen, int ifIndex) {
+        Objects.requireNonNull(ip, "IP address to be deleted via netlink message cannot be null");
+
+        final int ifaAddrAttrLength = NetlinkConstants.alignedLengthOf(
+                StructNlAttr.NLA_HEADERLEN + ip.getAddress().length);
+        final int length = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE
+                + ifaAddrAttrLength;
+        final byte[] bytes = new byte[length];
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+        byteBuffer.order(ByteOrder.nativeOrder());
+
+        final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+        nlmsghdr.nlmsg_len = length;
+        nlmsghdr.nlmsg_type = NetlinkConstants.RTM_DELADDR;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+        nlmsghdr.nlmsg_seq = seqNo;
+        nlmsghdr.pack(byteBuffer);
+
+        final byte family =
+                (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET);
+        // Actually kernel ignores scope and flags(only deal with IFA_F_MANAGETEMPADDR, it
+        // indicates that all relevant IPv6 temporary addresses should be deleted as well when
+        // user space intends to delete a global IPv6 address with IFA_F_MANAGETEMPADDR), so
+        // far IFA_F_MANAGETEMPADDR flag isn't used in user space, it's fine to ignore it.
+        // However, we need to add IFA_FLAGS attribute in RTM_DELADDR if flags parsing should
+        // be supported in the future.
+        final StructIfaddrMsg ifaddrmsg = new StructIfaddrMsg(family, prefixlen,
+                (short) 0 /* flags */, (short) 0 /* scope */, ifIndex);
+        ifaddrmsg.pack(byteBuffer);
+
+        final StructNlAttr address = new StructNlAttr(IFA_ADDRESS, ip);
+        address.pack(byteBuffer);
+
+        return bytes;
+    }
+
+    // This function helper gives the required buffer size for IFA_ADDRESS, IFA_CACHEINFO and
+    // IFA_FLAGS attributes encapsulation. However, that's not a mandatory requirement for all
+    // RtNetlinkAddressMessage, e.g. RTM_DELADDR sent from user space to kernel to delete an
+    // IP address only requires IFA_ADDRESS attribute. The caller should check if these attributes
+    // are necessary to carry when constructing a RtNetlinkAddressMessage.
     private int getRequiredSpace() {
         int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructIfaddrMsg.STRUCT_SIZE;
         // IFA_ADDRESS attr
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/StructIfaddrMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
index 2802726..f9781a7 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
@@ -18,7 +18,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.Field;
@@ -50,7 +49,6 @@
     @Field(order = 4, type = Type.S32)
     public final int index;
 
-    @VisibleForTesting
     public StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) {
         this.family = family;
         this.prefixLen = prefixLen;
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
index 79d5ff4..1f9bb7e 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java
@@ -16,8 +16,7 @@
 
 package com.android.net.module.util.netlink;
 
-import android.system.Os;
-import android.system.OsConstants;
+import static com.android.net.module.util.netlink.NetlinkUtils.ticksToMilliSeconds;
 
 import java.nio.ByteBuffer;
 
@@ -57,15 +56,6 @@
         return struct;
     }
 
-    // TODO: investigate whether this can change during device runtime and
-    // decide what (if anything) should be done about that.
-    private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK);
-
-    private static long ticksToMilliSeconds(int intClockTicks) {
-        final long longClockTicks = (long) intClockTicks & 0xffffffff;
-        return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND;
-    }
-
     /**
      * Explanatory notes, for reference.
      *
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/Ipv6PktInfo.java b/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java
new file mode 100644
index 0000000..0dccb72
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv6PktInfo.java
@@ -0,0 +1,42 @@
+/*
+ * 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.structs;
+
+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.net.Inet6Address;
+
+/**
+ * structure in6_pktinfo
+ *
+ * see also:
+ *
+ *     include/uapi/linux/ipv6.h
+ */
+public class Ipv6PktInfo extends Struct {
+    @Field(order = 0, type = Type.Ipv6Address)
+    public final Inet6Address addr; // IPv6 source or destination address
+    @Field(order = 1, type = Type.S32)
+    public final int ifindex;       // interface index
+
+    public Ipv6PktInfo(final Inet6Address addr, final int ifindex) {
+        this.addr = addr;
+        this.ifindex = ifindex;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java
new file mode 100644
index 0000000..49bafed
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/RouteInformationOption.java
@@ -0,0 +1,97 @@
+/*
+ * 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.structs;
+
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_RIO;
+
+import android.net.IpPrefix;
+
+import androidx.annotation.NonNull;
+
+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;
+import java.nio.ByteOrder;
+
+/**
+ * ICMPv6 route information option, as per https://tools.ietf.org/html/rfc4191.
+ *
+ * 0                   1                   2                   3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |     Type      |    Length     | Prefix Length |Resvd|Prf|Resvd|
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        Route Lifetime                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                   Prefix (Variable Length)                    |
+ * .                                                               .
+ * .                                                               .
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class RouteInformationOption extends Struct {
+    public enum Preference {
+        HIGH((byte) 0x1),
+        MEDIUM((byte) 0x0),
+        LOW((byte) 0x3),
+        RESERVED((byte) 0x2);
+
+        final byte mValue;
+        Preference(byte value) {
+            this.mValue = value;
+        }
+    }
+
+    @Field(order = 0, type = Type.S8)
+    public final byte type;
+    @Field(order = 1, type = Type.S8)
+    public final byte length; // Length in 8-byte octets
+    @Field(order = 2, type = Type.U8)
+    public final short prefixLen;
+    @Field(order = 3, type = Type.S8)
+    public final byte prf;
+    @Field(order = 4, type = Type.U32)
+    public final long routeLifetime;
+    @Field(order = 5, type = Type.ByteArray, arraysize = 16)
+    public final byte[] prefix;
+
+    RouteInformationOption(final byte type, final byte length, final short prefixLen,
+            final byte prf, final long routeLifetime, @NonNull final byte[] prefix) {
+        this.type = type;
+        this.length = length;
+        this.prefixLen = prefixLen;
+        this.prf = prf;
+        this.routeLifetime = routeLifetime;
+        this.prefix = prefix;
+    }
+
+    /**
+     * Build a Route Information option from the required specified parameters.
+     */
+    public static ByteBuffer build(final IpPrefix prefix, final Preference prf,
+            final long routeLifetime) {
+        // The prefix field is always assumed to have 16 bytes, but the number of leading
+        // bits in this prefix depends on IpPrefix#prefixLength, then we can simply set the
+        // option length to 3.
+        final RouteInformationOption option = new RouteInformationOption(
+                (byte) ICMPV6_ND_OPTION_RIO, (byte) 3 /* option length */,
+                (short) prefix.getPrefixLength(), (byte) (prf.mValue << 3), routeLifetime,
+                prefix.getRawAddress());
+        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 79ca3a3..0dcdf1e 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -19,7 +19,6 @@
 import static android.net.DnsResolver.TYPE_A;
 import static android.net.DnsResolver.TYPE_AAAA;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
 import static com.android.net.module.util.DnsPacketUtils.DnsRecordParser.domainNameToLabels;
 
@@ -245,9 +244,11 @@
      *     /                     RDATA                     /
      *     /                                               /
      *     +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
+     *
+     * Note that this class is meant to be used by composition and not inheritance, and
+     * that classes implementing more specific DNS records should call #parse.
      */
-    // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord, and construct
-    //  corresponding object from factory methods.
+    // TODO: Make DnsResourceRecord and DnsQuestion subclasses of DnsRecord.
     public static class DnsRecord {
         // Refer to RFC 1035 section 2.3.4 for MAXNAMESIZE.
         // NAME_NORMAL and NAME_COMPRESSION are used for checking name compression,
@@ -281,8 +282,7 @@
          * @param buf ByteBuffer input of record, must be in network byte order
          *         (which is the default).
          */
-        @VisibleForTesting(visibility = PACKAGE)
-        public DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
+        private DnsRecord(@RecordType int rType, @NonNull ByteBuffer buf)
                 throws BufferUnderflowException, ParseException {
             Objects.requireNonNull(buf);
             this.rType = rType;
@@ -307,6 +307,31 @@
         }
 
         /**
+         * Create a new DnsRecord or subclass of DnsRecord instance from a positioned ByteBuffer.
+         *
+         * Peek the nsType, sending the buffer to corresponding DnsRecord subclass constructors
+         * to allow constructing the corresponding object.
+         */
+        @VisibleForTesting(visibility = PRIVATE)
+        public static DnsRecord parse(@RecordType int rType, @NonNull ByteBuffer buf)
+                throws BufferUnderflowException, ParseException {
+            Objects.requireNonNull(buf);
+            final int oldPos = buf.position();
+            // Parsed name not used, just for jumping to nsType position.
+            DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+                    true /* isNameCompressionSupported */);
+            // Peek the nsType.
+            final int nsType = Short.toUnsignedInt(buf.getShort());
+            buf.position(oldPos);
+            // Return a DnsRecord instance by default for backward compatibility, this is useful
+            // when a partner supports new type of DnsRecord but does not inherit DnsRecord.
+            switch (nsType) {
+                default:
+                    return new DnsRecord(rType, buf);
+            }
+        }
+
+        /**
          * Make an A or AAAA record based on the specified parameters.
          *
          * @param rType Type of the record, can be {@link #ANSECTION}, {@link #ARSECTION}
@@ -507,7 +532,7 @@
             mRecords[i] = new ArrayList(count);
             for (int j = 0; j < count; ++j) {
                 try {
-                    mRecords[i].add(new DnsRecord(i, buffer));
+                    mRecords[i].add(DnsRecord.parse(i, buffer));
                 } catch (BufferUnderflowException e) {
                     throw new ParseException("Parse record fail", e);
                 }
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
index c47bfa0..105d783 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -130,16 +130,25 @@
 
         /**
          * Parses the domain / target name of a DNS record.
+         */
+        public static String parseName(final ByteBuffer buf, int depth,
+                boolean isNameCompressionSupported) throws
+                BufferUnderflowException, DnsPacket.ParseException {
+            return parseName(buf, depth, MAXLABELCOUNT, isNameCompressionSupported);
+        }
+
+        /**
+         * Parses the domain / target name of a DNS record.
          *
          * As described in RFC 1035 Section 4.1.3, the NAME field of a DNS Resource Record always
          * supports Name Compression, whereas domain names contained in the RDATA payload of a DNS
          * record may or may not support Name Compression, depending on the record TYPE. Moreover,
          * even if Name Compression is supported, its usage is left to the implementation.
          */
-        public static String parseName(ByteBuffer buf, int depth,
+        public static String parseName(final ByteBuffer buf, int depth, int maxLabelCount,
                 boolean isNameCompressionSupported) throws
                 BufferUnderflowException, DnsPacket.ParseException {
-            if (depth > MAXLABELCOUNT) {
+            if (depth > maxLabelCount) {
                 throw new DnsPacket.ParseException("Failed to parse name, too many labels");
             }
             final int len = Byte.toUnsignedInt(buf.get());
@@ -158,7 +167,8 @@
                             "Parse compression name fail, invalid compression");
                 }
                 buf.position(offset);
-                final String pointed = parseName(buf, depth + 1, isNameCompressionSupported);
+                final String pointed = parseName(buf, depth + 1, maxLabelCount,
+                        isNameCompressionSupported);
                 buf.position(oldPos);
                 return pointed;
             } else {
@@ -168,7 +178,8 @@
                 if (head.length() > MAXLABELSIZE) {
                     throw new DnsPacket.ParseException("Parse name fail, invalid label length");
                 }
-                final String tail = parseName(buf, depth + 1, isNameCompressionSupported);
+                final String tail = parseName(buf, depth + 1, maxLabelCount,
+                        isNameCompressionSupported);
                 return TextUtils.isEmpty(tail) ? head : head + "." + tail;
             }
         }
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 54382f9..94f355a 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -152,6 +152,7 @@
      * ICMPv6 constants.
      *
      * See also:
+     *     - https://tools.ietf.org/html/rfc4191
      *     - https://tools.ietf.org/html/rfc4443
      *     - https://tools.ietf.org/html/rfc4861
      */
@@ -169,6 +170,7 @@
     public static final int ICMPV6_ND_OPTION_TLLA  = 2;
     public static final int ICMPV6_ND_OPTION_PIO   = 3;
     public static final int ICMPV6_ND_OPTION_MTU   = 5;
+    public static final int ICMPV6_ND_OPTION_RIO   = 24;
     public static final int ICMPV6_ND_OPTION_RDNSS = 25;
     public static final int ICMPV6_ND_OPTION_PREF64 = 38;
 
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
index 9b38dee..dd0804c 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
@@ -22,11 +22,25 @@
 // Reject the packet
 #define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0)
 
+// *TWO* instructions: compare and if not equal jump over the accept statement
+#define BPF2_ACCEPT_IF_EQUAL(v) \
+	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, 1), \
+	BPF_ACCEPT
+
 // *TWO* instructions: compare and if equal jump over the reject statement
 #define BPF2_REJECT_IF_NOT_EQUAL(v) \
 	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \
 	BPF_REJECT
 
+// *TWO* instructions: compare and if none of the bits are set jump over the reject statement
+#define BPF2_REJECT_IF_ANY_BITS_SET(v) \
+	BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, (v), 0, 1), \
+	BPF_REJECT
+
+// loads skb->protocol
+#define BPF_LOAD_SKB_PROTOCOL \
+	BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_AD_OFF + SKF_AD_PROTOCOL)
+
 // 8-bit load relative to start of link layer (mac/ethernet) header.
 #define BPF_LOAD_MAC_RELATIVE_U8(ofs) \
 	BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
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/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
index b75939b..7946244 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DeviceConfigUtilsTest.java
@@ -17,12 +17,16 @@
 package com.android.net.module.util;
 
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.provider.DeviceConfig.NAMESPACE_TETHERING;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.net.module.util.FeatureVersions.CONNECTIVITY_MODULE_ID;
+import static com.android.net.module.util.FeatureVersions.NETWORK_STACK_MODULE_ID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.anyInt;
@@ -84,6 +88,8 @@
     private static final String TEST_GO_APEX_PACKAGE_NAME = "com.prefix.android.go.tethering";
     private static final String TEST_CONNRES_PACKAGE_NAME =
             "com.prefix.android.connectivity.resources";
+    private static final String TEST_NETWORKSTACK_NAME = "com.prefix.android.networkstack";
+    private static final String TEST_GO_NETWORKSTACK_NAME = "com.prefix.android.go.networkstack";
     private final PackageInfo mPackageInfo = new PackageInfo();
     private final PackageInfo mApexPackageInfo = new PackageInfo();
     private MockitoSession mSession;
@@ -246,10 +252,34 @@
     @Test
     public void testFeatureIsEnabledWithException() throws Exception {
         doThrow(NameNotFoundException.class).when(mPm).getPackageInfo(anyString(), anyInt());
+
+        // Feature should be enabled by flag value "1".
+        doReturn("1").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+        // Feature should be disabled by flag value "999999999".
+        doReturn("999999999").when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG));
         assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
                 TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+
+        // Follow defaultEnabled if the flag is not set
+        doReturn(null).when(() -> DeviceConfig.getProperty(eq(TEST_NAME_SPACE),
+                eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, false /* defaultEnabled */));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, true /* defaultEnabled */));
+        assertFalse(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, false /* defaultEnabled */));
+        assertTrue(DeviceConfigUtils.isFeatureEnabled(mContext, TEST_NAME_SPACE,
+                TEST_EXPERIMENT_FLAG, TEST_APEX_NAME, true /* defaultEnabled */));
     }
 
     @Test
@@ -322,4 +352,82 @@
         doThrow(new Resources.NotFoundException()).when(mResources).getInteger(someResId);
         assertEquals(2098, DeviceConfigUtils.getResIntegerConfig(mContext, someResId, 2098));
     }
+
+    @Test
+    public void testGetNetworkStackModuleVersionCaching() throws Exception {
+        final PackageInfo networkStackPackageInfo = new PackageInfo();
+        networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        doReturn(networkStackPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_NETWORKSTACK_NAME), anyInt());
+        assertEquals(TEST_PACKAGE_VERSION,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+
+        assertEquals(TEST_PACKAGE_VERSION,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+        // Package info is only queried once
+        verify(mPm, times(1)).getPackageInfo(anyString(), anyInt());
+        verify(mContext, never()).getPackageName();
+    }
+
+    @Test
+    public void testGetNetworkStackModuleVersionOnNonMainline() {
+        assertEquals(DeviceConfigUtils.DEFAULT_PACKAGE_VERSION,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+    }
+
+    @Test
+    public void testGetNetworkStackModuleVersion() throws Exception {
+        final PackageInfo networkStackPackageInfo = new PackageInfo();
+        final PackageInfo goNetworkStackPackageInfo = new PackageInfo();
+        networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        goNetworkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION + 1);
+        doReturn(goNetworkStackPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_NETWORKSTACK_NAME), anyInt());
+        // Verify the returned value is go module version.
+        assertEquals(TEST_PACKAGE_VERSION + 1,
+                DeviceConfigUtils.getNetworkStackModuleVersion(mContext));
+    }
+
+    @Test
+    public void testIsFeatureSupported_networkStackFeature() throws Exception {
+        // Supported for DEFAULT_PACKAGE_VERSION
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID));
+
+        final PackageInfo networkStackPackageInfo = new PackageInfo();
+        networkStackPackageInfo.setLongVersionCode(TEST_PACKAGE_VERSION);
+        doReturn(networkStackPackageInfo).when(mPm).getPackageInfo(
+                eq(TEST_NETWORKSTACK_NAME), anyInt());
+
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID));
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + NETWORK_STACK_MODULE_ID + 1));
+    }
+
+    @Test
+    public void testIsFeatureSupported_tetheringFeature() throws Exception {
+        assertTrue(DeviceConfigUtils.isFeatureSupported(
+                mContext, TEST_PACKAGE_VERSION + CONNECTIVITY_MODULE_ID));
+        // Return false because feature requires a future version.
+        assertFalse(DeviceConfigUtils.isFeatureSupported(
+                mContext, 889900000L + CONNECTIVITY_MODULE_ID));
+    }
+
+    @Test
+    public void testIsFeatureSupported_illegalModule() throws Exception {
+        assertThrows(IllegalArgumentException.class,
+                () -> DeviceConfigUtils.isFeatureSupported(mContext, TEST_PACKAGE_VERSION));
+    }
+
+    @Test
+    public void testIsTetheringFeatureForceDisabled() throws Exception {
+        doReturn("0").when(() -> DeviceConfig.getProperty(
+                eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
+        assertFalse(DeviceConfigUtils.isTetheringFeatureForceDisabled(TEST_EXPERIMENT_FLAG));
+
+        doReturn(TEST_FLAG_VALUE_STRING).when(
+                () -> DeviceConfig.getProperty(eq(NAMESPACE_TETHERING), eq(TEST_EXPERIMENT_FLAG)));
+        assertTrue(DeviceConfigUtils.isTetheringFeatureForceDisabled(TEST_EXPERIMENT_FLAG));
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
index 409c1eb..28e183a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
@@ -219,7 +219,7 @@
                 0x00, 0x04, /* Data length */
                 (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */};
         final DnsPacket.DnsRecord questionsFromBytes =
-                new DnsPacket.DnsRecord(DnsPacket.QDSECTION, ByteBuffer.wrap(qdWithTTLRData));
+                DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION, ByteBuffer.wrap(qdWithTTLRData));
         assertEquals(0, questionsFromBytes.ttl);
         assertNull(questionsFromBytes.getRR());
 
@@ -230,12 +230,12 @@
                 0x00, 0x01, /* Type */
                 0x00, 0x01, /* Class */};
         assertThrows(BufferUnderflowException.class, () ->
-                new DnsPacket.DnsRecord(DnsPacket.ANSECTION, ByteBuffer.wrap(anWithoutTTLRData)));
+                DnsPacket.DnsRecord.parse(DnsPacket.ANSECTION, ByteBuffer.wrap(anWithoutTTLRData)));
     }
 
     private void assertDnsRecordRoundTrip(DnsPacket.DnsRecord before)
             throws IOException {
-        final DnsPacket.DnsRecord after = new DnsPacket.DnsRecord(before.rType,
+        final DnsPacket.DnsRecord after = DnsPacket.DnsRecord.parse(before.rType,
                 ByteBuffer.wrap(before.getBytes()));
         assertEquals(after, before);
     }
@@ -393,7 +393,7 @@
                 "test.com", TYPE_AAAA, CLASS_IN);
         final DnsPacket.DnsRecord testAnswer = DnsPacket.DnsRecord.makeCNameRecord(
                 DnsPacket.ANSECTION, "test.com", CLASS_IN, 9, "www.test.com");
-        final DnsPacket.DnsRecord questionFromBytes = new DnsPacket.DnsRecord(DnsPacket.QDSECTION,
+        final DnsPacket.DnsRecord questionFromBytes = DnsPacket.DnsRecord.parse(DnsPacket.QDSECTION,
                 ByteBuffer.wrap(testQuestion.getBytes()));
         assertEquals(testQuestion, questionFromBytes);
         assertEquals(testQuestion.hashCode(), questionFromBytes.hashCode());
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
new file mode 100644
index 0000000..5eaf2ad
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DomainUtilsTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import libcore.util.HexEncoding;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DomainUtilsTest {
+    @Test
+    public void testEncodeInvalidDomain() {
+        byte[] buffer = DomainUtils.encode(".google.com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google.com.");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("-google.com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google.com-");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google..com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google!.com");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google.o");
+        assertNull(buffer);
+
+        buffer = DomainUtils.encode("google,com");
+        assertNull(buffer);
+    }
+
+    @Test
+    public void testEncodeValidDomainNamesWithoutCompression() {
+        // Single domain: "google.com"
+        String suffix = "06676F6F676C6503636F6D00";
+        byte[] buffer = DomainUtils.encode("google.com");
+        //assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // Single domain: "google-guest.com"
+        suffix = "0C676F6F676C652D677565737403636F6D00";
+        buffer = DomainUtils.encode("google-guest.com");
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "04636F727006676F6F676C6503636F6D00"                // corp.google.com
+                + "06676F6F676C6503636F6D00";                         // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "corp.google.com", "google.com"},
+                false /* compression */);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+
+        // domain search list: "example.corp.google.com", "corp..google.com"(invalid domain),
+        // "google.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "06676F6F676C6503636F6D00";                         // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "corp..google.com", "google.com"},
+                false /* compression */);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // Invalid domain search list: "corp..google.com", "..google.com"
+        buffer = DomainUtils.encode(new String[] {"corp..google.com", "..google.com"},
+                false /* compression */);
+        assertEquals(0, buffer.length);
+    }
+
+    @Test
+    public void testEncodeValidDomainNamesWithCompression() {
+        // domain search list: "example.corp.google.com", "corp.google.com", "google.com"
+        String suffix =
+                "076578616D706C6504636F727006676F6F676C6503636F6D00"  // example.corp.google.com
+                + "C008"                                              // corp.google.com
+                + "C00D";                                             // google.com
+        byte[] buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "corp.google.com", "google.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "a.example.corp.google.com", "google.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "0161C000"                                          // a.example.corp.google.com
+                + "C00D";                                             // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "a.example.corp.google.com", "google.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "google.com", "gle.com"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "C00D"                                              // google.com
+                + "03676C65C014";                                     // gle.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "google.com", "gle.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "google.com", "google"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "C00D";                                              // google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "google.com", "google"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "..google.com"(invalid domain), "google"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00"; // example.corp.google.com
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "..google.com", "google"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"
+        suffix = "076578616D706C6504636F727006676F6F676C6503636F6D00" // example.corp.google.com
+                + "06737566666978076578616D706C650365647502636E00"    // suffix.example.edu.cn
+                + "C028";                                             // edu.cn
+        buffer = DomainUtils.encode(new String[] {
+                "example.corp.google.com", "suffix.example.edu.cn", "edu.cn"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+
+        // domain search list: "google.com", "example.com", "sub.example.com"
+        suffix = "06676F6F676C6503636F6D00"                           // google.com
+                + "076578616D706C65C007"                              // example.com
+                + "03737562C00C";                                     // sub.example.com
+        buffer = DomainUtils.encode(new String[] {
+                "google.com", "example.com", "sub.example.com"}, true);
+        assertNotNull(buffer);
+        assertEquals(suffix, HexEncoding.encodeToString(buffer));
+    }
+
+    @Test
+    public void testDecodeDomainNames() {
+        ArrayList<String> suffixStringList;
+        String suffixes = "06676F6F676C6503636F6D00" // google.com
+                + "076578616D706C6503636F6D00"       // example.com
+                + "06676F6F676C6500";                // google
+        List<String> expected = Arrays.asList("google.com", "example.com");
+        ByteBuffer buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+        assertEquals(expected, suffixStringList);
+
+        // include suffix with invalid length: 64
+        suffixes = "06676F6F676C6503636F6D00"        // google.com
+                + "406578616D706C6503636F6D00"       // example.com(length=64)
+                + "06676F6F676C6500";                // google
+        expected = Arrays.asList("google.com");
+        buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+        assertEquals(expected, suffixStringList);
+
+        // include suffix with invalid length: 0
+        suffixes = "06676F6F676C6503636F6D00"         // google.com
+                + "076578616D706C6503636F6D00"        // example.com
+                + "00676F6F676C6500";                 // google(length=0)
+        expected = Arrays.asList("google.com", "example.com");
+        buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, false /* compression */);
+        assertEquals(expected, suffixStringList);
+
+        suffixes =
+                "076578616D706C6504636F727006676F6F676C6503636F6D00"  // example.corp.google.com
+                + "C008"                                              // corp.google.com
+                + "C00D";                                             // google.com
+        expected = Arrays.asList("example.corp.google.com", "corp.google.com", "google.com");
+        buffer = ByteBuffer.wrap(HexEncoding.decode(suffixes));
+        suffixStringList = DomainUtils.decode(buffer, true /* compression */);
+        assertEquals(expected, suffixStringList);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 6fbfbf9..3a72dd1 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -50,6 +50,7 @@
 import org.junit.runner.RunWith;
 
 import java.io.FileDescriptor;
+import java.net.InetAddress;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.nio.file.Files;
@@ -170,7 +171,9 @@
             assertEquals(0, response.position());
             assertEquals(ByteOrder.nativeOrder(), response.order());
 
-            final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(response);
+            final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE);
+            assertNotNull(msg);
+            final StructNlMsgHdr nlmsghdr = msg.getHeader();
             assertNotNull(nlmsghdr);
 
             if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) {
@@ -181,12 +184,17 @@
             assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0);
             assertEquals(testSeqno, nlmsghdr.nlmsg_seq);
             assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid);
+            assertTrue(msg instanceof RtNetlinkAddressMessage);
             addrMessageCount++;
 
-            final IfaddrMsg ifaMsg = Struct.parse(IfaddrMsg.class, response);
+            // From the query response we can see the RTM_NEWADDR messages representing for IPv4
+            // and IPv6 loopback address: 127.0.0.1 and ::1.
+            final StructIfaddrMsg ifaMsg = ((RtNetlinkAddressMessage) msg).getIfaddrHeader();
+            final InetAddress ipAddress = ((RtNetlinkAddressMessage) msg).getIpAddress();
             assertTrue(
                     "Non-IP address family: " + ifaMsg.family,
                     ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6);
+            assertTrue(ipAddress.isLoopbackAddress());
         }
 
         assertTrue(addrMessageCount > 0);
@@ -196,7 +204,7 @@
 
     /** A convenience method to create an RTM_GETADDR request message. */
     private static byte[] newGetAddrRequest(int seqNo) {
-        final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(RtgenMsg.class);
+        final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(StructIfaddrMsg.class);
         final byte[] bytes = new byte[length];
         final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
         byteBuffer.order(ByteOrder.nativeOrder());
@@ -208,38 +216,11 @@
         nlmsghdr.nlmsg_seq = seqNo;
         nlmsghdr.pack(byteBuffer);
 
-        final RtgenMsg rtgenMsg = new RtgenMsg();
-        rtgenMsg.family = (byte) AF_UNSPEC;
-        rtgenMsg.writeToByteBuffer(byteBuffer);
+        final StructIfaddrMsg addrMsg = new StructIfaddrMsg((byte) AF_UNSPEC /* family */,
+                (short) 0 /* prefixLen */, (short) 0 /* flags */, (short) 0 /* scope */,
+                0 /* index */);
+        addrMsg.pack(byteBuffer);
 
         return bytes;
     }
-
-    /** From uapi/linux/rtnetlink.h */
-    private static class RtgenMsg extends Struct {
-        @Field(order = 0, type = Type.U8)
-        public short family;
-    }
-
-    /**
-     * From uapi/linux/ifaddr.h
-     *
-     * Public ensures visibility to Struct class
-     */
-    public static class IfaddrMsg extends Struct {
-        @Field(order = 0, type = Type.U8)
-        public short family;
-
-        @Field(order = 1, type = Type.U8)
-        public short prefixlen;
-
-        @Field(order = 2, type = Type.U8)
-        public short flags;
-
-        @Field(order = 3, type = Type.U8)
-        public short scope;
-
-        @Field(order = 4, type = Type.S32)
-        public int index;
-    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
index 99d96b5..01126d2 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -179,6 +179,34 @@
     }
 
     @Test
+    public void testCreateRtmDelAddressMessage() {
+        // Hexadecimal representation of our created packet.
+        final String expectedDelAddressHex =
+                // struct nlmsghdr
+                "2C000000" + // length = 44
+                "1500" +     // type = 21 (RTM_DELADDR)
+                "0500" +     // flags = NLM_F_ACK | NLM_F_REQUEST
+                "01000000" + // seqno = 1
+                "00000000" + // pid = 0 (send to kernel)
+                // struct IfaddrMsg
+                "0A" +       // family = inet6
+                "40" +       // prefix len = 64
+                "00" +       // flags = 0
+                "00" +       // scope = RT_SCOPE_UNIVERSE
+                "3B000000" + // ifindex = 59
+                // struct nlattr: IFA_ADDRESS
+                "1400" +     // len
+                "0100" +     // type
+                "20010DB8000100000000000000000100"; // IP address = 2001:db8:1::100
+        final byte[] expectedDelAddress =
+                HexEncoding.decode(expectedDelAddressHex.toCharArray(), false);
+
+        final byte[] bytes = RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */,
+                TEST_GLOBAL_ADDRESS, (short) 64 /* prefix len */, 59 /* ifindex */);
+        assertArrayEquals(expectedDelAddress, bytes);
+    }
+
+    @Test
     public void testCreateRtmNewAddressMessage_nullIpAddress() {
         assertThrows(NullPointerException.class,
                 () -> RtNetlinkAddressMessage.newRtmNewAddressMessage(1 /* seqno */,
@@ -189,6 +217,13 @@
     }
 
     @Test
+    public void testCreateRtmDelAddressMessage_nullIpAddress() {
+        assertThrows(NullPointerException.class,
+                () -> RtNetlinkAddressMessage.newRtmDelAddressMessage(1 /* seqno */,
+                        null /* IP address */, (short) 0 /* prefix len */, 59 /* ifindex */));
+    }
+
+    @Test
     public void testCreateRtmNewAddressMessage_u32Flags() {
         // Hexadecimal representation of our created packet.
         final String expectedNewAddressHex =
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/testutils/Android.bp b/staticlibs/testutils/Android.bp
index be4ccfe..d88cb0b 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -57,6 +57,7 @@
     visibility: [
         "//frameworks/libs/net/common/tests:__subpackages__",
         "//frameworks/libs/net/client-libs/tests:__subpackages__",
+        "//packages/modules/Connectivity/tests/cts/hostside",
     ],
     // There are downstream branches using an old version of Kotlin
     // that used to reserve the right to make breaking changes to the
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index 6bcb8fc..f1f0975 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -72,7 +72,7 @@
         val cb = TestableNetworkCallback()
         val cm = context.getSystemService(ConnectivityManager::class.java)
                 ?: fail("Could not get ConnectivityManager")
-        cm.registerNetworkCallback(
+        cm.requestNetwork(
                 NetworkRequest.Builder()
                         .addTransportType(TRANSPORT_CELLULAR)
                         .addCapability(NET_CAPABILITY_INTERNET).build(), cb)
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt b/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.kt
new file mode 100644
index 0000000..9e97d51
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/DnsResolverModuleTest.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 resolv module update.
+ */
+annotation class DnsResolverModuleTest
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt b/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt
new file mode 100644
index 0000000..5952365
--- /dev/null
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/SkipMainlinePresubmit.kt
@@ -0,0 +1,25 @@
+/*
+ * 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
+
+/**
+ * Skip the test in presubmit runs for the reason specified in [reason].
+ *
+ * This annotation is typically used to document limitations that prevent a test from being
+ * executed in presubmit on older builds.
+ */
+annotation class SkipMainlinePresubmit(val reason: String)