Merge "Update net-tests-utils-host-common tag to "mts-networking""
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 68decf6..ea38eca 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -70,6 +70,7 @@
       "androidx.annotation_annotation",
       "framework-annotations-lib",
   ],
+  lint: { strict_updatability_linting: true },
 }
 
 java_defaults {
@@ -83,6 +84,19 @@
     ],
 }
 
+java_library {
+    name: "net-utils-dnspacket-common",
+    srcs: [
+    "framework/**/DnsPacket.java",
+    "framework/**/DnsPacketUtils.java",
+    ],
+    sdk_version: "module_current",
+    visibility: [
+        "//packages/services/Iwlan:__subpackages__",
+    ],
+    libs: ["framework-annotations-lib"],
+}
+
 filegroup {
     name: "net-utils-framework-common-srcs",
     srcs: ["framework/**/*.java"],
@@ -90,7 +104,6 @@
     visibility: [
         "//frameworks/base",
         "//packages/modules/Connectivity:__subpackages__",
-        "//frameworks/base/packages/Connectivity/framework",
     ],
 }
 
@@ -99,6 +112,7 @@
     srcs: [
         "device/com/android/net/module/util/BpfMap.java",
         "device/com/android/net/module/util/JniUtil.java",
+        "device/com/android/net/module/util/TcUtils.java",
     ],
     sdk_version: "system_current",
     min_sdk_version: "29",
@@ -118,6 +132,7 @@
         "com.android.tethering",
         "//apex_available:platform",
     ],
+    lint: { strict_updatability_linting: true },
 }
 
 java_library {
@@ -125,6 +140,7 @@
     srcs: [
         "device/com/android/net/module/util/HexDump.java",
         "device/com/android/net/module/util/Ipv6Utils.java",
+        "device/com/android/net/module/util/PacketBuilder.java",
         "device/com/android/net/module/util/Struct.java",
         "device/com/android/net/module/util/structs/*.java",
     ],
@@ -145,6 +161,7 @@
         "com.android.tethering",
         "//apex_available:platform",
     ],
+    lint: { strict_updatability_linting: true },
 }
 
 java_library {
@@ -169,6 +186,7 @@
         "com.android.tethering",
         "//apex_available:platform",
     ],
+    lint: { strict_updatability_linting: true },
 }
 
 java_library {
@@ -199,6 +217,7 @@
         "com.android.tethering",
         "//apex_available:platform",
     ],
+    lint: { strict_updatability_linting: true },
 }
 
 java_library {
@@ -229,6 +248,7 @@
         "//frameworks/libs/net/common/device",
         "//packages/modules/Wifi/framework/tests:__subpackages__",
     ],
+    lint: { strict_updatability_linting: true },
 }
 filegroup {
     name: "net-utils-services-common-srcs",
@@ -259,7 +279,9 @@
     ],
     visibility: [
         "//frameworks/base/services/net",
+        "//packages/modules/Connectivity/tests:__subpackages__",
     ],
+    lint: { strict_updatability_linting: true },
 }
 
 // Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
diff --git a/staticlibs/device/com/android/net/module/util/PacketBuilder.java b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
new file mode 100644
index 0000000..c908528
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/PacketBuilder.java
@@ -0,0 +1,257 @@
+/*
+ * 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.IpUtils.ipChecksum;
+import static com.android.net.module.util.IpUtils.tcpChecksum;
+import static com.android.net.module.util.IpUtils.udpChecksum;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.TCP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.UDP_CHECKSUM_OFFSET;
+import static com.android.net.module.util.NetworkStackConstants.UDP_LENGTH_OFFSET;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.TcpHeader;
+import com.android.net.module.util.structs.UdpHeader;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+
+/**
+ * The class is used to build a packet.
+ *
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                Layer 2 header (EthernetHeader)                | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                  Layer 3 header (Ipv4Header)                  |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Layer 4 header (TcpHeader, UdpHeader)               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                           Payload                             | (optional)
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Below is a sample code to build a packet.
+ *
+ * // Initialize builder
+ * final ByteBuffer buf = ByteBuffer.allocate(...);
+ * final PacketBuilder pb = new PacketBuilder(buf);
+ * // Write headers
+ * pb.writeL2Header(...);
+ * pb.writeIpHeader(...);
+ * pb.writeTcpHeader(...);
+ * // Write payload
+ * buf.putInt(...);
+ * buf.putShort(...);
+ * buf.putByte(...);
+ * // Finalize and use the packet
+ * pb.finalizePacket();
+ * sendPacket(buf);
+ */
+public class PacketBuilder {
+    private final ByteBuffer mBuffer;
+
+    private int mIpv4HeaderOffset = -1;
+    private int mTcpHeaderOffset = -1;
+    private int mUdpHeaderOffset = -1;
+
+    public PacketBuilder(@NonNull ByteBuffer buffer) {
+        mBuffer = buffer;
+    }
+
+    /**
+     * Write an ethernet header.
+     *
+     * @param srcMac source MAC address
+     * @param dstMac destination MAC address
+     * @param etherType ether type
+     */
+    public void writeL2Header(MacAddress srcMac, MacAddress dstMac, short etherType) throws
+            IOException {
+        final EthernetHeader ethv4Header = new EthernetHeader(dstMac, srcMac, etherType);
+        try {
+            ethv4Header.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write an IPv4 header.
+     * The IP header length and checksum are calculated and written back in #finalizePacket.
+     *
+     * @param tos type of service
+     * @param id the identification
+     * @param flagsAndFragmentOffset flags and fragment offset
+     * @param ttl time to live
+     * @param protocol protocol
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     */
+    public void writeIpv4Header(byte tos, short id, short flagsAndFragmentOffset, byte ttl,
+            byte protocol, @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp)
+            throws IOException {
+        mIpv4HeaderOffset = mBuffer.position();
+        final Ipv4Header ipv4Header = new Ipv4Header(tos,
+                (short) 0 /* totalLength, calculate in #finalizePacket */, id,
+                flagsAndFragmentOffset, ttl, protocol,
+                (short) 0 /* checksum, calculate in #finalizePacket */, srcIp, dstIp);
+
+        try {
+            ipv4Header.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write a TCP header.
+     * The TCP header checksum is calculated and written back in #finalizePacket.
+     *
+     * @param srcPort source port
+     * @param dstPort destination port
+     * @param seq sequence number
+     * @param ack acknowledgement number
+     * @param tcpFlags tcp flags
+     * @param window window size
+     * @param urgentPointer urgent pointer
+     */
+    public void writeTcpHeader(short srcPort, short dstPort, short seq, short ack,
+            byte tcpFlags, short window, short urgentPointer) throws IOException {
+        mTcpHeaderOffset = mBuffer.position();
+        final TcpHeader tcpHeader = new TcpHeader(srcPort, dstPort, seq, ack,
+                (short) ((short) 0x5000 | ((byte) 0x3f & tcpFlags)) /* dataOffsetAndControlBits,
+                dataOffset is always 5(*4bytes) because options not supported */, window,
+                (short) 0 /* checksum, calculate in #finalizePacket */,
+                urgentPointer);
+
+        try {
+            tcpHeader.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Write a UDP header.
+     * The UDP header length and checksum are calculated and written back in #finalizePacket.
+     *
+     * @param srcPort source port
+     * @param dstPort destination port
+     */
+    public void writeUdpHeader(short srcPort, short dstPort) throws IOException {
+        mUdpHeaderOffset = mBuffer.position();
+        final UdpHeader udpHeader = new UdpHeader(srcPort, dstPort,
+                (short) 0 /* length, calculate in #finalizePacket */,
+                (short) 0 /* checksum, calculate in #finalizePacket */);
+
+        try {
+            udpHeader.writeToByteBuffer(mBuffer);
+        } catch (IllegalArgumentException | BufferOverflowException e) {
+            throw new IOException("Error writing to buffer: ", e);
+        }
+    }
+
+    /**
+     * Finalize the packet.
+     *
+     * Call after writing L4 header (no payload) or payload to the buffer used by the builder.
+     * L3 header length, L3 header checksum and L4 header checksum are calculated and written back
+     * after finalization.
+     */
+    @NonNull
+    public ByteBuffer finalizePacket() throws IOException {
+        if (mIpv4HeaderOffset < 0) {
+            // TODO: add support for IPv6
+            throw new IOException("Packet is missing IPv4 header");
+        }
+
+        // Populate the IPv4 totalLength field.
+        mBuffer.putShort(mIpv4HeaderOffset + IPV4_LENGTH_OFFSET,
+                (short) (mBuffer.position() - mIpv4HeaderOffset));
+
+        // Populate the IPv4 header checksum field.
+        mBuffer.putShort(mIpv4HeaderOffset + IPV4_CHECKSUM_OFFSET,
+                ipChecksum(mBuffer, mIpv4HeaderOffset /* headerOffset */));
+
+        if (mTcpHeaderOffset > 0) {
+            // Populate the TCP header checksum field.
+            mBuffer.putShort(mTcpHeaderOffset + TCP_CHECKSUM_OFFSET, tcpChecksum(mBuffer,
+                    mIpv4HeaderOffset /* ipOffset */, mTcpHeaderOffset /* transportOffset */,
+                    mBuffer.position() - mTcpHeaderOffset /* transportLen */));
+        } else if (mUdpHeaderOffset > 0) {
+            // Populate the UDP header length field.
+            mBuffer.putShort(mUdpHeaderOffset + UDP_LENGTH_OFFSET,
+                    (short) (mBuffer.position() - mUdpHeaderOffset));
+
+            // Populate the UDP header checksum field.
+            mBuffer.putShort(mUdpHeaderOffset + UDP_CHECKSUM_OFFSET, udpChecksum(mBuffer,
+                    mIpv4HeaderOffset /* ipOffset */, mUdpHeaderOffset /* transportOffset */));
+        } else {
+            throw new IOException("Packet is missing neither TCP nor UDP header");
+        }
+
+        mBuffer.flip();
+        return mBuffer;
+    }
+
+    /**
+     * Allocate bytebuffer for building the packet.
+     *
+     * @param hasEther has ethernet header. Set this flag to indicate that the packet has an
+     *        ethernet header.
+     * @param l3proto the layer 3 protocol. Only {@code IPPROTO_IP} currently supported.
+     * @param l4proto the layer 4 protocol. Only {@code IPPROTO_TCP} and {@code IPPROTO_UDP}
+     *        currently supported.
+     * @param payloadLen length of the payload.
+     */
+    @NonNull
+    public static ByteBuffer allocate(boolean hasEther, int l3proto, int l4proto, int payloadLen) {
+        if (l3proto != IPPROTO_IP) {
+            // TODO: add support for IPv6
+            throw new IllegalArgumentException("Unsupported layer 3 protocol " + l3proto);
+        }
+
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            throw new IllegalArgumentException("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        if (payloadLen < 0) {
+            throw new IllegalArgumentException("Invalid payload length " + payloadLen);
+        }
+
+        int packetLen = 0;
+        if (hasEther) packetLen += Struct.getSize(EthernetHeader.class);
+        packetLen += Struct.getSize(Ipv4Header.class);
+        packetLen += (l4proto == IPPROTO_TCP) ? Struct.getSize(TcpHeader.class)
+                : Struct.getSize(UdpHeader.class);
+        packetLen += payloadLen;
+
+        return ByteBuffer.allocate(packetLen);
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/TcUtils.java b/staticlibs/device/com/android/net/module/util/TcUtils.java
new file mode 100644
index 0000000..14724d4
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/TcUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.net.module.util;
+
+import java.io.IOException;
+
+/**
+ * Contains mostly tc-related functionality.
+ */
+public class TcUtils {
+    /**
+     * Checks if the network interface uses an ethernet L2 header.
+     *
+     * @param iface the network interface.
+     * @return true if the interface uses an ethernet L2 header.
+     * @throws IOException
+     */
+    public static native boolean isEthernet(String iface) throws IOException;
+
+    /**
+     * Attach a tc bpf filter.
+     *
+     * Equivalent to the following 'tc' command:
+     * tc filter add dev .. in/egress prio .. protocol ipv6/ip bpf object-pinned
+     * /sys/fs/bpf/... direct-action
+     *
+     * @param ifIndex the network interface index.
+     * @param ingress ingress or egress qdisc.
+     * @param prio
+     * @param proto
+     * @param bpfProgPath
+     * @throws IOException
+     */
+    public static native void tcFilterAddDevBpf(int ifIndex, boolean ingress, short prio,
+            short proto, String bpfProgPath) throws IOException;
+
+    /**
+     * Delete a tc filter.
+     *
+     * Equivalent to the following 'tc' command:
+     * tc filter del dev .. in/egress prio .. protocol ..
+     *
+     * @param ifIndex the network interface index.
+     * @param ingress ingress or egress qdisc.
+     * @param prio the filter preference.
+     * @param proto protocol.
+     * @throws IOException
+     */
+    public static native void tcFilterDelDev(int ifIndex, boolean ingress, short prio,
+            short proto) throws IOException;
+}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
index 83a82b7..c44a5b4 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java
@@ -151,6 +151,7 @@
     public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1);
 
     // Device flags.
+    public static final int IFF_UP       = 1 << 0;
     public static final int IFF_LOWER_UP = 1 << 16;
 
     // Known values for struct rtmsg rtm_protocol.
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 a518c76..f7b0d02 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -114,9 +114,10 @@
         // and will overwrite the flags set above.
         byteBuffer.position(baseOffset);
         nlAttr = StructNlAttr.findNextAttrOfType(IFA_FLAGS, byteBuffer);
-        if (nlAttr != null) {
-            addrMsg.mFlags = nlAttr.getValueAsInt(0 /* default value */);
-        }
+        if (nlAttr == null) return null;
+        final Integer value = nlAttr.getValueAsInteger();
+        if (value == null) return null;
+        addrMsg.mFlags = value;
 
         return addrMsg;
     }
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 c5efcb2..1705f1c 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkRouteMessage.java
@@ -22,6 +22,7 @@
 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY;
 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY;
 
+import android.annotation.SuppressLint;
 import android.net.IpPrefix;
 import android.system.OsConstants;
 
@@ -107,6 +108,7 @@
      * @param header netlink message header.
      * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes.
      */
+    @SuppressLint("NewApi")
     @Nullable
     public static RtNetlinkRouteMessage parse(@NonNull final StructNlMsgHdr header,
             @NonNull final ByteBuffer byteBuffer) {
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java
index f6b2e0e..8226346 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java
@@ -16,6 +16,7 @@
 
 package com.android.net.module.util.netlink;
 
+import android.annotation.SuppressLint;
 import android.net.IpPrefix;
 import android.util.Log;
 
@@ -107,6 +108,7 @@
         this.lifetime = lifetime & 0xfff8;
     }
 
+    @SuppressLint("NewApi")
     private StructNdOptPref64(@NonNull ByteBuffer buf) {
         super(buf.get(), Byte.toUnsignedInt(buf.get()));
         if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
index 485e67c..a9b6495 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java
@@ -287,14 +287,22 @@
     }
 
     /**
-     * Get attribute value as Integer.
+     * Get attribute value as Integer, or null if malformed (e.g., length is not 4 bytes).
      */
-    public int getValueAsInt(int defaultValue) {
+    public Integer getValueAsInteger() {
         final ByteBuffer byteBuffer = getValueAsByteBuffer();
         if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
-            return defaultValue;
+            return null;
         }
-        return getValueAsByteBuffer().getInt();
+        return byteBuffer.getInt();
+    }
+
+    /**
+     * Get attribute value as Int, default value if malformed.
+     */
+    public int getValueAsInt(int defaultValue) {
+        final Integer value = getValueAsInteger();
+        return (value != null) ? value : defaultValue;
     }
 
     /**
@@ -341,6 +349,7 @@
     public String getValueAsString() {
         if (nla_value == null) return null;
         // Check the attribute value length after removing string termination flag '\0'.
+        // This assumes that all netlink strings are null-terminated.
         if (nla_value.length < (nla_len - NLA_HEADERLEN - 1)) return null;
 
         try {
diff --git a/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
new file mode 100644
index 0000000..5249454
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/Ipv4Header.java
@@ -0,0 +1,94 @@
+/*
+ * 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.net.module.util.structs;
+
+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;
+
+import java.net.Inet4Address;
+
+/**
+ * L3 IPv4 header as per https://tools.ietf.org/html/rfc791.
+ * This class doesn't contain options field.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |Version|  IHL  |Type of Service|          Total Length         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |         Identification        |Flags|      Fragment Offset    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Time to Live |    Protocol   |         Header Checksum       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                       Source Address                          |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Destination Address                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class Ipv4Header extends Struct {
+    // IP Version=IPv4, IHL is always 5(*4bytes) because options are not supported.
+    @VisibleForTesting
+    public static final byte IPHDR_VERSION_IHL = 0x45;
+
+    @Field(order = 0, type = Type.S8)
+    // version (4 bits), IHL (4 bits)
+    public final byte vi;
+    @Field(order = 1, type = Type.S8)
+    public final byte tos;
+    @Field(order = 2, type = Type.U16)
+    public final int totalLength;
+    @Field(order = 3, type = Type.S16)
+    public final short id;
+    @Field(order = 4, type = Type.S16)
+    // flags (3 bits), fragment offset (13 bits)
+    public final short flagsAndFragmentOffset;
+    @Field(order = 5, type = Type.U8)
+    public final short ttl;
+    @Field(order = 6, type = Type.S8)
+    public final byte protocol;
+    @Field(order = 7, type = Type.S16)
+    public final short checksum;
+    @Field(order = 8, type = Type.Ipv4Address)
+    public final Inet4Address srcIp;
+    @Field(order = 9, type = Type.Ipv4Address)
+    public final Inet4Address dstIp;
+
+    public Ipv4Header(final byte tos, final int totalLength, final short id,
+            final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+            final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+        this(IPHDR_VERSION_IHL, tos, totalLength, id, flagsAndFragmentOffset, ttl,
+                protocol, checksum, srcIp, dstIp);
+    }
+
+    private Ipv4Header(final byte vi, final byte tos, final int totalLength, final short id,
+            final short flagsAndFragmentOffset, final short ttl, final byte protocol,
+            final short checksum, final Inet4Address srcIp, final Inet4Address dstIp) {
+        this.vi = vi;
+        this.tos = tos;
+        this.totalLength = totalLength;
+        this.id = id;
+        this.flagsAndFragmentOffset = flagsAndFragmentOffset;
+        this.ttl = ttl;
+        this.protocol = protocol;
+        this.checksum = checksum;
+        this.srcIp = srcIp;
+        this.dstIp = dstIp;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
new file mode 100644
index 0000000..0c97401
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/TcpHeader.java
@@ -0,0 +1,79 @@
+/*
+ * 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.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;
+
+/**
+ * L4 TCP header as per https://tools.ietf.org/html/rfc793.
+ * This class does not contain option and data fields.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          Source Port          |       Destination Port        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        Sequence Number                        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Acknowledgment Number                      |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |  Data |           |U|A|P|R|S|F|                               |
+ * | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
+ * |       |           |G|K|H|T|N|N|                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Checksum            |         Urgent Pointer        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                    Options                    |    Padding    |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                             data                              |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+public class TcpHeader extends Struct {
+    @Field(order = 0, type = Type.U16)
+    public final int srcPort;
+    @Field(order = 1, type = Type.U16)
+    public final int dstPort;
+    @Field(order = 2, type = Type.U32)
+    public final long seq;
+    @Field(order = 3, type = Type.U32)
+    public final long ack;
+    @Field(order = 4, type = Type.S16)
+    // data Offset (4 bits), reserved (6 bits), control bits (6 bits)
+    // TODO: update with bitfields once class Struct supports it
+    public final short dataOffsetAndControlBits;
+    @Field(order = 5, type = Type.U16)
+    public final int window;
+    @Field(order = 6, type = Type.S16)
+    public final short checksum;
+    @Field(order = 7, type = Type.U16)
+    public final int urgentPointer;
+
+    public TcpHeader(final int srcPort, final int dstPort, final long seq, final long ack,
+            final short dataOffsetAndControlBits, final int window, final short checksum,
+            final int urgentPointer) {
+        this.srcPort = srcPort;
+        this.dstPort = dstPort;
+        this.seq = seq;
+        this.ack = ack;
+        this.dataOffsetAndControlBits = dataOffsetAndControlBits;
+        this.window = window;
+        this.checksum = checksum;
+        this.urgentPointer = urgentPointer;
+    }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
new file mode 100644
index 0000000..8b0316b
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/UdpHeader.java
@@ -0,0 +1,53 @@
+/*
+ * 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.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;
+
+/**
+ * L4 UDP header as per https://tools.ietf.org/html/rfc768.
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          Source Port          |       Destination Port        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |           Length              |          Checksum             |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                          data octets  ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ...
+ */
+public class UdpHeader extends Struct {
+    @Field(order = 0, type = Type.U16)
+    public final int srcPort;
+    @Field(order = 1, type = Type.U16)
+    public final int dstPort;
+    @Field(order = 2, type = Type.U16)
+    public final int length;
+    @Field(order = 3, type = Type.S16)
+    public final short checksum;
+
+    public UdpHeader(final int srcPort, final int dstPort, final int length,
+            final short checksum) {
+        this.srcPort = srcPort;
+        this.dstPort = dstPort;
+        this.length = length;
+        this.checksum = checksum;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/BestClock.java b/staticlibs/framework/com/android/net/module/util/BestClock.java
new file mode 100644
index 0000000..35391ad
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/BestClock.java
@@ -0,0 +1,78 @@
+/*
+ * 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.net.module.util;
+
+import android.util.Log;
+
+import java.time.Clock;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.util.Arrays;
+
+/**
+ * Single {@link Clock} that will return the best available time from a set of
+ * prioritized {@link Clock} instances.
+ * <p>
+ * For example, when {@link SystemClock#currentNetworkTimeClock()} isn't able to
+ * provide the time, this class could use {@link Clock#systemUTC()} instead.
+ *
+ * Note that this is re-implemented based on {@code android.os.BestClock} to be used inside
+ * the mainline module. And the class does NOT support serialization.
+ *
+ * @hide
+ */
+final public class BestClock extends Clock {
+    private static final String TAG = "BestClock";
+    private final ZoneId mZone;
+    private final Clock[] mClocks;
+
+    public BestClock(ZoneId zone, Clock... clocks) {
+        super();
+        this.mZone = zone;
+        this.mClocks = clocks;
+    }
+
+    @Override
+    public long millis() {
+        for (Clock clock : mClocks) {
+            try {
+                return clock.millis();
+            } catch (DateTimeException e) {
+                // Ignore and attempt the next clock
+                Log.w(TAG, e.toString());
+            }
+        }
+        throw new DateTimeException(
+                "No clocks in " + Arrays.toString(mClocks) + " were able to provide time");
+    }
+
+    @Override
+    public ZoneId getZone() {
+        return mZone;
+    }
+
+    @Override
+    public Clock withZone(ZoneId zone) {
+        return new BestClock(zone, mClocks);
+    }
+
+    @Override
+    public Instant instant() {
+        return Instant.ofEpochMilli(millis());
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
index 6e1af55..312ca48 100644
--- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java
@@ -180,4 +180,17 @@
         }
         return matches;
     }
+
+    /**
+     * Return sum of the given long array.
+     */
+    public static long total(@Nullable long[] array) {
+        long total = 0;
+        if (array != null) {
+            for (long value : array) {
+                total += value;
+            }
+        }
+        return total;
+    }
 }
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacket.java b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
index 5ac731c..080781c 100644
--- a/staticlibs/framework/com/android/net/module/util/DnsPacket.java
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacket.java
@@ -18,12 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.text.TextUtils;
+
+import com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
 
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
-import java.text.DecimalFormat;
-import java.text.FieldPosition;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -60,6 +59,11 @@
         public final int rcode;
         private final int[] mRecordCount;
 
+        /* If this bit in the 'flags' field is set to 0, the DNS message corresponding to this
+         * header is a query; otherwise, it is a response.
+         */
+        private static final int FLAGS_SECTION_QR_BIT = 15;
+
         /**
          * Create a new DnsHeader from a positioned ByteBuffer.
          *
@@ -80,6 +84,14 @@
         }
 
         /**
+         * Determines if the DNS message corresponding to this header is a response, as defined in
+         * RFC 1035 Section 4.1.1.
+         */
+        public boolean isResponse() {
+            return (flags & (1 << FLAGS_SECTION_QR_BIT)) != 0;
+        }
+
+        /**
          * Get record count by type.
          */
         public int getRecordCount(int type) {
@@ -95,12 +107,8 @@
      */
     public class DnsRecord {
         private static final int MAXNAMESIZE = 255;
-        private static final int MAXLABELSIZE = 63;
-        private static final int MAXLABELCOUNT = 128;
         public static final int NAME_NORMAL = 0;
         public static final int NAME_COMPRESSION = 0xC0;
-        private final DecimalFormat mByteFormat = new DecimalFormat();
-        private final FieldPosition mPos = new FieldPosition(0);
 
         private static final String TAG = "DnsRecord";
 
@@ -118,12 +126,13 @@
          * advanced to the end of the DNS header record.
          * This is meant to chain with other methods reading a DNS response in sequence.
          *
-         * @param ByteBuffer input of record, must be in network byte order
+         * @param buf ByteBuffer input of record, must be in network byte order
          *         (which is the default).
          */
         DnsRecord(int recordType, @NonNull ByteBuffer buf)
                 throws BufferUnderflowException, ParseException {
-            dName = parseName(buf, 0 /* Parse depth */);
+            dName = DnsRecordParser.parseName(buf, 0 /* Parse depth */,
+                    /* isNameCompressionSupported= */ true);
             if (dName.length() > MAXNAMESIZE) {
                 throw new ParseException(
                         "Parse name fail, name size is too long: " + dName.length());
@@ -150,66 +159,6 @@
             return (mRdata == null) ? null : mRdata.clone();
         }
 
-        /**
-         * Convert label from {@code byte[]} to {@code String}
-         *
-         * Follows the same conversion rules of the native code (ns_name.c in libc)
-         */
-        private String labelToString(@NonNull byte[] label) {
-            final StringBuffer sb = new StringBuffer();
-            for (int i = 0; i < label.length; ++i) {
-                int b = Byte.toUnsignedInt(label[i]);
-                // Control characters and non-ASCII characters.
-                if (b <= 0x20 || b >= 0x7f) {
-                    // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
-                    sb.append('\\');
-                    mByteFormat.format(b, sb, mPos);
-                } else if (b == '"' || b == '.' || b == ';' || b == '\\'
-                        || b == '(' || b == ')' || b == '@' || b == '$') {
-                    // Append the byte as an escaped character, e.g., "\:" for 0x3a.
-                    sb.append('\\');
-                    sb.append((char) b);
-                } else {
-                    // Append the byte as a character, e.g., "a" for 0x61.
-                    sb.append((char) b);
-                }
-            }
-            return sb.toString();
-        }
-
-        private String parseName(@NonNull ByteBuffer buf, int depth) throws
-                BufferUnderflowException, ParseException {
-            if (depth > MAXLABELCOUNT) {
-                throw new ParseException("Failed to parse name, too many labels");
-            }
-            final int len = Byte.toUnsignedInt(buf.get());
-            final int mask = len & NAME_COMPRESSION;
-            if (0 == len) {
-                return "";
-            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION) {
-                throw new ParseException("Parse name fail, bad label type");
-            } else if (mask == NAME_COMPRESSION) {
-                // Name compression based on RFC 1035 - 4.1.4 Message compression
-                final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get());
-                final int oldPos = buf.position();
-                if (offset >= oldPos - 2) {
-                    throw new ParseException("Parse compression name fail, invalid compression");
-                }
-                buf.position(offset);
-                final String pointed = parseName(buf, depth + 1);
-                buf.position(oldPos);
-                return pointed;
-            } else {
-                final byte[] label = new byte[len];
-                buf.get(label);
-                final String head = labelToString(label);
-                if (head.length() > MAXLABELSIZE) {
-                    throw new ParseException("Parse name fail, invalid label length");
-                }
-                final String tail = parseName(buf, depth + 1);
-                return TextUtils.isEmpty(tail) ? head : head + "." + tail;
-            }
-        }
     }
 
     public static final int QDSECTION = 0;
@@ -224,7 +173,10 @@
     protected final List<DnsRecord>[] mRecords;
 
     protected DnsPacket(@NonNull byte[] data) throws ParseException {
-        if (null == data) throw new ParseException("Parse header failed, null input data");
+        if (null == data) {
+            throw new ParseException("Parse header failed, null input data");
+        }
+
         final ByteBuffer buffer;
         try {
             buffer = ByteBuffer.wrap(data);
diff --git a/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
new file mode 100644
index 0000000..3448cad
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/DnsPacketUtils.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 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 com.android.net.module.util.DnsPacket.DnsRecord.NAME_COMPRESSION;
+import static com.android.net.module.util.DnsPacket.DnsRecord.NAME_NORMAL;
+
+import android.annotation.NonNull;
+import android.text.TextUtils;
+
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+
+/**
+ * Utilities for decoding the contents of a DnsPacket.
+ *
+ * @hide
+ */
+public final class DnsPacketUtils {
+    /**
+     * Reads the passed ByteBuffer from its current position and decodes a DNS record.
+     */
+    public static class DnsRecordParser {
+        private static final int MAXLABELSIZE = 63;
+        private static final int MAXLABELCOUNT = 128;
+
+        private static final DecimalFormat sByteFormat = new DecimalFormat();
+        private static final FieldPosition sPos = new FieldPosition(0);
+
+        /**
+         * Convert label from {@code byte[]} to {@code String}
+         *
+         * <p>Follows the same conversion rules of the native code (ns_name.c in libc).
+         */
+        private static String labelToString(@NonNull byte[] label) {
+            final StringBuffer sb = new StringBuffer();
+
+            for (int i = 0; i < label.length; ++i) {
+                int b = Byte.toUnsignedInt(label[i]);
+                // Control characters and non-ASCII characters.
+                if (b <= 0x20 || b >= 0x7f) {
+                    // Append the byte as an escaped decimal number, e.g., "\19" for 0x13.
+                    sb.append('\\');
+                    sByteFormat.format(b, sb, sPos);
+                } else if (b == '"' || b == '.' || b == ';' || b == '\\' || b == '(' || b == ')'
+                        || b == '@' || b == '$') {
+                    // Append the byte as an escaped character, e.g., "\:" for 0x3a.
+                    sb.append('\\');
+                    sb.append((char) b);
+                } else {
+                    // Append the byte as a character, e.g., "a" for 0x61.
+                    sb.append((char) b);
+                }
+            }
+            return sb.toString();
+        }
+
+        /**
+         * 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,
+                boolean isNameCompressionSupported) throws
+                BufferUnderflowException, DnsPacket.ParseException {
+            if (depth > MAXLABELCOUNT) {
+                throw new DnsPacket.ParseException("Failed to parse name, too many labels");
+            }
+            final int len = Byte.toUnsignedInt(buf.get());
+            final int mask = len & NAME_COMPRESSION;
+            if (0 == len) {
+                return "";
+            } else if (mask != NAME_NORMAL && mask != NAME_COMPRESSION
+                    || (!isNameCompressionSupported && mask == NAME_COMPRESSION)) {
+                throw new DnsPacket.ParseException("Parse name fail, bad label type: " + mask);
+            } else if (mask == NAME_COMPRESSION) {
+                // Name compression based on RFC 1035 - 4.1.4 Message compression
+                final int offset = ((len & ~NAME_COMPRESSION) << 8) + Byte.toUnsignedInt(buf.get());
+                final int oldPos = buf.position();
+                if (offset >= oldPos - 2) {
+                    throw new DnsPacket.ParseException(
+                            "Parse compression name fail, invalid compression");
+                }
+                buf.position(offset);
+                final String pointed = parseName(buf, depth + 1, isNameCompressionSupported);
+                buf.position(oldPos);
+                return pointed;
+            } else {
+                final byte[] label = new byte[len];
+                buf.get(label);
+                final String head = labelToString(label);
+                if (head.length() > MAXLABELSIZE) {
+                    throw new DnsPacket.ParseException("Parse name fail, invalid label length");
+                }
+                final String tail = parseName(buf, depth + 1, isNameCompressionSupported);
+                return TextUtils.isEmpty(tail) ? head : head + "." + tail;
+            }
+        }
+
+        private DnsRecordParser() {}
+    }
+
+    private DnsPacketUtils() {}
+}
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index 903214e..71a0c96 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -16,18 +16,24 @@
 
 package com.android.net.module.util;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_BIP;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IA;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MCX;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMTEL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
@@ -74,82 +80,46 @@
     };
 
     /**
-     * See android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE
-     * TODO: Use API constant when all downstream branches are S-based
-     */
-    public static final int NET_CAPABILITY_OEM_PRIVATE = 26;
-
-    /**
-     * See android.net.NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL
-     * TODO: Use API constant when all downstream branches are S-based
-     */
-    public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
-
-    /**
-     * See android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
-     * TODO: Use API constant when all downstream branches are S-based
-     */
-    public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
-
-    /**
-     * See android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE
-     * TODO: Use API constant when all downstream branches are S-based
-     */
-    public static final int NET_CAPABILITY_ENTERPRISE = 29;
-
-    /**
-     * See android.net.NetworkCapabilities.NET_CAPABILITY_VSIM
-     * TODO: Use API constant when all downstream branches are S-based
-     */
-    public static final int NET_CAPABILITY_VSIM = 30;
-
-    /**
-     * See android.net.NetworkCapabilities.NET_CAPABILITY_BIP
-     * TODO: Use API constant when all downstream branches are S-based
-     */
-    public static final int NET_CAPABILITY_BIP = 31;
-
-
-    /**
      * Capabilities that suggest that a network is restricted.
      * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted},
       * and {@code FORCE_RESTRICTED_CAPABILITIES}.
      */
     @VisibleForTesting
-    static final long RESTRICTED_CAPABILITIES =
-            (1 << NET_CAPABILITY_BIP)
-            | (1 << NET_CAPABILITY_CBS)
-            | (1 << NET_CAPABILITY_DUN)
-            | (1 << NET_CAPABILITY_EIMS)
-            | (1 << NET_CAPABILITY_ENTERPRISE)
-            | (1 << NET_CAPABILITY_FOTA)
-            | (1 << NET_CAPABILITY_IA)
-            | (1 << NET_CAPABILITY_IMS)
-            | (1 << NET_CAPABILITY_MCX)
-            | (1 << NET_CAPABILITY_RCS)
-            | (1 << NET_CAPABILITY_VEHICLE_INTERNAL)
-            | (1 << NET_CAPABILITY_VSIM)
-            | (1 << NET_CAPABILITY_XCAP);
+    static final long RESTRICTED_CAPABILITIES = packBitList(
+            NET_CAPABILITY_BIP,
+            NET_CAPABILITY_CBS,
+            NET_CAPABILITY_DUN,
+            NET_CAPABILITY_EIMS,
+            NET_CAPABILITY_ENTERPRISE,
+            NET_CAPABILITY_FOTA,
+            NET_CAPABILITY_IA,
+            NET_CAPABILITY_IMS,
+            NET_CAPABILITY_MCX,
+            NET_CAPABILITY_RCS,
+            NET_CAPABILITY_VEHICLE_INTERNAL,
+            NET_CAPABILITY_VSIM,
+            NET_CAPABILITY_XCAP,
+            NET_CAPABILITY_MMTEL);
 
     /**
      * Capabilities that force network to be restricted.
      * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
      */
-    private static final long FORCE_RESTRICTED_CAPABILITIES =
-            (1 << NET_CAPABILITY_ENTERPRISE)
-            | (1 << NET_CAPABILITY_OEM_PAID)
-            | (1 << NET_CAPABILITY_OEM_PRIVATE);
+    private static final long FORCE_RESTRICTED_CAPABILITIES = packBitList(
+            NET_CAPABILITY_ENTERPRISE,
+            NET_CAPABILITY_OEM_PAID,
+            NET_CAPABILITY_OEM_PRIVATE);
 
     /**
      * Capabilities that suggest that a network is unrestricted.
      * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
      */
     @VisibleForTesting
-    static final long UNRESTRICTED_CAPABILITIES =
-            (1 << NET_CAPABILITY_INTERNET)
-            | (1 << NET_CAPABILITY_MMS)
-            | (1 << NET_CAPABILITY_SUPL)
-            | (1 << NET_CAPABILITY_WIFI_P2P);
+    static final long UNRESTRICTED_CAPABILITIES = packBitList(
+            NET_CAPABILITY_INTERNET,
+            NET_CAPABILITY_MMS,
+            NET_CAPABILITY_SUPL,
+            NET_CAPABILITY_WIFI_P2P);
 
     /**
      * Get a transport that can be used to classify a network when displaying its info to users.
@@ -228,7 +198,26 @@
     }
 
     /**
+     * Packs a list of ints in the same way as packBits()
+     *
+     * Each passed int is the rank of a bit that should be set in the returned long.
+     * Example : passing (1,3) will return in 0b00001010 and passing (5,6,0) will return 0b01100001
+     *
+     * @param bits bits to pack
+     * @return a long with the specified bits set.
+     */
+    public static long packBitList(int... bits) {
+        return packBits(bits);
+    }
+
+    /**
      * Packs array of bits into a long value.
+     *
+     * Each passed int is the rank of a bit that should be set in the returned long.
+     * Example : passing [1,3] will return in 0b00001010 and passing [5,6,0] will return 0b01100001
+     *
+     * @param bits bits to pack
+     * @return a long with the specified bits set.
      */
     public static long packBits(int[] bits) {
         long packed = 0;
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index f7151d7..353fe69 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -89,9 +89,11 @@
     public static final int IPV4_MAX_MTU = 65_535;
     public static final int IPV4_HEADER_MIN_LEN = 20;
     public static final int IPV4_IHL_MASK = 0xf;
+    public static final int IPV4_LENGTH_OFFSET = 2;
     public static final int IPV4_FLAGS_OFFSET = 6;
     public static final int IPV4_FRAGMENT_MASK = 0x1fff;
     public static final int IPV4_PROTOCOL_OFFSET = 9;
+    public static final int IPV4_CHECKSUM_OFFSET = 10;
     public static final int IPV4_SRC_ADDR_OFFSET = 12;
     public static final int IPV4_DST_ADDR_OFFSET = 16;
     public static final int IPV4_ADDR_LEN = 4;
@@ -164,13 +166,29 @@
     public static final byte PIO_FLAG_AUTONOMOUS = (byte) (1 << 6);
 
     /**
+     * TCP constants.
+     *
+     * See also:
+     *     - https://tools.ietf.org/html/rfc793
+     */
+    public static final int TCP_HEADER_MIN_LEN = 20;
+    public static final int TCP_CHECKSUM_OFFSET = 16;
+    public static final byte TCPHDR_FIN = (byte) (1 << 0);
+    public static final byte TCPHDR_SYN = (byte) (1 << 1);
+    public static final byte TCPHDR_RST = (byte) (1 << 2);
+    public static final byte TCPHDR_PSH = (byte) (1 << 3);
+    public static final byte TCPHDR_ACK = (byte) (1 << 4);
+    public static final byte TCPHDR_URG = (byte) (1 << 5);
+
+    /**
      * UDP constants.
      *
      * See also:
      *     - https://tools.ietf.org/html/rfc768
      */
     public static final int UDP_HEADER_LEN = 8;
-
+    public static final int UDP_LENGTH_OFFSET = 4;
+    public static final int UDP_CHECKSUM_OFFSET = 6;
 
     /**
      * DHCP constants.
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
new file mode 100644
index 0000000..c4d415e
--- /dev/null
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStatsUtils.java
@@ -0,0 +1,175 @@
+/*
+ * 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.net.module.util;
+
+import android.app.usage.NetworkStats;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Various utilities used for NetworkStats related code.
+ *
+ * @hide
+ */
+public class NetworkStatsUtils {
+    // These constants must be synced with the definition in android.net.NetworkStats.
+    // TODO: update to formal APIs once all downstreams have these APIs.
+    private static final int SET_ALL = -1;
+    private static final int METERED_ALL = -1;
+    private static final int ROAMING_ALL = -1;
+    private static final int DEFAULT_NETWORK_ALL = -1;
+
+    /**
+     * Safely multiple a value by a rational.
+     * <p>
+     * Internally it uses integer-based math whenever possible, but switches
+     * over to double-based math if values would overflow.
+     * @hide
+     */
+    public static long multiplySafeByRational(long value, long num, long den) {
+        if (den == 0) {
+            throw new ArithmeticException("Invalid Denominator");
+        }
+        long x = value;
+        long y = num;
+
+        // Logic shamelessly borrowed from Math.multiplyExact()
+        long r = x * y;
+        long ax = Math.abs(x);
+        long ay = Math.abs(y);
+        if (((ax | ay) >>> 31 != 0)) {
+            // Some bits greater than 2^31 that might cause overflow
+            // Check the result using the divide operator
+            // and check for the special case of Long.MIN_VALUE * -1
+            if (((y != 0) && (r / y != x))
+                    || (x == Long.MIN_VALUE && y == -1)) {
+                // Use double math to avoid overflowing
+                return (long) (((double) num / den) * value);
+            }
+        }
+        return r / den;
+    }
+
+    /**
+     * Value of the match rule of the subscriberId to match networks with specific subscriberId.
+     *
+     * @hide
+     */
+    public static final int SUBSCRIBER_ID_MATCH_RULE_EXACT = 0;
+    /**
+     * Value of the match rule of the subscriberId to match networks with any subscriberId which
+     * includes null and non-null.
+     *
+     * @hide
+     */
+    public static final int SUBSCRIBER_ID_MATCH_RULE_ALL = 1;
+
+    /**
+     * Name representing {@link #bandwidthSetGlobalAlert(long)} limit when delivered to
+     * {@link AlertObserver#onQuotaLimitReached(String, String)}.
+     */
+    public static final String LIMIT_GLOBAL_ALERT = "globalAlert";
+
+    /**
+     * Return the constrained value by given the lower and upper bounds.
+     */
+    public static int constrain(int amount, int low, int high) {
+        if (low > high) throw new IllegalArgumentException("low(" + low + ") > high(" + high + ")");
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    /**
+     * Return the constrained value by given the lower and upper bounds.
+     */
+    public static long constrain(long amount, long low, long high) {
+        if (low > high) throw new IllegalArgumentException("low(" + low + ") > high(" + high + ")");
+        return amount < low ? low : (amount > high ? high : amount);
+    }
+
+    /**
+     * Convert structure from android.app.usage.NetworkStats to android.net.NetworkStats.
+     */
+    public static android.net.NetworkStats fromPublicNetworkStats(
+            NetworkStats publiceNetworkStats) {
+        android.net.NetworkStats stats = new android.net.NetworkStats(0L, 0);
+        while (publiceNetworkStats.hasNextBucket()) {
+            NetworkStats.Bucket bucket = new NetworkStats.Bucket();
+            publiceNetworkStats.getNextBucket(bucket);
+            final android.net.NetworkStats.Entry entry = fromBucket(bucket);
+            stats = stats.addEntry(entry);
+        }
+        return stats;
+    }
+
+    @VisibleForTesting
+    static android.net.NetworkStats.Entry fromBucket(NetworkStats.Bucket bucket) {
+        return new android.net.NetworkStats.Entry(
+                null /* IFACE_ALL */, bucket.getUid(), convertBucketState(bucket.getState()),
+                convertBucketTag(bucket.getTag()), convertBucketMetered(bucket.getMetered()),
+                convertBucketRoaming(bucket.getRoaming()),
+                convertBucketDefaultNetworkStatus(bucket.getDefaultNetworkStatus()),
+                bucket.getRxBytes(), bucket.getRxPackets(),
+                bucket.getTxBytes(), bucket.getTxPackets(), 0 /* operations */);
+    }
+
+    private static int convertBucketState(int networkStatsSet) {
+        switch (networkStatsSet) {
+            case NetworkStats.Bucket.STATE_ALL: return SET_ALL;
+            case NetworkStats.Bucket.STATE_DEFAULT: return android.net.NetworkStats.SET_DEFAULT;
+            case NetworkStats.Bucket.STATE_FOREGROUND:
+                return android.net.NetworkStats.SET_FOREGROUND;
+        }
+        return 0;
+    }
+
+    private static int convertBucketTag(int tag) {
+        switch (tag) {
+            case NetworkStats.Bucket.TAG_NONE: return android.net.NetworkStats.TAG_NONE;
+        }
+        return tag;
+    }
+
+    private static int convertBucketMetered(int metered) {
+        switch (metered) {
+            case NetworkStats.Bucket.METERED_ALL: return METERED_ALL;
+            case NetworkStats.Bucket.METERED_NO: return android.net.NetworkStats.METERED_NO;
+            case NetworkStats.Bucket.METERED_YES: return android.net.NetworkStats.METERED_YES;
+        }
+        return 0;
+    }
+
+    private static int convertBucketRoaming(int roaming) {
+        switch (roaming) {
+            case NetworkStats.Bucket.ROAMING_ALL: return ROAMING_ALL;
+            case NetworkStats.Bucket.ROAMING_NO: return android.net.NetworkStats.ROAMING_NO;
+            case NetworkStats.Bucket.ROAMING_YES: return android.net.NetworkStats.ROAMING_YES;
+        }
+        return 0;
+    }
+
+    private static int convertBucketDefaultNetworkStatus(int defaultNetworkStatus) {
+        switch (defaultNetworkStatus) {
+            case NetworkStats.Bucket.DEFAULT_NETWORK_ALL:
+                return DEFAULT_NETWORK_ALL;
+            case NetworkStats.Bucket.DEFAULT_NETWORK_NO:
+                return android.net.NetworkStats.DEFAULT_NETWORK_NO;
+            case NetworkStats.Bucket.DEFAULT_NETWORK_YES:
+                return android.net.NetworkStats.DEFAULT_NETWORK_YES;
+        }
+        return 0;
+    }
+}
diff --git a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
index 10eda57..0f3dc15 100644
--- a/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
+++ b/staticlibs/framework/com/android/net/module/util/PermissionUtils.java
@@ -16,13 +16,18 @@
 
 package com.android.net.module.util;
 
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
 import static android.Manifest.permission.NETWORK_STACK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
+import android.os.Binder;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 
@@ -80,4 +85,63 @@
         permissions.add(PERMISSION_MAINLINE_NETWORK_STACK);
         enforceAnyPermissionOf(context, permissions.toArray(new String[0]));
     }
+
+    /**
+     * If the CONNECTIVITY_USE_RESTRICTED_NETWORKS is not allowed for a particular process, throw a
+     * {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param message A message to include in the exception if it is thrown.
+     */
+    public static void enforceRestrictedNetworkPermission(
+            final @NonNull Context context, final @Nullable String message) {
+        context.enforceCallingOrSelfPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, message);
+    }
+
+    /**
+     * If the ACCESS_NETWORK_STATE is not allowed for a particular process, throw a
+     * {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param message A message to include in the exception if it is thrown.
+     */
+    public static void enforceAccessNetworkStatePermission(
+            final @NonNull Context context, final @Nullable String message) {
+        context.enforceCallingOrSelfPermission(ACCESS_NETWORK_STATE, message);
+    }
+
+    /**
+     * Return true if the context has DUMP permission.
+     */
+    public static boolean checkDumpPermission(Context context, String tag, PrintWriter pw) {
+        if (context.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump " + tag + " from from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " due to missing android.permission.DUMP permission");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Enforce that a given feature is available and if not, throw an
+     * {@link UnsupportedOperationException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param feature the feature name to enforce.
+     * @param errorMessage an optional error message to include.
+     */
+    public static void enforceSystemFeature(final @NonNull Context context,
+            final @NonNull String feature, final @Nullable String errorMessage) {
+        final boolean hasSystemFeature =
+                context.getPackageManager().hasSystemFeature(feature);
+        if (!hasSystemFeature) {
+            if (null == errorMessage) {
+                throw new UnsupportedOperationException();
+            }
+            throw new UnsupportedOperationException(errorMessage);
+        }
+    }
 }
diff --git a/staticlibs/native/OWNERS b/staticlibs/native/OWNERS
deleted file mode 100644
index 7655338..0000000
--- a/staticlibs/native/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-maze@google.com
diff --git a/staticlibs/native/README.md b/staticlibs/native/README.md
index 18d19c4..1f505c4 100644
--- a/staticlibs/native/README.md
+++ b/staticlibs/native/README.md
@@ -22,6 +22,9 @@
 
 * Each module creates a native library in their directory, which statically links against the
   common native library (e.g. libnet_utils_device_common_bpf), and calls the native registered
-  function by hardcoding the post-jarjar class_name.
-
-
+  function by hardcoding the post-jarjar class_name. Linkage *MUST* be static because common
+  functions in the file (e.g., `register_com_android_net_module_util_BpfMap`) will appear in the
+  library (`.so`) file, and different versions of the library loaded in the same process by
+  different modules will in general have different versions. It's important that each of these
+  libraries loads the common function from its own library. Static linkage should guarantee this
+  because static linkage resolves symbols at build time, not runtime.
\ No newline at end of file
diff --git a/staticlibs/native/bpf_headers/Android.bp b/staticlibs/native/bpf_headers/Android.bp
new file mode 100644
index 0000000..af8ec73
--- /dev/null
+++ b/staticlibs/native/bpf_headers/Android.bp
@@ -0,0 +1,85 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_headers {
+    name: "bpf_headers",
+    vendor_available: false,
+    host_supported: true,
+    native_bridge_supported: true,
+    header_libs: ["bpf_syscall_wrappers"],
+    export_header_lib_headers: ["bpf_syscall_wrappers"],
+    export_include_dirs: ["include"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.tethering",
+        "com.android.art.debug",
+    ],
+    visibility: [
+        "//bootable/libbootloader/vts",
+        "//cts/tests/tests/net/native",
+        "//frameworks/base/services/core/jni",
+        "//frameworks/native/libs/cputimeinstate",
+        "//frameworks/native/services/gpuservice",
+        "//frameworks/native/services/gpuservice/gpumem",
+        "//frameworks/native/services/gpuservice/tests/unittests",
+        "//frameworks/native/services/gpuservice/tracing",
+        "//packages/modules/Connectivity/bpf_progs",
+        "//packages/modules/Connectivity/netd",
+        "//packages/modules/Connectivity/service/native",
+        "//packages/modules/Connectivity/tests/unit/jni",
+        "//packages/modules/DnsResolver/tests",
+        "//system/bpf/bpfloader",
+        "//system/bpf/libbpf_android",
+        "//system/memory/libmeminfo",
+        "//system/netd/libnetdbpf",
+        "//system/netd/server",
+        "//system/netd/tests",
+        "//system/netd/tests/benchmarks",
+        "//test/vts-testcase/kernel/api/bpf_native_test",
+    ],
+}
+
+
+cc_test {
+    // TODO: Rename to bpf_map_test and modify .gcls as well.
+    name: "libbpf_android_test",
+    srcs: [
+        "BpfMapTest.cpp",
+    ],
+    defaults: ["bpf_defaults"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-error=unused-variable",
+    ],
+    static_libs: ["libgmock"],
+    shared_libs: [
+        "libbpf_android",
+        "libbase",
+        "liblog",
+        "libutils",
+    ],
+    require_root: true,
+    test_suites: ["general-tests"],
+}
diff --git a/staticlibs/native/bpf_headers/BpfMapTest.cpp b/staticlibs/native/bpf_headers/BpfMapTest.cpp
new file mode 100644
index 0000000..d0737b0
--- /dev/null
+++ b/staticlibs/native/bpf_headers/BpfMapTest.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/inet_diag.h>
+#include <linux/sock_diag.h>
+#include <net/if.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <gtest/gtest.h>
+
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfUtils.h"
+
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+using base::Result;
+using base::unique_fd;
+
+constexpr uint32_t TEST_MAP_SIZE = 10;
+constexpr uint32_t TEST_KEY1 = 1;
+constexpr uint32_t TEST_VALUE1 = 10;
+constexpr const char PINNED_MAP_PATH[] = "/sys/fs/bpf/testMap";
+
+class BpfMapTest : public testing::Test {
+  protected:
+    BpfMapTest() {}
+
+    void SetUp() {
+        EXPECT_EQ(0, setrlimitForTest());
+        if (!access(PINNED_MAP_PATH, R_OK)) {
+            EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+        }
+    }
+
+    void TearDown() {
+        if (!access(PINNED_MAP_PATH, R_OK)) {
+            EXPECT_EQ(0, remove(PINNED_MAP_PATH));
+        }
+    }
+
+    void checkMapInvalid(BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_FALSE(map.isValid());
+        EXPECT_EQ(-1, map.getMap().get());
+    }
+
+    void checkMapValid(BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_LE(0, map.getMap().get());
+        EXPECT_TRUE(map.isValid());
+    }
+
+    void writeToMapAndCheck(BpfMap<uint32_t, uint32_t>& map, uint32_t key, uint32_t value) {
+        ASSERT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+        uint32_t value_read;
+        ASSERT_EQ(0, findMapEntry(map.getMap(), &key, &value_read));
+        checkValueAndStatus(value, value_read);
+    }
+
+    void checkValueAndStatus(uint32_t refValue, Result<uint32_t> value) {
+        ASSERT_RESULT_OK(value);
+        ASSERT_EQ(refValue, value.value());
+    }
+
+    void populateMap(uint32_t total, BpfMap<uint32_t, uint32_t>& map) {
+        for (uint32_t key = 0; key < total; key++) {
+            uint32_t value = key * 10;
+            EXPECT_RESULT_OK(map.writeValue(key, value, BPF_ANY));
+        }
+    }
+
+    void expectMapEmpty(BpfMap<uint32_t, uint32_t>& map) {
+        Result<bool> isEmpty = map.isEmpty();
+        ASSERT_RESULT_OK(isEmpty);
+        ASSERT_TRUE(isEmpty.value());
+    }
+};
+
+TEST_F(BpfMapTest, constructor) {
+    BpfMap<uint32_t, uint32_t> testMap1;
+    checkMapInvalid(testMap1);
+
+    BpfMap<uint32_t, uint32_t> testMap2(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    checkMapValid(testMap2);
+}
+
+TEST_F(BpfMapTest, basicHelpers) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    uint32_t key = TEST_KEY1;
+    uint32_t value_write = TEST_VALUE1;
+    writeToMapAndCheck(testMap, key, value_write);
+    Result<uint32_t> value_read = testMap.readValue(key);
+    checkValueAndStatus(value_write, value_read);
+    Result<uint32_t> key_read = testMap.getFirstKey();
+    checkValueAndStatus(key, key_read);
+    ASSERT_RESULT_OK(testMap.deleteValue(key));
+    ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_read));
+    ASSERT_EQ(ENOENT, errno);
+}
+
+TEST_F(BpfMapTest, reset) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    uint32_t key = TEST_KEY1;
+    uint32_t value_write = TEST_VALUE1;
+    writeToMapAndCheck(testMap, key, value_write);
+
+    testMap.reset(-1);
+    checkMapInvalid(testMap);
+    ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+    ASSERT_EQ(EBADF, errno);
+}
+
+TEST_F(BpfMapTest, moveConstructor) {
+    BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    BpfMap<uint32_t, uint32_t> testMap2;
+    testMap2 = std::move(testMap1);
+    uint32_t key = TEST_KEY1;
+    checkMapInvalid(testMap1);
+    uint32_t value = TEST_VALUE1;
+    writeToMapAndCheck(testMap2, key, value);
+}
+
+TEST_F(BpfMapTest, SetUpMap) {
+    EXPECT_NE(0, access(PINNED_MAP_PATH, R_OK));
+    BpfMap<uint32_t, uint32_t> testMap1(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    ASSERT_EQ(0, bpfFdPin(testMap1.getMap(), PINNED_MAP_PATH));
+    EXPECT_EQ(0, access(PINNED_MAP_PATH, R_OK));
+    checkMapValid(testMap1);
+    BpfMap<uint32_t, uint32_t> testMap2;
+    EXPECT_RESULT_OK(testMap2.init(PINNED_MAP_PATH));
+    checkMapValid(testMap2);
+    uint32_t key = TEST_KEY1;
+    uint32_t value = TEST_VALUE1;
+    writeToMapAndCheck(testMap1, key, value);
+    Result<uint32_t> value_read = testMap2.readValue(key);
+    checkValueAndStatus(value, value_read);
+}
+
+TEST_F(BpfMapTest, iterate) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    populateMap(TEST_MAP_SIZE, testMap);
+    int totalCount = 0;
+    int totalSum = 0;
+    const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+                                                              BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+        totalCount++;
+        totalSum += key;
+        return map.deleteValue(key);
+    };
+    EXPECT_RESULT_OK(testMap.iterate(iterateWithDeletion));
+    EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+    EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) / 2, (uint32_t)totalSum);
+    expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, iterateWithValue) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    populateMap(TEST_MAP_SIZE, testMap);
+    int totalCount = 0;
+    int totalSum = 0;
+    const auto iterateWithDeletion = [&totalCount, &totalSum](const uint32_t& key,
+                                                              const uint32_t& value,
+                                                              BpfMap<uint32_t, uint32_t>& map) {
+        EXPECT_GE((uint32_t)TEST_MAP_SIZE, key);
+        EXPECT_EQ(value, key * 10);
+        totalCount++;
+        totalSum += value;
+        return map.deleteValue(key);
+    };
+    EXPECT_RESULT_OK(testMap.iterateWithValue(iterateWithDeletion));
+    EXPECT_EQ((int)TEST_MAP_SIZE, totalCount);
+    EXPECT_EQ(((1 + TEST_MAP_SIZE - 1) * (TEST_MAP_SIZE - 1)) * 5, (uint32_t)totalSum);
+    expectMapEmpty(testMap);
+}
+
+TEST_F(BpfMapTest, mapIsEmpty) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    expectMapEmpty(testMap);
+    uint32_t key = TEST_KEY1;
+    uint32_t value_write = TEST_VALUE1;
+    writeToMapAndCheck(testMap, key, value_write);
+    Result<bool> isEmpty = testMap.isEmpty();
+    ASSERT_RESULT_OK(isEmpty);
+    ASSERT_FALSE(isEmpty.value());
+    ASSERT_RESULT_OK(testMap.deleteValue(key));
+    ASSERT_GT(0, findMapEntry(testMap.getMap(), &key, &value_write));
+    ASSERT_EQ(ENOENT, errno);
+    expectMapEmpty(testMap);
+    int entriesSeen = 0;
+    EXPECT_RESULT_OK(testMap.iterate(
+            [&entriesSeen](const unsigned int&,
+                           const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+                entriesSeen++;
+                return {};
+            }));
+    EXPECT_EQ(0, entriesSeen);
+    EXPECT_RESULT_OK(testMap.iterateWithValue(
+            [&entriesSeen](const unsigned int&, const unsigned int&,
+                           const BpfMap<unsigned int, unsigned int>&) -> Result<void> {
+                entriesSeen++;
+                return {};
+            }));
+    EXPECT_EQ(0, entriesSeen);
+}
+
+TEST_F(BpfMapTest, mapClear) {
+    BpfMap<uint32_t, uint32_t> testMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
+    populateMap(TEST_MAP_SIZE, testMap);
+    Result<bool> isEmpty = testMap.isEmpty();
+    ASSERT_RESULT_OK(isEmpty);
+    ASSERT_FALSE(*isEmpty);
+    ASSERT_RESULT_OK(testMap.clear());
+    expectMapEmpty(testMap);
+}
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/TEST_MAPPING b/staticlibs/native/bpf_headers/TEST_MAPPING
new file mode 100644
index 0000000..9ec8a40
--- /dev/null
+++ b/staticlibs/native/bpf_headers/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "libbpf_android_test"
+    }
+  ]
+}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfMap.h b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
new file mode 100644
index 0000000..bdffc0f
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfMap.h
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2018 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 <linux/bpf.h>
+
+#include <android-base/result.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+#include <utils/Log.h>
+#include "bpf/BpfUtils.h"
+
+namespace android {
+namespace bpf {
+
+// This is a class wrapper for eBPF maps. The eBPF map is a special in-kernel
+// data structure that stores data in <Key, Value> pairs. It can be read/write
+// from userspace by passing syscalls with the map file descriptor. This class
+// is used to generalize the procedure of interacting with eBPF maps and hide
+// the implementation detail from other process. Besides the basic syscalls
+// wrapper, it also provides some useful helper functions as well as an iterator
+// nested class to iterate the map more easily.
+//
+// NOTE: A kernel eBPF map may be accessed by both kernel and userspace
+// processes at the same time. Or if the map is pinned as a virtual file, it can
+// be obtained by multiple eBPF map class object and accessed concurrently.
+// Though the map class object and the underlying kernel map are thread safe, it
+// is not safe to iterate over a map while another thread or process is deleting
+// from it. In this case the iteration can return duplicate entries.
+template <class Key, class Value>
+class BpfMap {
+  public:
+    BpfMap<Key, Value>() {};
+
+  protected:
+    // flag must be within BPF_OBJ_FLAG_MASK, ie. 0, BPF_F_RDONLY, BPF_F_WRONLY
+    BpfMap<Key, Value>(const char* pathname, uint32_t flags) {
+        int map_fd = mapRetrieve(pathname, flags);
+        if (map_fd >= 0) mMapFd.reset(map_fd);
+    }
+
+  public:
+    explicit BpfMap<Key, Value>(const char* pathname) : BpfMap<Key, Value>(pathname, 0) {}
+
+    BpfMap<Key, Value>(bpf_map_type map_type, uint32_t max_entries, uint32_t map_flags = 0) {
+        int map_fd = createMap(map_type, sizeof(Key), sizeof(Value), max_entries, map_flags);
+        if (map_fd >= 0) mMapFd.reset(map_fd);
+    }
+
+    base::Result<Key> getFirstKey() const {
+        Key firstKey;
+        if (getFirstMapKey(mMapFd, &firstKey)) {
+            return ErrnoErrorf("Get firstKey map {} failed", mMapFd.get());
+        }
+        return firstKey;
+    }
+
+    base::Result<Key> getNextKey(const Key& key) const {
+        Key nextKey;
+        if (getNextMapKey(mMapFd, &key, &nextKey)) {
+            return ErrnoErrorf("Get next key of map {} failed", mMapFd.get());
+        }
+        return nextKey;
+    }
+
+    base::Result<void> writeValue(const Key& key, const Value& value, uint64_t flags) {
+        if (writeToMapEntry(mMapFd, &key, &value, flags)) {
+            return ErrnoErrorf("Write to map {} failed", mMapFd.get());
+        }
+        return {};
+    }
+
+    base::Result<Value> readValue(const Key key) const {
+        Value value;
+        if (findMapEntry(mMapFd, &key, &value)) {
+            return ErrnoErrorf("Read value of map {} failed", mMapFd.get());
+        }
+        return value;
+    }
+
+    base::Result<void> deleteValue(const Key& key) {
+        if (deleteMapEntry(mMapFd, &key)) {
+            return ErrnoErrorf("Delete entry from map {} failed", mMapFd.get());
+        }
+        return {};
+    }
+
+    // Function that tries to get map from a pinned path.
+    base::Result<void> init(const char* path);
+
+    // Iterate through the map and handle each key retrieved based on the filter
+    // without modification of map content.
+    base::Result<void> iterate(
+            const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
+                    filter) const;
+
+    // Iterate through the map and get each <key, value> pair, handle each <key,
+    // value> pair based on the filter without modification of map content.
+    base::Result<void> iterateWithValue(
+            const std::function<base::Result<void>(const Key& key, const Value& value,
+                                                   const BpfMap<Key, Value>& map)>& filter) const;
+
+    // Iterate through the map and handle each key retrieved based on the filter
+    base::Result<void> iterate(
+            const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>&
+                    filter);
+
+    // Iterate through the map and get each <key, value> pair, handle each <key,
+    // value> pair based on the filter.
+    base::Result<void> iterateWithValue(
+            const std::function<base::Result<void>(const Key& key, const Value& value,
+                                                   BpfMap<Key, Value>& map)>& filter);
+
+    const base::unique_fd& getMap() const { return mMapFd; };
+
+    // Copy assignment operator
+    BpfMap<Key, Value>& operator=(const BpfMap<Key, Value>& other) {
+        if (this != &other) mMapFd.reset(fcntl(other.mMapFd.get(), F_DUPFD_CLOEXEC, 0));
+        return *this;
+    }
+
+    // Move assignment operator
+    BpfMap<Key, Value>& operator=(BpfMap<Key, Value>&& other) noexcept {
+        mMapFd = std::move(other.mMapFd);
+        other.reset(-1);
+        return *this;
+    }
+
+    void reset(base::unique_fd fd) = delete;
+
+    void reset(int fd) { mMapFd.reset(fd); }
+
+    bool isValid() const { return mMapFd != -1; }
+
+    base::Result<void> clear() {
+        while (true) {
+            auto key = getFirstKey();
+            if (!key.ok()) {
+                if (key.error().code() == ENOENT) return {};  // empty: success
+                return key.error();                           // Anything else is an error
+            }
+            auto res = deleteValue(key.value());
+            if (!res.ok()) {
+                // Someone else could have deleted the key, so ignore ENOENT
+                if (res.error().code() == ENOENT) continue;
+                ALOGE("Failed to delete data %s", strerror(res.error().code()));
+                return res.error();
+            }
+        }
+    }
+
+    base::Result<bool> isEmpty() const {
+        auto key = getFirstKey();
+        if (!key.ok()) {
+            // Return error code ENOENT means the map is empty
+            if (key.error().code() == ENOENT) return true;
+            return key.error();
+        }
+        return false;
+    }
+
+  private:
+    base::unique_fd mMapFd;
+};
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::init(const char* path) {
+    mMapFd = base::unique_fd(mapRetrieveRW(path));
+    if (mMapFd == -1) {
+        return ErrnoErrorf("Pinned map not accessible or does not exist: ({})", path);
+    }
+    return {};
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterate(
+        const std::function<base::Result<void>(const Key& key, const BpfMap<Key, Value>& map)>&
+                filter) const {
+    base::Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const base::Result<Key>& nextKey = getNextKey(curKey.value());
+        base::Result<void> status = filter(curKey.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterateWithValue(
+        const std::function<base::Result<void>(const Key& key, const Value& value,
+                                               const BpfMap<Key, Value>& map)>& filter) const {
+    base::Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const base::Result<Key>& nextKey = getNextKey(curKey.value());
+        base::Result<Value> curValue = readValue(curKey.value());
+        if (!curValue.ok()) return curValue.error();
+        base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterate(
+        const std::function<base::Result<void>(const Key& key, BpfMap<Key, Value>& map)>& filter) {
+    base::Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const base::Result<Key>& nextKey = getNextKey(curKey.value());
+        base::Result<void> status = filter(curKey.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+base::Result<void> BpfMap<Key, Value>::iterateWithValue(
+        const std::function<base::Result<void>(const Key& key, const Value& value,
+                                               BpfMap<Key, Value>& map)>& filter) {
+    base::Result<Key> curKey = getFirstKey();
+    while (curKey.ok()) {
+        const base::Result<Key>& nextKey = getNextKey(curKey.value());
+        base::Result<Value> curValue = readValue(curKey.value());
+        if (!curValue.ok()) return curValue.error();
+        base::Result<void> status = filter(curKey.value(), curValue.value(), *this);
+        if (!status.ok()) return status;
+        curKey = nextKey;
+    }
+    if (curKey.error().code() == ENOENT) return {};
+    return curKey.error();
+}
+
+template <class Key, class Value>
+class BpfMapRO : public BpfMap<Key, Value> {
+  public:
+    explicit BpfMapRO<Key, Value>(const char* pathname)
+        : BpfMap<Key, Value>(pathname, BPF_F_RDONLY) {}
+};
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
new file mode 100644
index 0000000..265d4b6
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 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 <linux/if_ether.h>
+#include <linux/pfkeyv2.h>
+#include <net/if.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+
+#include "BpfSyscallWrappers.h"
+
+// The buffer size for the buffer that records program loading logs, needs to be large enough for
+// the largest kernel program.
+
+namespace android {
+namespace bpf {
+
+constexpr const int OVERFLOW_COUNTERSET = 2;
+
+constexpr const uint64_t NONEXISTENT_COOKIE = 0;
+
+static inline uint64_t getSocketCookie(int sockFd) {
+    uint64_t sock_cookie;
+    socklen_t cookie_len = sizeof(sock_cookie);
+    int res = getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len);
+    if (res < 0) {
+        res = -errno;
+        ALOGE("Failed to get socket cookie: %s\n", strerror(errno));
+        errno = -res;
+        // 0 is an invalid cookie. See sock_gen_cookie.
+        return NONEXISTENT_COOKIE;
+    }
+    return sock_cookie;
+}
+
+static inline int synchronizeKernelRCU() {
+    // This is a temporary hack for network stats map swap on devices running
+    // 4.9 kernels. The kernel code of socket release on pf_key socket will
+    // explicitly call synchronize_rcu() which is exactly what we need.
+    int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+
+    if (pfSocket < 0) {
+        int ret = -errno;
+        ALOGE("create PF_KEY socket failed: %s", strerror(errno));
+        return ret;
+    }
+
+    // When closing socket, synchronize_rcu() gets called in sock_release().
+    if (close(pfSocket)) {
+        int ret = -errno;
+        ALOGE("failed to close the PF_KEY socket: %s", strerror(errno));
+        return ret;
+    }
+    return 0;
+}
+
+static inline int setrlimitForTest() {
+    // Set the memory rlimit for the test process if the default MEMLOCK rlimit is not enough.
+    struct rlimit limit = {
+            .rlim_cur = 1073741824,  // 1 GiB
+            .rlim_max = 1073741824,  // 1 GiB
+    };
+    int res = setrlimit(RLIMIT_MEMLOCK, &limit);
+    if (res) {
+        ALOGE("Failed to set the default MEMLOCK rlimit: %s", strerror(errno));
+    }
+    return res;
+}
+
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+
+static inline unsigned kernelVersion() {
+    struct utsname buf;
+    int ret = uname(&buf);
+    if (ret) return 0;
+
+    unsigned kver_major;
+    unsigned kver_minor;
+    unsigned kver_sub;
+    char unused;
+    ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub, &unused);
+    // Check the device kernel version
+    if (ret < 3) return 0;
+
+    return KVER(kver_major, kver_minor, kver_sub);
+}
+
+static inline bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+    return kernelVersion() >= KVER(major, minor, sub);
+}
+
+#define SKIP_IF_BPF_SUPPORTED                                                    \
+    do {                                                                         \
+        if (android::bpf::isAtLeastKernelVersion(4, 9, 0)) {                     \
+            GTEST_LOG_(INFO) << "This test is skipped since bpf is supported\n"; \
+            return;                                                              \
+        }                                                                        \
+    } while (0)
+
+#define SKIP_IF_BPF_NOT_SUPPORTED                                                    \
+    do {                                                                             \
+        if (!android::bpf::isAtLeastKernelVersion(4, 9, 0)) {                        \
+            GTEST_LOG_(INFO) << "This test is skipped since bpf is not supported\n"; \
+            return;                                                                  \
+        }                                                                            \
+    } while (0)
+
+#define SKIP_IF_EXTENDED_BPF_NOT_SUPPORTED                                        \
+    do {                                                                          \
+        if (!android::bpf::isAtLeastKernelVersion(4, 14, 0)) {                    \
+            GTEST_LOG_(INFO) << "This test is skipped since extended bpf feature" \
+                             << "not supported\n";                                \
+            return;                                                               \
+        }                                                                         \
+    } while (0)
+
+#define SKIP_IF_XDP_NOT_SUPPORTED                                \
+    do {                                                         \
+        if (!android::bpf::isAtLeastKernelVersion(5, 9, 0)) {    \
+            GTEST_LOG_(INFO) << "This test is skipped since xdp" \
+                             << "not supported\n";               \
+            return;                                              \
+        }                                                        \
+    } while (0)
+
+}  // namespace bpf
+}  // namespace android
diff --git a/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h b/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
new file mode 100644
index 0000000..bc4168e
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ * Android BPF library - public API
+ *
+ * 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 <log/log.h>
+
+#include <android-base/properties.h>
+
+namespace android {
+namespace bpf {
+
+// Wait for bpfloader to load BPF programs.
+static inline void waitForProgsLoaded() {
+    // infinite loop until success with 5/10/20/40/60/60/60... delay
+    for (int delay = 5;; delay *= 2) {
+        if (delay > 60) delay = 60;
+        if (android::base::WaitForProperty("bpf.progs_loaded", "1", std::chrono::seconds(delay)))
+            return;
+        ALOGW("Waited %ds for bpf.progs_loaded, still waiting...", delay);
+    }
+}
+
+}  // 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
new file mode 100644
index 0000000..878bb10
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -0,0 +1,216 @@
+/* Common BPF helpers to be used by all BPF programs loaded by Android */
+
+#include <linux/bpf.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "bpf_map_def.h"
+
+/******************************************************************************
+ * WARNING: CHANGES TO THIS FILE OUTSIDE OF AOSP/MASTER ARE LIKELY TO BREAK   *
+ * DEVICE COMPATIBILITY WITH MAINLINE MODULES SHIPPING EBPF CODE.             *
+ *                                                                            *
+ * THIS WILL LIKELY RESULT IN BRICKED DEVICES AT SOME ARBITRARY FUTURE TIME   *
+ *                                                                            *
+ * THAT GOES ESPECIALLY FOR THE 'SECTION' 'LICENSE' AND 'CRITICAL' MACROS     *
+ *                                                                            *
+ * We strongly suggest that if you need changes to bpfloader functionality    *
+ * you get your changes reviewed and accepted into aosp/master.               *
+ *                                                                            *
+ ******************************************************************************/
+
+/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
+ * before #include "bpf_helpers.h" to change which bpfloaders will
+ * process the resulting .o file.
+ *
+ * While this will work outside of mainline too, there just is no point to
+ * using it when the .o and the bpfloader ship in sync with each other.
+ */
+#ifndef BPFLOADER_MIN_VER
+#define BPFLOADER_MIN_VER DEFAULT_BPFLOADER_MIN_VER
+#endif
+
+#ifndef BPFLOADER_MAX_VER
+#define BPFLOADER_MAX_VER DEFAULT_BPFLOADER_MAX_VER
+#endif
+
+/* place things in different elf sections */
+#define SECTION(NAME) __attribute__((section(NAME), used))
+
+/* Must be present in every program, example usage:
+ *   LICENSE("GPL"); or LICENSE("Apache 2.0");
+ *
+ * We also take this opportunity to embed a bunch of other useful values in
+ * the resulting .o (This is to enable some limited forward compatibility
+ * with mainline module shipped ebpf programs)
+ *
+ * The bpfloader_{min/max}_ver defines the [min, max) range of bpfloader
+ * versions that should load this .o file (bpfloaders outside of this range
+ * will simply ignore/skip this *entire* .o)
+ * The [inclusive,exclusive) matches what we do for kernel ver dependencies.
+ *
+ * The size_of_bpf_{map,prog}_def allow the bpfloader to load programs where
+ * these structures have been extended with additional fields (they will of
+ * course simply be ignored then).
+ *
+ * If missing, bpfloader_{min/max}_ver default to 0/0x10000 ie. [v0.0, v1.0),
+ * while size_of_bpf_{map/prog}_def default to 32/20 which are the v0.0 sizes.
+ */
+#define LICENSE(NAME)                                                                           \
+    unsigned int _bpfloader_min_ver SECTION("bpfloader_min_ver") = BPFLOADER_MIN_VER;           \
+    unsigned int _bpfloader_max_ver SECTION("bpfloader_max_ver") = BPFLOADER_MAX_VER;           \
+    size_t _size_of_bpf_map_def SECTION("size_of_bpf_map_def") = sizeof(struct bpf_map_def);    \
+    size_t _size_of_bpf_prog_def SECTION("size_of_bpf_prog_def") = sizeof(struct bpf_prog_def); \
+    char _license[] SECTION("license") = (NAME)
+
+/* flag the resulting bpf .o file as critical to system functionality,
+ * loading all kernel version appropriate programs in it must succeed
+ * for bpfloader success
+ */
+#define CRITICAL(REASON) char _critical[] SECTION("critical") = (REASON)
+
+/*
+ * Helper functions called from eBPF programs written in C. These are
+ * implemented in the kernel sources.
+ */
+
+#define KVER_NONE 0
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+#define KVER_INF 0xFFFFFFFFu
+
+/* generic functions */
+
+/*
+ * Type-unsafe bpf map functions - avoid if possible.
+ *
+ * Using these it is possible to pass in keys/values of the wrong type/size,
+ * or, for 'bpf_map_lookup_elem_unsafe' receive into a pointer to the wrong type.
+ * You will not get a compile time failure, and for certain types of errors you
+ * might not even get a failure from the kernel's ebpf verifier during program load,
+ * instead stuff might just not work right at runtime.
+ *
+ * Instead please use:
+ *   DEFINE_BPF_MAP(foo_map, TYPE, KeyType, ValueType, num_entries)
+ * where TYPE can be something like HASH or ARRAY, and num_entries is an integer.
+ *
+ * This defines the map (hence this should not be used in a header file included
+ * from multiple locations) and provides type safe accessors:
+ *   ValueType * bpf_foo_map_lookup_elem(const KeyType *)
+ *   int bpf_foo_map_update_elem(const KeyType *, const ValueType *, flags)
+ *   int bpf_foo_map_delete_elem(const KeyType *)
+ *
+ * This will make sure that if you change the type of a map you'll get compile
+ * errors at any spots you forget to update with the new type.
+ *
+ * Note: these all take pointers to const map because from the C/eBPF point of view
+ * the map struct is really just a readonly map definition of the in kernel object.
+ * Runtime modification of the map defining struct is meaningless, since
+ * the contents is only ever used during bpf program loading & map creation
+ * by the bpf loader, and not by the eBPF program itself.
+ */
+static void* (*bpf_map_lookup_elem_unsafe)(const struct bpf_map_def* map,
+                                           const void* key) = (void*)BPF_FUNC_map_lookup_elem;
+static int (*bpf_map_update_elem_unsafe)(const struct bpf_map_def* map, const void* key,
+                                         const void* value, unsigned long long flags) = (void*)
+        BPF_FUNC_map_update_elem;
+static int (*bpf_map_delete_elem_unsafe)(const struct bpf_map_def* map,
+                                         const void* key) = (void*)BPF_FUNC_map_delete_elem;
+
+/* type safe macro to declare a map and related accessor functions */
+#define DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, usr, grp, md)     \
+    const struct bpf_map_def SECTION("maps") the_map = {                                         \
+            .type = BPF_MAP_TYPE_##TYPE,                                                         \
+            .key_size = sizeof(TypeOfKey),                                                       \
+            .value_size = sizeof(TypeOfValue),                                                   \
+            .max_entries = (num_entries),                                                        \
+            .map_flags = 0,                                                                      \
+            .uid = (usr),                                                                        \
+            .gid = (grp),                                                                        \
+            .mode = (md),                                                                        \
+            .bpfloader_min_ver = DEFAULT_BPFLOADER_MIN_VER,                                      \
+            .bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER,                                      \
+            .min_kver = KVER_NONE,                                                               \
+            .max_kver = KVER_INF,                                                                \
+    };                                                                                           \
+                                                                                                 \
+    static inline __always_inline __unused TypeOfValue* bpf_##the_map##_lookup_elem(             \
+            const TypeOfKey* k) {                                                                \
+        return bpf_map_lookup_elem_unsafe(&the_map, k);                                          \
+    };                                                                                           \
+                                                                                                 \
+    static inline __always_inline __unused int bpf_##the_map##_update_elem(                      \
+            const TypeOfKey* k, const TypeOfValue* v, unsigned long long flags) {                \
+        return bpf_map_update_elem_unsafe(&the_map, k, v, flags);                                \
+    };                                                                                           \
+                                                                                                 \
+    static inline __always_inline __unused int bpf_##the_map##_delete_elem(const TypeOfKey* k) { \
+        return bpf_map_delete_elem_unsafe(&the_map, k);                                          \
+    };
+
+#define DEFINE_BPF_MAP(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, AID_ROOT, 0600)
+
+#define DEFINE_BPF_MAP_GWO(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, gid, 0620)
+
+#define DEFINE_BPF_MAP_GRO(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, gid, 0640)
+
+#define DEFINE_BPF_MAP_GRW(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, gid) \
+    DEFINE_BPF_MAP_UGM(the_map, TYPE, TypeOfKey, TypeOfValue, num_entries, AID_ROOT, gid, 0660)
+
+static int (*bpf_probe_read)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read;
+static int (*bpf_probe_read_str)(void* dst, int size, void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_str;
+static unsigned long long (*bpf_ktime_get_ns)(void) = (void*) BPF_FUNC_ktime_get_ns;
+static unsigned long long (*bpf_ktime_get_boot_ns)(void) = (void*)BPF_FUNC_ktime_get_boot_ns;
+static int (*bpf_trace_printk)(const char* fmt, int fmt_size, ...) = (void*) BPF_FUNC_trace_printk;
+static unsigned long long (*bpf_get_current_pid_tgid)(void) = (void*) BPF_FUNC_get_current_pid_tgid;
+static unsigned long long (*bpf_get_current_uid_gid)(void) = (void*) BPF_FUNC_get_current_uid_gid;
+static unsigned long long (*bpf_get_smp_processor_id)(void) = (void*) BPF_FUNC_get_smp_processor_id;
+
+#define DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+                                       opt)                                                        \
+    const struct bpf_prog_def SECTION("progs") the_prog##_def = {                                  \
+            .uid = (prog_uid),                                                                     \
+            .gid = (prog_gid),                                                                     \
+            .min_kver = (min_kv),                                                                  \
+            .max_kver = (max_kv),                                                                  \
+            .optional = (opt),                                                                     \
+            .bpfloader_min_ver = DEFAULT_BPFLOADER_MIN_VER,                                        \
+            .bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER,                                        \
+    };                                                                                             \
+    SECTION(SECTION_NAME)                                                                          \
+    int the_prog
+
+// Programs (here used in the sense of functions/sections) marked optional are allowed to fail
+// to load (for example due to missing kernel patches).
+// The bpfloader will just ignore these failures and continue processing the next section.
+//
+// A non-optional program (function/section) failing to load causes a failure and aborts
+// processing of the entire .o, if the .o is additionally marked critical, this will result
+// in the entire bpfloader process terminating with a failure and not setting the bpf.progs_loaded
+// system property.  This in turn results in waitForProgsLoaded() never finishing.
+//
+// ie. a non-optional program in a critical .o is mandatory for kernels matching the min/max kver.
+
+// programs requiring a kernel version >= min_kv && < max_kv
+#define DEFINE_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv) \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, \
+                                   false)
+#define DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, \
+                                            max_kv)                                             \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, max_kv, true)
+
+// programs requiring a kernel version >= min_kv
+#define DEFINE_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv)                 \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \
+                                   false)
+#define DEFINE_OPTIONAL_BPF_PROG_KVER(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv)        \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, min_kv, KVER_INF, \
+                                   true)
+
+// programs with no kernel version requirements
+#define DEFINE_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, false)
+#define DEFINE_OPTIONAL_BPF_PROG(SECTION_NAME, prog_uid, prog_gid, the_prog) \
+    DEFINE_BPF_PROG_KVER_RANGE_OPT(SECTION_NAME, prog_uid, prog_gid, the_prog, 0, KVER_INF, true)
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
new file mode 100644
index 0000000..02b2096
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_map_def.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+/* This file is separate because it's included both by eBPF programs (via include
+ * in bpf_helpers.h) and directly by the boot time bpfloader (Loader.cpp).
+ */
+
+#include <linux/bpf.h>
+
+// Pull in AID_* constants from //system/core/libcutils/include/private/android_filesystem_config.h
+#include <private/android_filesystem_config.h>
+
+/******************************************************************************
+ *                                                                            *
+ *                          ! ! ! W A R N I N G ! ! !                         *
+ *                                                                            *
+ * CHANGES TO THESE STRUCTURE DEFINITIONS OUTSIDE OF AOSP/MASTER *WILL* BREAK *
+ * MAINLINE MODULE COMPATIBILITY                                              *
+ *                                                                            *
+ * AND THUS MAY RESULT IN YOUR DEVICE BRICKING AT SOME ARBITRARY POINT IN     *
+ * THE FUTURE                                                                 *
+ *                                                                            *
+ * (and even in aosp/master you may only append new fields at the very end,   *
+ *  you may *never* delete fields, change their types, ordering, insert in    *
+ *  the middle, etc.  If a mainline module using the old definition has       *
+ *  already shipped (which happens roughly monthly), then it's set in stone)  *
+ *                                                                            *
+ ******************************************************************************/
+
+// These are the values used if these fields are missing
+#define DEFAULT_BPFLOADER_MIN_VER 0u        // v0.0 (this is inclusive ie. >= v0.0)
+#define DEFAULT_BPFLOADER_MAX_VER 0x10000u  // v1.0 (this is exclusive ie. < v1.0)
+#define DEFAULT_SIZEOF_BPF_MAP_DEF 32       // v0.0 struct: enum (uint sized) + 7 uint
+#define DEFAULT_SIZEOF_BPF_PROG_DEF 20      // v0.0 struct: 4 uint + bool + 3 byte alignment pad
+
+/*
+ * The bpf_{map,prog}_def structures are compiled for different architectures.
+ * Once by the BPF compiler for the BPF architecture, and once by a C++
+ * compiler for the native Android architecture for the bpfloader.
+ *
+ * For things to work, their layout must be the same between the two.
+ * The BPF architecture is platform independent ('64-bit LSB bpf').
+ * So this effectively means these structures must be the same layout
+ * on 5 architectures, all of them little endian:
+ *   64-bit BPF, x86_64, arm  and  32-bit x86 and arm
+ *
+ * As such for any types we use inside of these structs we must make sure that
+ * the size and alignment are the same, so the same amount of padding is used.
+ *
+ * Currently we only use: bool, enum bpf_map_type and unsigned int.
+ * Additionally we use char for padding.
+ *
+ * !!! WARNING: HERE BE DRAGONS !!!
+ *
+ * Be particularly careful with 64-bit integers.
+ * You will need to manually override their alignment to 8 bytes.
+ *
+ * To quote some parts of https://gcc.gnu.org/bugzilla/show_bug.cgi?id=69560
+ *
+ * Some types have weaker alignment requirements when they are structure members.
+ *
+ * unsigned long long on x86 is such a type.
+ *
+ * C distinguishes C11 _Alignof (the minimum alignment the type is guaranteed
+ * to have in all contexts, so 4, see min_align_of_type) from GNU C __alignof
+ * (the normal alignment of the type, so 8).
+ *
+ * alignof / _Alignof == minimum alignment required by target ABI
+ * __alignof / __alignof__ == preferred alignment
+ *
+ * When in a struct, apparently the minimum alignment is used.
+ */
+
+_Static_assert(sizeof(bool) == 1, "sizeof bool != 1");
+_Static_assert(__alignof__(bool) == 1, "__alignof__ bool != 1");
+_Static_assert(_Alignof(bool) == 1, "_Alignof bool != 1");
+
+_Static_assert(sizeof(char) == 1, "sizeof char != 1");
+_Static_assert(__alignof__(char) == 1, "__alignof__ char != 1");
+_Static_assert(_Alignof(char) == 1, "_Alignof char != 1");
+
+// This basically verifies that an enum is 'just' a 32-bit int
+_Static_assert(sizeof(enum bpf_map_type) == 4, "sizeof enum bpf_map_type != 4");
+_Static_assert(__alignof__(enum bpf_map_type) == 4, "__alignof__ enum bpf_map_type != 4");
+_Static_assert(_Alignof(enum bpf_map_type) == 4, "_Alignof enum bpf_map_type != 4");
+
+// Linux kernel requires sizeof(int) == 4, sizeof(void*) == sizeof(long), sizeof(long long) == 8
+_Static_assert(sizeof(unsigned int) == 4, "sizeof unsigned int != 4");
+_Static_assert(__alignof__(unsigned int) == 4, "__alignof__ unsigned int != 4");
+_Static_assert(_Alignof(unsigned int) == 4, "_Alignof unsigned int != 4");
+
+// We don't currently use any 64-bit types in these structs, so this is purely to document issue.
+// Here sizeof & __alignof__ are consistent, but _Alignof is not: compile for 'aosp_cf_x86_phone'
+_Static_assert(sizeof(unsigned long long) == 8, "sizeof unsigned long long != 8");
+_Static_assert(__alignof__(unsigned long long) == 8, "__alignof__ unsigned long long != 8");
+// BPF wants 8, but 32-bit x86 wants 4
+//_Static_assert(_Alignof(unsigned long long) == 8, "_Alignof unsigned long long != 8");
+
+/*
+ * Map structure to be used by Android eBPF C programs. The Android eBPF loader
+ * uses this structure from eBPF object to create maps at boot time.
+ *
+ * The eBPF C program should define structure in the maps section using
+ * SECTION("maps") otherwise it will be ignored by the eBPF loader.
+ *
+ * For example:
+ *   const struct bpf_map_def SECTION("maps") mymap { .type=... , .key_size=... }
+ *
+ * See 'bpf_helpers.h' for helpful macros for eBPF program use.
+ */
+struct bpf_map_def {
+    enum bpf_map_type type;
+    unsigned int key_size;
+    unsigned int value_size;
+    unsigned int max_entries;
+    unsigned int map_flags;
+
+    // The following are not supported by the Android bpfloader:
+    //   unsigned int inner_map_idx;
+    //   unsigned int numa_node;
+
+    unsigned int uid;   // uid_t
+    unsigned int gid;   // gid_t
+    unsigned int mode;  // mode_t
+
+    // The following fields were added in version 0.1
+    unsigned int bpfloader_min_ver;  // if missing, defaults to 0, ie. v0.0
+    unsigned int bpfloader_max_ver;  // if missing, defaults to 0x10000, ie. v1.0
+
+    // The following fields were added in version 0.2
+    // kernelVersion() must be >= min_kver and < max_kver
+    unsigned int min_kver;
+    unsigned int max_kver;
+};
+
+// This needs to be updated whenever the above structure definition is expanded.
+_Static_assert(sizeof(struct bpf_map_def) == 48, "sizeof struct bpf_map_def != 48");
+_Static_assert(__alignof__(struct bpf_map_def) == 4, "__alignof__ struct bpf_map_def != 4");
+_Static_assert(_Alignof(struct bpf_map_def) == 4, "_Alignof struct bpf_map_def != 4");
+
+struct bpf_prog_def {
+    unsigned int uid;
+    unsigned int gid;
+
+    // kernelVersion() must be >= min_kver and < max_kver
+    unsigned int min_kver;
+    unsigned int max_kver;
+
+    bool optional;  // program section (ie. function) may fail to load, continue onto next func.
+    char pad0[3];
+
+    // The following fields were added in version 0.1
+    unsigned int bpfloader_min_ver;  // if missing, defaults to 0, ie. v0.0
+    unsigned int bpfloader_max_ver;  // if missing, defaults to 0x10000, ie. v1.0
+
+    // No new fields in version 0.2
+};
+
+// This needs to be updated whenever the above structure definition is expanded.
+_Static_assert(sizeof(struct bpf_prog_def) == 28, "sizeof struct bpf_prog_def != 28");
+_Static_assert(__alignof__(struct bpf_prog_def) == 4, "__alignof__ struct bpf_prog_def != 4");
+_Static_assert(_Alignof(struct bpf_prog_def) == 4, "_Alignof struct bpf_prog_def != 4");
diff --git a/staticlibs/native/bpf_syscall_wrappers/Android.bp b/staticlibs/native/bpf_syscall_wrappers/Android.bp
index 1416b6b..c1bfd80 100644
--- a/staticlibs/native/bpf_syscall_wrappers/Android.bp
+++ b/staticlibs/native/bpf_syscall_wrappers/Android.bp
@@ -19,7 +19,7 @@
 cc_library_headers {
     name: "bpf_syscall_wrappers",
     vendor_available: false,
-    host_supported: false,
+    host_supported: true,
     native_bridge_supported: true,
     export_include_dirs: ["include"],
     cflags: [
@@ -30,12 +30,18 @@
     min_sdk_version: "30",
     apex_available: [
         "//apex_available:platform",
+        "com.android.mediaprovider",
         "com.android.tethering",
     ],
     visibility: [
+        "//frameworks/libs/net/common/native/bpf_headers",
         "//frameworks/libs/net/common/native/bpfmapjni",
+        "//frameworks/libs/net/common/native/tcutils",
+        "//packages/modules/Connectivity/netd",
         "//packages/modules/Connectivity/service",
+        "//packages/modules/Connectivity/service/native",
         "//packages/modules/Connectivity/Tethering",
+        "//packages/providers/MediaProvider/jni",
         "//system/bpf/libbpf_android",
         "//system/memory/lmkd",
     ],
diff --git a/staticlibs/native/bpfmapjni/Android.bp b/staticlibs/native/bpfmapjni/Android.bp
index b7af22d..8d3c90b 100644
--- a/staticlibs/native/bpfmapjni/Android.bp
+++ b/staticlibs/native/bpfmapjni/Android.bp
@@ -18,7 +18,10 @@
 
 cc_library_static {
     name: "libnet_utils_device_common_bpfjni",
-    srcs: ["com_android_net_module_util_BpfMap.cpp"],
+    srcs: [
+        "com_android_net_module_util_BpfMap.cpp",
+        "com_android_net_module_util_TcUtils.cpp",
+    ],
     header_libs: [
         "bpf_syscall_wrappers",
         "jni_headers",
@@ -27,6 +30,9 @@
         "liblog",
         "libnativehelper_compat_libc++",
     ],
+    static_libs: [
+        "libtcutils",
+    ],
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
new file mode 100644
index 0000000..e5a3668
--- /dev/null
+++ b/staticlibs/native/bpfmapjni/com_android_net_module_util_TcUtils.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_utf_chars.h>
+#include <tcutils/tcutils.h>
+
+namespace android {
+
+static void throwIOException(JNIEnv *env, const char *msg, int error) {
+  jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg,
+                       strerror(error));
+}
+
+static jboolean com_android_net_module_util_TcUtils_isEthernet(JNIEnv *env,
+                                                               jobject clazz,
+                                                               jstring iface) {
+  ScopedUtfChars interface(env, iface);
+  bool result = false;
+  int error = isEthernet(interface.c_str(), result);
+  if (error) {
+    throwIOException(
+        env, "com_android_net_module_util_TcUtils_isEthernet error: ", error);
+  }
+  // result is not touched when error is returned; leave false.
+  return result;
+}
+
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
+// /sys/fs/bpf/... direct-action
+static void com_android_net_module_util_TcUtils_tcFilterAddDevBpf(
+    JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
+    jshort proto, jstring bpfProgPath) {
+  ScopedUtfChars pathname(env, bpfProgPath);
+  int error = tcAddBpfFilter(ifIndex, ingress, prio, proto, pathname.c_str());
+  if (error) {
+    throwIOException(
+        env,
+        "com_android_net_module_util_TcUtils_tcFilterAddDevBpf error: ", error);
+  }
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+static void com_android_net_module_util_TcUtils_tcFilterDelDev(
+    JNIEnv *env, jobject clazz, jint ifIndex, jboolean ingress, jshort prio,
+    jshort proto) {
+  int error = tcDeleteFilter(ifIndex, ingress, prio, proto);
+  if (error) {
+    throwIOException(
+        env,
+        "com_android_net_module_util_TcUtils_tcFilterDelDev error: ", error);
+  }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+    /* name, signature, funcPtr */
+    {"isEthernet", "(Ljava/lang/String;)Z",
+     (void *)com_android_net_module_util_TcUtils_isEthernet},
+    {"tcFilterAddDevBpf", "(IZSSLjava/lang/String;)V",
+     (void *)com_android_net_module_util_TcUtils_tcFilterAddDevBpf},
+    {"tcFilterDelDev", "(IZSS)V",
+     (void *)com_android_net_module_util_TcUtils_tcFilterDelDev},
+};
+
+int register_com_android_net_module_util_TcUtils(JNIEnv *env,
+                                                 char const *class_name) {
+  return jniRegisterNativeMethods(env, class_name, gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/staticlibs/native/ip_checksum/Android.bp b/staticlibs/native/ip_checksum/Android.bp
new file mode 100644
index 0000000..9878d73
--- /dev/null
+++ b/staticlibs/native/ip_checksum/Android.bp
@@ -0,0 +1,46 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libip_checksum",
+
+    srcs: [
+        "checksum.c",
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    export_include_dirs: ["."],
+
+    // Needed because libnetutils depends on libip_checksum, and libnetutils has
+    // vendor_available = true. Making this library vendor_available does not create any maintenance
+    // burden or version skew issues because this library is only static, not dynamic, and thus is
+    // not installed on the device.
+    //
+    // TODO: delete libnetutils from the VNDK in T, and remove this.
+    vendor_available: true,
+
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+}
diff --git a/staticlibs/native/ip_checksum/checksum.c b/staticlibs/native/ip_checksum/checksum.c
new file mode 100644
index 0000000..04217a7
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.c
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.c - ipv4/ipv6 checksum calculation
+ */
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/tcp.h>
+#include <netinet/udp.h>
+
+#include "checksum.h"
+
+/* function: ip_checksum_add
+ * adds data to a checksum. only known to work on little-endian hosts
+ * current - the current checksum (or 0 to start a new checksum)
+ *   data        - the data to add to the checksum
+ *   len         - length of data
+ */
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len) {
+    uint32_t checksum = current;
+    int left = len;
+    const uint16_t* data_16 = data;
+
+    while (left > 1) {
+        checksum += *data_16;
+        data_16++;
+        left -= 2;
+    }
+    if (left) {
+        checksum += *(uint8_t*)data_16;
+    }
+
+    return checksum;
+}
+
+/* function: ip_checksum_fold
+ * folds a 32-bit partial checksum into 16 bits
+ *   temp_sum - sum from ip_checksum_add
+ *   returns: the folded checksum in network byte order
+ */
+uint16_t ip_checksum_fold(uint32_t temp_sum) {
+    while (temp_sum > 0xffff) {
+        temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
+    }
+    return temp_sum;
+}
+
+/* function: ip_checksum_finish
+ * folds and closes the checksum
+ *   temp_sum - sum from ip_checksum_add
+ *   returns: a header checksum value in network byte order
+ */
+uint16_t ip_checksum_finish(uint32_t temp_sum) {
+    return ~ip_checksum_fold(temp_sum);
+}
+
+/* function: ip_checksum
+ * combined ip_checksum_add and ip_checksum_finish
+ *   data - data to checksum
+ *   len  - length of data
+ */
+uint16_t ip_checksum(const void* data, int len) {
+    // TODO: consider starting from 0xffff so the checksum of a buffer entirely consisting of zeros
+    // is correctly calculated as 0.
+    uint32_t temp_sum;
+
+    temp_sum = ip_checksum_add(0, data, len);
+    return ip_checksum_finish(temp_sum);
+}
+
+/* function: ipv6_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp/icmp headers
+ *   ip6      - the ipv6 header
+ *   len      - the transport length (transport header + payload)
+ *   protocol - the transport layer protocol, can be different from ip6->ip6_nxt for fragments
+ */
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol) {
+    uint32_t checksum_len = htonl(len);
+    uint32_t checksum_next = htonl(protocol);
+
+    uint32_t current = 0;
+
+    current = ip_checksum_add(current, &(ip6->ip6_src), sizeof(struct in6_addr));
+    current = ip_checksum_add(current, &(ip6->ip6_dst), sizeof(struct in6_addr));
+    current = ip_checksum_add(current, &checksum_len, sizeof(checksum_len));
+    current = ip_checksum_add(current, &checksum_next, sizeof(checksum_next));
+
+    return current;
+}
+
+/* function: ipv4_pseudo_header_checksum
+ * calculate the pseudo header checksum for use in tcp/udp headers
+ *   ip      - the ipv4 header
+ *   len     - the transport length (transport header + payload)
+ */
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len) {
+    uint16_t temp_protocol, temp_length;
+
+    temp_protocol = htons(ip->protocol);
+    temp_length = htons(len);
+
+    uint32_t current = 0;
+
+    current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
+    current = ip_checksum_add(current, &(ip->daddr), sizeof(uint32_t));
+    current = ip_checksum_add(current, &temp_protocol, sizeof(uint16_t));
+    current = ip_checksum_add(current, &temp_length, sizeof(uint16_t));
+
+    return current;
+}
+
+/* function: ip_checksum_adjust
+ * calculates a new checksum given a previous checksum and the old and new pseudo-header checksums
+ *   checksum    - the header checksum in the original packet in network byte order
+ *   old_hdr_sum - the pseudo-header checksum of the original packet
+ *   new_hdr_sum - the pseudo-header checksum of the translated packet
+ *   returns: the new header checksum in network byte order
+ */
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) {
+    // Algorithm suggested in RFC 1624.
+    // http://tools.ietf.org/html/rfc1624#section-3
+    checksum = ~checksum;
+    uint16_t folded_sum = ip_checksum_fold(checksum + new_hdr_sum);
+    uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
+    if (folded_sum > folded_old) {
+        return ~(folded_sum - folded_old);
+    } else {
+        return ~(folded_sum - folded_old - 1);  // end-around borrow
+    }
+}
diff --git a/staticlibs/native/ip_checksum/checksum.h b/staticlibs/native/ip_checksum/checksum.h
new file mode 100644
index 0000000..868217c
--- /dev/null
+++ b/staticlibs/native/ip_checksum/checksum.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2011 Daniel Drown
+ *
+ * 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.
+ *
+ * checksum.h - checksum functions
+ */
+#ifndef __CHECKSUM_H__
+#define __CHECKSUM_H__
+
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <stdint.h>
+
+uint32_t ip_checksum_add(uint32_t current, const void* data, int len);
+uint16_t ip_checksum_finish(uint32_t temp_sum);
+uint16_t ip_checksum(const void* data, int len);
+
+uint32_t ipv6_pseudo_header_checksum(const struct ip6_hdr* ip6, uint32_t len, uint8_t protocol);
+uint32_t ipv4_pseudo_header_checksum(const struct iphdr* ip, uint16_t len);
+
+uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum);
+
+#endif /* __CHECKSUM_H__ */
diff --git a/staticlibs/native/tcutils/Android.bp b/staticlibs/native/tcutils/Android.bp
new file mode 100644
index 0000000..c26fca6
--- /dev/null
+++ b/staticlibs/native/tcutils/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libtcutils",
+    srcs: ["tcutils.cpp"],
+    export_include_dirs: ["include"],
+    header_libs: ["bpf_syscall_wrappers"],
+    shared_libs: [
+        "liblog",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+    sdk_version: "30",
+    min_sdk_version: "30",
+    apex_available: [
+        "com.android.tethering",
+        "//apex_available:platform",
+    ],
+    visibility: [
+        "//frameworks/libs/net/common/native/bpfmapjni",
+        "//packages/modules/Connectivity:__subpackages__",
+        "//system/netd/server",
+    ],
+}
diff --git a/staticlibs/native/tcutils/include/tcutils/tcutils.h b/staticlibs/native/tcutils/include/tcutils/tcutils.h
new file mode 100644
index 0000000..d1e1bb7
--- /dev/null
+++ b/staticlibs/native/tcutils/include/tcutils/tcutils.h
@@ -0,0 +1,28 @@
+/*
+ * 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 <cstdint>
+
+namespace android {
+
+int isEthernet(const char *iface, bool &isEthernet);
+int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
+                   const char *bpfProgPath);
+int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto);
+
+} // namespace android
diff --git a/staticlibs/native/tcutils/kernelversion.h b/staticlibs/native/tcutils/kernelversion.h
new file mode 100644
index 0000000..59b9e05
--- /dev/null
+++ b/staticlibs/native/tcutils/kernelversion.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+// -----------------------------------------------------------------------------
+// TODO - This should be replaced with BpfUtils in bpf_headers.
+// Currently, bpf_headers contains a bunch requirements it doesn't actually provide, such as a
+// non-ndk liblog version, and some version of libbase. libtcutils does not have access to either of
+// these, so I think this will have to wait until we figure out a way around this.
+//
+// In the mean time copying verbatim from:
+//   frameworks/libs/net/common/native/bpf_headers
+
+#include <stdio.h>
+#include <sys/utsname.h>
+
+#define KVER(a, b, c) (((a) << 24) + ((b) << 16) + (c))
+
+namespace android {
+
+unsigned kernelVersion() {
+  struct utsname buf;
+  int ret = uname(&buf);
+  if (ret)
+    return 0;
+
+  unsigned kver_major;
+  unsigned kver_minor;
+  unsigned kver_sub;
+  char discard;
+  ret = sscanf(buf.release, "%u.%u.%u%c", &kver_major, &kver_minor, &kver_sub,
+               &discard);
+  // Check the device kernel version
+  if (ret < 3)
+    return 0;
+
+  return KVER(kver_major, kver_minor, kver_sub);
+}
+
+bool isAtLeastKernelVersion(unsigned major, unsigned minor, unsigned sub) {
+  return kernelVersion() >= KVER(major, minor, sub);
+}
+
+}
diff --git a/staticlibs/native/tcutils/scopeguard.h b/staticlibs/native/tcutils/scopeguard.h
new file mode 100644
index 0000000..76bbb93
--- /dev/null
+++ b/staticlibs/native/tcutils/scopeguard.h
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+// -----------------------------------------------------------------------------
+// TODO: figure out a way to use libbase_ndk. This is currently not working
+// because of missing apex availability. For now, we can use a copy of
+// ScopeGuard which is very lean compared to unique_fd. This code has been
+// copied verbatim from:
+// https://cs.android.com/android/platform/superproject/+/master:system/libbase/include/android-base/scopeguard.h
+
+#pragma once
+
+#include <utility> // for std::move, std::forward
+
+namespace android {
+namespace base {
+
+// ScopeGuard ensures that the specified functor is executed no matter how the
+// current scope exits.
+template <typename F> class ScopeGuard {
+public:
+  ScopeGuard(F &&f) : f_(std::forward<F>(f)), active_(true) {}
+
+  ScopeGuard(ScopeGuard &&that) noexcept
+      : f_(std::move(that.f_)), active_(that.active_) {
+    that.active_ = false;
+  }
+
+  template <typename Functor>
+  ScopeGuard(ScopeGuard<Functor> &&that)
+      : f_(std::move(that.f_)), active_(that.active_) {
+    that.active_ = false;
+  }
+
+  ~ScopeGuard() {
+    if (active_)
+      f_();
+  }
+
+  ScopeGuard() = delete;
+  ScopeGuard(const ScopeGuard &) = delete;
+  void operator=(const ScopeGuard &) = delete;
+  void operator=(ScopeGuard &&that) = delete;
+
+  void Disable() { active_ = false; }
+
+  bool active() const { return active_; }
+
+private:
+  template <typename Functor> friend class ScopeGuard;
+
+  F f_;
+  bool active_;
+};
+
+template <typename F> ScopeGuard<F> make_scope_guard(F &&f) {
+  return ScopeGuard<F>(std::forward<F>(f));
+}
+
+} // namespace base
+} // namespace android
diff --git a/staticlibs/native/tcutils/tcutils.cpp b/staticlibs/native/tcutils/tcutils.cpp
new file mode 100644
index 0000000..e30a500
--- /dev/null
+++ b/staticlibs/native/tcutils/tcutils.cpp
@@ -0,0 +1,350 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "TcUtils"
+
+#include "tcutils/tcutils.h"
+
+#include "kernelversion.h"
+#include "scopeguard.h"
+
+#include <android/log.h>
+#include <arpa/inet.h>
+#include <cerrno>
+#include <cstring>
+#include <libgen.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/pkt_cls.h>
+#include <linux/pkt_sched.h>
+#include <linux/rtnetlink.h>
+#include <net/if.h>
+#include <stdarg.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <utility>
+
+#define BPF_FD_JUST_USE_INT
+#include <BpfSyscallWrappers.h>
+#undef BPF_FD_JUST_USE_INT
+
+// The maximum length of TCA_BPF_NAME. Sync from net/sched/cls_bpf.c.
+#define CLS_BPF_NAME_LEN 256
+
+// Classifier name. See cls_bpf_ops in net/sched/cls_bpf.c.
+#define CLS_BPF_KIND_NAME "bpf"
+
+namespace android {
+namespace {
+
+void logError(const char *fmt...) {
+  va_list args;
+  va_start(args, fmt);
+  __android_log_vprint(ANDROID_LOG_ERROR, LOG_TAG, fmt, args);
+  va_end(args);
+}
+
+const sockaddr_nl KERNEL_NLADDR = {AF_NETLINK, 0, 0, 0};
+const uint16_t NETLINK_REQUEST_FLAGS = NLM_F_REQUEST | NLM_F_ACK;
+
+int sendAndProcessNetlinkResponse(const void *req, int len) {
+  // TODO: use unique_fd instead of ScopeGuard
+  int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
+  if (fd == -1) {
+    int error = errno;
+    logError("socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE): %d",
+             error);
+    return -error;
+  }
+  auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
+
+  static constexpr int on = 1;
+  if (setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &on, sizeof(on))) {
+    int error = errno;
+    logError("setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, 1): %d", error);
+    return -error;
+  }
+
+  // this is needed to get valid strace netlink parsing, it allocates the pid
+  if (bind(fd, (const struct sockaddr *)&KERNEL_NLADDR,
+           sizeof(KERNEL_NLADDR))) {
+    int error = errno;
+    logError("bind(fd, {AF_NETLINK, 0, 0}: %d)", error);
+    return -error;
+  }
+
+  // we do not want to receive messages from anyone besides the kernel
+  if (connect(fd, (const struct sockaddr *)&KERNEL_NLADDR,
+              sizeof(KERNEL_NLADDR))) {
+    int error = errno;
+    logError("connect(fd, {AF_NETLINK, 0, 0}): %d", error);
+    return -error;
+  }
+
+  int rv = send(fd, req, len, 0);
+
+  if (rv == -1) {
+    int error = errno;
+    logError("send(fd, req, len, 0) failed: %d", error);
+    return -error;
+  }
+
+  if (rv != len) {
+    logError("send(fd, req, len = %d, 0) returned invalid message size %d", len,
+             rv);
+    return -EMSGSIZE;
+  }
+
+  struct {
+    nlmsghdr h;
+    nlmsgerr e;
+    char buf[256];
+  } resp = {};
+
+  rv = recv(fd, &resp, sizeof(resp), MSG_TRUNC);
+
+  if (rv == -1) {
+    int error = errno;
+    logError("recv() failed: %d", error);
+    return -error;
+  }
+
+  if (rv < (int)NLMSG_SPACE(sizeof(struct nlmsgerr))) {
+    logError("recv() returned short packet: %d", rv);
+    return -EBADMSG;
+  }
+
+  if (resp.h.nlmsg_len != (unsigned)rv) {
+    logError("recv() returned invalid header length: %d != %d",
+             resp.h.nlmsg_len, rv);
+    return -EBADMSG;
+  }
+
+  if (resp.h.nlmsg_type != NLMSG_ERROR) {
+    logError("recv() did not return NLMSG_ERROR message: %d",
+             resp.h.nlmsg_type);
+    return -ENOMSG;
+  }
+
+  if (resp.e.error) {
+    logError("NLMSG_ERROR message return error: %d", resp.e.error);
+  }
+  return resp.e.error; // returns 0 on success
+}
+
+int hardwareAddressType(const char *interface) {
+  int fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+  if (fd < 0)
+    return -errno;
+  auto scopeGuard = base::make_scope_guard([fd] { close(fd); });
+
+  struct ifreq ifr = {};
+  // We use strncpy() instead of strlcpy() since kernel has to be able
+  // to handle non-zero terminated junk passed in by userspace anyway,
+  // and this way too long interface names (more than IFNAMSIZ-1 = 15
+  // characters plus terminating NULL) will not get truncated to 15
+  // characters and zero-terminated and thus potentially erroneously
+  // match a truncated interface if one were to exist.
+  strncpy(ifr.ifr_name, interface, sizeof(ifr.ifr_name));
+
+  if (ioctl(fd, SIOCGIFHWADDR, &ifr, sizeof(ifr))) {
+    return -errno;
+  }
+  return ifr.ifr_hwaddr.sa_family;
+}
+
+} // namespace
+
+int isEthernet(const char *iface, bool &isEthernet) {
+  int rv = hardwareAddressType(iface);
+  if (rv < 0) {
+    logError("Get hardware address type of interface %s failed: %s", iface,
+             strerror(-rv));
+    return -rv;
+  }
+
+  // Backwards compatibility with pre-GKI kernels that use various custom
+  // ARPHRD_* for their cellular interface
+  switch (rv) {
+  // ARPHRD_PUREIP on at least some Mediatek Android kernels
+  // example: wembley with 4.19 kernel
+  case 520:
+  // in Linux 4.14+ rmnet support was upstreamed and ARHRD_RAWIP became 519,
+  // but it is 530 on at least some Qualcomm Android 4.9 kernels with rmnet
+  // example: Pixel 3 family
+  case 530:
+    // >5.4 kernels are GKI2.0 and thus upstream compatible, however 5.10
+    // shipped with Android S, so (for safety) let's limit ourselves to
+    // >5.10, ie. 5.11+ as a guarantee we're on Android T+ and thus no
+    // longer need this non-upstream compatibility logic
+    static bool is_pre_5_11_kernel = !isAtLeastKernelVersion(5, 11, 0);
+    if (is_pre_5_11_kernel)
+      return false;
+  }
+
+  switch (rv) {
+  case ARPHRD_ETHER:
+    isEthernet = true;
+    return 0;
+  case ARPHRD_NONE:
+  case ARPHRD_PPP:
+  case ARPHRD_RAWIP:
+    isEthernet = false;
+    return 0;
+  default:
+    logError("Unknown hardware address type %d on interface %s", rv, iface);
+    return -ENOENT;
+  }
+}
+
+// tc filter add dev .. in/egress prio 1 protocol ipv6/ip bpf object-pinned
+// /sys/fs/bpf/... direct-action
+int tcAddBpfFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto,
+                   const char *bpfProgPath) {
+  const int bpfFd = bpf::retrieveProgram(bpfProgPath);
+  if (bpfFd == -1) {
+    logError("retrieveProgram failed: %d", errno);
+    return -errno;
+  }
+  auto scopeGuard = base::make_scope_guard([bpfFd] { close(bpfFd); });
+
+  struct {
+    nlmsghdr n;
+    tcmsg t;
+    struct {
+      nlattr attr;
+      // The maximum classifier name length is defined in
+      // tcf_proto_ops in include/net/sch_generic.h.
+      char str[NLMSG_ALIGN(sizeof(CLS_BPF_KIND_NAME))];
+    } kind;
+    struct {
+      nlattr attr;
+      struct {
+        nlattr attr;
+        __u32 u32;
+      } fd;
+      struct {
+        nlattr attr;
+        char str[NLMSG_ALIGN(CLS_BPF_NAME_LEN)];
+      } name;
+      struct {
+        nlattr attr;
+        __u32 u32;
+      } flags;
+    } options;
+  } req = {
+      .n =
+          {
+              .nlmsg_len = sizeof(req),
+              .nlmsg_type = RTM_NEWTFILTER,
+              .nlmsg_flags = NETLINK_REQUEST_FLAGS | NLM_F_EXCL | NLM_F_CREATE,
+          },
+      .t =
+          {
+              .tcm_family = AF_UNSPEC,
+              .tcm_ifindex = ifIndex,
+              .tcm_handle = TC_H_UNSPEC,
+              .tcm_parent = TC_H_MAKE(TC_H_CLSACT, ingress ? TC_H_MIN_INGRESS
+                                                           : TC_H_MIN_EGRESS),
+              .tcm_info =
+                  static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
+                                     htons(static_cast<uint16_t>(proto))),
+          },
+      .kind =
+          {
+              .attr =
+                  {
+                      .nla_len = sizeof(req.kind),
+                      .nla_type = TCA_KIND,
+                  },
+              .str = CLS_BPF_KIND_NAME,
+          },
+      .options =
+          {
+              .attr =
+                  {
+                      .nla_len = sizeof(req.options),
+                      .nla_type = NLA_F_NESTED | TCA_OPTIONS,
+                  },
+              .fd =
+                  {
+                      .attr =
+                          {
+                              .nla_len = sizeof(req.options.fd),
+                              .nla_type = TCA_BPF_FD,
+                          },
+                      .u32 = static_cast<__u32>(bpfFd),
+                  },
+              .name =
+                  {
+                      .attr =
+                          {
+                              .nla_len = sizeof(req.options.name),
+                              .nla_type = TCA_BPF_NAME,
+                          },
+                      // Visible via 'tc filter show', but
+                      // is overwritten by strncpy below
+                      .str = "placeholder",
+                  },
+              .flags =
+                  {
+                      .attr =
+                          {
+                              .nla_len = sizeof(req.options.flags),
+                              .nla_type = TCA_BPF_FLAGS,
+                          },
+                      .u32 = TCA_BPF_FLAG_ACT_DIRECT,
+                  },
+          },
+  };
+
+  snprintf(req.options.name.str, sizeof(req.options.name.str), "%s:[*fsobj]",
+           basename(bpfProgPath));
+
+  int error = sendAndProcessNetlinkResponse(&req, sizeof(req));
+  return error;
+}
+
+// tc filter del dev .. in/egress prio .. protocol ..
+int tcDeleteFilter(int ifIndex, bool ingress, uint16_t prio, uint16_t proto) {
+  const struct {
+    nlmsghdr n;
+    tcmsg t;
+  } req = {
+      .n =
+          {
+              .nlmsg_len = sizeof(req),
+              .nlmsg_type = RTM_DELTFILTER,
+              .nlmsg_flags = NETLINK_REQUEST_FLAGS,
+          },
+      .t =
+          {
+              .tcm_family = AF_UNSPEC,
+              .tcm_ifindex = ifIndex,
+              .tcm_handle = TC_H_UNSPEC,
+              .tcm_parent = TC_H_MAKE(TC_H_CLSACT, ingress ? TC_H_MIN_INGRESS
+                                                           : TC_H_MIN_EGRESS),
+              .tcm_info =
+                  static_cast<__u32>((static_cast<uint16_t>(prio) << 16) |
+                                     htons(static_cast<uint16_t>(proto))),
+          },
+  };
+
+  return sendAndProcessNetlinkResponse(&req, sizeof(req));
+}
+
+} // namespace android
diff --git a/staticlibs/netd/Android.bp b/staticlibs/netd/Android.bp
index 530ccd3..e249e19 100644
--- a/staticlibs/netd/Android.bp
+++ b/staticlibs/netd/Android.bp
@@ -21,11 +21,10 @@
     sdk_version: "system_current",
     min_sdk_version: "29",
     static_libs: [
-        "netd_aidl_interface-V7-java",
+        "netd_aidl_interface-V8-java",
     ],
     apex_available: [
         "//apex_available:platform", // used from services.net
-        "com.android.bluetooth.updatable",
         "com.android.tethering",
         "com.android.wifi",
     ],
@@ -45,10 +44,11 @@
 cc_library_static {
     name: "netd_aidl_interface-lateststable-ndk",
     whole_static_libs: [
-        "netd_aidl_interface-V7-ndk",
+        "netd_aidl_interface-V8-ndk",
     ],
     apex_available: [
         "com.android.resolv",
+        "com.android.tethering",
     ],
     min_sdk_version: "29",
 }
@@ -56,7 +56,7 @@
 cc_library_static {
     name: "netd_aidl_interface-lateststable-cpp",
     whole_static_libs: [
-        "netd_aidl_interface-V7-cpp",
+        "netd_aidl_interface-V8-cpp",
     ],
 }
 
@@ -89,7 +89,6 @@
             // either outside the system server or use jarjar to rename the generated AIDL classes.
             apex_available: [
                 "//apex_available:platform", // used from services.net
-                "com.android.bluetooth.updatable",
                 "com.android.tethering",
                 "com.android.wifi",
             ],
@@ -99,6 +98,7 @@
         ndk: {
             apex_available: [
                 "//apex_available:platform",
+                "com.android.tethering",
             ],
             // This is necessary for the DnsResovler tests to run in Android Q.
             // Soong would recognize this value and produce the Q compatible aidl library.
@@ -113,6 +113,7 @@
         "5",
         "6",
         "7",
+        "8",
     ],
 }
 
@@ -125,7 +126,6 @@
     ],
     apex_available: [
         "//apex_available:platform",
-        "com.android.bluetooth.updatable",
         "com.android.wifi",
         "com.android.tethering",
     ],
@@ -149,7 +149,6 @@
         java: {
             apex_available: [
                 "//apex_available:platform",
-                "com.android.bluetooth.updatable",
                 "com.android.wifi",
                 "com.android.tethering",
             ],
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash b/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash
new file mode 100644
index 0000000..0933816
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/.hash
@@ -0,0 +1 @@
+e8cf8586fc5da9063818d8775e9a21c4b0addb5b
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl
new file mode 100644
index 0000000..ec03d86
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetd.aidl
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetd {
+  boolean isAlive();
+  boolean firewallReplaceUidChain(in @utf8InCpp String chainName, boolean isAllowlist, in int[] uids);
+  boolean bandwidthEnableDataSaver(boolean enable);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreatePhysical(int netId, int permission);
+  /**
+   * @deprecated use networkCreate() instead.
+   */
+  void networkCreateVpn(int netId, boolean secure);
+  void networkDestroy(int netId);
+  void networkAddInterface(int netId, in @utf8InCpp String iface);
+  void networkRemoveInterface(int netId, in @utf8InCpp String iface);
+  void networkAddUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRemoveUidRanges(int netId, in android.net.UidRangeParcel[] uidRanges);
+  void networkRejectNonSecureVpn(boolean add, in android.net.UidRangeParcel[] uidRanges);
+  void socketDestroy(in android.net.UidRangeParcel[] uidRanges, in int[] exemptUids);
+  boolean tetherApplyDnsInterfaces();
+  android.net.TetherStatsParcel[] tetherGetStats();
+  void interfaceAddAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  void interfaceDelAddress(in @utf8InCpp String ifName, in @utf8InCpp String addrString, int prefixLength);
+  @utf8InCpp String getProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter);
+  void setProcSysNet(int ipversion, int which, in @utf8InCpp String ifname, in @utf8InCpp String parameter, in @utf8InCpp String value);
+  void ipSecSetEncapSocketOwner(in ParcelFileDescriptor socket, int newUid);
+  int ipSecAllocateSpi(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecAddSecurityAssociation(int transformId, int mode, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int underlyingNetId, int spi, int markValue, int markMask, in @utf8InCpp String authAlgo, in byte[] authKey, in int authTruncBits, in @utf8InCpp String cryptAlgo, in byte[] cryptKey, in int cryptTruncBits, in @utf8InCpp String aeadAlgo, in byte[] aeadKey, in int aeadIcvBits, int encapType, int encapLocalPort, int encapRemotePort, int interfaceId);
+  void ipSecDeleteSecurityAssociation(int transformId, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecApplyTransportModeTransform(in ParcelFileDescriptor socket, int transformId, int direction, in @utf8InCpp String sourceAddress, in @utf8InCpp String destinationAddress, int spi);
+  void ipSecRemoveTransportModeTransform(in ParcelFileDescriptor socket);
+  void ipSecAddSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecUpdateSecurityPolicy(int transformId, int selAddrFamily, int direction, in @utf8InCpp String tmplSrcAddress, in @utf8InCpp String tmplDstAddress, int spi, int markValue, int markMask, int interfaceId);
+  void ipSecDeleteSecurityPolicy(int transformId, int selAddrFamily, int direction, int markValue, int markMask, int interfaceId);
+  void ipSecAddTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecUpdateTunnelInterface(in @utf8InCpp String deviceName, in @utf8InCpp String localAddress, in @utf8InCpp String remoteAddress, int iKey, int oKey, int interfaceId);
+  void ipSecRemoveTunnelInterface(in @utf8InCpp String deviceName);
+  void wakeupAddInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void wakeupDelInterface(in @utf8InCpp String ifName, in @utf8InCpp String prefix, int mark, int mask);
+  void setIPv6AddrGenMode(in @utf8InCpp String ifName, int mode);
+  void idletimerAddInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void idletimerRemoveInterface(in @utf8InCpp String ifName, int timeout, in @utf8InCpp String classLabel);
+  void strictUidCleartextPenalty(int uid, int policyPenalty);
+  @utf8InCpp String clatdStart(in @utf8InCpp String ifName, in @utf8InCpp String nat64Prefix);
+  void clatdStop(in @utf8InCpp String ifName);
+  boolean ipfwdEnabled();
+  @utf8InCpp String[] ipfwdGetRequesterList();
+  void ipfwdEnableForwarding(in @utf8InCpp String requester);
+  void ipfwdDisableForwarding(in @utf8InCpp String requester);
+  void ipfwdAddInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void ipfwdRemoveInterfaceForward(in @utf8InCpp String fromIface, in @utf8InCpp String toIface);
+  void bandwidthSetInterfaceQuota(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceQuota(in @utf8InCpp String ifName);
+  void bandwidthSetInterfaceAlert(in @utf8InCpp String ifName, long bytes);
+  void bandwidthRemoveInterfaceAlert(in @utf8InCpp String ifName);
+  void bandwidthSetGlobalAlert(long bytes);
+  void bandwidthAddNaughtyApp(int uid);
+  void bandwidthRemoveNaughtyApp(int uid);
+  void bandwidthAddNiceApp(int uid);
+  void bandwidthRemoveNiceApp(int uid);
+  void tetherStart(in @utf8InCpp String[] dhcpRanges);
+  void tetherStop();
+  boolean tetherIsEnabled();
+  void tetherInterfaceAdd(in @utf8InCpp String ifName);
+  void tetherInterfaceRemove(in @utf8InCpp String ifName);
+  @utf8InCpp String[] tetherInterfaceList();
+  void tetherDnsSet(int netId, in @utf8InCpp String[] dnsAddrs);
+  @utf8InCpp String[] tetherDnsList();
+  void networkAddRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkRemoveRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop);
+  void networkAddLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  void networkRemoveLegacyRoute(int netId, in @utf8InCpp String ifName, in @utf8InCpp String destination, in @utf8InCpp String nextHop, int uid);
+  int networkGetDefault();
+  void networkSetDefault(int netId);
+  void networkClearDefault();
+  void networkSetPermissionForNetwork(int netId, int permission);
+  void networkSetPermissionForUser(int permission, in int[] uids);
+  void networkClearPermissionForUser(in int[] uids);
+  void trafficSetNetPermForUids(int permission, in int[] uids);
+  void networkSetProtectAllow(int uid);
+  void networkSetProtectDeny(int uid);
+  boolean networkCanProtect(int uid);
+  void firewallSetFirewallType(int firewalltype);
+  void firewallSetInterfaceRule(in @utf8InCpp String ifName, int firewallRule);
+  void firewallSetUidRule(int childChain, int uid, int firewallRule);
+  void firewallEnableChildChain(int childChain, boolean enable);
+  @utf8InCpp String[] interfaceGetList();
+  android.net.InterfaceConfigurationParcel interfaceGetCfg(in @utf8InCpp String ifName);
+  void interfaceSetCfg(in android.net.InterfaceConfigurationParcel cfg);
+  void interfaceSetIPv6PrivacyExtensions(in @utf8InCpp String ifName, boolean enable);
+  void interfaceClearAddrs(in @utf8InCpp String ifName);
+  void interfaceSetEnableIPv6(in @utf8InCpp String ifName, boolean enable);
+  void interfaceSetMtu(in @utf8InCpp String ifName, int mtu);
+  void tetherAddForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void tetherRemoveForward(in @utf8InCpp String intIface, in @utf8InCpp String extIface);
+  void setTcpRWmemorySize(in @utf8InCpp String rmemValues, in @utf8InCpp String wmemValues);
+  void registerUnsolicitedEventListener(android.net.INetdUnsolicitedEventListener listener);
+  void firewallAddUidInterfaceRules(in @utf8InCpp String ifName, in int[] uids);
+  void firewallRemoveUidInterfaceRules(in int[] uids);
+  void trafficSwapActiveStatsMap();
+  IBinder getOemNetd();
+  void tetherStartWithConfiguration(in android.net.TetherConfigParcel config);
+  android.net.MarkMaskParcel getFwmarkForNetwork(int netId);
+  void networkAddRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkUpdateRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void networkRemoveRouteParcel(int netId, in android.net.RouteInfoParcel routeInfo);
+  void tetherOffloadRuleAdd(in android.net.TetherOffloadRuleParcel rule);
+  void tetherOffloadRuleRemove(in android.net.TetherOffloadRuleParcel rule);
+  android.net.TetherStatsParcel[] tetherOffloadGetStats();
+  void tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes);
+  android.net.TetherStatsParcel tetherOffloadGetAndClearStats(int ifIndex);
+  void networkCreate(in android.net.NativeNetworkConfig config);
+  void networkAddUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  void networkRemoveUidRangesParcel(in android.net.netd.aidl.NativeUidRangeConfig uidRangesConfig);
+  const int IPV4 = 4;
+  const int IPV6 = 6;
+  const int CONF = 1;
+  const int NEIGH = 2;
+  const String IPSEC_INTERFACE_PREFIX = "ipsec";
+  const int IPV6_ADDR_GEN_MODE_EUI64 = 0;
+  const int IPV6_ADDR_GEN_MODE_NONE = 1;
+  const int IPV6_ADDR_GEN_MODE_STABLE_PRIVACY = 2;
+  const int IPV6_ADDR_GEN_MODE_RANDOM = 3;
+  const int IPV6_ADDR_GEN_MODE_DEFAULT = 0;
+  const int PENALTY_POLICY_ACCEPT = 1;
+  const int PENALTY_POLICY_LOG = 2;
+  const int PENALTY_POLICY_REJECT = 3;
+  const int LOCAL_NET_ID = 99;
+  const int DUMMY_NET_ID = 51;
+  const int UNREACHABLE_NET_ID = 52;
+  const String NEXTHOP_NONE = "";
+  const String NEXTHOP_UNREACHABLE = "unreachable";
+  const String NEXTHOP_THROW = "throw";
+  const int PERMISSION_NONE = 0;
+  const int PERMISSION_NETWORK = 1;
+  const int PERMISSION_SYSTEM = 2;
+  const int NO_PERMISSIONS = 0;
+  const int PERMISSION_INTERNET = 4;
+  const int PERMISSION_UPDATE_DEVICE_STATS = 8;
+  const int PERMISSION_UNINSTALLED = -1;
+  /**
+   * @deprecated use FIREWALL_ALLOWLIST.
+   */
+  const int FIREWALL_WHITELIST = 0;
+  const int FIREWALL_ALLOWLIST = 0;
+  /**
+   * @deprecated use FIREWALL_DENYLIST.
+   */
+  const int FIREWALL_BLACKLIST = 1;
+  const int FIREWALL_DENYLIST = 1;
+  const int FIREWALL_RULE_ALLOW = 1;
+  const int FIREWALL_RULE_DENY = 2;
+  const int FIREWALL_CHAIN_NONE = 0;
+  const int FIREWALL_CHAIN_DOZABLE = 1;
+  const int FIREWALL_CHAIN_STANDBY = 2;
+  const int FIREWALL_CHAIN_POWERSAVE = 3;
+  const int FIREWALL_CHAIN_RESTRICTED = 4;
+  const String IF_STATE_UP = "up";
+  const String IF_STATE_DOWN = "down";
+  const String IF_FLAG_BROADCAST = "broadcast";
+  const String IF_FLAG_LOOPBACK = "loopback";
+  const String IF_FLAG_POINTOPOINT = "point-to-point";
+  const String IF_FLAG_RUNNING = "running";
+  const String IF_FLAG_MULTICAST = "multicast";
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl
new file mode 100644
index 0000000..31775df
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/INetdUnsolicitedEventListener.aidl
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2018, 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+interface INetdUnsolicitedEventListener {
+  oneway void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs, int uid);
+  oneway void onQuotaLimitReached(@utf8InCpp String alertName, @utf8InCpp String ifName);
+  oneway void onInterfaceDnsServerInfo(@utf8InCpp String ifName, long lifetimeS, in @utf8InCpp String[] servers);
+  oneway void onInterfaceAddressUpdated(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAddressRemoved(@utf8InCpp String addr, @utf8InCpp String ifName, int flags, int scope);
+  oneway void onInterfaceAdded(@utf8InCpp String ifName);
+  oneway void onInterfaceRemoved(@utf8InCpp String ifName);
+  oneway void onInterfaceChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onInterfaceLinkStateChanged(@utf8InCpp String ifName, boolean up);
+  oneway void onRouteChanged(boolean updated, @utf8InCpp String route, @utf8InCpp String gateway, @utf8InCpp String ifName);
+  oneway void onStrictCleartextDetected(int uid, @utf8InCpp String hex);
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl
new file mode 100644
index 0000000..1869d8d
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/InterfaceConfigurationParcel.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable InterfaceConfigurationParcel {
+  @utf8InCpp String ifName;
+  @utf8InCpp String hwAddr;
+  @utf8InCpp String ipv4Addr;
+  int prefixLength;
+  @utf8InCpp String[] flags;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl
new file mode 100644
index 0000000..8ea20d1
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/MarkMaskParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable MarkMaskParcel {
+  int mark;
+  int mask;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl
new file mode 100644
index 0000000..77d814b
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkConfig.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeNetworkConfig {
+  int netId;
+  android.net.NativeNetworkType networkType = android.net.NativeNetworkType.PHYSICAL;
+  int permission;
+  boolean secure;
+  android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl
new file mode 100644
index 0000000..06c8979
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeNetworkType.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeNetworkType {
+  PHYSICAL = 0,
+  VIRTUAL = 1,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl
new file mode 100644
index 0000000..8a8be83
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/NativeVpnType.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+@Backing(type="int")
+enum NativeVpnType {
+  SERVICE = 1,
+  PLATFORM = 2,
+  LEGACY = 3,
+  OEM = 4,
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl
new file mode 100644
index 0000000..5ef95e6
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/RouteInfoParcel.aidl
@@ -0,0 +1,40 @@
+/**
+ * Copyright (c) 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+parcelable RouteInfoParcel {
+  @utf8InCpp String destination;
+  @utf8InCpp String ifName;
+  @utf8InCpp String nextHop;
+  int mtu;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl
new file mode 100644
index 0000000..7b39c22
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherConfigParcel.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherConfigParcel {
+  boolean usingLegacyDnsProxy;
+  @utf8InCpp String[] dhcpRanges;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl
new file mode 100644
index 0000000..983e986
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherOffloadRuleParcel.aidl
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherOffloadRuleParcel {
+  int inputInterfaceIndex;
+  int outputInterfaceIndex;
+  byte[] destination;
+  int prefixLength;
+  byte[] srcL2Address;
+  byte[] dstL2Address;
+  int pmtu = 1500;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl
new file mode 100644
index 0000000..5f1b722
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/TetherStatsParcel.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+parcelable TetherStatsParcel {
+  @utf8InCpp String iface;
+  long rxBytes;
+  long rxPackets;
+  long txBytes;
+  long txPackets;
+  int ifIndex = 0;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl
new file mode 100644
index 0000000..72e987a
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/UidRangeParcel.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable UidRangeParcel {
+  int start;
+  int stop;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl
new file mode 100644
index 0000000..9bb679f
--- /dev/null
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/8/android/net/netd/aidl/NativeUidRangeConfig.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.net.netd.aidl;
+/* @hide */
+@JavaDerive(equals=true, toString=true) @JavaOnlyImmutable
+parcelable NativeUidRangeConfig {
+  int netId;
+  android.net.UidRangeParcel[] uidRanges;
+  int subPriority;
+}
diff --git a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl
index 76562b2..77d814b 100644
--- a/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl
+++ b/staticlibs/netd/aidl_api/netd_aidl_interface/current/android/net/NativeNetworkConfig.aidl
@@ -40,4 +40,5 @@
   int permission;
   boolean secure;
   android.net.NativeVpnType vpnType = android.net.NativeVpnType.PLATFORM;
+  boolean excludeLocalRoutes = false;
 }
diff --git a/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl b/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl
index 2c4f83a..e654a1f 100644
--- a/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl
+++ b/staticlibs/netd/binder/android/net/NativeNetworkConfig.aidl
@@ -49,4 +49,9 @@
 
     /** For virtual networks. The type of VPN to create.  Ignored for all other network types. */
     NativeVpnType vpnType = NativeVpnType.PLATFORM;
+
+    /**
+     * For virtual networks. Whether local traffic is excluded from the VPN.
+     */
+    boolean excludeLocalRoutes = false;
 }
diff --git a/staticlibs/netd/libnetdutils/Android.bp b/staticlibs/netd/libnetdutils/Android.bp
new file mode 100644
index 0000000..732e37d
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Android.bp
@@ -0,0 +1,67 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library {
+    name: "libnetdutils",
+    srcs: [
+        "DumpWriter.cpp",
+        "Fd.cpp",
+        "InternetAddresses.cpp",
+        "Log.cpp",
+        "Netfilter.cpp",
+        "Netlink.cpp",
+        "Slice.cpp",
+        "Socket.cpp",
+        "SocketOption.cpp",
+        "Status.cpp",
+        "Syscalls.cpp",
+        "UniqueFd.cpp",
+        "UniqueFile.cpp",
+        "Utils.cpp",
+    ],
+    defaults: ["netd_defaults"],
+    cflags: ["-Wall", "-Werror"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    export_shared_lib_headers: [
+        "libbase",
+    ],
+    export_include_dirs: ["include"],
+    sanitize: {
+        cfi: true,
+    },
+
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.resolv",
+        "com.android.tethering",
+    ],
+    min_sdk_version: "29",
+}
+
+cc_test {
+    name: "netdutils_test",
+    srcs: [
+        "BackoffSequenceTest.cpp",
+        "FdTest.cpp",
+        "InternetAddressesTest.cpp",
+        "LogTest.cpp",
+        "MemBlockTest.cpp",
+        "SliceTest.cpp",
+        "StatusTest.cpp",
+        "SyscallsTest.cpp",
+        "ThreadUtilTest.cpp",
+    ],
+    defaults: ["netd_defaults"],
+    test_suites: ["device-tests"],
+    static_libs: [
+        "libgmock",
+        "libnetdutils",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+}
diff --git a/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp b/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp
new file mode 100644
index 0000000..b6653fe
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/BackoffSequenceTest.cpp
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "netdutils/BackoffSequence.h"
+
+namespace android {
+namespace netdutils {
+
+TEST(BackoffSequence, defaults) {
+    BackoffSequence<uint32_t> backoff;
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000001U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000002U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000004U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000008U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000010U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000020U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000040U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000080U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000100U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000200U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000400U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000800U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00001000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00002000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00004000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00008000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00010000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00020000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00040000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00080000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00100000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00200000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00400000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00800000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x01000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x02000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x04000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x08000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x10000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x20000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x40000000U, backoff.getNextTimeout());
+    EXPECT_EQ(0x80000000U, backoff.getNextTimeout());
+    // Maxes out, and stays there, ad infinitum.
+    for (int i = 0; i < 10; i++) {
+        EXPECT_TRUE(backoff.hasNextTimeout());
+        EXPECT_EQ(0xffffffffU, backoff.getNextTimeout());
+    }
+}
+
+TEST(BackoffSequence, backoffToOncePerHour) {
+    auto backoff = BackoffSequence<uint32_t>::Builder()
+            .withInitialRetransmissionTime(1)
+            .withMaximumRetransmissionTime(3600)
+            .build();
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000001U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000002U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000004U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000008U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000010U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000020U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000040U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000080U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000100U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000200U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000400U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000800U, backoff.getNextTimeout());
+    // Maxes out, and stays there, ad infinitum.
+    for (int i = 0; i < 10; i++) {
+        EXPECT_TRUE(backoff.hasNextTimeout());
+        EXPECT_EQ(3600U, backoff.getNextTimeout());
+    }
+}
+
+TEST(BackoffSequence, simpleMaxRetransCount) {
+    auto backoff = BackoffSequence<uint32_t>::Builder()
+            .withInitialRetransmissionTime(3)
+            .withMaximumRetransmissionCount(7)
+            .build();
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000003U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000006U, backoff.getNextTimeout());
+    EXPECT_EQ(0x0000000cU, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000018U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000030U, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000060U, backoff.getNextTimeout());
+    EXPECT_EQ(0x000000c0U, backoff.getNextTimeout());
+
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(BackoffSequence, simpleMaxDuration) {
+    auto backoff = BackoffSequence<int>::Builder()
+            .withInitialRetransmissionTime(3)
+            .withMaximumRetransmissionDuration(7)
+            .withEndOfSequenceIndicator(-1)
+            .build();
+
+    EXPECT_TRUE(backoff.hasNextTimeout());
+    EXPECT_EQ(0x00000003, backoff.getNextTimeout());
+    EXPECT_EQ(0x00000004, backoff.getNextTimeout());
+
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+        EXPECT_EQ(-1, backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, ZeroInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::seconds>::Builder()
+            .withInitialRetransmissionTime(std::chrono::seconds(0))
+            .build();
+
+    for (int i = 0; i < 10; i++) {
+        // TODO: Decide whether this needs fixing, and how.
+        EXPECT_TRUE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, MaxRetransDurationGreaterThanInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::milliseconds>::Builder()
+            .withInitialRetransmissionTime(std::chrono::milliseconds(5))
+            .withMaximumRetransmissionDuration(std::chrono::milliseconds(3))
+            .build();
+
+    EXPECT_EQ(std::chrono::milliseconds(3), backoff.getNextTimeout());
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, MaxRetransDurationEqualsInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::hours>::Builder()
+            .withInitialRetransmissionTime(std::chrono::hours(5))
+            .withMaximumRetransmissionDuration(std::chrono::hours(5))
+            .build();
+
+    EXPECT_EQ(std::chrono::hours(5), backoff.getNextTimeout());
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+TEST(PathologicalBackoffSequence, MaxRetransTimeAndDurationGreaterThanInitialRetransTime) {
+    auto backoff = BackoffSequence<std::chrono::nanoseconds>::Builder()
+            .withInitialRetransmissionTime(std::chrono::nanoseconds(7))
+            .withMaximumRetransmissionTime(std::chrono::nanoseconds(3))
+            .withMaximumRetransmissionDuration(std::chrono::nanoseconds(5))
+            .build();
+
+    EXPECT_EQ(std::chrono::nanoseconds(3), backoff.getNextTimeout());
+    EXPECT_EQ(std::chrono::nanoseconds(2), backoff.getNextTimeout());
+    for (int i = 0; i < 10; i++) {
+        EXPECT_FALSE(backoff.hasNextTimeout());
+        EXPECT_EQ(backoff.getEndOfSequenceIndicator(), backoff.getNextTimeout());
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/DumpWriter.cpp b/staticlibs/netd/libnetdutils/DumpWriter.cpp
new file mode 100644
index 0000000..092ddba
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/DumpWriter.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "netdutils/DumpWriter.h"
+
+#include <unistd.h>
+#include <limits>
+
+#include <android-base/stringprintf.h>
+#include <utils/String8.h>
+
+using android::base::StringAppendV;
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+const char kIndentString[] = "  ";
+const size_t kIndentStringLen = strlen(kIndentString);
+
+}  // namespace
+
+DumpWriter::DumpWriter(int fd) : mIndentLevel(0), mFd(fd) {}
+
+void DumpWriter::incIndent() {
+    if (mIndentLevel < std::numeric_limits<decltype(mIndentLevel)>::max()) {
+        mIndentLevel++;
+    }
+}
+
+void DumpWriter::decIndent() {
+    if (mIndentLevel > std::numeric_limits<decltype(mIndentLevel)>::min()) {
+        mIndentLevel--;
+    }
+}
+
+void DumpWriter::println(const std::string& line) {
+    if (!line.empty()) {
+        for (int i = 0; i < mIndentLevel; i++) {
+            ::write(mFd, kIndentString, kIndentStringLen);
+        }
+        ::write(mFd, line.c_str(), line.size());
+    }
+    ::write(mFd, "\n", 1);
+}
+
+// NOLINTNEXTLINE(cert-dcl50-cpp): Grandfathered C-style variadic function.
+void DumpWriter::println(const char* fmt, ...) {
+    std::string line;
+    va_list ap;
+    va_start(ap, fmt);
+    StringAppendV(&line, fmt, ap);
+    va_end(ap);
+    println(line);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Fd.cpp b/staticlibs/netd/libnetdutils/Fd.cpp
new file mode 100644
index 0000000..2651f90
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Fd.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Fd.h"
+
+namespace android {
+namespace netdutils {
+
+std::ostream& operator<<(std::ostream& os, const Fd& fd) {
+    return os << "Fd[" << fd.get() << "]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/FdTest.cpp b/staticlibs/netd/libnetdutils/FdTest.cpp
new file mode 100644
index 0000000..7080f83
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/FdTest.cpp
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/MockSyscalls.h"
+#include "netdutils/Status.h"
+#include "netdutils/Syscalls.h"
+
+using testing::Mock;
+using testing::Return;
+using testing::StrictMock;
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Force implicit conversion from UniqueFd -> Fd
+inline Fd toFd(const UniqueFd& fd) {
+    return fd;
+}
+
+}  // namespace
+
+TEST(Fd, smoke) {
+    // Expect the following lines to compile
+    Fd fd1(1);
+    Fd fd2(fd1);
+    Fd fd3 = fd2;
+    const Fd fd4(8);
+    const Fd fd5(fd4);
+    const Fd fd6 = fd5;
+    EXPECT_TRUE(isWellFormed(fd3));
+    EXPECT_TRUE(isWellFormed(fd6));
+
+    // Corner case
+    Fd zero(0);
+    EXPECT_TRUE(isWellFormed(zero));
+
+    // Invalid file descriptors
+    Fd bad(-1);
+    Fd weird(-9);
+    EXPECT_FALSE(isWellFormed(bad));
+    EXPECT_FALSE(isWellFormed(weird));
+
+    // Default constructor
+    EXPECT_EQ(Fd(-1), Fd());
+    std::stringstream ss;
+    ss << fd3 << " " << fd6 << " " << bad << " " << weird;
+    EXPECT_EQ("Fd[1] Fd[8] Fd[-1] Fd[-9]", ss.str());
+}
+
+class UniqueFdTest : public testing::Test {
+  protected:
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+};
+
+TEST_F(UniqueFdTest, operatorOstream) {
+    UniqueFd u(97);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    std::stringstream ss;
+    ss << u;
+    EXPECT_EQ("UniqueFd[Fd[97]]", ss.str());
+    u.reset();
+}
+
+TEST_F(UniqueFdTest, destructor) {
+    {
+        UniqueFd u(98);
+        EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, reset) {
+    UniqueFd u(99);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+    u.reset();
+
+    // Expectation above should be upon reset
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, moveConstructor) {
+    constexpr Fd kFd(101);
+    UniqueFd u1(kFd);
+    {
+        UniqueFd u2(std::move(u1));
+        // NOLINTNEXTLINE bugprone-use-after-move
+        EXPECT_FALSE(isWellFormed(u1));
+        EXPECT_TRUE(isWellFormed(u2));
+        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, moveAssignment) {
+    constexpr Fd kFd(102);
+    UniqueFd u1(kFd);
+    {
+        UniqueFd u2 = std::move(u1);
+        // NOLINTNEXTLINE bugprone-use-after-move
+        EXPECT_FALSE(isWellFormed(u1));
+        EXPECT_TRUE(isWellFormed(u2));
+        UniqueFd u3;
+        u3 = std::move(u2);
+        // NOLINTNEXTLINE bugprone-use-after-move
+        EXPECT_FALSE(isWellFormed(u2));
+        EXPECT_TRUE(isWellFormed(u3));
+        EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    }
+    // Expectation above should be upon leaving nested scope
+    Mock::VerifyAndClearExpectations(&mSyscalls);
+}
+
+TEST_F(UniqueFdTest, constConstructor) {
+    constexpr Fd kFd(103);
+    const UniqueFd u(kFd);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(status::ok));
+}
+
+TEST_F(UniqueFdTest, closeFailure) {
+    constexpr Fd kFd(103);
+    UniqueFd u(kFd);
+    EXPECT_CALL(mSyscalls, close(toFd(u))).WillOnce(Return(statusFromErrno(EINTR, "test")));
+    EXPECT_DEBUG_DEATH(u.reset(), "");
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/InternetAddresses.cpp b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
new file mode 100644
index 0000000..322f1b1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/InternetAddresses.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "netdutils/InternetAddresses.h"
+
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+namespace android {
+
+using base::StringPrintf;
+
+namespace netdutils {
+
+std::string IPAddress::toString() const noexcept {
+    char repr[INET6_ADDRSTRLEN] = "\0";
+
+    switch (mData.family) {
+        case AF_UNSPEC:
+            return "<unspecified>";
+        case AF_INET: {
+            const in_addr v4 = mData.ip.v4;
+            inet_ntop(AF_INET, &v4, repr, sizeof(repr));
+            break;
+        }
+        case AF_INET6: {
+            const in6_addr v6 = mData.ip.v6;
+            inet_ntop(AF_INET6, &v6, repr, sizeof(repr));
+            break;
+        }
+        default:
+            return "<unknown_family>";
+    }
+
+    if (mData.family == AF_INET6 && mData.scope_id > 0) {
+        return StringPrintf("%s%%%u", repr, mData.scope_id);
+    }
+
+    return repr;
+}
+
+bool IPAddress::forString(const std::string& repr, IPAddress* ip) {
+    const addrinfo hints = {
+            .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV,
+    };
+    addrinfo* res;
+    const int ret = getaddrinfo(repr.c_str(), nullptr, &hints, &res);
+    ScopedAddrinfo res_cleanup(res);
+    if (ret != 0) {
+        return false;
+    }
+
+    bool rval = true;
+    switch (res[0].ai_family) {
+        case AF_INET: {
+            sockaddr_in* sin = (sockaddr_in*) res[0].ai_addr;
+            if (ip) *ip = IPAddress(sin->sin_addr);
+            break;
+        }
+        case AF_INET6: {
+            sockaddr_in6* sin6 = (sockaddr_in6*) res[0].ai_addr;
+            if (ip) *ip = IPAddress(sin6->sin6_addr, sin6->sin6_scope_id);
+            break;
+        }
+        default:
+            rval = false;
+            break;
+    }
+
+    return rval;
+}
+
+IPPrefix::IPPrefix(const IPAddress& ip, int length) : IPPrefix(ip) {
+    // Silently treat CIDR lengths like "-1" as meaning the full bit length
+    // appropriate to the address family.
+    if (length < 0) return;
+    if (length >= mData.cidrlen) return;
+
+    switch (mData.family) {
+        case AF_UNSPEC:
+            break;
+        case AF_INET: {
+            const in_addr_t mask = (length > 0) ? (~0U) << (IPV4_ADDR_BITS - length) : 0U;
+            mData.ip.v4.s_addr &= htonl(mask);
+            mData.cidrlen = static_cast<uint8_t>(length);
+            break;
+        }
+        case AF_INET6: {
+            // The byte in which this CIDR length falls.
+            const int which = length / 8;
+            const int mask = (length % 8 == 0) ? 0 : 0xff << (8 - length % 8);
+            mData.ip.v6.s6_addr[which] &= mask;
+            for (int i = which + 1; i < IPV6_ADDR_LEN; i++) {
+                mData.ip.v6.s6_addr[i] = 0U;
+            }
+            mData.cidrlen = static_cast<uint8_t>(length);
+            break;
+        }
+        default:
+            // TODO: Complain bitterly about possible data corruption?
+            return;
+    }
+}
+
+bool IPPrefix::isUninitialized() const noexcept {
+    static const internal_::compact_ipdata empty{};
+    return mData == empty;
+}
+
+bool IPPrefix::forString(const std::string& repr, IPPrefix* prefix) {
+    size_t index = repr.find('/');
+    if (index == std::string::npos) return false;
+
+    // Parse the IP address.
+    IPAddress ip;
+    if (!IPAddress::forString(repr.substr(0, index), &ip)) return false;
+
+    // Parse the prefix length. Can't use base::ParseUint because it accepts non-base 10 input.
+    const char* prefixString = repr.c_str() + index + 1;
+    if (!isdigit(*prefixString)) return false;
+    char* endptr;
+    unsigned long prefixlen = strtoul(prefixString, &endptr, 10);
+    if (*endptr != '\0') return false;
+
+    uint8_t maxlen = (ip.family() == AF_INET) ? 32 : 128;
+    if (prefixlen > maxlen) return false;
+
+    *prefix = IPPrefix(ip, prefixlen);
+    return true;
+}
+
+std::string IPPrefix::toString() const noexcept {
+    return StringPrintf("%s/%d", ip().toString().c_str(), mData.cidrlen);
+}
+
+std::string IPSockAddr::toString() const noexcept {
+    switch (mData.family) {
+        case AF_INET6:
+            return StringPrintf("[%s]:%u", ip().toString().c_str(), mData.port);
+        default:
+            return StringPrintf("%s:%u", ip().toString().c_str(), mData.port);
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
new file mode 100644
index 0000000..f75fa76
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/InternetAddressesTest.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <cstdint>
+#include <limits>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <android-base/macros.h>
+#include <gtest/gtest.h>
+
+#include "netdutils/InternetAddresses.h"
+
+namespace android {
+namespace netdutils {
+namespace {
+
+enum Relation { EQ, LT };
+
+std::ostream& operator<<(std::ostream& os, Relation relation) {
+    switch (relation) {
+        case EQ: os << "eq"; break;
+        case LT: os << "lt"; break;
+        default: os << "?!"; break;
+    }
+    return os;
+}
+
+template <typename T>
+struct OperatorExpectation {
+    const Relation relation;
+    const T obj1;
+    const T obj2;
+
+    std::string toString() const {
+        std::stringstream output;
+        output << obj1 << " " << relation << " " << obj2;
+        return output.str();
+    }
+};
+
+template <typename T>
+void testGamutOfOperators(const OperatorExpectation<T>& expectation) {
+    switch (expectation.relation) {
+        case EQ:
+            EXPECT_TRUE(expectation.obj1 == expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 <= expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 >= expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 != expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 < expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 > expectation.obj2);
+            break;
+
+        case LT:
+            EXPECT_TRUE(expectation.obj1 < expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 <= expectation.obj2);
+            EXPECT_TRUE(expectation.obj1 != expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 > expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 >= expectation.obj2);
+            EXPECT_FALSE(expectation.obj1 == expectation.obj2);
+            break;
+
+        default:
+            FAIL() << "Unknown relation given in test expectation";
+    }
+}
+
+const in_addr IPV4_ANY{htonl(INADDR_ANY)};
+const in_addr IPV4_LOOPBACK{htonl(INADDR_LOOPBACK)};
+const in_addr IPV4_ONES{~0U};
+const in6_addr IPV6_ANY = IN6ADDR_ANY_INIT;
+const in6_addr IPV6_LOOPBACK = IN6ADDR_LOOPBACK_INIT;
+const in6_addr FE80{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}};
+const in6_addr FE80_1{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,1}}};
+const in6_addr FE80_2{{{0xfe,0x80,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}};
+const uint8_t ff = std::numeric_limits<uint8_t>::max();
+const in6_addr IPV6_ONES{{{ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff}}};
+
+TEST(IPAddressTest, GamutOfOperators) {
+    const std::vector<OperatorExpectation<IPAddress>> kExpectations{
+            {EQ, IPAddress(), IPAddress()},
+            {EQ, IPAddress(IPV4_ONES), IPAddress(IPV4_ONES)},
+            {EQ, IPAddress(IPV6_ONES), IPAddress(IPV6_ONES)},
+            {EQ, IPAddress(FE80_1), IPAddress(FE80_1)},
+            {EQ, IPAddress(FE80_2), IPAddress(FE80_2)},
+            {LT, IPAddress(), IPAddress(IPV4_ANY)},
+            {LT, IPAddress(), IPAddress(IPV4_ONES)},
+            {LT, IPAddress(), IPAddress(IPV6_ANY)},
+            {LT, IPAddress(), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(IPV4_ANY), IPAddress(IPV4_ONES)},
+            {LT, IPAddress(IPV4_ANY), IPAddress(IPV6_ANY)},
+            {LT, IPAddress(IPV4_ONES), IPAddress(IPV6_ANY)},
+            {LT, IPAddress(IPV4_ONES), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(IPV6_ANY), IPAddress(IPV6_LOOPBACK)},
+            {LT, IPAddress(IPV6_ANY), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(IPV6_LOOPBACK), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(FE80_1), IPAddress(FE80_2)},
+            {LT, IPAddress(FE80_1), IPAddress(IPV6_ONES)},
+            {LT, IPAddress(FE80_2), IPAddress(IPV6_ONES)},
+            // Sort by scoped_id within the same address.
+            {LT, IPAddress(FE80_1), IPAddress(FE80_1, 1)},
+            {LT, IPAddress(FE80_1, 1), IPAddress(FE80_1, 2)},
+            // Sort by address first, scope_id second.
+            {LT, IPAddress(FE80_1, 2), IPAddress(FE80_2, 1)},
+    };
+
+    size_t tests_run = 0;
+    for (const auto& expectation : kExpectations) {
+        SCOPED_TRACE(expectation.toString());
+        EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation));
+        tests_run++;
+    }
+    EXPECT_EQ(kExpectations.size(), tests_run);
+}
+
+TEST(IPAddressTest, ScopeIds) {
+    // Scope IDs ignored for IPv4 addresses.
+    const IPAddress ones(IPV4_ONES);
+    EXPECT_EQ(0U, ones.scope_id());
+    const IPAddress ones22(ones, 22);
+    EXPECT_EQ(0U, ones22.scope_id());
+    EXPECT_EQ(ones, ones22);
+    const IPAddress ones23(ones, 23);
+    EXPECT_EQ(0U, ones23.scope_id());
+    EXPECT_EQ(ones22, ones23);
+
+    EXPECT_EQ("fe80::1%22", IPAddress(FE80_1, 22).toString());
+    EXPECT_EQ("fe80::2%23", IPAddress(FE80_2, 23).toString());
+
+    // Verify that given an IPAddress with a scope_id an address without a
+    // scope_id can be constructed (just in case it's useful).
+    const IPAddress fe80_intf22(FE80_1, 22);
+    EXPECT_EQ(22U, fe80_intf22.scope_id());
+    EXPECT_EQ(fe80_intf22, IPAddress(fe80_intf22));
+    EXPECT_EQ(IPAddress(FE80_1), IPAddress(fe80_intf22, 0));
+}
+
+TEST(IPAddressTest, forString) {
+    IPAddress ip;
+
+    EXPECT_FALSE(IPAddress::forString("not_an_ip", &ip));
+    EXPECT_FALSE(IPAddress::forString("not_an_ip", nullptr));
+    EXPECT_EQ(IPAddress(), IPAddress::forString("not_an_ip"));
+
+    EXPECT_EQ(IPAddress(IPV4_ANY), IPAddress::forString("0.0.0.0"));
+    EXPECT_EQ(IPAddress(IPV4_ONES), IPAddress::forString("255.255.255.255"));
+    EXPECT_EQ(IPAddress(IPV4_LOOPBACK), IPAddress::forString("127.0.0.1"));
+
+    EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("::"));
+    EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("::0"));
+    EXPECT_EQ(IPAddress(IPV6_ANY), IPAddress::forString("0::"));
+    EXPECT_EQ(IPAddress(IPV6_LOOPBACK), IPAddress::forString("::1"));
+    EXPECT_EQ(IPAddress(IPV6_LOOPBACK), IPAddress::forString("0::1"));
+    EXPECT_EQ(IPAddress(FE80_1), IPAddress::forString("fe80::1"));
+    EXPECT_EQ(IPAddress(FE80_1, 22), IPAddress::forString("fe80::1%22"));
+    // This relies upon having a loopback interface named "lo" with ifindex 1.
+    EXPECT_EQ(IPAddress(FE80_1, 1), IPAddress::forString("fe80::1%lo"));
+}
+
+TEST(IPPrefixTest, forString) {
+    IPPrefix prefix;
+
+    EXPECT_FALSE(IPPrefix::forString("", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("invalid", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("192.0.2.0", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001::db8::", &prefix));
+
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8:://32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/32z", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/32/", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/0x20", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8:: /32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/ 32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString(" 2001:db8::/32", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/32 ", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/+32", &prefix));
+
+    EXPECT_FALSE(IPPrefix::forString("192.0.2.0/33", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/129", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("192.0.2.0/-1", &prefix));
+    EXPECT_FALSE(IPPrefix::forString("2001:db8::/-1", &prefix));
+
+    EXPECT_TRUE(IPPrefix::forString("2001:db8::/32", &prefix));
+    EXPECT_EQ("2001:db8::/32", prefix.toString());
+    EXPECT_EQ(IPPrefix(IPAddress::forString("2001:db8::"), 32), prefix);
+
+    EXPECT_EQ(IPPrefix(), IPPrefix::forString("invalid"));
+
+    EXPECT_EQ("0.0.0.0/0", IPPrefix::forString("0.0.0.0/0").toString());
+    EXPECT_EQ("::/0", IPPrefix::forString("::/0").toString());
+    EXPECT_EQ("192.0.2.128/25", IPPrefix::forString("192.0.2.131/25").toString());
+    EXPECT_EQ("2001:db8:1:2:3:4:5:4/126",
+              IPPrefix::forString("2001:db8:1:2:3:4:5:6/126").toString());
+}
+
+TEST(IPPrefixTest, IPv4Truncation) {
+    const auto prefixStr = [](int length) -> std::string {
+        return IPPrefix(IPAddress(IPV4_ONES), length).toString();
+    };
+
+    EXPECT_EQ("0.0.0.0/0", prefixStr(0));
+
+    EXPECT_EQ("128.0.0.0/1", prefixStr(1));
+    EXPECT_EQ("192.0.0.0/2", prefixStr(2));
+    EXPECT_EQ("224.0.0.0/3", prefixStr(3));
+    EXPECT_EQ("240.0.0.0/4", prefixStr(4));
+    EXPECT_EQ("248.0.0.0/5", prefixStr(5));
+    EXPECT_EQ("252.0.0.0/6", prefixStr(6));
+    EXPECT_EQ("254.0.0.0/7", prefixStr(7));
+    EXPECT_EQ("255.0.0.0/8", prefixStr(8));
+
+    EXPECT_EQ("255.128.0.0/9", prefixStr(9));
+    EXPECT_EQ("255.192.0.0/10", prefixStr(10));
+    EXPECT_EQ("255.224.0.0/11", prefixStr(11));
+    EXPECT_EQ("255.240.0.0/12", prefixStr(12));
+    EXPECT_EQ("255.248.0.0/13", prefixStr(13));
+    EXPECT_EQ("255.252.0.0/14", prefixStr(14));
+    EXPECT_EQ("255.254.0.0/15", prefixStr(15));
+    EXPECT_EQ("255.255.0.0/16", prefixStr(16));
+
+    EXPECT_EQ("255.255.128.0/17", prefixStr(17));
+    EXPECT_EQ("255.255.192.0/18", prefixStr(18));
+    EXPECT_EQ("255.255.224.0/19", prefixStr(19));
+    EXPECT_EQ("255.255.240.0/20", prefixStr(20));
+    EXPECT_EQ("255.255.248.0/21", prefixStr(21));
+    EXPECT_EQ("255.255.252.0/22", prefixStr(22));
+    EXPECT_EQ("255.255.254.0/23", prefixStr(23));
+    EXPECT_EQ("255.255.255.0/24", prefixStr(24));
+
+    EXPECT_EQ("255.255.255.128/25", prefixStr(25));
+    EXPECT_EQ("255.255.255.192/26", prefixStr(26));
+    EXPECT_EQ("255.255.255.224/27", prefixStr(27));
+    EXPECT_EQ("255.255.255.240/28", prefixStr(28));
+    EXPECT_EQ("255.255.255.248/29", prefixStr(29));
+    EXPECT_EQ("255.255.255.252/30", prefixStr(30));
+    EXPECT_EQ("255.255.255.254/31", prefixStr(31));
+    EXPECT_EQ("255.255.255.255/32", prefixStr(32));
+}
+
+TEST(IPPrefixTest, IPv6Truncation) {
+    const auto prefixStr = [](int length) -> std::string {
+        return IPPrefix(IPAddress(IPV6_ONES), length).toString();
+    };
+
+    EXPECT_EQ("::/0", prefixStr(0));
+
+    EXPECT_EQ("8000::/1", prefixStr(1));
+    EXPECT_EQ("c000::/2", prefixStr(2));
+    EXPECT_EQ("e000::/3", prefixStr(3));
+    EXPECT_EQ("f000::/4", prefixStr(4));
+    EXPECT_EQ("f800::/5", prefixStr(5));
+    EXPECT_EQ("fc00::/6", prefixStr(6));
+    EXPECT_EQ("fe00::/7", prefixStr(7));
+    EXPECT_EQ("ff00::/8", prefixStr(8));
+
+    EXPECT_EQ("ff80::/9", prefixStr(9));
+    EXPECT_EQ("ffc0::/10", prefixStr(10));
+    EXPECT_EQ("ffe0::/11", prefixStr(11));
+    EXPECT_EQ("fff0::/12", prefixStr(12));
+    EXPECT_EQ("fff8::/13", prefixStr(13));
+    EXPECT_EQ("fffc::/14", prefixStr(14));
+    EXPECT_EQ("fffe::/15", prefixStr(15));
+    EXPECT_EQ("ffff::/16", prefixStr(16));
+
+    EXPECT_EQ("ffff:8000::/17", prefixStr(17));
+    EXPECT_EQ("ffff:c000::/18", prefixStr(18));
+    EXPECT_EQ("ffff:e000::/19", prefixStr(19));
+    EXPECT_EQ("ffff:f000::/20", prefixStr(20));
+    EXPECT_EQ("ffff:f800::/21", prefixStr(21));
+    EXPECT_EQ("ffff:fc00::/22", prefixStr(22));
+    EXPECT_EQ("ffff:fe00::/23", prefixStr(23));
+    EXPECT_EQ("ffff:ff00::/24", prefixStr(24));
+
+    EXPECT_EQ("ffff:ff80::/25", prefixStr(25));
+    EXPECT_EQ("ffff:ffc0::/26", prefixStr(26));
+    EXPECT_EQ("ffff:ffe0::/27", prefixStr(27));
+    EXPECT_EQ("ffff:fff0::/28", prefixStr(28));
+    EXPECT_EQ("ffff:fff8::/29", prefixStr(29));
+    EXPECT_EQ("ffff:fffc::/30", prefixStr(30));
+    EXPECT_EQ("ffff:fffe::/31", prefixStr(31));
+    EXPECT_EQ("ffff:ffff::/32", prefixStr(32));
+
+    EXPECT_EQ("ffff:ffff:8000::/33", prefixStr(33));
+    EXPECT_EQ("ffff:ffff:c000::/34", prefixStr(34));
+    EXPECT_EQ("ffff:ffff:e000::/35", prefixStr(35));
+    EXPECT_EQ("ffff:ffff:f000::/36", prefixStr(36));
+    EXPECT_EQ("ffff:ffff:f800::/37", prefixStr(37));
+    EXPECT_EQ("ffff:ffff:fc00::/38", prefixStr(38));
+    EXPECT_EQ("ffff:ffff:fe00::/39", prefixStr(39));
+    EXPECT_EQ("ffff:ffff:ff00::/40", prefixStr(40));
+
+    EXPECT_EQ("ffff:ffff:ff80::/41", prefixStr(41));
+    EXPECT_EQ("ffff:ffff:ffc0::/42", prefixStr(42));
+    EXPECT_EQ("ffff:ffff:ffe0::/43", prefixStr(43));
+    EXPECT_EQ("ffff:ffff:fff0::/44", prefixStr(44));
+    EXPECT_EQ("ffff:ffff:fff8::/45", prefixStr(45));
+    EXPECT_EQ("ffff:ffff:fffc::/46", prefixStr(46));
+    EXPECT_EQ("ffff:ffff:fffe::/47", prefixStr(47));
+    EXPECT_EQ("ffff:ffff:ffff::/48", prefixStr(48));
+
+    EXPECT_EQ("ffff:ffff:ffff:8000::/49", prefixStr(49));
+    EXPECT_EQ("ffff:ffff:ffff:c000::/50", prefixStr(50));
+    EXPECT_EQ("ffff:ffff:ffff:e000::/51", prefixStr(51));
+    EXPECT_EQ("ffff:ffff:ffff:f000::/52", prefixStr(52));
+    EXPECT_EQ("ffff:ffff:ffff:f800::/53", prefixStr(53));
+    EXPECT_EQ("ffff:ffff:ffff:fc00::/54", prefixStr(54));
+    EXPECT_EQ("ffff:ffff:ffff:fe00::/55", prefixStr(55));
+    EXPECT_EQ("ffff:ffff:ffff:ff00::/56", prefixStr(56));
+
+    EXPECT_EQ("ffff:ffff:ffff:ff80::/57", prefixStr(57));
+    EXPECT_EQ("ffff:ffff:ffff:ffc0::/58", prefixStr(58));
+    EXPECT_EQ("ffff:ffff:ffff:ffe0::/59", prefixStr(59));
+    EXPECT_EQ("ffff:ffff:ffff:fff0::/60", prefixStr(60));
+    EXPECT_EQ("ffff:ffff:ffff:fff8::/61", prefixStr(61));
+    EXPECT_EQ("ffff:ffff:ffff:fffc::/62", prefixStr(62));
+    EXPECT_EQ("ffff:ffff:ffff:fffe::/63", prefixStr(63));
+    EXPECT_EQ("ffff:ffff:ffff:ffff::/64", prefixStr(64));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:8000::/65", prefixStr(65));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:c000::/66", prefixStr(66));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:e000::/67", prefixStr(67));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:f000::/68", prefixStr(68));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:f800::/69", prefixStr(69));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fc00::/70", prefixStr(70));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fe00::/71", prefixStr(71));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ff00::/72", prefixStr(72));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ff80::/73", prefixStr(73));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffc0::/74", prefixStr(74));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffe0::/75", prefixStr(75));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fff0::/76", prefixStr(76));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fff8::/77", prefixStr(77));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fffc::/78", prefixStr(78));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:fffe::/79", prefixStr(79));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff::/80", prefixStr(80));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:8000::/81", prefixStr(81));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:c000::/82", prefixStr(82));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:e000::/83", prefixStr(83));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:f000::/84", prefixStr(84));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:f800::/85", prefixStr(85));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fc00::/86", prefixStr(86));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fe00::/87", prefixStr(87));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ff00::/88", prefixStr(88));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ff80::/89", prefixStr(89));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffc0::/90", prefixStr(90));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffe0::/91", prefixStr(91));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fff0::/92", prefixStr(92));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fff8::/93", prefixStr(93));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fffc::/94", prefixStr(94));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:fffe::/95", prefixStr(95));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff::/96", prefixStr(96));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:8000:0/97", prefixStr(97));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:c000:0/98", prefixStr(98));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:e000:0/99", prefixStr(99));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:f000:0/100", prefixStr(100));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:f800:0/101", prefixStr(101));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fc00:0/102", prefixStr(102));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fe00:0/103", prefixStr(103));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ff00:0/104", prefixStr(104));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ff80:0/105", prefixStr(105));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffc0:0/106", prefixStr(106));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffe0:0/107", prefixStr(107));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fff0:0/108", prefixStr(108));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fff8:0/109", prefixStr(109));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fffc:0/110", prefixStr(110));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:fffe:0/111", prefixStr(111));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:0/112", prefixStr(112));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:8000/113", prefixStr(113));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:c000/114", prefixStr(114));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:e000/115", prefixStr(115));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:f000/116", prefixStr(116));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:f800/117", prefixStr(117));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fc00/118", prefixStr(118));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fe00/119", prefixStr(119));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff00/120", prefixStr(120));
+
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ff80/121", prefixStr(121));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffc0/122", prefixStr(122));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffe0/123", prefixStr(123));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff0/124", prefixStr(124));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fff8/125", prefixStr(125));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffc/126", prefixStr(126));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe/127", prefixStr(127));
+    EXPECT_EQ("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128", prefixStr(128));
+}
+
+TEST(IPPrefixTest, TruncationOther) {
+    const struct {
+        const char* ip;
+        const int cidrLen;
+        const char* ipTruncated;
+    } testExpectations[] = {
+            {"192.0.2.0", 24, "192.0.2.0"},
+            {"192.0.2.0", 23, "192.0.2.0"},
+            {"192.0.2.0", 22, "192.0.0.0"},
+            {"192.0.2.0", 1, "128.0.0.0"},
+            {"2001:db8:cafe:d00d::", 56, "2001:db8:cafe:d000::"},
+            {"2001:db8:cafe:d00d::", 48, "2001:db8:cafe::"},
+            {"2001:db8:cafe:d00d::", 47, "2001:db8:cafe::"},
+            {"2001:db8:cafe:d00d::", 46, "2001:db8:cafc::"},
+    };
+
+    for (const auto& expectation : testExpectations) {
+        IPAddress ip;
+        EXPECT_TRUE(IPAddress::forString(expectation.ip, &ip))
+                << "Failed to parse IP address " << expectation.ip;
+
+        IPAddress ipTruncated;
+        EXPECT_TRUE(IPAddress::forString(expectation.ipTruncated, &ipTruncated))
+                << "Failed to parse IP address " << expectation.ipTruncated;
+
+        IPPrefix prefix(ip, expectation.cidrLen);
+
+        EXPECT_EQ(expectation.cidrLen, prefix.length())
+                << "Unexpected cidrLen " << expectation.cidrLen;
+        EXPECT_EQ(ipTruncated, prefix.ip())
+                << "Unexpected IP truncation: " << prefix.ip() << ", expected: " << ipTruncated;
+    }
+}
+
+TEST(IPPrefixTest, GamutOfOperators) {
+    const std::vector<OperatorExpectation<IPPrefix>> kExpectations{
+            {EQ, IPPrefix(), IPPrefix()},
+            {EQ, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV4_ANY), 0)},
+            {EQ, IPPrefix(IPAddress(IPV4_ANY), IPV4_ADDR_BITS), IPPrefix(IPAddress(IPV4_ANY))},
+            {EQ, IPPrefix(IPAddress(IPV6_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 0)},
+            {EQ, IPPrefix(IPAddress(IPV6_ANY), IPV6_ADDR_BITS), IPPrefix(IPAddress(IPV6_ANY))},
+            // Needlessly fully-specified IPv6 link-local address.
+            {EQ, IPPrefix(IPAddress(FE80_1)), IPPrefix(IPAddress(FE80_1, 0), IPV6_ADDR_BITS)},
+            // Different IPv6 link-local addresses within the same /64, no scoped_id: same /64.
+            {EQ, IPPrefix(IPAddress(FE80_1), 64), IPPrefix(IPAddress(FE80_2), 64)},
+            // Different IPv6 link-local address within the same /64, same scoped_id: same /64.
+            {EQ, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_2, 17), 64)},
+            // Unspecified < IPv4.
+            {LT, IPPrefix(), IPPrefix(IPAddress(IPV4_ANY), 0)},
+            // Same IPv4 base address sorts by prefix length.
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV4_ANY), 1)},
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 1), IPPrefix(IPAddress(IPV4_ANY), IPV4_ADDR_BITS)},
+            // Truncation means each base IPv4 address is different.
+            {LT, IPPrefix(IPAddress(IPV4_ONES), 0), IPPrefix(IPAddress(IPV4_ONES), 1)},
+            {LT, IPPrefix(IPAddress(IPV4_ONES), 1), IPPrefix(IPAddress(IPV4_ONES), IPV4_ADDR_BITS)},
+            // Sort by base IPv4 addresses first.
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress::forString("0.0.0.1"))},
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 1), IPPrefix(IPAddress::forString("0.0.0.1"))},
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 24), IPPrefix(IPAddress::forString("0.0.0.1"))},
+            // IPv4 < IPv6.
+            {LT, IPPrefix(IPAddress(IPV4_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 0)},
+            {LT, IPPrefix(IPAddress(IPV4_ONES)), IPPrefix(IPAddress(IPV6_ANY))},
+            // Unspecified < IPv6.
+            {LT, IPPrefix(), IPPrefix(IPAddress(IPV6_ANY), 0)},
+            // Same IPv6 base address sorts by prefix length.
+            {LT, IPPrefix(IPAddress(IPV6_ANY), 0), IPPrefix(IPAddress(IPV6_ANY), 1)},
+            {LT, IPPrefix(IPAddress(IPV6_ANY), 1), IPPrefix(IPAddress(IPV6_ANY), IPV6_ADDR_BITS)},
+            // Truncation means each base IPv6 address is different.
+            {LT, IPPrefix(IPAddress(IPV6_ONES), 0), IPPrefix(IPAddress(IPV6_ONES), 1)},
+            {LT, IPPrefix(IPAddress(IPV6_ONES), 1), IPPrefix(IPAddress(IPV6_ONES), IPV6_ADDR_BITS)},
+            // Different IPv6 link-local address in same /64, different scoped_id: different /64.
+            {LT, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_2, 22), 64)},
+            {LT, IPPrefix(IPAddress(FE80_1, 17), 64), IPPrefix(IPAddress(FE80_1, 18), 64)},
+            {LT, IPPrefix(IPAddress(FE80_1, 18), 64), IPPrefix(IPAddress(FE80_1, 19), 64)},
+    };
+
+    size_t tests_run = 0;
+    for (const auto& expectation : kExpectations) {
+        SCOPED_TRACE(expectation.toString());
+        EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation));
+        tests_run++;
+    }
+    EXPECT_EQ(kExpectations.size(), tests_run);
+}
+
+TEST(IPSockAddrTest, GamutOfOperators) {
+    const std::vector<OperatorExpectation<IPSockAddr>> kExpectations{
+            {EQ, IPSockAddr(), IPSockAddr()},
+            {EQ, IPSockAddr(IPAddress(IPV4_ANY)), IPSockAddr(IPAddress(IPV4_ANY), 0)},
+            {EQ, IPSockAddr(IPAddress(IPV6_ANY)), IPSockAddr(IPAddress(IPV6_ANY), 0)},
+            {EQ, IPSockAddr(IPAddress(FE80_1), 80), IPSockAddr(IPAddress(FE80_1), 80)},
+            {EQ, IPSockAddr(IPAddress(FE80_1, 17)), IPSockAddr(IPAddress(FE80_1, 17), 0)},
+            {LT, IPSockAddr(IPAddress(IPV4_ANY), 0), IPSockAddr(IPAddress(IPV4_ANY), 1)},
+            {LT, IPSockAddr(IPAddress(IPV4_ANY), 53), IPSockAddr(IPAddress(IPV4_ANY), 123)},
+            {LT, IPSockAddr(IPAddress(IPV4_ONES), 123), IPSockAddr(IPAddress(IPV6_ANY), 53)},
+            {LT, IPSockAddr(IPAddress(IPV6_ANY), 0), IPSockAddr(IPAddress(IPV6_ANY), 1)},
+            {LT, IPSockAddr(IPAddress(IPV6_ANY), 53), IPSockAddr(IPAddress(IPV6_ANY), 123)},
+            {LT, IPSockAddr(IPAddress(FE80_1), 80), IPSockAddr(IPAddress(FE80_1, 17), 80)},
+            {LT, IPSockAddr(IPAddress(FE80_1, 17), 80), IPSockAddr(IPAddress(FE80_1, 22), 80)},
+    };
+
+    size_t tests_run = 0;
+    for (const auto& expectation : kExpectations) {
+        SCOPED_TRACE(expectation.toString());
+        EXPECT_NO_FATAL_FAILURE(testGamutOfOperators(expectation));
+        tests_run++;
+    }
+    EXPECT_EQ(kExpectations.size(), tests_run);
+}
+
+TEST(IPSockAddrTest, toString) {
+    EXPECT_EQ("<unspecified>:0", IPSockAddr().toString());
+    EXPECT_EQ("0.0.0.0:0", IPSockAddr(IPAddress(IPV4_ANY)).toString());
+    EXPECT_EQ("255.255.255.255:67", IPSockAddr(IPAddress(IPV4_ONES), 67).toString());
+    EXPECT_EQ("[::]:0", IPSockAddr(IPAddress(IPV6_ANY)).toString());
+    EXPECT_EQ("[::1]:53", IPSockAddr(IPAddress(IPV6_LOOPBACK), 53).toString());
+    EXPECT_EQ("[fe80::1]:0", IPSockAddr(IPAddress(FE80_1)).toString());
+    EXPECT_EQ("[fe80::2%17]:123", IPSockAddr(IPAddress(FE80_2, 17), 123).toString());
+}
+
+TEST(CompatIPDataTest, ConversionsClearUnneededValues) {
+    const uint32_t idx = 17;
+    const IPSockAddr linkLocalNtpSockaddr(IPAddress(FE80_2, idx), 123);
+    EXPECT_EQ(IPAddress(FE80_2, idx), linkLocalNtpSockaddr.ip());
+    // IPSockAddr(IPSockaddr.ip()) see the port cleared.
+    EXPECT_EQ(0, IPSockAddr(linkLocalNtpSockaddr.ip()).port());
+    const IPPrefix linkLocalPrefix(linkLocalNtpSockaddr.ip(), 64);
+    EXPECT_EQ(IPAddress(FE80, idx), linkLocalPrefix.ip());
+    // IPPrefix(IPPrefix.ip()) see the CIDR length cleared.
+    EXPECT_EQ(IPV6_ADDR_BITS, IPPrefix(linkLocalPrefix.ip()).length());
+}
+
+}  // namespace
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Log.cpp b/staticlibs/netd/libnetdutils/Log.cpp
new file mode 100644
index 0000000..d2ce98f
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Log.cpp
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "netdutils/Log.h"
+#include "netdutils/Slice.h"
+
+#include <chrono>
+#include <ctime>
+#include <iomanip>
+#include <mutex>
+#include <sstream>
+
+#include <android-base/strings.h>
+#include <log/log.h>
+
+using ::android::base::Join;
+using ::android::base::StringPrintf;
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+std::string makeTimestampedEntry(const std::string& entry) {
+    using ::std::chrono::duration_cast;
+    using ::std::chrono::milliseconds;
+    using ::std::chrono::system_clock;
+
+    std::stringstream tsEntry;
+    const auto now = system_clock::now();
+    const auto time_sec = system_clock::to_time_t(now);
+    tsEntry << std::put_time(std::localtime(&time_sec), "%m-%d %H:%M:%S.") << std::setw(3)
+            << std::setfill('0')
+            << duration_cast<milliseconds>(now - system_clock::from_time_t(time_sec)).count() << " "
+            << entry;
+
+    return tsEntry.str();
+}
+
+}  // namespace
+
+std::string LogEntry::toString() const {
+    std::vector<std::string> text;
+
+    if (!mMsg.empty()) text.push_back(mMsg);
+    if (!mFunc.empty()) {
+        text.push_back(StringPrintf("%s(%s)", mFunc.c_str(), Join(mArgs, ", ").c_str()));
+    }
+    if (!mReturns.empty()) {
+        text.push_back("->");
+        text.push_back(StringPrintf("(%s)", Join(mReturns, ", ").c_str()));
+    }
+    if (!mUid.empty()) text.push_back(mUid);
+    if (!mDuration.empty()) text.push_back(StringPrintf("(%s)", mDuration.c_str()));
+
+    return Join(text, " ");
+}
+
+LogEntry& LogEntry::message(const std::string& message) {
+    mMsg = message;
+    return *this;
+}
+
+LogEntry& LogEntry::function(const std::string& function_name) {
+    mFunc = function_name;
+    return *this;
+}
+
+LogEntry& LogEntry::prettyFunction(const std::string& pretty_function) {
+    // __PRETTY_FUNCTION__ generally seems to be of the form:
+    //
+    //     qualifed::returnType qualified::function(args...)
+    //
+    // where the qualified forms include "(anonymous namespace)" in the
+    // "::"-delimited list and keywords like "virtual" (where applicable).
+    //
+    // Here we try to convert strings like:
+    //
+    //     virtual binder::Status android::net::NetdNativeService::isAlive(bool *)
+    //     netdutils::LogEntry android::netd::(anonymous namespace)::AAA::BBB::function()
+    //
+    // into just "NetdNativeService::isAlive" or "BBB::function". Note that
+    // without imposing convention, how to easily identify any namespace/class
+    // name boundary is not obvious.
+    const size_t endFuncName = pretty_function.rfind('(');
+    const size_t precedingSpace = pretty_function.rfind(' ', endFuncName);
+    size_t substrStart = (precedingSpace != std::string::npos) ? precedingSpace + 1 : 0;
+
+    const size_t beginFuncName = pretty_function.rfind("::", endFuncName);
+    if (beginFuncName != std::string::npos && substrStart < beginFuncName) {
+        const size_t previousNameBoundary = pretty_function.rfind("::", beginFuncName - 1);
+        if (previousNameBoundary < beginFuncName && substrStart < previousNameBoundary) {
+            substrStart = previousNameBoundary + 2;
+        } else {
+            substrStart = beginFuncName + 2;
+        }
+    }
+
+    mFunc = pretty_function.substr(substrStart, endFuncName - substrStart);
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::string& val) {
+    mArgs.push_back(val.empty() ? "\"\"" : val);
+    return *this;
+}
+
+template <>
+LogEntry& LogEntry::arg<>(bool val) {
+    mArgs.push_back(val ? "true" : "false");
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::vector<int32_t>& val) {
+    mArgs.push_back(StringPrintf("[%s]", Join(val, ", ").c_str()));
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::vector<uint8_t>& val) {
+    mArgs.push_back('{' + toHex(makeSlice(val)) + '}');
+    return *this;
+}
+
+LogEntry& LogEntry::arg(const std::vector<std::string>& val) {
+    mArgs.push_back(StringPrintf("[%s]", Join(val, ", ").c_str()));
+    return *this;
+}
+
+LogEntry& LogEntry::returns(const std::string& rval) {
+    mReturns.push_back(rval);
+    return *this;
+}
+
+LogEntry& LogEntry::returns(bool rval) {
+    mReturns.push_back(rval ? "true" : "false");
+    return *this;
+}
+
+LogEntry& LogEntry::returns(const Status& status) {
+    mReturns.push_back(status.msg());
+    return *this;
+}
+
+LogEntry& LogEntry::withUid(uid_t uid) {
+    mUid = StringPrintf("(uid=%d)", uid);
+    return *this;
+}
+
+LogEntry& LogEntry::withAutomaticDuration() {
+    using ms = std::chrono::duration<float, std::ratio<1, 1000>>;
+
+    const std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
+    std::stringstream duration;
+    duration << std::setprecision(1) << std::chrono::duration_cast<ms>(end - mStart).count()
+             << "ms";
+    mDuration = duration.str();
+    return *this;
+}
+
+LogEntry& LogEntry::withDuration(const std::string& duration) {
+    mDuration = duration;
+    return *this;
+}
+
+Log::~Log() {
+    // TODO: dump the last N entries to the android log for possible posterity.
+    info(LogEntry().function(__FUNCTION__));
+}
+
+void Log::forEachEntry(const std::function<void(const std::string&)>& perEntryFn) const {
+    // We make a (potentially expensive) copy of the log buffer (including
+    // all strings), in case the |perEntryFn| takes its sweet time.
+    std::deque<std::string> entries;
+    {
+        std::shared_lock<std::shared_mutex> guard(mLock);
+        entries.assign(mEntries.cbegin(), mEntries.cend());
+    }
+
+    for (const std::string& entry : entries) perEntryFn(entry);
+}
+
+void Log::record(Log::Level lvl, const std::string& entry) {
+    switch (lvl) {
+        case Level::LOG:
+            break;
+        case Level::INFO:
+            ALOG(LOG_INFO, mTag.c_str(), "%s", entry.c_str());
+            break;
+        case Level::WARN:
+            ALOG(LOG_WARN, mTag.c_str(), "%s", entry.c_str());
+            break;
+        case Level::ERROR:
+            ALOG(LOG_ERROR, mTag.c_str(), "%s", entry.c_str());
+            break;
+    }
+
+    std::lock_guard guard(mLock);
+    mEntries.push_back(makeTimestampedEntry(entry));
+    while (mEntries.size() > mMaxEntries) mEntries.pop_front();
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/LogTest.cpp b/staticlibs/netd/libnetdutils/LogTest.cpp
new file mode 100644
index 0000000..1270560
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/LogTest.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Log.h"
+
+android::netdutils::LogEntry globalFunctionName() {
+    return android::netdutils::LogEntry().function(__FUNCTION__);
+}
+
+android::netdutils::LogEntry globalPrettyFunctionName() {
+    return android::netdutils::LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+}
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+LogEntry functionName() {
+    return LogEntry().function(__FUNCTION__);
+}
+
+LogEntry prettyFunctionName() {
+    return LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+}
+
+}  // namespace
+
+class AAA {
+  public:
+    AAA() = default;
+
+    LogEntry functionName() {
+        return LogEntry().function(__FUNCTION__);
+    }
+
+    LogEntry prettyFunctionName() {
+        return LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+    }
+
+    class BBB {
+      public:
+        BBB() = default;
+
+        LogEntry functionName() {
+            return LogEntry().function(__FUNCTION__);
+        }
+
+        LogEntry prettyFunctionName() {
+            return LogEntry().prettyFunction(__PRETTY_FUNCTION__);
+        }
+    };
+};
+
+TEST(LogEntryTest, Empty) {
+    LogEntry empty;
+    EXPECT_EQ("", empty.toString());
+}
+
+TEST(LogEntryTest, GlobalFunction) {
+    EXPECT_EQ("globalFunctionName()", ::globalFunctionName().toString());
+}
+
+TEST(LogEntryTest, GlobalPrettyFunction) {
+    EXPECT_EQ("globalPrettyFunctionName()", ::globalPrettyFunctionName().toString());
+}
+
+TEST(LogEntryTest, UnnamedNamespaceFunction) {
+    const LogEntry entry = functionName();
+    EXPECT_EQ("functionName()", entry.toString());
+}
+
+TEST(LogEntryTest, UnnamedNamespacePrettyFunction) {
+    const LogEntry entry = prettyFunctionName();
+    EXPECT_EQ("prettyFunctionName()", entry.toString());
+}
+
+TEST(LogEntryTest, ClassFunction) {
+    const LogEntry entry = AAA().functionName();
+    EXPECT_EQ("functionName()", entry.toString());
+}
+
+TEST(LogEntryTest, ClassPrettyFunction) {
+    const LogEntry entry = AAA().prettyFunctionName();
+    EXPECT_EQ("AAA::prettyFunctionName()", entry.toString());
+}
+
+TEST(LogEntryTest, InnerClassFunction) {
+    const LogEntry entry = AAA::BBB().functionName();
+    EXPECT_EQ("functionName()", entry.toString());
+}
+
+TEST(LogEntryTest, InnerClassPrettyFunction) {
+    const LogEntry entry = AAA::BBB().prettyFunctionName();
+    EXPECT_EQ("BBB::prettyFunctionName()", entry.toString());
+}
+
+TEST(LogEntryTest, PrintChainedArguments) {
+    const LogEntry entry = LogEntry()
+            .function("testFunc")
+            .arg("hello")
+            .arg(42)
+            .arg(true);
+    EXPECT_EQ("testFunc(hello, 42, true)", entry.toString());
+}
+
+TEST(LogEntryTest, PrintIntegralTypes) {
+    const LogEntry entry = LogEntry()
+            .function("testFunc")
+            .arg('A')
+            .arg(100U)
+            .arg(-1000LL);
+    EXPECT_EQ("testFunc(65, 100, -1000)", entry.toString());
+}
+
+TEST(LogEntryTest, PrintHex) {
+    const std::vector<uint8_t> buf{0xDE, 0xAD, 0xBE, 0xEF};
+    const LogEntry entry = LogEntry().function("testFunc").arg(buf);
+    EXPECT_EQ("testFunc({deadbeef})", entry.toString());
+}
+
+TEST(LogEntryTest, PrintArgumentPack) {
+    const LogEntry entry = LogEntry().function("testFunc").args("hello", 42, false);
+    EXPECT_EQ("testFunc(hello, 42, false)", entry.toString());
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/MemBlockTest.cpp b/staticlibs/netd/libnetdutils/MemBlockTest.cpp
new file mode 100644
index 0000000..6455a7e
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/MemBlockTest.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include <algorithm>
+#include <cstdint>
+#include <utility>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/MemBlock.h"
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+namespace {
+
+constexpr unsigned DNS_PACKET_SIZE = 512;
+constexpr int ARBITRARY_VALUE = 0x55;
+
+MemBlock makeArbitraryMemBlock(size_t len) {
+    MemBlock result(len);
+    // Do some fictional work before returning.
+    for (Slice slice = result.get(); !slice.empty(); slice = drop(slice, 1)) {
+        slice.base()[0] = ARBITRARY_VALUE;
+    }
+    return result;
+}
+
+void checkAllZeros(Slice slice) {
+    for (; !slice.empty(); slice = drop(slice, 1)) {
+        EXPECT_EQ(0U, slice.base()[0]);
+    }
+}
+
+void checkArbitraryMemBlock(const MemBlock& block, size_t expectedSize) {
+    Slice slice = block.get();
+    EXPECT_EQ(expectedSize, slice.size());
+    EXPECT_NE(nullptr, slice.base());
+    for (; !slice.empty(); slice = drop(slice, 1)) {
+        EXPECT_EQ(ARBITRARY_VALUE, slice.base()[0]);
+    }
+}
+
+void checkHelloMello(Slice dest, Slice src) {
+    EXPECT_EQ('h', dest.base()[0]);
+    EXPECT_EQ('e', dest.base()[1]);
+    EXPECT_EQ('l', dest.base()[2]);
+    EXPECT_EQ('l', dest.base()[3]);
+    EXPECT_EQ('o', dest.base()[4]);
+
+    src.base()[0] = 'm';
+    EXPECT_EQ('h', dest.base()[0]);
+}
+
+}  // namespace
+
+TEST(MemBlockTest, Empty) {
+    MemBlock empty;
+    EXPECT_TRUE(empty.get().empty());
+    EXPECT_EQ(nullptr, empty.get().base());
+}
+
+TEST(MemBlockTest, ExplicitZero) {
+    MemBlock zero(0);
+    EXPECT_TRUE(zero.get().empty());
+    EXPECT_EQ(nullptr, zero.get().base());
+}
+
+TEST(MemBlockTest, BasicAllocation) {
+    MemBlock dnsPacket(DNS_PACKET_SIZE);
+    Slice slice = dnsPacket.get();
+    EXPECT_EQ(DNS_PACKET_SIZE, slice.size());
+    // Verify the space is '\0'-initialized.
+    ASSERT_NO_FATAL_FAILURE(checkAllZeros(slice));
+    EXPECT_NE(nullptr, slice.base());
+}
+
+TEST(MemBlockTest, MoveConstruction) {
+    MemBlock block(makeArbitraryMemBlock(DNS_PACKET_SIZE));
+    ASSERT_NO_FATAL_FAILURE(checkArbitraryMemBlock(block, DNS_PACKET_SIZE));
+}
+
+TEST(MemBlockTest, MoveAssignmentOrConstruction) {
+    MemBlock block = makeArbitraryMemBlock(DNS_PACKET_SIZE);
+    ASSERT_NO_FATAL_FAILURE(checkArbitraryMemBlock(block, DNS_PACKET_SIZE));
+}
+
+TEST(MemBlockTest, StdMoveAssignment) {
+    constexpr unsigned SIZE = 10;
+
+    MemBlock block;
+    EXPECT_TRUE(block.get().empty());
+    EXPECT_EQ(nullptr, block.get().base());
+
+    {
+        MemBlock block2 = makeArbitraryMemBlock(SIZE);
+        EXPECT_EQ(SIZE, block2.get().size());
+        // More fictional work.
+        for (unsigned i = 0; i < SIZE; i++) {
+            block2.get().base()[i] = i;
+        }
+        block = std::move(block2);
+    }
+
+    EXPECT_EQ(SIZE, block.get().size());
+    for (unsigned i = 0; i < SIZE; i++) {
+        EXPECT_EQ(i, block.get().base()[i]);
+    }
+}
+
+TEST(MemBlockTest, ConstructionFromSlice) {
+    uint8_t data[] = {'h', 'e', 'l', 'l', 'o'};
+    Slice dataSlice(Slice(data, sizeof(data) / sizeof(data[0])));
+
+    MemBlock dataCopy(dataSlice);
+    ASSERT_NO_FATAL_FAILURE(checkHelloMello(dataCopy.get(), dataSlice));
+}
+
+TEST(MemBlockTest, ImplicitCastToSlice) {
+    uint8_t data[] = {'h', 'e', 'l', 'l', 'o'};
+    Slice dataSlice(Slice(data, sizeof(data) / sizeof(data[0])));
+
+    MemBlock dataCopy(dataSlice.size());
+    // NOTE: no explicit MemBlock::get().
+    // Verify the space is '\0'-initialized.
+    ASSERT_NO_FATAL_FAILURE(checkAllZeros(dataCopy));
+    copy(dataCopy, dataSlice);
+    ASSERT_NO_FATAL_FAILURE(checkHelloMello(dataCopy, dataSlice));
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Netfilter.cpp b/staticlibs/netd/libnetdutils/Netfilter.cpp
new file mode 100644
index 0000000..bb43de0
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Netfilter.cpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <arpa/inet.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+#include <ios>
+
+#include "netdutils/Netfilter.h"
+
+std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg) {
+    return os << std::hex << "nfgenmsg["
+              << "family: 0x" << static_cast<int>(msg.nfgen_family) << ", version: 0x"
+              << static_cast<int>(msg.version) << ", res_id: 0x" << ntohs(msg.res_id) << "]"
+              << std::dec;
+}
diff --git a/staticlibs/netd/libnetdutils/Netlink.cpp b/staticlibs/netd/libnetdutils/Netlink.cpp
new file mode 100644
index 0000000..824c0f2
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Netlink.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <ios>
+#include <linux/netlink.h>
+
+#include "netdutils/Math.h"
+#include "netdutils/Netlink.h"
+
+namespace android {
+namespace netdutils {
+
+void forEachNetlinkMessage(const Slice buf,
+                           const std::function<void(const nlmsghdr&, const Slice)>& onMsg) {
+    Slice tail = buf;
+    while (tail.size() >= sizeof(nlmsghdr)) {
+        nlmsghdr hdr = {};
+        extract(tail, hdr);
+        const auto len = std::max<size_t>(hdr.nlmsg_len, sizeof(hdr));
+        onMsg(hdr, drop(take(tail, len), sizeof(hdr)));
+        tail = drop(tail, align(len, 2));
+    }
+}
+
+void forEachNetlinkAttribute(const Slice buf,
+                             const std::function<void(const nlattr&, const Slice)>& onAttr) {
+    Slice tail = buf;
+    while (tail.size() >= sizeof(nlattr)) {
+        nlattr hdr = {};
+        extract(tail, hdr);
+        const auto len = std::max<size_t>(hdr.nla_len, sizeof(hdr));
+        onAttr(hdr, drop(take(tail, len), sizeof(hdr)));
+        tail = drop(tail, align(len, 2));
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs) {
+    return (lhs.nl_family == rhs.nl_family) && (lhs.nl_pid == rhs.nl_pid) &&
+           (lhs.nl_groups == rhs.nl_groups);
+}
+
+bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs) {
+    return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr) {
+    return os << std::hex << "nlmsghdr["
+              << "len: 0x" << hdr.nlmsg_len << ", type: 0x" << hdr.nlmsg_type << ", flags: 0x"
+              << hdr.nlmsg_flags << ", seq: 0x" << hdr.nlmsg_seq << ", pid: 0x" << hdr.nlmsg_pid
+              << "]" << std::dec;
+}
+
+std::ostream& operator<<(std::ostream& os, const nlattr& attr) {
+    return os << std::hex << "nlattr["
+              << "len: 0x" << attr.nla_len << ", type: 0x" << attr.nla_type << "]" << std::dec;
+}
+
+std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr) {
+    return os << std::hex << "sockaddr_nl["
+              << "family: " << addr.nl_family << ", pid: " << addr.nl_pid
+              << ", groups: " << addr.nl_groups << "]" << std::dec;
+}
diff --git a/staticlibs/netd/libnetdutils/Slice.cpp b/staticlibs/netd/libnetdutils/Slice.cpp
new file mode 100644
index 0000000..7a07d47
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Slice.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <sstream>
+
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Convert one byte to a two character hexadecimal string
+const std::string toHex(uint8_t byte) {
+    const std::array<char, 16> kLookup = {
+        {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}};
+    return {kLookup[byte >> 4], kLookup[byte & 0xf]};
+}
+
+}  // namespace
+
+std::string toString(const Slice s) {
+    return std::string(reinterpret_cast<char*>(s.base()), s.size());
+}
+
+std::string toHex(const Slice s, int wrap) {
+    Slice tail = s;
+    int count = 0;
+    std::stringstream ss;
+    while (!tail.empty()) {
+        uint8_t byte = 0;
+        extract(tail, byte);
+        ss << toHex(byte);
+        if ((++count % wrap) == 0) {
+            ss << "\n";
+        }
+        tail = drop(tail, 1);
+    }
+    return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Slice& slice) {
+    return os << std::hex << "Slice[base: " << reinterpret_cast<void*>(slice.base())
+              << ", limit: " << reinterpret_cast<void*>(slice.limit()) << ", size: 0x"
+              << slice.size() << "]" << std::dec;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/SliceTest.cpp b/staticlibs/netd/libnetdutils/SliceTest.cpp
new file mode 100644
index 0000000..a496933
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/SliceTest.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <array>
+#include <cstdint>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+class SliceTest : public testing::Test {
+  protected:
+    std::array<char, 256> mRaw = {};
+};
+
+TEST_F(SliceTest, smoke) {
+    Slice s1 = makeSlice(mRaw);
+    Slice s2 = makeSlice(mRaw);
+    auto p = split(s1, 14);
+    s2 = p.first; // avoid warn-unused error
+    std::stringstream ss;
+    ss << Slice();
+    EXPECT_EQ("Slice[base: 0x0, limit: 0x0, size: 0x0]", ss.str());
+    constexpr size_t kBytes = 14;
+    EXPECT_EQ(s1.base(), take(s1, kBytes).base());
+    EXPECT_EQ(kBytes, take(s1, kBytes).size());
+    EXPECT_EQ(s1.base() + kBytes, drop(s1, kBytes).base());
+    EXPECT_EQ(s1.size() - kBytes, drop(s1, kBytes).size());
+    double a = 0;
+    double b = 0;
+    int c = 0;
+    EXPECT_EQ(sizeof(a), extract(s1, a));
+    EXPECT_EQ(sizeof(a) + sizeof(b), extract(s1, a, b));
+    EXPECT_EQ(sizeof(a) + sizeof(b) + sizeof(c), extract(s1, a, b, c));
+}
+
+TEST_F(SliceTest, constructor) {
+    // Expect the following lines to compile
+    Slice s1 = makeSlice(mRaw);
+    Slice s2(s1);
+    Slice s3 = s2;
+    const Slice s4(s3);
+    const Slice s5 = s4;
+    s3 = s5;
+    Slice s6(mRaw.data(), mRaw.size());
+    Slice s7(mRaw.data(), mRaw.data() + mRaw.size());
+    struct {
+      int a;
+      double b;
+      float c;
+    } anon;
+    makeSlice(anon);
+    EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()), s1.base());
+    EXPECT_EQ(reinterpret_cast<uint8_t*>(mRaw.data()) + mRaw.size(), s1.limit());
+    EXPECT_EQ(mRaw.size(), s1.size());
+    EXPECT_FALSE(mRaw.empty());
+    EXPECT_TRUE(Slice().empty());
+    EXPECT_TRUE(Slice(nullptr, static_cast<size_t>(0)).empty());
+    EXPECT_TRUE(Slice(nullptr, nullptr).empty());
+}
+
+TEST_F(SliceTest, extract) {
+    struct A {
+        int a, b;
+        bool operator==(const A& other) const { return a == other.a && b == other.b; }
+    };
+    struct B {
+        char str[12];
+        bool b;
+        int i;
+        bool operator==(const B& other) const {
+            return b == other.b && i == other.i && 0 == strncmp(str, other.str, 12);
+        }
+    };
+
+    A origA1 = {1, 2};
+    A origA2 = {3, 4};
+    B origB = {"hello world", true, 1234};
+
+    // Populate buffer for extracting.
+    Slice buffer = makeSlice(mRaw);
+    copy(buffer, makeSlice(origA1));
+    copy(drop(buffer, sizeof(origA1)), makeSlice(origB));
+    copy(drop(buffer, sizeof(origA1) + sizeof(origB)), makeSlice(origA2));
+
+    {
+        // Non-variadic extract
+        A a1{};
+        size_t len = extract(buffer, a1);
+        EXPECT_EQ(sizeof(A), len);
+        EXPECT_EQ(origA1, a1);
+    }
+
+    {
+        // Variadic extract, 2 destinations
+        A a1{};
+        B b{};
+        size_t len = extract(buffer, a1, b);
+        EXPECT_EQ(sizeof(A) + sizeof(B), len);
+        EXPECT_EQ(origA1, a1);
+        EXPECT_EQ(origB, b);
+    }
+
+    {
+        // Variadic extract, 3 destinations
+        A a1{}, a2{};
+        B b{};
+        size_t len = extract(buffer, a1, b, a2);
+        EXPECT_EQ(2 * sizeof(A) + sizeof(B), len);
+        EXPECT_EQ(origA1, a1);
+        EXPECT_EQ(origB, b);
+        EXPECT_EQ(origA2, a2);
+    }
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Socket.cpp b/staticlibs/netd/libnetdutils/Socket.cpp
new file mode 100644
index 0000000..e962b6e
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Socket.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <arpa/inet.h>
+
+#include "netdutils/Slice.h"
+#include "netdutils/Socket.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::string> toString(const in6_addr& addr) {
+    std::array<char, INET6_ADDRSTRLEN> out = {};
+    auto* rv = inet_ntop(AF_INET6, &addr, out.data(), out.size());
+    if (rv == nullptr) {
+        return statusFromErrno(errno, "inet_ntop() failed");
+    }
+    return std::string(out.data());
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/SocketOption.cpp b/staticlibs/netd/libnetdutils/SocketOption.cpp
new file mode 100644
index 0000000..023df6e
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/SocketOption.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#include "netdutils/SocketOption.h"
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <utility>
+
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+Status enableSockopt(Fd sock, int level, int optname) {
+    auto& sys = sSyscalls.get();
+    const int on = 1;
+    return sys.setsockopt(sock, level, optname, &on, sizeof(on));
+}
+
+Status enableTcpKeepAlives(Fd sock, unsigned idleTime, unsigned numProbes, unsigned probeInterval) {
+    RETURN_IF_NOT_OK(enableSockopt(sock, SOL_SOCKET, SO_KEEPALIVE));
+
+    auto& sys = sSyscalls.get();
+    if (idleTime != 0) {
+        RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPIDLE, &idleTime, sizeof(idleTime)));
+    }
+    if (numProbes != 0) {
+        RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPCNT, &numProbes, sizeof(numProbes)));
+    }
+    if (probeInterval != 0) {
+        RETURN_IF_NOT_OK(sys.setsockopt(sock, SOL_TCP, TCP_KEEPINTVL, &probeInterval,
+                sizeof(probeInterval)));
+    }
+
+    return status::ok;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Status.cpp b/staticlibs/netd/libnetdutils/Status.cpp
new file mode 100644
index 0000000..acd8f11
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Status.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Status.h"
+
+#include <sstream>
+
+#include "android-base/stringprintf.h"
+
+namespace android {
+namespace netdutils {
+
+Status statusFromErrno(int err, const std::string& msg) {
+    return Status(err, base::StringPrintf("[%s] : %s", strerror(err), msg.c_str()));
+}
+
+bool equalToErrno(const Status& status, int err) {
+    return status.code() == err;
+}
+
+std::string toString(const Status& status) {
+    std::stringstream ss;
+    ss << status;
+    return ss.str();
+}
+
+std::ostream& operator<<(std::ostream& os, const Status& s) {
+    return os << "Status[code: " << s.code() << ", msg: \"" << s.msg() << "\"]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/StatusTest.cpp b/staticlibs/netd/libnetdutils/StatusTest.cpp
new file mode 100644
index 0000000..4cfc3bb
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/StatusTest.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+
+#include <sstream>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace netdutils {
+namespace {
+
+TEST(StatusTest, valueSemantics) {
+    // Default constructor
+    EXPECT_EQ(status::ok, Status());
+
+    // Copy constructor
+    Status status1(1);
+    Status status2(status1);  // NOLINT(performance-unnecessary-copy-initialization)
+    EXPECT_EQ(1, status2.code());
+
+    // Copy assignment
+    Status status3;
+    status3 = status2;
+    EXPECT_EQ(1, status3.code());
+
+    // Same with const objects
+    const Status status4(4);
+    const Status status5(status4);  // NOLINT(performance-unnecessary-copy-initialization)
+    Status status6;
+    status6 = status5;
+    EXPECT_EQ(4, status6.code());
+}
+
+TEST(StatusTest, errorMessages) {
+    Status s(42, "for tea too");
+    EXPECT_EQ(42, s.code());
+    EXPECT_FALSE(s.ok());
+    EXPECT_EQ(s.msg(), "for tea too");
+}
+
+TEST(StatusOrTest, moveSemantics) {
+    // Status objects should be cheaply movable.
+    EXPECT_TRUE(std::is_nothrow_move_constructible<Status>::value);
+    EXPECT_TRUE(std::is_nothrow_move_assignable<Status>::value);
+
+    // Should move from a temporary Status (twice)
+    Status s(Status(Status(42, "move me")));
+    EXPECT_EQ(42, s.code());
+    EXPECT_EQ(s.msg(), "move me");
+
+    Status s2(666, "EDAEMON");
+    EXPECT_NE(s, s2);
+    s = s2;  // Invokes the move-assignment operator.
+    EXPECT_EQ(666, s.code());
+    EXPECT_EQ(s.msg(), "EDAEMON");
+    EXPECT_EQ(s, s2);
+
+    // A moved-from Status can be re-used.
+    s2 = s;
+
+    // Now both objects are valid.
+    EXPECT_EQ(666, s.code());
+    EXPECT_EQ(s.msg(), "EDAEMON");
+    EXPECT_EQ(s, s2);
+}
+
+TEST(StatusTest, ignoredStatus) {
+    statusFromErrno(ENOTTY, "Not a typewriter, what did you expect?").ignoreError();
+}
+
+TEST(StatusOrTest, ostream) {
+    {
+      StatusOr<int> so(11);
+      std::stringstream ss;
+      ss << so;
+      // TODO: Fix StatusOr to optionally output "value:".
+      EXPECT_EQ("StatusOr[status: Status[code: 0, msg: \"\"]]", ss.str());
+    }
+    {
+      StatusOr<int> err(status::undefined);
+      std::stringstream ss;
+      ss << err;
+      EXPECT_EQ("StatusOr[status: Status[code: 2147483647, msg: \"undefined\"]]", ss.str());
+    }
+}
+
+}  // namespace
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Syscalls.cpp b/staticlibs/netd/libnetdutils/Syscalls.cpp
new file mode 100644
index 0000000..9f653f7
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Syscalls.cpp
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include "netdutils/Syscalls.h"
+
+#include <atomic>
+#include <type_traits>
+#include <utility>
+
+namespace android {
+namespace netdutils {
+namespace {
+
+// Retry syscall fn as long as it returns -1 with errno == EINTR
+template <typename FnT, typename... Params>
+typename std::result_of<FnT(Params...)>::type syscallRetry(FnT fn, Params&&... params) {
+    auto rv = fn(std::forward<Params>(params)...);
+    while ((rv == -1) && (errno == EINTR)) {
+        rv = fn(std::forward<Params>(params)...);
+    }
+    return rv;
+}
+
+}  // namespace
+
+// Production implementation of Syscalls that forwards to libc syscalls.
+class RealSyscalls final : public Syscalls {
+  public:
+    ~RealSyscalls() override = default;
+
+    StatusOr<UniqueFd> open(const std::string& pathname, int flags, mode_t mode) const override {
+        UniqueFd fd(::open(pathname.c_str(), flags, mode));
+        if (!isWellFormed(fd)) {
+            return statusFromErrno(errno, "open(\"" + pathname + "\"...) failed");
+        }
+        return fd;
+    }
+
+    StatusOr<UniqueFd> socket(int domain, int type, int protocol) const override {
+        UniqueFd sock(::socket(domain, type, protocol));
+        if (!isWellFormed(sock)) {
+            return statusFromErrno(errno, "socket() failed");
+        }
+        return sock;
+    }
+
+    Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const override {
+        auto rv = ::getsockname(sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "getsockname() failed");
+        }
+        return status::ok;
+    }
+
+    Status getsockopt(Fd sock, int level, int optname, void* optval,
+                      socklen_t* optlen) const override {
+        auto rv = ::getsockopt(sock.get(), level, optname, optval, optlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "getsockopt() failed");
+        }
+        return status::ok;
+    }
+
+    Status setsockopt(Fd sock, int level, int optname, const void* optval,
+                      socklen_t optlen) const override {
+        auto rv = ::setsockopt(sock.get(), level, optname, optval, optlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "setsockopt() failed");
+        }
+        return status::ok;
+    }
+
+    Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const override {
+        auto rv = ::bind(sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "bind() failed");
+        }
+        return status::ok;
+    }
+
+    Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const override {
+        auto rv = syscallRetry(::connect, sock.get(), addr, addrlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "connect() failed");
+        }
+        return status::ok;
+    }
+
+    StatusOr<ifreq> ioctl(Fd sock, unsigned long request, ifreq* ifr) const override {
+        auto rv = ::ioctl(sock.get(), request, ifr);
+        if (rv == -1) {
+            return statusFromErrno(errno, "ioctl() failed");
+        }
+        return *ifr;
+    }
+
+    StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const override {
+        UniqueFd fd(::eventfd(initval, flags));
+        if (!isWellFormed(fd)) {
+            return statusFromErrno(errno, "eventfd() failed");
+        }
+        return fd;
+    }
+
+    StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const override {
+        timespec ts = {};
+        ts.tv_sec = timeout;
+        ts.tv_nsec = (timeout - ts.tv_sec) * 1e9;
+        auto rv = syscallRetry(::ppoll, fds, nfds, &ts, nullptr);
+        if (rv == -1) {
+            return statusFromErrno(errno, "ppoll() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<size_t> writev(Fd fd, const std::vector<iovec>& iov) const override {
+        auto rv = syscallRetry(::writev, fd.get(), iov.data(), iov.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "writev() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<size_t> write(Fd fd, const Slice buf) const override {
+        auto rv = syscallRetry(::write, fd.get(), buf.base(), buf.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "write() failed");
+        }
+        return static_cast<size_t>(rv);
+    }
+
+    StatusOr<Slice> read(Fd fd, const Slice buf) const override {
+        auto rv = syscallRetry(::read, fd.get(), buf.base(), buf.size());
+        if (rv == -1) {
+            return statusFromErrno(errno, "read() failed");
+        }
+        return Slice(buf.base(), rv);
+    }
+
+    StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst,
+                            socklen_t dstlen) const override {
+        auto rv = syscallRetry(::sendto, sock.get(), buf.base(), buf.size(), flags, dst, dstlen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "sendto() failed");
+        }
+        return static_cast<size_t>(rv);
+    }
+
+    StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src,
+                             socklen_t* srclen) const override {
+        auto rv = syscallRetry(::recvfrom, sock.get(), dst.base(), dst.size(), flags, src, srclen);
+        if (rv == -1) {
+            return statusFromErrno(errno, "recvfrom() failed");
+        }
+        if (rv == 0) {
+            return status::eof;
+        }
+        return take(dst, rv);
+    }
+
+    Status shutdown(Fd fd, int how) const override {
+        auto rv = ::shutdown(fd.get(), how);
+        if (rv == -1) {
+            return statusFromErrno(errno, "shutdown() failed");
+        }
+        return status::ok;
+    }
+
+    Status close(Fd fd) const override {
+        auto rv = ::close(fd.get());
+        if (rv == -1) {
+            return statusFromErrno(errno, "close() failed");
+        }
+        return status::ok;
+    }
+
+    StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const override {
+        UniqueFile file(::fopen(path.c_str(), mode.c_str()));
+        if (file == nullptr) {
+            return statusFromErrno(errno, "fopen(\"" + path + "\", \"" + mode + "\") failed");
+        }
+        return file;
+    }
+
+    StatusOr<pid_t> fork() const override {
+        pid_t rv = ::fork();
+        if (rv == -1) {
+            return statusFromErrno(errno, "fork() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<int> vfprintf(FILE* file, const char* format, va_list ap) const override {
+        auto rv = ::vfprintf(file, format, ap);
+        if (rv == -1) {
+            return statusFromErrno(errno, "vfprintf() failed");
+        }
+        return rv;
+    }
+
+    StatusOr<int> vfscanf(FILE* file, const char* format, va_list ap) const override {
+        auto rv = ::vfscanf(file, format, ap);
+        if (rv == -1) {
+            return statusFromErrno(errno, "vfscanf() failed");
+        }
+        return rv;
+    }
+
+    Status fclose(FILE* file) const override {
+        auto rv = ::fclose(file);
+        if (rv == -1) {
+            return statusFromErrno(errno, "fclose() failed");
+        }
+        return status::ok;
+    }
+};
+
+SyscallsHolder::~SyscallsHolder() {
+    delete &get();
+}
+
+Syscalls& SyscallsHolder::get() {
+    while (true) {
+        // memory_order_relaxed gives the compiler and hardware more
+        // freedom. If we get a stale value (this should only happen
+        // early in the execution of a program) the exchange code below
+        // will loop until we get the most current value.
+        auto* syscalls = mSyscalls.load(std::memory_order_relaxed);
+        // Common case returns existing syscalls
+        if (syscalls) {
+            return *syscalls;
+        }
+
+        // This code will execute on first get()
+        std::unique_ptr<Syscalls> tmp(new RealSyscalls());
+        Syscalls* expected = nullptr;
+        bool success = mSyscalls.compare_exchange_strong(expected, tmp.get());
+        if (success) {
+            // Ownership was transferred to mSyscalls already, must release()
+            return *tmp.release();
+        }
+    }
+}
+
+Syscalls& SyscallsHolder::swap(Syscalls& syscalls) {
+    return *mSyscalls.exchange(&syscalls);
+}
+
+SyscallsHolder sSyscalls;
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/SyscallsTest.cpp b/staticlibs/netd/libnetdutils/SyscallsTest.cpp
new file mode 100644
index 0000000..78ffab5
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/SyscallsTest.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+#include <gtest/gtest.h>
+
+#include "netdutils/Handle.h"
+#include "netdutils/Math.h"
+#include "netdutils/MockSyscalls.h"
+#include "netdutils/Netfilter.h"
+#include "netdutils/Netlink.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+#include "netdutils/Syscalls.h"
+
+using testing::_;
+using testing::ByMove;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+
+namespace android {
+namespace netdutils {
+
+class SyscallsTest : public testing::Test {
+  protected:
+    StrictMock<ScopedMockSyscalls> mSyscalls;
+};
+
+TEST(syscalls, scopedMock) {
+    auto& old = sSyscalls.get();
+    {
+        StrictMock<ScopedMockSyscalls> s;
+        EXPECT_EQ(&s, &sSyscalls.get());
+    }
+    EXPECT_EQ(&old, &sSyscalls.get());
+}
+
+TEST_F(SyscallsTest, open) {
+    const char kPath[] = "/test/path/please/ignore";
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 883;
+    constexpr mode_t kMode = 37373;
+    const auto& sys = sSyscalls.get();
+    EXPECT_CALL(mSyscalls, open(kPath, kFlags, kMode)).WillOnce(Return(ByMove(UniqueFd(kFd))));
+    EXPECT_CALL(mSyscalls, close(kFd)).WillOnce(Return(status::ok));
+    auto result = sys.open(kPath, kFlags, kMode);
+    EXPECT_EQ(status::ok, result.status());
+    EXPECT_EQ(kFd, result.value());
+}
+
+TEST_F(SyscallsTest, getsockname) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, getsockname(kFd, _, _))
+        .WillOnce(Invoke([expected](Fd, sockaddr* addr, socklen_t* addrlen) {
+            memcpy(addr, &expected, sizeof(expected));
+            EXPECT_EQ(*addrlen, static_cast<socklen_t>(sizeof(expected)));
+            return status::ok;
+        }));
+    const auto result = sys.getsockname<sockaddr_nl>(kFd);
+    EXPECT_TRUE(isOk(result));
+    EXPECT_EQ(expected, result.value());
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, getsockname(kFd, _, _)).WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.getsockname<sockaddr_nl>(kFd).status());
+}
+
+TEST_F(SyscallsTest, setsockopt) {
+    constexpr Fd kFd(40);
+    constexpr int kLevel = 50;
+    constexpr int kOptname = 70;
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.setsockopt(kFd, kLevel, kOptname, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, setsockopt(kFd, kLevel, kOptname, &expected, sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.setsockopt(kFd, kLevel, kOptname, expected));
+}
+
+TEST_F(SyscallsTest, getsockopt) {
+    constexpr Fd kFd(40);
+    constexpr int kLevel = 50;
+    constexpr int kOptname = 70;
+    sockaddr_nl expected = {};
+    socklen_t optLen = 0;
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, getsockopt(kFd, kLevel, kOptname, &expected, &optLen))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.getsockopt(kFd, kLevel, kOptname, &expected, &optLen));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, getsockopt(kFd, kLevel, kOptname, &expected, &optLen))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.getsockopt(kFd, kLevel, kOptname, &expected, &optLen));
+}
+
+TEST_F(SyscallsTest, bind) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.bind(kFd, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, bind(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.bind(kFd, expected));
+}
+
+TEST_F(SyscallsTest, connect) {
+    constexpr Fd kFd(40);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(status::ok));
+    EXPECT_EQ(status::ok, sys.connect(kFd, expected));
+
+    // Failure
+    const Status kError = statusFromErrno(EINVAL, "test");
+    EXPECT_CALL(mSyscalls, connect(kFd, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(kError));
+    EXPECT_EQ(kError, sys.connect(kFd, expected));
+}
+
+TEST_F(SyscallsTest, sendto) {
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 0;
+    std::array<char, 10> payload;
+    const auto slice = makeSlice(payload);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, sendto(kFd, slice, kFlags, asSockaddrPtr(&expected), sizeof(expected)))
+        .WillOnce(Return(slice.size()));
+    EXPECT_EQ(status::ok, sys.sendto(kFd, slice, kFlags, expected));
+}
+
+TEST_F(SyscallsTest, recvfrom) {
+    constexpr Fd kFd(40);
+    constexpr int kFlags = 0;
+    std::array<char, 10> payload;
+    const auto dst = makeSlice(payload);
+    const auto used = take(dst, 8);
+    sockaddr_nl expected = {};
+    auto& sys = sSyscalls.get();
+
+    // Success
+    EXPECT_CALL(mSyscalls, recvfrom(kFd, dst, kFlags, _, _))
+            .WillOnce(Invoke(
+                    [expected, used](Fd, const Slice, int, sockaddr* src, socklen_t* srclen) {
+                        *srclen = sizeof(expected);
+                        memcpy(src, &expected, *srclen);
+                        return used;
+                    }));
+    auto result = sys.recvfrom<sockaddr_nl>(kFd, dst, kFlags);
+    EXPECT_EQ(status::ok, result.status());
+    EXPECT_EQ(used, result.value().first);
+    EXPECT_EQ(expected, result.value().second);
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp b/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp
new file mode 100644
index 0000000..8fad8b8
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/ThreadUtilTest.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <string>
+
+#include <android-base/expected.h>
+#include <gtest/gtest.h>
+#include <netdutils/ThreadUtil.h>
+
+namespace android::netdutils {
+
+namespace {
+
+android::base::expected<std::string, int> getThreadName() {
+    char name[16] = {};
+    if (const int ret = pthread_getname_np(pthread_self(), name, sizeof(name)); ret != 0) {
+        return android::base::unexpected(ret);
+    }
+    return std::string(name);
+}
+
+class NoopRun {
+  public:
+    explicit NoopRun(const std::string& name = "") : mName(name) { instanceNum++; }
+
+    // Destructor happens in the thread.
+    ~NoopRun() {
+        if (checkName) {
+            auto expected = getThreadName();
+            EXPECT_TRUE(expected.has_value());
+            EXPECT_EQ(mExpectedName, expected.value());
+        }
+        instanceNum--;
+    }
+
+    void run() {}
+
+    std::string threadName() { return mName; }
+
+    // Set the expected thread name which will be used to check if it matches the actual thread
+    // name which is returned from the system call. The check will happen in the destructor.
+    void setExpectedName(const std::string& expectedName) {
+        checkName = true;
+        mExpectedName = expectedName;
+    }
+
+    static bool waitForAllReleased(int timeoutMs) {
+        constexpr int intervalMs = 20;
+        int limit = timeoutMs / intervalMs;
+        for (int i = 1; i < limit; i++) {
+            if (instanceNum == 0) {
+                return true;
+            }
+            usleep(intervalMs * 1000);
+        }
+        return false;
+    }
+
+    // To track how many instances are alive.
+    static std::atomic<int> instanceNum;
+
+  private:
+    std::string mName;
+    std::string mExpectedName;
+    bool checkName = false;
+};
+
+std::atomic<int> NoopRun::instanceNum;
+
+}  // namespace
+
+TEST(ThreadUtilTest, objectReleased) {
+    NoopRun::instanceNum = 0;
+    NoopRun* obj = new NoopRun();
+    EXPECT_EQ(1, NoopRun::instanceNum);
+    threadLaunch(obj);
+
+    // Wait for the object released along with the thread exited.
+    EXPECT_TRUE(NoopRun::waitForAllReleased(1000));
+    EXPECT_EQ(0, NoopRun::instanceNum);
+}
+
+TEST(ThreadUtilTest, SetThreadName) {
+    NoopRun::instanceNum = 0;
+
+    // Test thread name empty.
+    NoopRun* obj1 = new NoopRun();
+    obj1->setExpectedName("");
+
+    // Test normal case.
+    NoopRun* obj2 = new NoopRun("TestName");
+    obj2->setExpectedName("TestName");
+
+    // Test thread name too long.
+    std::string name("TestNameTooooLong");
+    NoopRun* obj3 = new NoopRun(name);
+    obj3->setExpectedName(name.substr(0, 15));
+
+    // Thread names are examined in their destructors.
+    EXPECT_EQ(3, NoopRun::instanceNum);
+    threadLaunch(obj1);
+    threadLaunch(obj2);
+    threadLaunch(obj3);
+
+    EXPECT_TRUE(NoopRun::waitForAllReleased(1000));
+    EXPECT_EQ(0, NoopRun::instanceNum);
+}
+
+}  // namespace android::netdutils
diff --git a/staticlibs/netd/libnetdutils/UniqueFd.cpp b/staticlibs/netd/libnetdutils/UniqueFd.cpp
new file mode 100644
index 0000000..1cb30ed
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/UniqueFd.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <algorithm>
+
+#include "netdutils/UniqueFd.h"
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+void UniqueFd::reset(Fd fd) {
+    auto& sys = sSyscalls.get();
+    std::swap(fd, mFd);
+    if (isWellFormed(fd)) {
+        expectOk(sys.close(fd));
+    }
+}
+
+std::ostream& operator<<(std::ostream& os, const UniqueFd& fd) {
+    return os << "UniqueFd[" << static_cast<Fd>(fd) << "]";
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/UniqueFile.cpp b/staticlibs/netd/libnetdutils/UniqueFile.cpp
new file mode 100644
index 0000000..21e8779
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/UniqueFile.cpp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <algorithm>
+
+#include "netdutils/Syscalls.h"
+#include "netdutils/UniqueFile.h"
+
+namespace android {
+namespace netdutils {
+
+void UniqueFileDtor::operator()(FILE* file) const {
+    const auto& sys = sSyscalls.get();
+    sys.fclose(file).ignoreError();
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/Utils.cpp b/staticlibs/netd/libnetdutils/Utils.cpp
new file mode 100644
index 0000000..16ec882
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/Utils.cpp
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+#include <map>
+
+#include <net/if.h>
+
+#include "dirent.h"
+#include "netdutils/Status.h"
+#include "netdutils/Utils.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames() {
+    std::vector<std::string> ifaceNames;
+    DIR* d;
+    struct dirent* de;
+
+    if (!(d = opendir("/sys/class/net"))) {
+        return statusFromErrno(errno, "Cannot open iface directory");
+    }
+    while ((de = readdir(d))) {
+        if ((de->d_type != DT_DIR) && (de->d_type != DT_LNK)) continue;
+        if (de->d_name[0] == '.') continue;
+        ifaceNames.push_back(std::string(de->d_name));
+    }
+    closedir(d);
+    return ifaceNames;
+}
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList() {
+    std::map<std::string, uint32_t> ifacePairs;
+
+    ASSIGN_OR_RETURN(auto ifaceNames, getIfaceNames());
+
+    for (const auto& name : ifaceNames) {
+        uint32_t ifaceIndex = if_nametoindex(name.c_str());
+        if (ifaceIndex) {
+            ifacePairs.insert(std::pair<std::string, uint32_t>(name, ifaceIndex));
+        }
+    }
+    return ifacePairs;
+}
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h b/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h
new file mode 100644
index 0000000..a52e72d
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/BackoffSequence.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETDUTILS_BACKOFFSEQUENCE_H
+#define NETDUTILS_BACKOFFSEQUENCE_H
+
+#include <stdint.h>
+#include <algorithm>
+#include <chrono>
+#include <limits>
+
+namespace android {
+namespace netdutils {
+
+// Encapsulate some RFC 3315 section 14 -style backoff mechanics.
+//
+//     https://tools.ietf.org/html/rfc3315#section-14
+template<typename time_type = std::chrono::seconds, typename counter_type = uint32_t>
+class BackoffSequence {
+  public:
+    struct Parameters {
+        time_type initialRetransTime{TIME_UNITY};
+        counter_type maxRetransCount{0U};
+        time_type maxRetransTime{TIME_ZERO};
+        time_type maxRetransDuration{TIME_ZERO};
+        time_type endOfSequenceIndicator{TIME_ZERO};
+    };
+
+    BackoffSequence() : BackoffSequence(Parameters{}) {}
+    BackoffSequence(const BackoffSequence &) = default;
+    BackoffSequence(BackoffSequence &&) = default;
+    BackoffSequence& operator=(const BackoffSequence &) = default;
+    BackoffSequence& operator=(BackoffSequence &&) = default;
+
+    bool hasNextTimeout() const noexcept {
+        return !maxRetransCountExceed() && !maxRetransDurationExceeded();
+    }
+
+    // Returns 0 when the sequence is exhausted.
+    time_type getNextTimeout() {
+        if (!hasNextTimeout()) return getEndOfSequenceIndicator();
+
+        mRetransTime = getNextTimeoutAfter(mRetransTime);
+
+        mRetransCount++;
+        mTotalRetransDuration += mRetransTime;
+        return mRetransTime;
+    }
+
+    time_type getEndOfSequenceIndicator() const noexcept {
+        return mParams.endOfSequenceIndicator;
+    }
+
+    class Builder {
+      public:
+        Builder() {}
+
+        constexpr Builder& withInitialRetransmissionTime(time_type irt) {
+            mParams.initialRetransTime = irt;
+            return *this;
+        }
+        constexpr Builder& withMaximumRetransmissionCount(counter_type mrc) {
+            mParams.maxRetransCount = mrc;
+            return *this;
+        }
+        constexpr Builder& withMaximumRetransmissionTime(time_type mrt) {
+            mParams.maxRetransTime = mrt;
+            return *this;
+        }
+        constexpr Builder& withMaximumRetransmissionDuration(time_type mrd) {
+            mParams.maxRetransDuration = mrd;
+            return *this;
+        }
+        constexpr Builder& withEndOfSequenceIndicator(time_type eos) {
+            mParams.endOfSequenceIndicator = eos;
+            return *this;
+        }
+
+        constexpr BackoffSequence build() const {
+            return BackoffSequence(mParams);
+        }
+
+      private:
+        Parameters mParams;
+    };
+
+  private:
+    static constexpr int PER_ITERATION_SCALING_FACTOR = 2;
+    static constexpr time_type TIME_ZERO = time_type();
+    static constexpr time_type TIME_UNITY = time_type(1);
+
+    constexpr BackoffSequence(const struct Parameters &params)
+            : mParams(params),
+              mRetransCount(0),
+              mRetransTime(TIME_ZERO),
+              mTotalRetransDuration(TIME_ZERO) {}
+
+    constexpr bool maxRetransCountExceed() const {
+        return (mParams.maxRetransCount > 0) && (mRetransCount >= mParams.maxRetransCount);
+    }
+
+    constexpr bool maxRetransDurationExceeded() const {
+        return (mParams.maxRetransDuration > TIME_ZERO) &&
+               (mTotalRetransDuration >= mParams.maxRetransDuration);
+    }
+
+    time_type getNextTimeoutAfter(time_type lastTimeout) const {
+        // TODO: Support proper random jitter. Also, consider supporting some
+        // per-iteration scaling factor other than doubling.
+        time_type nextTimeout = (lastTimeout > TIME_ZERO)
+                ? PER_ITERATION_SCALING_FACTOR * lastTimeout
+                : mParams.initialRetransTime;
+
+        // Check if overflow occurred.
+        if (nextTimeout < lastTimeout) {
+            nextTimeout = std::numeric_limits<time_type>::max();
+        }
+
+        // Cap to maximum allowed, if necessary.
+        if (mParams.maxRetransTime > TIME_ZERO) {
+            nextTimeout = std::min(nextTimeout, mParams.maxRetransTime);
+        }
+
+        // Don't overflow the maximum total duration.
+        if (mParams.maxRetransDuration > TIME_ZERO) {
+            nextTimeout = std::min(nextTimeout, mParams.maxRetransDuration - lastTimeout);
+        }
+        return nextTimeout;
+    }
+
+    const Parameters mParams;
+    counter_type mRetransCount;
+    time_type mRetransTime;
+    time_type mTotalRetransDuration;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_BACKOFFSEQUENCE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h b/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h
new file mode 100644
index 0000000..a50b5e6
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/DumpWriter.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETDUTILS_DUMPWRITER_H_
+#define NETDUTILS_DUMPWRITER_H_
+
+#include <string>
+
+namespace android {
+namespace netdutils {
+
+class DumpWriter {
+  public:
+    DumpWriter(int fd);
+
+    void incIndent();
+    void decIndent();
+
+    void println(const std::string& line);
+    template <size_t n>
+    void println(const char line[n]) {
+        println(std::string(line));
+    }
+    // Hint to the compiler that it should apply printf validation of
+    // arguments (beginning at position 3) of the format (specified in
+    // position 2). Note that position 1 is the implicit "this" argument.
+    void println(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3)));
+    void blankline() { println(""); }
+
+  private:
+    uint8_t mIndentLevel;
+    int mFd;
+};
+
+class ScopedIndent {
+  public:
+    ScopedIndent() = delete;
+    ScopedIndent(const ScopedIndent&) = delete;
+    ScopedIndent(ScopedIndent&&) = delete;
+    explicit ScopedIndent(DumpWriter& dw) : mDw(dw) { mDw.incIndent(); }
+    ~ScopedIndent() { mDw.decIndent(); }
+    ScopedIndent& operator=(const ScopedIndent&) = delete;
+    ScopedIndent& operator=(ScopedIndent&&) = delete;
+
+    // TODO: consider additional {inc,dec}Indent methods and a counter that
+    // can be used to unwind all pending increments on exit.
+
+  private:
+    DumpWriter& mDw;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_DUMPWRITER_H_
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Fd.h b/staticlibs/netd/libnetdutils/include/netdutils/Fd.h
new file mode 100644
index 0000000..7db4087
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Fd.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_FD_H
+#define NETUTILS_FD_H
+
+#include <ostream>
+
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Strongly typed wrapper for file descriptors with value semantics.
+// This class should typically hold unowned file descriptors.
+class Fd {
+  public:
+    constexpr Fd() = default;
+
+    constexpr Fd(int fd) : mFd(fd) {}
+
+    int get() const { return mFd; }
+
+    bool operator==(const Fd& other) const { return get() == other.get(); }
+    bool operator!=(const Fd& other) const { return get() != other.get(); }
+
+  private:
+    int mFd = -1;
+};
+
+// Return true if fd appears valid (non-negative)
+inline bool isWellFormed(const Fd fd) {
+    return fd.get() >= 0;
+}
+
+std::ostream& operator<<(std::ostream& os, const Fd& fd);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_FD_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Handle.h b/staticlibs/netd/libnetdutils/include/netdutils/Handle.h
new file mode 100644
index 0000000..82083d4
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Handle.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_HANDLE_H
+#define NETUTILS_HANDLE_H
+
+#include <ostream>
+
+namespace android {
+namespace netdutils {
+
+// Opaque, strongly typed wrapper for integer-like handles.
+// Explicitly avoids implementing arithmetic operations.
+//
+// This class is intended to avoid common errors when reordering
+// arguments to functions, typos and other cases where plain integer
+// types would silently cover up the mistake.
+//
+// usage:
+// DEFINE_HANDLE(ProductId, uint64_t);
+// DEFINE_HANDLE(ThumbnailHash, uint64_t);
+// void foo(ProductId p, ThumbnailHash th) {...}
+//
+// void test() {
+//     ProductId p(88);
+//     ThumbnailHash th1(100), th2(200);
+//
+//     foo(p, th1);        <- ok!
+//     foo(th1, p);        <- disallowed!
+//     th1 += 10;          <- disallowed!
+//     p = th2;            <- disallowed!
+//     assert(th1 != th2); <- ok!
+// }
+template <typename T, typename TagT>
+class Handle {
+  public:
+    constexpr Handle() = default;
+    constexpr Handle(const T& value) : mValue(value) {}
+
+    const T get() const { return mValue; }
+
+    bool operator==(const Handle& that) const { return get() == that.get(); }
+    bool operator!=(const Handle& that) const { return get() != that.get(); }
+
+  private:
+    T mValue;
+};
+
+#define DEFINE_HANDLE(name, type) \
+    struct _##name##Tag {};       \
+    using name = ::android::netdutils::Handle<type, _##name##Tag>;
+
+template <typename T, typename TagT>
+inline std::ostream& operator<<(std::ostream& os, const Handle<T, TagT>& handle) {
+    return os << handle.get();
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_HANDLE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
new file mode 100644
index 0000000..d5cbe2b
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/InternetAddresses.h
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 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 <netdb.h>
+#include <netinet/in.h>
+#include <stdint.h>
+#include <cstring>
+#include <limits>
+#include <string>
+
+#include "netdutils/NetworkConstants.h"
+
+namespace android {
+namespace netdutils {
+
+namespace internal_ {
+
+// A structure to hold data for dealing with Internet addresses (IPAddress) and
+// related types such as IPSockAddr and IPPrefix.
+struct compact_ipdata {
+    uint8_t family{AF_UNSPEC};
+    uint8_t cidrlen{0U};  // written and read in host-byte order
+    in_port_t port{0U};   // written and read in host-byte order
+    uint32_t scope_id{0U};
+    union {
+        in_addr v4;
+        in6_addr v6;
+    } ip{.v6 = IN6ADDR_ANY_INIT};  // written and read in network-byte order
+
+    // Classes that use compact_ipdata and this method should be sure to clear
+    // (i.e. zero or make uniform) any fields not relevant to the class.
+    friend bool operator==(const compact_ipdata& a, const compact_ipdata& b) {
+        if ((a.family != b.family) || (a.cidrlen != b.cidrlen) || (a.port != b.port) ||
+            (a.scope_id != b.scope_id)) {
+            return false;
+        }
+        switch (a.family) {
+            case AF_UNSPEC:
+                // After the above checks, two AF_UNSPEC objects can be
+                // considered equal, for convenience.
+                return true;
+            case AF_INET: {
+                const in_addr v4a = a.ip.v4;
+                const in_addr v4b = b.ip.v4;
+                return (v4a.s_addr == v4b.s_addr);
+            }
+            case AF_INET6: {
+                const in6_addr v6a = a.ip.v6;
+                const in6_addr v6b = b.ip.v6;
+                return IN6_ARE_ADDR_EQUAL(&v6a, &v6b);
+            }
+        }
+        return false;
+    }
+
+    // Classes that use compact_ipdata and this method should be sure to clear
+    // (i.e. zero or make uniform) any fields not relevant to the class.
+    friend bool operator!=(const compact_ipdata& a, const compact_ipdata& b) { return !(a == b); }
+
+    // Classes that use compact_ipdata and this method should be sure to clear
+    // (i.e. zero or make uniform) any fields not relevant to the class.
+    friend bool operator<(const compact_ipdata& a, const compact_ipdata& b) {
+        if (a.family != b.family) return (a.family < b.family);
+        switch (a.family) {
+            case AF_INET: {
+                const in_addr v4a = a.ip.v4;
+                const in_addr v4b = b.ip.v4;
+                if (v4a.s_addr != v4b.s_addr) return (ntohl(v4a.s_addr) < ntohl(v4b.s_addr));
+                break;
+            }
+            case AF_INET6: {
+                const in6_addr v6a = a.ip.v6;
+                const in6_addr v6b = b.ip.v6;
+                const int cmp = std::memcmp(v6a.s6_addr, v6b.s6_addr, IPV6_ADDR_LEN);
+                if (cmp != 0) return cmp < 0;
+                break;
+            }
+        }
+        if (a.cidrlen != b.cidrlen) return (a.cidrlen < b.cidrlen);
+        if (a.port != b.port) return (a.port < b.port);
+        return (a.scope_id < b.scope_id);
+    }
+};
+
+static_assert(AF_UNSPEC <= std::numeric_limits<uint8_t>::max(), "AF_UNSPEC value too large");
+static_assert(AF_INET <= std::numeric_limits<uint8_t>::max(), "AF_INET value too large");
+static_assert(AF_INET6 <= std::numeric_limits<uint8_t>::max(), "AF_INET6 value too large");
+static_assert(sizeof(compact_ipdata) == 24U, "compact_ipdata unexpectedly large");
+
+}  // namespace internal_
+
+struct AddrinfoDeleter {
+    void operator()(struct addrinfo* p) const {
+        if (p != nullptr) {
+            freeaddrinfo(p);
+        }
+    }
+};
+
+typedef std::unique_ptr<struct addrinfo, struct AddrinfoDeleter> ScopedAddrinfo;
+
+inline bool usesScopedIds(const in6_addr& ipv6) {
+    return (IN6_IS_ADDR_LINKLOCAL(&ipv6) || IN6_IS_ADDR_MC_LINKLOCAL(&ipv6));
+}
+
+class IPPrefix;
+class IPSockAddr;
+
+class IPAddress {
+  public:
+    static bool forString(const std::string& repr, IPAddress* ip);
+    static IPAddress forString(const std::string& repr) {
+        IPAddress ip;
+        if (!forString(repr, &ip)) return IPAddress();
+        return ip;
+    }
+
+    IPAddress() = default;
+    IPAddress(const IPAddress&) = default;
+    IPAddress(IPAddress&&) = default;
+
+    explicit IPAddress(const in_addr& ipv4)
+        : mData({AF_INET, IPV4_ADDR_BITS, 0U, 0U, {.v4 = ipv4}}) {}
+    explicit IPAddress(const in6_addr& ipv6)
+        : mData({AF_INET6, IPV6_ADDR_BITS, 0U, 0U, {.v6 = ipv6}}) {}
+    IPAddress(const in6_addr& ipv6, uint32_t scope_id)
+        : mData({AF_INET6,
+                 IPV6_ADDR_BITS,
+                 0U,
+                 // Sanity check: scoped_ids only for link-local addresses.
+                 usesScopedIds(ipv6) ? scope_id : 0U,
+                 {.v6 = ipv6}}) {}
+    IPAddress(const IPAddress& ip, uint32_t scope_id) : IPAddress(ip) {
+        mData.scope_id = (family() == AF_INET6 && usesScopedIds(mData.ip.v6)) ? scope_id : 0U;
+    }
+
+    IPAddress& operator=(const IPAddress&) = default;
+    IPAddress& operator=(IPAddress&&) = default;
+
+    constexpr sa_family_t family() const noexcept { return mData.family; }
+    constexpr uint32_t scope_id() const noexcept { return mData.scope_id; }
+
+    std::string toString() const noexcept;
+
+    friend std::ostream& operator<<(std::ostream& os, const IPAddress& ip) {
+        os << ip.toString();
+        return os;
+    }
+    friend bool operator==(const IPAddress& a, const IPAddress& b) { return (a.mData == b.mData); }
+    friend bool operator!=(const IPAddress& a, const IPAddress& b) { return (a.mData != b.mData); }
+    friend bool operator<(const IPAddress& a, const IPAddress& b) { return (a.mData < b.mData); }
+    friend bool operator>(const IPAddress& a, const IPAddress& b) { return (b.mData < a.mData); }
+    friend bool operator<=(const IPAddress& a, const IPAddress& b) { return (a < b) || (a == b); }
+    friend bool operator>=(const IPAddress& a, const IPAddress& b) { return (b < a) || (a == b); }
+
+  private:
+    friend class IPPrefix;
+    friend class IPSockAddr;
+
+    explicit IPAddress(const internal_::compact_ipdata& ipdata) : mData(ipdata) {
+        mData.port = 0U;
+        switch (mData.family) {
+            case AF_INET:
+                mData.cidrlen = IPV4_ADDR_BITS;
+                mData.scope_id = 0U;
+                break;
+            case AF_INET6:
+                mData.cidrlen = IPV6_ADDR_BITS;
+                if (usesScopedIds(ipdata.ip.v6)) mData.scope_id = ipdata.scope_id;
+                break;
+            default:
+                mData.cidrlen = 0U;
+                mData.scope_id = 0U;
+                break;
+        }
+    }
+
+    internal_::compact_ipdata mData{};
+};
+
+class IPPrefix {
+  public:
+    static bool forString(const std::string& repr, IPPrefix* prefix);
+    static IPPrefix forString(const std::string& repr) {
+        IPPrefix prefix;
+        if (!forString(repr, &prefix)) return IPPrefix();
+        return prefix;
+    }
+
+    IPPrefix() = default;
+    IPPrefix(const IPPrefix&) = default;
+    IPPrefix(IPPrefix&&) = default;
+
+    explicit IPPrefix(const IPAddress& ip) : mData(ip.mData) {}
+
+    // Truncate the IP address |ip| at length |length|. Lengths greater than
+    // the address-family-relevant maximum, along with negative values, are
+    // interpreted as if the address-family-relevant maximum had been given.
+    IPPrefix(const IPAddress& ip, int length);
+
+    IPPrefix& operator=(const IPPrefix&) = default;
+    IPPrefix& operator=(IPPrefix&&) = default;
+
+    constexpr sa_family_t family() const noexcept { return mData.family; }
+    IPAddress ip() const noexcept { return IPAddress(mData); }
+    in_addr addr4() const noexcept { return mData.ip.v4; }
+    in6_addr addr6() const noexcept { return mData.ip.v6; }
+    constexpr int length() const noexcept { return mData.cidrlen; }
+
+    bool isUninitialized() const noexcept;
+    std::string toString() const noexcept;
+
+    friend std::ostream& operator<<(std::ostream& os, const IPPrefix& prefix) {
+        os << prefix.toString();
+        return os;
+    }
+    friend bool operator==(const IPPrefix& a, const IPPrefix& b) { return (a.mData == b.mData); }
+    friend bool operator!=(const IPPrefix& a, const IPPrefix& b) { return (a.mData != b.mData); }
+    friend bool operator<(const IPPrefix& a, const IPPrefix& b) { return (a.mData < b.mData); }
+    friend bool operator>(const IPPrefix& a, const IPPrefix& b) { return (b.mData < a.mData); }
+    friend bool operator<=(const IPPrefix& a, const IPPrefix& b) { return (a < b) || (a == b); }
+    friend bool operator>=(const IPPrefix& a, const IPPrefix& b) { return (b < a) || (a == b); }
+
+  private:
+    internal_::compact_ipdata mData{};
+};
+
+// An Internet socket address.
+//
+// Cannot represent other types of socket addresses (e.g. UNIX socket address, et cetera).
+class IPSockAddr {
+  public:
+    // TODO: static forString
+
+    static IPSockAddr toIPSockAddr(const std::string& repr, in_port_t port) {
+        return IPSockAddr(IPAddress::forString(repr), port);
+    }
+    static IPSockAddr toIPSockAddr(const sockaddr& sa) {
+        switch (sa.sa_family) {
+            case AF_INET:
+                return IPSockAddr(*reinterpret_cast<const sockaddr_in*>(&sa));
+            case AF_INET6:
+                return IPSockAddr(*reinterpret_cast<const sockaddr_in6*>(&sa));
+            default:
+                return IPSockAddr();
+        }
+    }
+    static IPSockAddr toIPSockAddr(const sockaddr_storage& ss) {
+        return toIPSockAddr(*reinterpret_cast<const sockaddr*>(&ss));
+    }
+
+    IPSockAddr() = default;
+    IPSockAddr(const IPSockAddr&) = default;
+    IPSockAddr(IPSockAddr&&) = default;
+
+    explicit IPSockAddr(const IPAddress& ip) : mData(ip.mData) {}
+    IPSockAddr(const IPAddress& ip, in_port_t port) : mData(ip.mData) { mData.port = port; }
+    explicit IPSockAddr(const sockaddr_in& ipv4sa)
+        : IPSockAddr(IPAddress(ipv4sa.sin_addr), ntohs(ipv4sa.sin_port)) {}
+    explicit IPSockAddr(const sockaddr_in6& ipv6sa)
+        : IPSockAddr(IPAddress(ipv6sa.sin6_addr, ipv6sa.sin6_scope_id), ntohs(ipv6sa.sin6_port)) {}
+
+    IPSockAddr& operator=(const IPSockAddr&) = default;
+    IPSockAddr& operator=(IPSockAddr&&) = default;
+
+    constexpr sa_family_t family() const noexcept { return mData.family; }
+    IPAddress ip() const noexcept { return IPAddress(mData); }
+    constexpr in_port_t port() const noexcept { return mData.port; }
+
+    // Implicit conversion to sockaddr_storage.
+    operator sockaddr_storage() const noexcept {
+        sockaddr_storage ss;
+        ss.ss_family = mData.family;
+        switch (mData.family) {
+            case AF_INET:
+                reinterpret_cast<sockaddr_in*>(&ss)->sin_addr = mData.ip.v4;
+                reinterpret_cast<sockaddr_in*>(&ss)->sin_port = htons(mData.port);
+                break;
+            case AF_INET6:
+                reinterpret_cast<sockaddr_in6*>(&ss)->sin6_addr = mData.ip.v6;
+                reinterpret_cast<sockaddr_in6*>(&ss)->sin6_port = htons(mData.port);
+                reinterpret_cast<sockaddr_in6*>(&ss)->sin6_scope_id = mData.scope_id;
+                break;
+        }
+        return ss;
+    }
+
+    std::string toString() const noexcept;
+
+    friend std::ostream& operator<<(std::ostream& os, const IPSockAddr& prefix) {
+        os << prefix.toString();
+        return os;
+    }
+    friend bool operator==(const IPSockAddr& a, const IPSockAddr& b) {
+        return (a.mData == b.mData);
+    }
+    friend bool operator!=(const IPSockAddr& a, const IPSockAddr& b) {
+        return (a.mData != b.mData);
+    }
+    friend bool operator<(const IPSockAddr& a, const IPSockAddr& b) { return (a.mData < b.mData); }
+    friend bool operator>(const IPSockAddr& a, const IPSockAddr& b) { return (b.mData < a.mData); }
+    friend bool operator<=(const IPSockAddr& a, const IPSockAddr& b) { return (a < b) || (a == b); }
+    friend bool operator>=(const IPSockAddr& a, const IPSockAddr& b) { return (b < a) || (a == b); }
+
+  private:
+    internal_::compact_ipdata mData{};
+};
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Log.h b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
new file mode 100644
index 0000000..77ae649
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Log.h
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETUTILS_LOG_H
+#define NETUTILS_LOG_H
+
+#include <chrono>
+#include <deque>
+#include <shared_mutex>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include <android-base/stringprintf.h>
+#include <android-base/thread_annotations.h>
+
+#include <netdutils/Status.h>
+
+namespace android {
+namespace netdutils {
+
+class LogEntry {
+  public:
+    LogEntry() = default;
+    LogEntry(const LogEntry&) = default;
+    LogEntry(LogEntry&&) = default;
+    ~LogEntry() = default;
+    LogEntry& operator=(const LogEntry&) = default;
+    LogEntry& operator=(LogEntry&&) = default;
+
+    std::string toString() const;
+
+    ///
+    // Helper methods that make it easy to build up a LogEntry message.
+    // If performance becomes a factor the implementations could be inlined.
+    ///
+    LogEntry& message(const std::string& message);
+
+    // For calling with __FUNCTION__.
+    LogEntry& function(const std::string& function_name);
+    // For calling with __PRETTY_FUNCTION__.
+    LogEntry& prettyFunction(const std::string& pretty_function);
+
+    // Convenience methods for each of the common types of function arguments.
+    LogEntry& arg(const std::string& val);
+    // Intended for binary buffers, formats as hex
+    LogEntry& arg(const std::vector<uint8_t>& val);
+    LogEntry& arg(const std::vector<int32_t>& val);
+    LogEntry& arg(const std::vector<std::string>& val);
+    template <typename IntT, typename = std::enable_if_t<std::is_arithmetic_v<IntT>>>
+    LogEntry& arg(IntT val) {
+        mArgs.push_back(std::to_string(val));
+        return *this;
+    }
+    // Not using a plain overload here to avoid the implicit conversion from
+    // any pointer to bool, which causes string literals to print as 'true'.
+    template <>
+    LogEntry& arg<>(bool val);
+
+    template <typename... Args>
+    LogEntry& args(const Args&... a) {
+        // Cleverness ahead: we throw away the initializer_list filled with
+        // zeroes, all we care about is calling arg() for each argument.
+        (void) std::initializer_list<int>{(arg(a), 0)...};
+        return *this;
+    }
+
+    // Some things can return more than one value, or have multiple output
+    // parameters, so each of these adds to the mReturns vector.
+    LogEntry& returns(const std::string& rval);
+    LogEntry& returns(const Status& status);
+    LogEntry& returns(bool rval);
+    template <class T>
+    LogEntry& returns(T val) {
+        mReturns.push_back(std::to_string(val));
+        return *this;
+    }
+
+    LogEntry& withUid(uid_t uid);
+
+    // Append the duration computed since the creation of this instance.
+    LogEntry& withAutomaticDuration();
+    // Append the string-ified duration computed by some other means.
+    LogEntry& withDuration(const std::string& duration);
+
+  private:
+    std::chrono::steady_clock::time_point mStart = std::chrono::steady_clock::now();
+    std::string mMsg{};
+    std::string mFunc{};
+    std::vector<std::string> mArgs{};
+    std::vector<std::string> mReturns{};
+    std::string mUid{};
+    std::string mDuration{};
+};
+
+class Log {
+  public:
+    Log() = delete;
+    Log(const std::string& tag) : Log(tag, MAX_ENTRIES) {}
+    Log(const std::string& tag, size_t maxEntries) : mTag(tag), mMaxEntries(maxEntries) {}
+    Log(const Log&) = delete;
+    Log(Log&&) = delete;
+    ~Log();
+    Log& operator=(const Log&) = delete;
+    Log& operator=(Log&&) = delete;
+
+    LogEntry newEntry() const { return LogEntry(); }
+
+    // Record a log entry in internal storage only.
+    void log(const std::string& entry) { record(Level::LOG, entry); }
+    template <size_t n>
+    void log(const char entry[n]) { log(std::string(entry)); }
+    void log(const LogEntry& entry) { log(entry.toString()); }
+    void log(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        log(result);
+    }
+
+    // Record a log entry in internal storage and to ALOGI as well.
+    void info(const std::string& entry) { record(Level::INFO, entry); }
+    template <size_t n>
+    void info(const char entry[n]) { info(std::string(entry)); }
+    void info(const LogEntry& entry) { info(entry.toString()); }
+    void info(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        info(result);
+    }
+
+    // Record a log entry in internal storage and to ALOGW as well.
+    void warn(const std::string& entry) { record(Level::WARN, entry); }
+    template <size_t n>
+    void warn(const char entry[n]) { warn(std::string(entry)); }
+    void warn(const LogEntry& entry) { warn(entry.toString()); }
+    void warn(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        warn(result);
+    }
+
+    // Record a log entry in internal storage and to ALOGE as well.
+    void error(const std::string& entry) { record(Level::ERROR, entry); }
+    template <size_t n>
+    void error(const char entry[n]) { error(std::string(entry)); }
+    void error(const LogEntry& entry) { error(entry.toString()); }
+    void error(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+        using ::android::base::StringAppendV;
+        std::string result;
+        va_list ap;
+        va_start(ap, fmt);
+        StringAppendV(&result, fmt, ap);
+        va_end(ap);
+        error(result);
+    }
+
+    // Iterates over every entry in the log in chronological order. Operates
+    // on a copy of the log entries, and so perEntryFn may itself call one of
+    // the logging functions if needed.
+    void forEachEntry(const std::function<void(const std::string&)>& perEntryFn) const;
+
+  private:
+    static constexpr const size_t MAX_ENTRIES = 750U;
+    const std::string mTag;
+    const size_t mMaxEntries;
+
+    // The LOG level adds an entry to mEntries but does not output the message
+    // to the system log. All other levels append to mEntries and output to the
+    // the system log.
+    enum class Level {
+        LOG,
+        INFO,
+        WARN,
+        ERROR,
+    };
+
+    void record(Level lvl, const std::string& entry);
+
+    mutable std::shared_mutex mLock;
+    std::deque<const std::string> mEntries;  // GUARDED_BY(mLock), when supported
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_LOG_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Math.h b/staticlibs/netd/libnetdutils/include/netdutils/Math.h
new file mode 100644
index 0000000..c41fbf5
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Math.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_MATH_H
+#define NETUTILS_MATH_H
+
+#include <algorithm>
+#include <cstdint>
+
+namespace android {
+namespace netdutils {
+
+template <class T>
+inline constexpr const T mask(const int shift) {
+    return (1 << shift) - 1;
+}
+
+// Align x up to the nearest integer multiple of 2^shift
+template <class T>
+inline constexpr const T align(const T& x, const int shift) {
+    return (x + mask<T>(shift)) & ~mask<T>(shift);
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MATH_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h b/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h
new file mode 100644
index 0000000..fd4d612
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/MemBlock.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETUTILS_MEMBLOCK_H
+#define NETUTILS_MEMBLOCK_H
+
+#include <memory>
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+// A class to encapsulate self-deleting byte arrays while preserving access
+// to the underlying length (without the length being part of the type, e.g.
+// std::array<>). By design, the only interface to the underlying bytes is
+// via Slice, to encourage safer memory access usage.
+//
+// No thread-safety guarantees whatsoever.
+class MemBlock {
+  public:
+    MemBlock() : MemBlock(0U) {}
+    explicit MemBlock(size_t len)
+            : mData((len > 0U) ? new uint8_t[len]{} : nullptr),
+              mLen(len) {}
+    // Allocate memory of size src.size() and copy src into this MemBlock.
+    explicit MemBlock(Slice src) : MemBlock(src.size()) {
+        copy(get(), src);
+    }
+
+    // No copy construction or assignment.
+    MemBlock(const MemBlock&) = delete;
+    MemBlock& operator=(const MemBlock&) = delete;
+
+    // Move construction and assignment are okay.
+    MemBlock(MemBlock&&) = default;
+    MemBlock& operator=(MemBlock&&) = default;
+
+    // Even though this method is const, the memory wrapped by the
+    // returned Slice is mutable.
+    Slice get() const noexcept { return Slice(mData.get(), mLen); }
+
+    // Implicit cast to Slice.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    operator const Slice() const noexcept { return get(); }
+
+  private:
+    std::unique_ptr<uint8_t[]> mData;
+    size_t mLen;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MEMBLOCK_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Misc.h b/staticlibs/netd/libnetdutils/include/netdutils/Misc.h
new file mode 100644
index 0000000..d344f81
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Misc.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_MISC_H
+#define NETUTILS_MISC_H
+
+#include <map>
+
+namespace android {
+namespace netdutils {
+
+// Lookup key in map, returing a default value if key is not found
+template <typename U, typename V>
+inline const V& findWithDefault(const std::map<U, V>& map, const U& key, const V& dflt) {
+    auto it = map.find(key);
+    return (it == map.end()) ? dflt : it->second;
+}
+
+// Movable, copiable, scoped lambda (or std::function) runner. Useful
+// for running arbitrary cleanup or logging code when exiting a scope.
+//
+// Compare to defer in golang.
+template <typename FnT>
+class Cleanup {
+  public:
+    Cleanup() = delete;
+    explicit Cleanup(FnT fn) : mFn(fn) {}
+    ~Cleanup() { if (!mReleased) mFn(); }
+
+    void release() { mReleased = true; }
+
+  private:
+    bool mReleased{false};
+    FnT mFn;
+};
+
+// Helper to make a new Cleanup. Avoids complex or impossible syntax
+// when wrapping lambdas.
+//
+// Usage:
+// auto cleanup = makeCleanup([](){ your_code_here; });
+template <typename FnT>
+Cleanup<FnT> makeCleanup(FnT fn) {
+    return Cleanup<FnT>(fn);
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MISC_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h b/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h
new file mode 100644
index 0000000..f57b55c
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/MockSyscalls.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_MOCK_SYSCALLS_H
+#define NETUTILS_MOCK_SYSCALLS_H
+
+#include <atomic>
+#include <cassert>
+#include <memory>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "netdutils/Syscalls.h"
+
+namespace android {
+namespace netdutils {
+
+class MockSyscalls : public Syscalls {
+  public:
+    virtual ~MockSyscalls() = default;
+    // Use Return(ByMove(...)) to deal with movable return types.
+    MOCK_CONST_METHOD3(open,
+                       StatusOr<UniqueFd>(const std::string& pathname, int flags, mode_t mode));
+    MOCK_CONST_METHOD3(socket, StatusOr<UniqueFd>(int domain, int type, int protocol));
+    MOCK_CONST_METHOD3(getsockname, Status(Fd sock, sockaddr* addr, socklen_t* addrlen));
+    MOCK_CONST_METHOD5(getsockopt, Status(Fd sock, int level, int optname, void* optval,
+                                          socklen_t *optlen));
+    MOCK_CONST_METHOD5(setsockopt, Status(Fd sock, int level, int optname, const void* optval,
+                                          socklen_t optlen));
+
+    MOCK_CONST_METHOD3(bind, Status(Fd sock, const sockaddr* addr, socklen_t addrlen));
+    MOCK_CONST_METHOD3(connect, Status(Fd sock, const sockaddr* addr, socklen_t addrlen));
+    MOCK_CONST_METHOD3(ioctl, StatusOr<ifreq>(Fd sock, unsigned long request, ifreq* ifr));
+
+    // Use Return(ByMove(...)) to deal with movable return types.
+    MOCK_CONST_METHOD2(eventfd, StatusOr<UniqueFd>(unsigned int initval, int flags));
+    MOCK_CONST_METHOD3(ppoll, StatusOr<int>(pollfd* fds, nfds_t nfds, double timeout));
+
+    MOCK_CONST_METHOD2(writev, StatusOr<size_t>(Fd fd, const std::vector<iovec>& iov));
+    MOCK_CONST_METHOD2(write, StatusOr<size_t>(Fd fd, const Slice buf));
+    MOCK_CONST_METHOD2(read, StatusOr<Slice>(Fd fd, const Slice buf));
+    MOCK_CONST_METHOD5(sendto, StatusOr<size_t>(Fd sock, const Slice buf, int flags,
+                                                const sockaddr* dst, socklen_t dstlen));
+    MOCK_CONST_METHOD5(recvfrom, StatusOr<Slice>(Fd sock, const Slice dst, int flags, sockaddr* src,
+                                                 socklen_t* srclen));
+    MOCK_CONST_METHOD2(shutdown, Status(Fd fd, int how));
+    MOCK_CONST_METHOD1(close, Status(Fd fd));
+
+    MOCK_CONST_METHOD2(fopen,
+                       StatusOr<UniqueFile>(const std::string& path, const std::string& mode));
+    MOCK_CONST_METHOD3(vfprintf, StatusOr<int>(FILE* file, const char* format, va_list ap));
+    MOCK_CONST_METHOD3(vfscanf, StatusOr<int>(FILE* file, const char* format, va_list ap));
+    MOCK_CONST_METHOD1(fclose, Status(FILE* file));
+    MOCK_CONST_METHOD0(fork, StatusOr<pid_t>());
+};
+
+// For the lifetime of this mock, replace the contents of sSyscalls
+// with a pointer to this mock. Behavior is undefined if multiple
+// ScopedMockSyscalls instances exist concurrently.
+class ScopedMockSyscalls : public MockSyscalls {
+  public:
+    ScopedMockSyscalls() : mOld(sSyscalls.swap(*this)) { assert((mRefcount++) == 1); }
+    virtual ~ScopedMockSyscalls() {
+        sSyscalls.swap(mOld);
+        assert((mRefcount--) == 0);
+    }
+
+  private:
+    std::atomic<int> mRefcount{0};
+    Syscalls& mOld;
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_MOCK_SYSCALLS_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h b/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h
new file mode 100644
index 0000000..22736f1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Netfilter.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_NETFILTER_H
+#define NETUTILS_NETFILTER_H
+
+#include <ostream>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+
+std::ostream& operator<<(std::ostream& os, const nfgenmsg& msg);
+
+#endif /* NETUTILS_NETFILTER_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h b/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h
new file mode 100644
index 0000000..ee5183a
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Netlink.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_NETLINK_H
+#define NETUTILS_NETLINK_H
+
+#include <functional>
+#include <ostream>
+#include <linux/netlink.h>
+
+#include "netdutils/Slice.h"
+
+namespace android {
+namespace netdutils {
+
+// Invoke onMsg once for each netlink message in buf. onMsg will be
+// invoked with an aligned and deserialized header along with a Slice
+// containing the message payload.
+//
+// Assume that the first message begins at offset zero within buf.
+void forEachNetlinkMessage(const Slice buf,
+                           const std::function<void(const nlmsghdr&, const Slice)>& onMsg);
+
+// Invoke onAttr once for each netlink attribute in buf. onAttr will be
+// invoked with an aligned and deserialized header along with a Slice
+// containing the attribute payload.
+//
+// Assume that the first attribute begins at offset zero within buf.
+void forEachNetlinkAttribute(const Slice buf,
+                             const std::function<void(const nlattr&, const Slice)>& onAttr);
+
+}  // namespace netdutils
+}  // namespace android
+
+bool operator==(const sockaddr_nl& lhs, const sockaddr_nl& rhs);
+bool operator!=(const sockaddr_nl& lhs, const sockaddr_nl& rhs);
+
+std::ostream& operator<<(std::ostream& os, const nlmsghdr& hdr);
+std::ostream& operator<<(std::ostream& os, const nlattr& attr);
+std::ostream& operator<<(std::ostream& os, const sockaddr_nl& addr);
+
+#endif /* NETUTILS_NETLINK_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h b/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h
new file mode 100644
index 0000000..dead9a1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/NetworkConstants.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 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
+
+namespace android {
+namespace netdutils {
+
+// See also NetworkConstants.java in frameworks/base.
+constexpr int IPV4_ADDR_LEN = 4;
+constexpr int IPV4_ADDR_BITS = 32;
+constexpr int IPV6_ADDR_LEN = 16;
+constexpr int IPV6_ADDR_BITS = 128;
+
+// Referred from SHA256_DIGEST_LENGTH in boringssl
+constexpr size_t SHA256_SIZE = 32;
+
+}  // namespace netdutils
+}  // namespace android
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h b/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h
new file mode 100644
index 0000000..c170684
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/ResponseCode.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef NETDUTILS_RESPONSECODE_H
+#define NETDUTILS_RESPONSECODE_H
+
+namespace android {
+namespace netdutils {
+
+class ResponseCode {
+    // Keep in sync with
+    // frameworks/base/services/java/com/android/server/NetworkManagementService.java
+  public:
+    // 100 series - Requestion action was initiated; expect another reply
+    // before proceeding with a new command.
+    // clang-format off
+    static constexpr int ActionInitiated                = 100;
+    static constexpr int InterfaceListResult            = 110;
+    static constexpr int TetherInterfaceListResult      = 111;
+    static constexpr int TetherDnsFwdTgtListResult      = 112;
+    static constexpr int TtyListResult                  = 113;
+    static constexpr int TetheringStatsListResult       = 114;
+    static constexpr int TetherDnsFwdNetIdResult        = 115;
+
+    // 200 series - Requested action has been successfully completed
+    static constexpr int CommandOkay                    = 200;
+    static constexpr int TetherStatusResult             = 210;
+    static constexpr int IpFwdStatusResult              = 211;
+    static constexpr int InterfaceGetCfgResult          = 213;
+    // Formerly: int SoftapStatusResult                 = 214;
+    static constexpr int UsbRNDISStatusResult           = 215;
+    static constexpr int InterfaceRxCounterResult       = 216;
+    static constexpr int InterfaceTxCounterResult       = 217;
+    static constexpr int InterfaceRxThrottleResult      = 218;
+    static constexpr int InterfaceTxThrottleResult      = 219;
+    static constexpr int QuotaCounterResult             = 220;
+    static constexpr int TetheringStatsResult           = 221;
+    // NOTE: keep synced with bionic/libc/dns/net/gethnamaddr.c
+    static constexpr int DnsProxyQueryResult            = 222;
+    static constexpr int ClatdStatusResult              = 223;
+
+    // 400 series - The command was accepted but the requested action
+    // did not take place.
+    static constexpr int OperationFailed                = 400;
+    static constexpr int DnsProxyOperationFailed        = 401;
+    static constexpr int ServiceStartFailed             = 402;
+    static constexpr int ServiceStopFailed              = 403;
+
+    // 500 series - The command was not accepted and the requested
+    // action did not take place.
+    static constexpr int CommandSyntaxError             = 500;
+    static constexpr int CommandParameterError          = 501;
+
+    // 600 series - Unsolicited broadcasts
+    static constexpr int InterfaceChange                = 600;
+    static constexpr int BandwidthControl               = 601;
+    static constexpr int ServiceDiscoveryFailed         = 602;
+    static constexpr int ServiceDiscoveryServiceAdded   = 603;
+    static constexpr int ServiceDiscoveryServiceRemoved = 604;
+    static constexpr int ServiceRegistrationFailed      = 605;
+    static constexpr int ServiceRegistrationSucceeded   = 606;
+    static constexpr int ServiceResolveFailed           = 607;
+    static constexpr int ServiceResolveSuccess          = 608;
+    static constexpr int ServiceSetHostnameFailed       = 609;
+    static constexpr int ServiceSetHostnameSuccess      = 610;
+    static constexpr int ServiceGetAddrInfoFailed       = 611;
+    static constexpr int ServiceGetAddrInfoSuccess      = 612;
+    static constexpr int InterfaceClassActivity         = 613;
+    static constexpr int InterfaceAddressChange         = 614;
+    static constexpr int InterfaceDnsInfo               = 615;
+    static constexpr int RouteChange                    = 616;
+    static constexpr int StrictCleartext                = 617;
+    // clang-format on
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_RESPONSECODE_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Slice.h b/staticlibs/netd/libnetdutils/include/netdutils/Slice.h
new file mode 100644
index 0000000..717fbd1
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Slice.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_SLICE_H
+#define NETUTILS_SLICE_H
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <ostream>
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace netdutils {
+
+// Immutable wrapper for a linear region of unowned bytes.
+// Slice represents memory as a half-closed interval [base, limit).
+//
+// Note that without manually invoking the Slice() constructor, it is
+// impossible to increase the size of a slice. This guarantees that
+// applications that properly use the slice API will never access
+// memory outside of a slice.
+//
+// Note that const Slice still wraps mutable memory, however copy
+// assignment and move assignment to slice are disabled.
+class Slice {
+  public:
+    Slice() = default;
+
+    // Create a slice beginning at base and continuing to but not including limit
+    Slice(void* base, void* limit) : mBase(toUint8(base)), mLimit(toUint8(limit)) {}
+
+    // Create a slice beginning at base and continuing for size bytes
+    Slice(void* base, size_t size) : Slice(base, toUint8(base) + size) {}
+
+    // Return the address of the first byte in this slice
+    uint8_t* base() const { return mBase; }
+
+    // Return the address of the first byte following this slice
+    uint8_t* limit() const { return mLimit; }
+
+    // Return the size of this slice in bytes
+    size_t size() const { return limit() - base(); }
+
+    // Return true if size() == 0
+    bool empty() const { return base() == limit(); }
+
+  private:
+    static uint8_t* toUint8(void* ptr) { return reinterpret_cast<uint8_t*>(ptr); }
+
+    uint8_t* mBase = nullptr;
+    uint8_t* mLimit = nullptr;
+};
+
+// Return slice representation of ref which must be a POD type
+template <typename T>
+inline const Slice makeSlice(const T& ref) {
+    static_assert(std::is_pod<T>::value, "value must be a POD type");
+    static_assert(!std::is_pointer<T>::value, "value must not be a pointer type");
+    return {const_cast<T*>(&ref), sizeof(ref)};
+}
+
+// Return slice representation of string data()
+inline const Slice makeSlice(const std::string& s) {
+    using ValueT = std::string::value_type;
+    return {const_cast<ValueT*>(s.data()), s.size() * sizeof(ValueT)};
+}
+
+// Return slice representation of vector data()
+template <typename T>
+inline const Slice makeSlice(const std::vector<T>& v) {
+    return {const_cast<T*>(v.data()), v.size() * sizeof(T)};
+}
+
+// Return slice representation of array data()
+template <typename U, size_t V>
+inline const Slice makeSlice(const std::array<U, V>& a) {
+    return {const_cast<U*>(a.data()), a.size() * sizeof(U)};
+}
+
+// Return prefix and suffix of Slice s ending and starting at position cut
+inline std::pair<const Slice, const Slice> split(const Slice s, size_t cut) {
+    const size_t tmp = std::min(cut, s.size());
+    return {{s.base(), s.base() + tmp}, {s.base() + tmp, s.limit()}};
+}
+
+// Return prefix of Slice s ending at position cut
+inline const Slice take(const Slice s, size_t cut) {
+    return std::get<0>(split(s, cut));
+}
+
+// Return suffix of Slice s starting at position cut
+inline const Slice drop(const Slice s, size_t cut) {
+    return std::get<1>(split(s, cut));
+}
+
+// Copy from src into dst. Bytes copied is the lesser of dst.size() and src.size()
+inline size_t copy(const Slice dst, const Slice src) {
+    const auto min = std::min(dst.size(), src.size());
+    memcpy(dst.base(), src.base(), min);
+    return min;
+}
+
+// Base case for variadic extract below
+template <typename Head>
+inline size_t extract(const Slice src, Head& head) {
+    return copy(makeSlice(head), src);
+}
+
+// Copy from src into one or more pointers to POD data.  If src.size()
+// is less than the sum of all data pointers a suffix of data will be
+// left unmodified. Return the number of bytes copied.
+template <typename Head, typename... Tail>
+inline size_t extract(const Slice src, Head& head, Tail&... tail) {
+    const auto extracted = extract(src, head);
+    return extracted + extract(drop(src, extracted), tail...);
+}
+
+// Return a string containing a copy of the contents of s
+std::string toString(const Slice s);
+
+// Return a string containing a hexadecimal representation of the contents of s.
+// This function inserts a newline into its output every wrap bytes.
+std::string toHex(const Slice s, int wrap = INT_MAX);
+
+inline bool operator==(const Slice& lhs, const Slice& rhs) {
+    return (lhs.base() == rhs.base()) && (lhs.limit() == rhs.limit());
+}
+
+inline bool operator!=(const Slice& lhs, const Slice& rhs) {
+    return !(lhs == rhs);
+}
+
+std::ostream& operator<<(std::ostream& os, const Slice& slice);
+
+// Return suffix of Slice s starting at the first match of byte c. If no matched
+// byte, return an empty Slice.
+inline const Slice findFirstMatching(const Slice s, uint8_t c) {
+    uint8_t* match = (uint8_t*)memchr(s.base(), c, s.size());
+    if (!match) return Slice();
+    return drop(s, match - s.base());
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_SLICE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Socket.h b/staticlibs/netd/libnetdutils/include/netdutils/Socket.h
new file mode 100644
index 0000000..e5aaab9
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Socket.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_SOCKET_H
+#define NETDUTILS_SOCKET_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <string>
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+inline sockaddr* asSockaddrPtr(void* addr) {
+    return reinterpret_cast<sockaddr*>(addr);
+}
+
+inline const sockaddr* asSockaddrPtr(const void* addr) {
+    return reinterpret_cast<const sockaddr*>(addr);
+}
+
+// Return a string representation of addr or Status if there was a
+// failure during conversion.
+StatusOr<std::string> toString(const in6_addr& addr);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SOCKET_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h b/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h
new file mode 100644
index 0000000..3b0aab7
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/SocketOption.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NETDUTILS_SOCKETOPTION_H
+#define NETDUTILS_SOCKETOPTION_H
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <string>
+
+#include "netdutils/Fd.h"
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Turn on simple "boolean" socket options.
+//
+// This is simple wrapper for options that are enabled via code of the form:
+//
+//     int on = 1;
+//     setsockopt(..., &on, sizeof(on));
+Status enableSockopt(Fd sock, int level, int optname);
+
+// Turn on TCP keepalives, and set keepalive parameters for this socket.
+//
+// A parameter value of zero does not set that parameter.
+//
+// Typical system defaults are:
+//
+//     idleTime (in seconds)
+//     $ cat /proc/sys/net/ipv4/tcp_keepalive_time
+//     7200
+//
+//     numProbes
+//     $ cat /proc/sys/net/ipv4/tcp_keepalive_probes
+//     9
+//
+//     probeInterval (in seconds)
+//     $ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
+//     75
+Status enableTcpKeepAlives(Fd sock, unsigned idleTime, unsigned numProbes, unsigned probeInterval);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SOCKETOPTION_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Status.h b/staticlibs/netd/libnetdutils/include/netdutils/Status.h
new file mode 100644
index 0000000..7b0bd47
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Status.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_STATUS_H
+#define NETUTILS_STATUS_H
+
+#include <cassert>
+#include <limits>
+#include <ostream>
+
+#include <android-base/result.h>
+
+namespace android {
+namespace netdutils {
+
+// Simple status implementation suitable for use on the stack in low
+// or moderate performance code. This can definitely be improved but
+// for now short string optimization is expected to keep the common
+// success case fast.
+//
+// Status is implicitly movable via the default noexcept move constructor
+// and noexcept move-assignment operator.
+class [[nodiscard]] Status {
+  public:
+    Status() = default;
+    explicit Status(int code) : mCode(code) {}
+
+    // Constructs an error Status, |code| must be non-zero.
+    Status(int code, std::string msg) : mCode(code), mMsg(std::move(msg)) { assert(!ok()); }
+
+    Status(android::base::Result<void> result)
+        : mCode(result.ok() ? 0 : static_cast<int>(result.error().code())),
+          mMsg(result.ok() ? "" : result.error().message()) {}
+
+    int code() const { return mCode; }
+
+    bool ok() const { return code() == 0; }
+
+    const std::string& msg() const { return mMsg; }
+
+    // Explicitly ignores the Status without triggering [[nodiscard]] errors.
+    void ignoreError() const {}
+
+    bool operator==(const Status& other) const { return code() == other.code(); }
+    bool operator!=(const Status& other) const { return !(*this == other); }
+
+  private:
+    int mCode = 0;
+    std::string mMsg;
+};
+
+namespace status {
+
+const Status ok{0};
+// EOF is not part of errno space, we'll place it far above the
+// highest existing value.
+const Status eof{0x10001, "end of file"};
+const Status undefined{std::numeric_limits<int>::max(), "undefined"};
+
+}  // namespace status
+
+// Return true if status is "OK". This is sometimes preferable to
+// status.ok() when we want to check the state of Status-like objects
+// that implicitly cast to Status.
+inline bool isOk(const Status& status) {
+    return status.ok();
+}
+
+// For use only in tests. Used for both Status and Status-like objects. See also isOk().
+#define EXPECT_OK(status) EXPECT_TRUE(isOk(status))
+#define ASSERT_OK(status) ASSERT_TRUE(isOk(status))
+
+// Documents that status is expected to be ok. This function may log
+// (or assert when running in debug mode) if status has an unexpected value.
+inline void expectOk(const Status& /*status*/) {
+    // TODO: put something here, for now this function serves solely as documentation.
+}
+
+// Convert POSIX errno to a Status object.
+// If Status is extended to have more features, this mapping may
+// become more complex.
+Status statusFromErrno(int err, const std::string& msg);
+
+// Helper that checks Status-like object (notably StatusOr) against a
+// value in the errno space.
+bool equalToErrno(const Status& status, int err);
+
+// Helper that converts Status-like object (notably StatusOr) to a
+// message.
+std::string toString(const Status& status);
+
+std::ostream& operator<<(std::ostream& os, const Status& s);
+
+// Evaluate 'stmt' to a Status object and if it results in an error, return that
+// error.  Use 'tmp' as a variable name to avoid shadowing any variables named
+// tmp.
+#define RETURN_IF_NOT_OK_IMPL(tmp, stmt)           \
+    do {                                           \
+        ::android::netdutils::Status tmp = (stmt); \
+        if (!isOk(tmp)) {                          \
+            return tmp;                            \
+        }                                          \
+    } while (false)
+
+// Create a unique variable name to avoid shadowing local variables.
+#define RETURN_IF_NOT_OK_CONCAT(line, stmt) RETURN_IF_NOT_OK_IMPL(__CONCAT(_status_, line), stmt)
+
+// Macro to allow exception-like handling of error return values.
+//
+// If the evaluation of stmt results in an error, return that error
+// from current function.
+//
+// Example usage:
+// Status bar() { ... }
+//
+// RETURN_IF_NOT_OK(status);
+// RETURN_IF_NOT_OK(bar());
+#define RETURN_IF_NOT_OK(stmt) RETURN_IF_NOT_OK_CONCAT(__LINE__, stmt)
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_STATUS_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h b/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h
new file mode 100644
index 0000000..c7aa4e4
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/StatusOr.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_STATUSOR_H
+#define NETUTILS_STATUSOR_H
+
+#include <cassert>
+#include "netdutils/Status.h"
+
+namespace android {
+namespace netdutils {
+
+// Wrapper around a combination of Status and application value type.
+// T may be any copyable or movable type.
+template <typename T>
+class [[nodiscard]] StatusOr {
+  public:
+    // Constructs a new StatusOr with status::undefined status.
+    // This is marked 'explicit' to try to catch cases like 'return {};',
+    // where people think StatusOr<std::vector<int>> will be initialized
+    // with an empty vector, instead of a status::undefined.
+    explicit StatusOr() = default;
+
+    // Implicit copy constructor and construction from T.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    StatusOr(Status status) : mStatus(std::move(status)) { assert(!isOk(mStatus)); }
+
+    // Implicit construction from T. It is convenient and sensible to be able
+    // to do 'return T()' when the return type is StatusOr<T>.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    StatusOr(const T& value) : mStatus(status::ok), mValue(value) {}
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    StatusOr(T&& value) : mStatus(status::ok), mValue(std::move(value)) {}
+
+    // Move constructor ok (if T supports move)
+    StatusOr(StatusOr&&) noexcept = default;
+    // Move assignment ok (if T supports move)
+    StatusOr& operator=(StatusOr&&) noexcept = default;
+    // Copy constructor ok (if T supports copy)
+    StatusOr(const StatusOr&) = default;
+    // Copy assignment ok (if T supports copy)
+    StatusOr& operator=(const StatusOr&) = default;
+
+    // Returns a const reference to wrapped type.
+    // It is an error to call value() when !isOk(status())
+    const T& value() const & { return mValue; }
+    const T&& value() const && { return mValue; }
+
+    // Returns an rvalue reference to wrapped type
+    // It is an error to call value() when !isOk(status())
+    //
+    // If T is expensive to copy but supports efficient move, it can be moved
+    // out of a StatusOr as follows:
+    //   T value = std::move(statusor).value();
+    T& value() & { return mValue; }
+    T&& value() && { return mValue; }
+
+    // Returns the Status object assigned at construction time.
+    const Status status() const { return mStatus; }
+
+    // Explicitly ignores the Status without triggering [[nodiscard]] errors.
+    void ignoreError() const {}
+
+    // Implicit cast to Status.
+    // NOLINTNEXTLINE(google-explicit-constructor)
+    operator Status() const { return status(); }
+
+  private:
+    Status mStatus = status::undefined;
+    T mValue;
+};
+
+template <typename T>
+inline std::ostream& operator<<(std::ostream& os, const StatusOr<T>& s) {
+    return os << "StatusOr[status: " << s.status() << "]";
+}
+
+#define ASSIGN_OR_RETURN_IMPL(tmp, lhs, stmt) \
+    auto tmp = (stmt);                        \
+    RETURN_IF_NOT_OK(tmp);                    \
+    lhs = std::move(tmp.value());
+
+#define ASSIGN_OR_RETURN_CONCAT(line, lhs, stmt) \
+    ASSIGN_OR_RETURN_IMPL(__CONCAT(_status_or_, line), lhs, stmt)
+
+// Macro to allow exception-like handling of error return values.
+//
+// If the evaluation of stmt results in an error, return that error
+// from the current function. Otherwise, assign the result to lhs.
+//
+// This macro supports both move and copy assignment operators. lhs
+// may be either a new local variable or an existing non-const
+// variable accessible in the current scope.
+//
+// Example usage:
+// StatusOr<MyType> foo() { ... }
+//
+// ASSIGN_OR_RETURN(auto myVar, foo());
+// ASSIGN_OR_RETURN(myExistingVar, foo());
+// ASSIGN_OR_RETURN(myMemberVar, foo());
+#define ASSIGN_OR_RETURN(lhs, stmt) ASSIGN_OR_RETURN_CONCAT(__LINE__, lhs, stmt)
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_STATUSOR_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h b/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h
new file mode 100644
index 0000000..e7b4326
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Stopwatch.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NETDUTILS_STOPWATCH_H
+#define NETDUTILS_STOPWATCH_H
+
+#include <chrono>
+
+namespace android {
+namespace netdutils {
+
+class Stopwatch {
+  private:
+    using clock = std::chrono::steady_clock;
+    using time_point = std::chrono::time_point<clock>;
+
+  public:
+    Stopwatch() : mStart(clock::now()) {}
+    virtual ~Stopwatch() = default;
+
+    int64_t timeTakenUs() const { return getElapsedUs(clock::now()); }
+    int64_t getTimeAndResetUs() {
+        const auto& now = clock::now();
+        int64_t elapsed = getElapsedUs(now);
+        mStart = now;
+        return elapsed;
+    }
+
+  private:
+    time_point mStart;
+
+    int64_t getElapsedUs(const time_point& now) const {
+        return (std::chrono::duration_cast<std::chrono::microseconds>(now - mStart)).count();
+    }
+};
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_STOPWATCH_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h b/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h
new file mode 100644
index 0000000..36fcd85
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Syscalls.h
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_SYSCALLS_H
+#define NETDUTILS_SYSCALLS_H
+
+#include <memory>
+
+#include <net/if.h>
+#include <poll.h>
+#include <sys/eventfd.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+#include "netdutils/Fd.h"
+#include "netdutils/Slice.h"
+#include "netdutils/Socket.h"
+#include "netdutils/Status.h"
+#include "netdutils/StatusOr.h"
+#include "netdutils/UniqueFd.h"
+#include "netdutils/UniqueFile.h"
+
+namespace android {
+namespace netdutils {
+
+class Syscalls {
+  public:
+    virtual ~Syscalls() = default;
+
+    virtual StatusOr<UniqueFd> open(const std::string& pathname, int flags,
+                                    mode_t mode = 0) const = 0;
+
+    virtual StatusOr<UniqueFd> socket(int domain, int type, int protocol) const = 0;
+
+    virtual Status getsockname(Fd sock, sockaddr* addr, socklen_t* addrlen) const = 0;
+
+    virtual Status getsockopt(Fd sock, int level, int optname, void *optval,
+                              socklen_t *optlen) const = 0;
+
+    virtual Status setsockopt(Fd sock, int level, int optname, const void* optval,
+                              socklen_t optlen) const = 0;
+
+    virtual Status bind(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0;
+
+    virtual Status connect(Fd sock, const sockaddr* addr, socklen_t addrlen) const = 0;
+
+    virtual StatusOr<ifreq> ioctl(Fd sock, unsigned long request, ifreq* ifr) const = 0;
+
+    virtual StatusOr<UniqueFd> eventfd(unsigned int initval, int flags) const = 0;
+
+    virtual StatusOr<int> ppoll(pollfd* fds, nfds_t nfds, double timeout) const = 0;
+
+    virtual StatusOr<size_t> writev(Fd fd, const std::vector<iovec>& iov) const = 0;
+
+    virtual StatusOr<size_t> write(Fd fd, const Slice buf) const = 0;
+
+    virtual StatusOr<Slice> read(Fd fd, const Slice buf) const = 0;
+
+    virtual StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const sockaddr* dst,
+                                    socklen_t dstlen) const = 0;
+
+    virtual StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags, sockaddr* src,
+                                     socklen_t* srclen) const = 0;
+
+    virtual Status shutdown(Fd fd, int how) const = 0;
+
+    virtual Status close(Fd fd) const = 0;
+
+    virtual StatusOr<UniqueFile> fopen(const std::string& path, const std::string& mode) const = 0;
+
+    virtual StatusOr<int> vfprintf(FILE* file, const char* format, va_list ap) const = 0;
+
+    virtual StatusOr<int> vfscanf(FILE* file, const char* format, va_list ap) const = 0;
+
+    virtual Status fclose(FILE* file) const = 0;
+
+    virtual StatusOr<pid_t> fork() const = 0;
+
+    // va_args helpers
+    // va_start doesn't work when the preceding argument is a reference
+    // type so we're forced to use const char*.
+    StatusOr<int> fprintf(FILE* file, const char* format, ...) const {
+        va_list ap;
+        va_start(ap, format);
+        auto result = vfprintf(file, format, ap);
+        va_end(ap);
+        return result;
+    }
+
+    // va_start doesn't work when the preceding argument is a reference
+    // type so we're forced to use const char*.
+    StatusOr<int> fscanf(FILE* file, const char* format, ...) const {
+        va_list ap;
+        va_start(ap, format);
+        auto result = vfscanf(file, format, ap);
+        va_end(ap);
+        return result;
+    }
+
+    // Templated helpers that forward directly to methods declared above
+    template <typename SockaddrT>
+    StatusOr<SockaddrT> getsockname(Fd sock) const {
+        SockaddrT addr = {};
+        socklen_t addrlen = sizeof(addr);
+        RETURN_IF_NOT_OK(getsockname(sock, asSockaddrPtr(&addr), &addrlen));
+        return addr;
+    }
+
+    template <typename SockoptT>
+    Status getsockopt(Fd sock, int level, int optname, void* optval, socklen_t* optlen) const {
+        return getsockopt(sock, level, optname, optval, optlen);
+    }
+
+    template <typename SockoptT>
+    Status setsockopt(Fd sock, int level, int optname, const SockoptT& opt) const {
+        return setsockopt(sock, level, optname, &opt, sizeof(opt));
+    }
+
+    template <typename SockaddrT>
+    Status bind(Fd sock, const SockaddrT& addr) const {
+        return bind(sock, asSockaddrPtr(&addr), sizeof(addr));
+    }
+
+    template <typename SockaddrT>
+    Status connect(Fd sock, const SockaddrT& addr) const {
+        return connect(sock, asSockaddrPtr(&addr), sizeof(addr));
+    }
+
+    template <size_t size>
+    StatusOr<std::array<uint16_t, size>> ppoll(const std::array<Fd, size>& fds, uint16_t events,
+                                               double timeout) const {
+        std::array<pollfd, size> tmp;
+        for (size_t i = 0; i < size; ++i) {
+            tmp[i].fd = fds[i].get();
+            tmp[i].events = events;
+            tmp[i].revents = 0;
+        }
+        RETURN_IF_NOT_OK(ppoll(tmp.data(), tmp.size(), timeout).status());
+        std::array<uint16_t, size> out;
+        for (size_t i = 0; i < size; ++i) {
+            out[i] = tmp[i].revents;
+        }
+        return out;
+    }
+
+    template <typename SockaddrT>
+    StatusOr<size_t> sendto(Fd sock, const Slice buf, int flags, const SockaddrT& dst) const {
+        return sendto(sock, buf, flags, asSockaddrPtr(&dst), sizeof(dst));
+    }
+
+    // Ignore src sockaddr
+    StatusOr<Slice> recvfrom(Fd sock, const Slice dst, int flags) const {
+        return recvfrom(sock, dst, flags, nullptr, nullptr);
+    }
+
+    template <typename SockaddrT>
+    StatusOr<std::pair<Slice, SockaddrT>> recvfrom(Fd sock, const Slice dst, int flags) const {
+        SockaddrT addr = {};
+        socklen_t addrlen = sizeof(addr);
+        ASSIGN_OR_RETURN(auto used, recvfrom(sock, dst, flags, asSockaddrPtr(&addr), &addrlen));
+        return std::make_pair(used, addr);
+    }
+};
+
+// Specialized singleton that supports zero initialization and runtime
+// override of contained pointer.
+class SyscallsHolder {
+  public:
+    ~SyscallsHolder();
+
+    // Return a pointer to an unowned instance of Syscalls.
+    Syscalls& get();
+
+    // Testing only: set the value returned by getSyscalls. Return the old value.
+    // Callers are responsible for restoring the previous value returned
+    // by getSyscalls to avoid leaks.
+    Syscalls& swap(Syscalls& syscalls);
+
+  private:
+    std::atomic<Syscalls*> mSyscalls{nullptr};
+};
+
+// Syscalls instance used throughout netdutils
+extern SyscallsHolder sSyscalls;
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_SYSCALLS_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h b/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h
new file mode 100644
index 0000000..62e6f70
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/ThreadUtil.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_THREADUTIL_H
+#define NETDUTILS_THREADUTIL_H
+
+#include <pthread.h>
+#include <memory>
+
+#include <android-base/logging.h>
+
+namespace android {
+namespace netdutils {
+
+struct scoped_pthread_attr {
+    scoped_pthread_attr() { pthread_attr_init(&attr); }
+    ~scoped_pthread_attr() { pthread_attr_destroy(&attr); }
+
+    int detach() { return -pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); }
+
+    pthread_attr_t attr;
+};
+
+inline void setThreadName(std::string name) {
+    // MAX_TASK_COMM_LEN=16 is not exported by bionic.
+    const size_t MAX_TASK_COMM_LEN = 16;
+
+    // Crop name to 16 bytes including the NUL byte, as required by pthread_setname_np()
+    if (name.size() >= MAX_TASK_COMM_LEN) name.resize(MAX_TASK_COMM_LEN - 1);
+
+    if (int ret = pthread_setname_np(pthread_self(), name.c_str()); ret != 0) {
+        LOG(WARNING) << "Unable to set thread name to " << name << ": " << strerror(ret);
+    }
+}
+
+template <typename T>
+inline void* runAndDelete(void* obj) {
+    std::unique_ptr<T> handler(reinterpret_cast<T*>(obj));
+    setThreadName(handler->threadName().c_str());
+    handler->run();
+    return nullptr;
+}
+
+template <typename T>
+inline int threadLaunch(T* obj) {
+    if (obj == nullptr) {
+        return -EINVAL;
+    }
+
+    scoped_pthread_attr scoped_attr;
+
+    int rval = scoped_attr.detach();
+    if (rval != 0) {
+        return rval;
+    }
+
+    pthread_t thread;
+    rval = pthread_create(&thread, &scoped_attr.attr, &runAndDelete<T>, obj);
+    if (rval != 0) {
+        LOG(WARNING) << __func__ << ": pthread_create failed: " << rval;
+        return -rval;
+    }
+
+    return rval;
+}
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif  // NETDUTILS_THREADUTIL_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h b/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h
new file mode 100644
index 0000000..42c1090
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/UidConstants.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#ifndef NETDUTILS_UID_CONSTANTS_H
+#define NETDUTILS_UID_CONSTANTS_H
+
+// These are used by both eBPF kernel programs and netd, we cannot put them in NetdConstant.h since
+// we have to minimize the number of headers included by the BPF kernel program.
+#define MIN_SYSTEM_UID 0
+#define MAX_SYSTEM_UID 9999
+
+#define PER_USER_RANGE 100000
+
+#endif  // NETDUTILS_UID_CONSTANTS_H
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h
new file mode 100644
index 0000000..61101f9
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFd.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETUTILS_UNIQUEFD_H
+#define NETUTILS_UNIQUEFD_H
+
+#include <unistd.h>
+#include <ostream>
+
+#include "netdutils/Fd.h"
+
+namespace android {
+namespace netdutils {
+
+// Stricter unique_fd implementation that:
+// *) Does not implement release()
+// *) Does not implicitly cast to int
+// *) Uses a strongly typed wrapper (Fd) for the underlying file descriptor
+//
+// Users of UniqueFd should endeavor to treat this as a completely
+// opaque object. The only code that should interpret the wrapped
+// value is in Syscalls.h
+class UniqueFd {
+  public:
+    UniqueFd() = default;
+
+    UniqueFd(Fd fd) : mFd(fd) {}
+
+    ~UniqueFd() { reset(); }
+
+    // Disallow copy
+    UniqueFd(const UniqueFd&) = delete;
+    UniqueFd& operator=(const UniqueFd&) = delete;
+
+    // Allow move
+    UniqueFd(UniqueFd&& other) { std::swap(mFd, other.mFd); }
+    UniqueFd& operator=(UniqueFd&& other) {
+        std::swap(mFd, other.mFd);
+        return *this;
+    }
+
+    // Cleanup any currently owned Fd, replacing it with the optional
+    // parameter fd
+    void reset(Fd fd = Fd());
+
+    // Implict cast to Fd
+    operator const Fd &() const { return mFd; }
+
+  private:
+    Fd mFd;
+};
+
+std::ostream& operator<<(std::ostream& os, const UniqueFd& fd);
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_UNIQUEFD_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h
new file mode 100644
index 0000000..6dd6d67
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/UniqueFile.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#ifndef NETDUTILS_UNIQUEFILE_H
+#define NETDUTILS_UNIQUEFILE_H
+
+#include <stdio.h>
+#include <memory>
+
+namespace android {
+namespace netdutils {
+
+struct UniqueFileDtor {
+    void operator()(FILE* file) const;
+};
+
+using UniqueFile = std::unique_ptr<FILE, UniqueFileDtor>;
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETDUTILS_UNIQUEFILE_H */
diff --git a/staticlibs/netd/libnetdutils/include/netdutils/Utils.h b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
new file mode 100644
index 0000000..83c583b
--- /dev/null
+++ b/staticlibs/netd/libnetdutils/include/netdutils/Utils.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+#ifndef NETUTILS_UTILS_H
+#define NETUTILS_UTILS_H
+
+#include "netdutils/StatusOr.h"
+
+namespace android {
+namespace netdutils {
+
+StatusOr<std::vector<std::string>> getIfaceNames();
+
+StatusOr<std::map<std::string, uint32_t>> getIfaceList();
+
+}  // namespace netdutils
+}  // namespace android
+
+#endif /* NETUTILS_UTILS_H */
diff --git a/staticlibs/tests/unit/Android.bp b/staticlibs/tests/unit/Android.bp
index 07a8200..21e8c64 100644
--- a/staticlibs/tests/unit/Android.bp
+++ b/staticlibs/tests/unit/Android.bp
@@ -30,7 +30,8 @@
         "//packages/modules/Connectivity/tests:__subpackages__",
         "//packages/modules/Connectivity/Tethering/tests:__subpackages__",
         "//packages/modules/NetworkStack/tests/integration",
-    ]
+    ],
+    lint: { strict_updatability_linting: true },
 }
 
 android_test {
@@ -46,4 +47,5 @@
     ],
     jarjar_rules: "jarjar-rules.txt",
     test_suites: ["device-tests"],
+    lint: { strict_updatability_linting: true },
 }
diff --git a/staticlibs/tests/unit/lint-baseline.xml b/staticlibs/tests/unit/lint-baseline.xml
deleted file mode 100644
index e3a6c1e..0000000
--- a/staticlibs/tests/unit/lint-baseline.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0">
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.app.AppOpsManager#noteOp`"
-        errorLine1="        when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="frameworks/libs/net/common/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java"
-            line="109"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.app.AppOpsManager#noteOp`"
-        errorLine1="        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="frameworks/libs/net/common/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java"
-            line="111"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.app.AppOpsManager#noteOp`"
-        errorLine1="        when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),"
-        errorLine2="                         ~~~~~~">
-        <location
-            file="frameworks/libs/net/common/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java"
-            line="114"
-            column="26"/>
-    </issue>
-
-    <issue
-        id="NewApi"
-        message="Call requires API level R (current min is 29): `android.net.NetworkCapabilities()`"
-        errorLine1="        val nc = NetworkCapabilities()"
-        errorLine2="                 ~~~~~~~~~~~~~~~~~~~~~">
-        <location
-            file="frameworks/libs/net/common/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt"
-            line="93"
-            column="18"/>
-    </issue>
-
-</issues>
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
index f4a7d10..649b30e 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTest.kt
@@ -18,85 +18,187 @@
 
 import android.util.Log
 import com.android.testutils.tryTest
-import kotlin.test.assertFailsWith
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
 import kotlin.test.fail
 
-private val TAG = CleanupTest::class.toString()
+private val TAG = CleanupTest::class.simpleName
 
 @RunWith(JUnit4::class)
 class CleanupTest {
     class TestException1 : Exception()
     class TestException2 : Exception()
+    class TestException3 : Exception()
 
     @Test
     fun testNotThrow() {
         var x = 1
-        tryTest {
+        val result = tryTest {
             x = 2
             Log.e(TAG, "Do nothing")
+            6
         } cleanup {
-            assert(x == 2)
+            assertTrue(x == 2)
             x = 3
             Log.e(TAG, "Do nothing")
         }
-        assert(x == 3)
+        assertTrue(x == 3)
+        assertTrue(result == 6)
     }
 
     @Test
     fun testThrowTry() {
         var x = 1
-        assertFailsWith<TestException1> {
+        val thrown = assertFailsWith<TestException1> {
             tryTest {
                 x = 2
                 throw TestException1()
                 x = 4
             } cleanup {
-                assert(x == 2)
+                assertTrue(x == 2)
                 x = 3
                 Log.e(TAG, "Do nothing")
             }
         }
-        assert(x == 3)
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+        assertTrue(x == 3)
     }
 
     @Test
     fun testThrowCleanup() {
         var x = 1
-        assertFailsWith<TestException2> {
+        val thrown = assertFailsWith<TestException2> {
             tryTest {
                 x = 2
                 Log.e(TAG, "Do nothing")
             } cleanup {
-                assert(x == 2)
+                assertTrue(x == 2)
                 x = 3
                 throw TestException2()
                 x = 4
             }
         }
-        assert(x == 3)
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+        assertTrue(x == 3)
     }
 
     @Test
     fun testThrowBoth() {
         var x = 1
-        try {
+        val thrown = assertFailsWith<TestException1> {
             tryTest {
                 x = 2
                 throw TestException1()
                 x = 3
             } cleanup {
-                assert(x == 2)
+                assertTrue(x == 2)
                 x = 4
                 throw TestException2()
                 x = 5
             }
-            fail("Expected failure with TestException1")
-        } catch (e: TestException1) {
-            assert(e.suppressedExceptions[0] is TestException2)
         }
-        assert(x == 4)
+        assertTrue(thrown.suppressedExceptions[0] is TestException2)
+        assertTrue(x == 4)
+    }
+
+    @Test
+    fun testReturn() {
+        val resultIfSuccess = 11
+        val resultIfException = 12
+        fun doTestReturn(crash: Boolean) = tryTest {
+            if (crash) throw RuntimeException() else resultIfSuccess
+        }.catch<RuntimeException> {
+            resultIfException
+        } cleanup {}
+
+        assertTrue(6 == tryTest { 6 } cleanup { Log.e(TAG, "tested") })
+        assertEquals(resultIfSuccess, doTestReturn(crash = false))
+        assertEquals(resultIfException, doTestReturn(crash = true))
+    }
+
+    @Test
+    fun testCatch() {
+        var x = 1
+        tryTest {
+            x = 2
+            throw TestException1()
+            x = 3
+        }.catch<TestException1> {
+            x = 4
+        }.catch<TestException2> {
+            x = 5
+        } cleanup {
+            assertTrue(x == 4)
+            x = 6
+        }
+        assertTrue(x == 6)
+    }
+
+    @Test
+    fun testNotCatch() {
+        var x = 1
+        assertFailsWith<TestException1> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            }.catch<TestException2> {
+                fail("Caught TestException2 instead of TestException1")
+            } cleanup {
+                assertTrue(x == 2)
+                x = 3
+            }
+        }
+        assertTrue(x == 3)
+    }
+
+    @Test
+    fun testThrowInCatch() {
+        var x = 1
+        val thrown = assertFailsWith<TestException2> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            }.catch<TestException1> {
+                x = 3
+                throw TestException2()
+            } cleanup {
+                assertTrue(x == 3)
+                x = 4
+            }
+        }
+        assertTrue(x == 4)
+        assertTrue(thrown.suppressedExceptions.isEmpty())
+    }
+
+    @Test
+    fun testMultipleCleanups() {
+        var x = 1
+        val thrown = assertFailsWith<TestException1> {
+            tryTest {
+                x = 2
+                throw TestException1()
+            } cleanupStep {
+                assertTrue(x == 2)
+                x = 3
+                throw TestException2()
+                x = 4
+            } cleanupStep {
+                assertTrue(x == 3)
+                x = 5
+                throw TestException3()
+                x = 6
+            } cleanup {
+                assertTrue(x == 5)
+                x = 7
+            }
+        }
+        assertEquals(2, thrown.suppressedExceptions.size)
+        assertTrue(thrown.suppressedExceptions[0] is TestException2)
+        assertTrue(thrown.suppressedExceptions[1] is TestException3)
+        assert(x == 7)
     }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
index ba4e679..8a13397 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CleanupTestJava.java
@@ -20,6 +20,7 @@
 import static com.android.testutils.MiscAsserts.assertThrows;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.util.Log;
 
@@ -31,18 +32,21 @@
     private static final String TAG = CleanupTestJava.class.getSimpleName();
     private static final class TestException1 extends Exception {}
     private static final class TestException2 extends Exception {}
+    private static final class TestException3 extends Exception {}
 
     @Test
     public void testNotThrow() {
         final AtomicInteger x = new AtomicInteger(1);
-        testAndCleanup(() -> {
+        final int a = testAndCleanup(() -> {
             x.compareAndSet(1, 2);
             Log.e(TAG, "Do nothing");
+            return 6;
         }, () -> {
                 x.compareAndSet(2, 3);
                 Log.e(TAG, "Do nothing");
             });
         assertEquals(3, x.get());
+        assertEquals(6, a);
     }
 
     @Test
@@ -91,4 +95,27 @@
         );
         assertEquals(3, x.get());
     }
+
+    @Test
+    public void testMultipleCleanups() {
+        final AtomicInteger x = new AtomicInteger(1);
+        final TestException1 exception = assertThrows(TestException1.class, () ->
+                testAndCleanup(() -> {
+                    x.compareAndSet(1, 2);
+                    throw new TestException1();
+                }, () -> {
+                        x.compareAndSet(2, 3);
+                        throw new TestException2();
+                    }, () -> {
+                        x.compareAndSet(3, 4);
+                        throw new TestException3();
+                    }, () -> {
+                        x.compareAndSet(4, 5);
+                    })
+        );
+        assertEquals(2, exception.getSuppressed().length);
+        assertTrue(exception.getSuppressed()[0] instanceof TestException2);
+        assertTrue(exception.getSuppressed()[1] instanceof TestException3);
+        assertEquals(5, x.get());
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
index 96648a5..911483a 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt
@@ -77,4 +77,13 @@
         assertFalse(CollectionUtils.contains(arrayOf("A", "B", "C"), "D"))
         assertFalse(CollectionUtils.contains(null, "A"))
     }
+
+    @Test
+    fun testTotal() {
+        assertEquals(10, CollectionUtils.total(longArrayOf(3, 6, 1)))
+        assertEquals(10, CollectionUtils.total(longArrayOf(6, 1, 3)))
+        assertEquals(10, CollectionUtils.total(longArrayOf(1, 3, 6)))
+        assertEquals(3, CollectionUtils.total(longArrayOf(1, 1, 1)))
+        assertEquals(0, CollectionUtils.total(null))
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/module/util/DnsPacketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
similarity index 100%
rename from staticlibs/tests/unit/src/com/android/module/util/DnsPacketTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketTest.java
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
new file mode 100644
index 0000000..48777ac
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/DnsPacketUtilsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 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 com.android.net.module.util.DnsPacketUtils.DnsRecordParser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsPacketUtilsTest {
+
+    /**
+     * Verifies that the compressed NAME field in the answer section of the DNS message is parsed
+     * successfully when name compression is permitted. Additionally, verifies that a
+     * {@link DnsPacket.ParseException} is thrown in a hypothetical scenario where name compression
+     * is not expected.
+     */
+    @Test
+    public void testParsingAnswerSectionNameCompressed() throws Exception {
+        final byte[] v4blobNameCompressedAnswer = new byte[] {
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                (byte) 0x81, (byte) 0x80, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x01, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                (byte) 0xc0, 0x0c, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        final int answerOffsetBytePosition = 32;
+        final ByteBuffer nameCompressedBuf = ByteBuffer.wrap(v4blobNameCompressedAnswer);
+
+        nameCompressedBuf.position(answerOffsetBytePosition);
+        assertThrows(DnsPacket.ParseException.class, () -> DnsRecordParser.parseName(
+                nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */false));
+
+        nameCompressedBuf.position(answerOffsetBytePosition);
+        String domainName = DnsRecordParser.parseName(
+                nameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */true);
+        assertEquals(domainName, "www.google.com");
+    }
+
+    /**
+     * Verifies that an uncompressed NAME field in the answer section of the DNS message is parsed
+     * successfully irrespective of whether name compression is permitted.
+     */
+    @Test
+    public void testParsingAnswerSectionNoNameCompression() throws Exception {
+        final byte[] v4blobNoNameCompression = new byte[] {
+                /* Header */
+                0x55, 0x66, /* Transaction ID */
+                (byte) 0x81, (byte) 0x80, /* Flags */
+                0x00, 0x01, /* Questions */
+                0x00, 0x01, /* Answer RRs */
+                0x00, 0x00, /* Authority RRs */
+                0x00, 0x00, /* Additional RRs */
+                /* Queries */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                /* Answers */
+                0x03, 0x77, 0x77, 0x77, 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6c, 0x65,
+                0x03, 0x63, 0x6f, 0x6d, 0x00, /* Name */
+                0x00, 0x01, /* Type */
+                0x00, 0x01, /* Class */
+                0x00, 0x00, 0x01, 0x2b, /* TTL */
+                0x00, 0x04, /* Data length */
+                (byte) 0xac, (byte) 0xd9, (byte) 0xa1, (byte) 0x84 /* Address */
+        };
+        final int answerOffsetBytePosition = 32;
+        final ByteBuffer notNameCompressedBuf = ByteBuffer.wrap(v4blobNoNameCompression);
+
+        notNameCompressedBuf.position(answerOffsetBytePosition);
+        String domainName = DnsRecordParser.parseName(
+                notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ true);
+        assertEquals(domainName, "www.google.com");
+
+        notNameCompressedBuf.position(answerOffsetBytePosition);
+        domainName = DnsRecordParser.parseName(
+                notNameCompressedBuf, /* depth= */ 0, /* isNameCompressionSupported= */ false);
+        assertEquals(domainName, "www.google.com");
+    }
+}
diff --git a/staticlibs/tests/unit/src/android/net/util/IpRangeTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java
similarity index 95%
rename from staticlibs/tests/unit/src/android/net/util/IpRangeTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java
index 677db69..20bbd4a 100644
--- a/staticlibs/tests/unit/src/android/net/util/IpRangeTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/IpRangeTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package com.android.net.module.util;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -22,14 +22,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.annotation.SuppressLint;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.IpRange;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -94,6 +93,7 @@
         }
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testConstructor() {
         IpRange r = new IpRange(new IpPrefix(IPV4_ADDR, 32));
@@ -121,6 +121,7 @@
         assertEquals(IPV6_RANGE_END, r.getEndAddr());
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testContainsRangeEqualRanges() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
@@ -131,6 +132,7 @@
         assertEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testContainsRangeSubset() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 64));
@@ -141,6 +143,7 @@
         assertNotEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testContainsRangeTruncatesLowerOrderBits() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 100));
@@ -151,6 +154,7 @@
         assertEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testContainsRangeSubsetSameStartAddr() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
@@ -161,6 +165,7 @@
         assertNotEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testContainsRangeOverlapping() {
         final IpRange r1 = new IpRange(new IpPrefix(address("2001:db9::"), 32));
@@ -171,6 +176,7 @@
         assertNotEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testOverlapsRangeEqualRanges() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
@@ -181,6 +187,7 @@
         assertEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testOverlapsRangeSubset() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 35));
@@ -191,6 +198,7 @@
         assertNotEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testOverlapsRangeDisjoint() {
         final IpRange r1 = new IpRange(new IpPrefix(IPV6_ADDR, 32));
@@ -201,6 +209,7 @@
         assertNotEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testOverlapsRangePartialOverlapLow() {
         final IpRange r1 = new IpRange(new IpPrefix(address("2001:db9::"), 32));
@@ -211,6 +220,7 @@
         assertNotEquals(r1, r2);
     }
 
+    @SuppressLint("NewApi")
     @Test
     public void testOverlapsRangePartialOverlapHigh() {
         final IpRange r1 = new IpRange(new IpPrefix(address("2001:db7::"), 32));
diff --git a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
similarity index 98%
rename from staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
index 45493bd..09f0490 100644
--- a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LinkPropertiesUtilsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package com.android.net.module.util;
 
 import static com.android.testutils.MiscAsserts.assertSameElements;
 
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.SuppressLint;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.LinkAddress;
@@ -32,7 +33,6 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.LinkPropertiesUtils;
 import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
 
@@ -47,6 +47,7 @@
 
 @RunWith(AndroidJUnit4.class)
 public final class LinkPropertiesUtilsTest {
+    @SuppressLint("NewApi")
     private static final IpPrefix PREFIX = new IpPrefix(toInetAddress("75.208.6.0"), 24);
     private static final InetAddress V4_ADDR = toInetAddress("75.208.6.1");
     private static final InetAddress V6_ADDR  = toInetAddress(
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
index 3fb1e92..84018a5 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
@@ -42,6 +42,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import androidx.annotation.RequiresApi;
+
 import com.android.testutils.DevSdkIgnoreRule;
 
 import org.junit.Assert;
@@ -56,6 +58,7 @@
 import java.util.HashMap;
 
 /** Unit tests for {@link LocationPermissionChecker}. */
+@RequiresApi(Build.VERSION_CODES.R)
 public class LocationPermissionCheckerTest {
     @Rule
     public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
diff --git a/staticlibs/tests/unit/src/android/net/util/MacAddressUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java
similarity index 96%
rename from staticlibs/tests/unit/src/android/net/util/MacAddressUtilsTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java
index 8988571..2550756 100644
--- a/staticlibs/tests/unit/src/android/net/util/MacAddressUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/MacAddressUtilsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package com.android.net.module.util;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -24,8 +24,6 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.MacAddressUtils;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/staticlibs/tests/unit/src/android/net/util/NetUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java
similarity index 97%
rename from staticlibs/tests/unit/src/android/net/util/NetUtilsTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java
index d523e14..902c18e 100644
--- a/staticlibs/tests/unit/src/android/net/util/NetUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetUtilsTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package com.android.net.module.util;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -27,8 +27,6 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.net.module.util.NetUtils;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
index 5f15c6a..256ea1e 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.net.module.util
 
+import android.annotation.TargetApi
 import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_BIP
 import android.net.NetworkCapabilities.NET_CAPABILITY_CBS
 import android.net.NetworkCapabilities.NET_CAPABILITY_EIMS
 import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
@@ -29,8 +31,10 @@
 import android.net.NetworkCapabilities.TRANSPORT_VPN
 import android.net.NetworkCapabilities.TRANSPORT_WIFI
 import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE
+import android.os.Build
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
 import com.android.net.module.util.NetworkCapabilitiesUtils.RESTRICTED_CAPABILITIES
 import com.android.net.module.util.NetworkCapabilitiesUtils.UNRESTRICTED_CAPABILITIES
 import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport
@@ -88,7 +92,9 @@
         assertTrue(bits contentEquals unpackBits(packedBits))
     }
 
-    @Test
+    // NetworkCapabilities constructor and Builder are not available until R. Mark TargetApi to
+    // ignore the linter error since it's used in only unit test.
+    @Test @TargetApi(Build.VERSION_CODES.R)
     fun testInferRestrictedCapability() {
         val nc = NetworkCapabilities()
         // Default capabilities don't have restricted capability.
@@ -106,6 +112,12 @@
         // as restricted when there is no any unrestricted capability.
         nc.removeCapability(NET_CAPABILITY_INTERNET)
         assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+        if (!SdkLevel.isAtLeastS()) return
+        // BIP deserves its specific test because it's the first capability over 30, meaning the
+        // shift will overflow
+        nc.removeCapability(NET_CAPABILITY_CBS)
+        nc.addCapability(NET_CAPABILITY_BIP)
+        assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
     }
 
     @Test
@@ -118,9 +130,17 @@
         assertEquals((1 shl NET_CAPABILITY_CBS).toLong() and RESTRICTED_CAPABILITIES,
                 (1 shl NET_CAPABILITY_CBS).toLong())
 
+        // verify BIP is also restricted
+        // BIP is not available in R and before, but the BIP constant is inlined so
+        // this test can still run on R.
+        assertEquals((1L shl NET_CAPABILITY_BIP) and RESTRICTED_CAPABILITIES,
+                (1L shl NET_CAPABILITY_BIP))
+
         // verify default is not restricted
         assertEquals((1 shl NET_CAPABILITY_INTERNET).toLong() and RESTRICTED_CAPABILITIES, 0)
 
+        assertTrue(RESTRICTED_CAPABILITIES > 0)
+
         // just to see
         assertEquals(RESTRICTED_CAPABILITIES and UNRESTRICTED_CAPABILITIES, 0)
     }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
new file mode 100644
index 0000000..2785ea9
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/NetworkStatsUtilsTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.net.module.util
+
+import android.net.NetworkStats
+import android.text.TextUtils
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class NetworkStatsUtilsTest {
+    @Test
+    fun testMultiplySafeByRational() {
+        // Verify basic cases that the method equals to a * b / c.
+        assertEquals(3 * 5 / 2, NetworkStatsUtils.multiplySafeByRational(3, 5, 2))
+
+        // Verify input with zeros.
+        assertEquals(0 * 7 / 3, NetworkStatsUtils.multiplySafeByRational(0, 7, 3))
+        assertEquals(7 * 0 / 3, NetworkStatsUtils.multiplySafeByRational(7, 0, 3))
+        assertEquals(0 * 0 / 1, NetworkStatsUtils.multiplySafeByRational(0, 0, 1))
+        assertEquals(0, NetworkStatsUtils.multiplySafeByRational(0, Long.MAX_VALUE, Long.MAX_VALUE))
+        assertEquals(0, NetworkStatsUtils.multiplySafeByRational(Long.MAX_VALUE, 0, Long.MAX_VALUE))
+        assertFailsWith<ArithmeticException> {
+            NetworkStatsUtils.multiplySafeByRational(7, 3, 0)
+        }
+        assertFailsWith<ArithmeticException> {
+            NetworkStatsUtils.multiplySafeByRational(0, 0, 0)
+        }
+
+        // Verify cases where a * b overflows.
+        assertEquals(101, NetworkStatsUtils.multiplySafeByRational(
+                101, Long.MAX_VALUE, Long.MAX_VALUE))
+        assertEquals(721, NetworkStatsUtils.multiplySafeByRational(
+                Long.MAX_VALUE, 721, Long.MAX_VALUE))
+        assertEquals(Long.MAX_VALUE, NetworkStatsUtils.multiplySafeByRational(
+                Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE))
+        assertFailsWith<ArithmeticException> {
+            NetworkStatsUtils.multiplySafeByRational(Long.MAX_VALUE, Long.MAX_VALUE, 0)
+        }
+    }
+
+    @Test
+    fun testConstrain() {
+        assertFailsWith<IllegalArgumentException> {
+            NetworkStatsUtils.constrain(5, 6, 3) // low > high
+        }
+        assertEquals(3, NetworkStatsUtils.constrain(5, 1, 3))
+        assertEquals(3, NetworkStatsUtils.constrain(3, 1, 3))
+        assertEquals(2, NetworkStatsUtils.constrain(2, 1, 3))
+        assertEquals(1, NetworkStatsUtils.constrain(1, 1, 3))
+        assertEquals(1, NetworkStatsUtils.constrain(0, 1, 3))
+
+        assertEquals(11, NetworkStatsUtils.constrain(15, 11, 11))
+        assertEquals(11, NetworkStatsUtils.constrain(11, 11, 11))
+        assertEquals(11, NetworkStatsUtils.constrain(1, 11, 11))
+    }
+
+    @Test
+    fun testBucketToEntry() {
+        val bucket = makeMockBucket(android.app.usage.NetworkStats.Bucket.UID_ALL,
+                android.app.usage.NetworkStats.Bucket.TAG_NONE,
+                android.app.usage.NetworkStats.Bucket.STATE_DEFAULT,
+                android.app.usage.NetworkStats.Bucket.METERED_YES,
+                android.app.usage.NetworkStats.Bucket.ROAMING_NO,
+                android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12)
+        val entry = NetworkStatsUtils.fromBucket(bucket)
+        val expectedEntry = NetworkStats.Entry(null /* IFACE_ALL */, NetworkStats.UID_ALL,
+            NetworkStats.SET_DEFAULT, NetworkStats.TAG_NONE, NetworkStats.METERED_YES,
+            NetworkStats.ROAMING_NO, NetworkStats.DEFAULT_NETWORK_ALL, 1024, 8, 2048, 12,
+            0 /* operations */)
+
+        // TODO: Use assertEquals once all downstreams accept null iface in
+        // NetworkStats.Entry#equals.
+        assertEntryEquals(expectedEntry, entry)
+    }
+
+    private fun makeMockBucket(
+        uid: Int,
+        tag: Int,
+        state: Int,
+        metered: Int,
+        roaming: Int,
+        defaultNetwork: Int,
+        rxBytes: Long,
+        rxPackets: Long,
+        txBytes: Long,
+        txPackets: Long
+    ): android.app.usage.NetworkStats.Bucket {
+        val ret: android.app.usage.NetworkStats.Bucket =
+                mock(android.app.usage.NetworkStats.Bucket::class.java)
+        doReturn(uid).`when`(ret).getUid()
+        doReturn(tag).`when`(ret).getTag()
+        doReturn(state).`when`(ret).getState()
+        doReturn(metered).`when`(ret).getMetered()
+        doReturn(roaming).`when`(ret).getRoaming()
+        doReturn(defaultNetwork).`when`(ret).getDefaultNetworkStatus()
+        doReturn(rxBytes).`when`(ret).getRxBytes()
+        doReturn(rxPackets).`when`(ret).getRxPackets()
+        doReturn(txBytes).`when`(ret).getTxBytes()
+        doReturn(txPackets).`when`(ret).getTxPackets()
+        return ret
+    }
+
+    /**
+     * Assert that the two {@link NetworkStats.Entry} are equals.
+     */
+    private fun assertEntryEquals(left: NetworkStats.Entry, right: NetworkStats.Entry) {
+        TextUtils.equals(left.iface, right.iface)
+        assertEquals(left.uid, right.uid)
+        assertEquals(left.set, right.set)
+        assertEquals(left.tag, right.tag)
+        assertEquals(left.metered, right.metered)
+        assertEquals(left.roaming, right.roaming)
+        assertEquals(left.defaultNetwork, right.defaultNetwork)
+        assertEquals(left.rxBytes, right.rxBytes)
+        assertEquals(left.rxPackets, right.rxPackets)
+        assertEquals(left.txBytes, right.txBytes)
+        assertEquals(left.txPackets, right.txPackets)
+        assertEquals(left.operations, right.operations)
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
new file mode 100644
index 0000000..8f9a1f9
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PacketBuilderTest.java
@@ -0,0 +1,532 @@
+/*
+ * 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.net.module.util;
+
+import static android.system.OsConstants.IPPROTO_IP;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
+
+import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
+import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK;
+import static com.android.net.module.util.NetworkStackConstants.TCP_HEADER_MIN_LEN;
+import static com.android.net.module.util.NetworkStackConstants.UDP_HEADER_LEN;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Ipv4Header;
+import com.android.net.module.util.structs.TcpHeader;
+import com.android.net.module.util.structs.UdpHeader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class PacketBuilderTest {
+    private static final MacAddress SRC_MAC = MacAddress.fromString("11:22:33:44:55:66");
+    private static final MacAddress DST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+    private static final Inet4Address IPV4_SRC_ADDR = addr("192.0.2.1");
+    private static final Inet4Address IPV4_DST_ADDR = addr("198.51.100.1");
+    private static final short SRC_PORT = 9876;
+    private static final short DST_PORT = 433;
+    private static final short SEQ_NO = 13579;
+    private static final short ACK_NO = 24680;
+    private static final byte TYPE_OF_SERVICE = 0;
+    private static final short ID = 27149;
+    private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0
+    private static final byte TIME_TO_LIVE = (byte) 0x40;
+    private static final short WINDOW = (short) 0x2000;
+    private static final short URGENT_POINTER = 0;
+    private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[] {
+            (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+    });
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0))
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                       type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0) /
+                //           b'\xde\xad\xbe\xef')
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0))
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x28,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x8c,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0xe5, (byte) 0xe5, (byte) 0x00, (byte) 0x00
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_TCPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                    tos=0, id=27149, flags='DF') /
+                //           scapy.TCP(sport=9876, dport=433, seq=13579, ack=24680,
+                //                     flags='A', window=8192, urgptr=0) /
+                //           b'\xde\xad\xbe\xef')
+                // IPv4 header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x2c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x06, (byte) 0xe4, (byte) 0x88,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // TCP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x00, (byte) 0x35, (byte) 0x0b,
+                (byte) 0x00, (byte) 0x00, (byte) 0x60, (byte) 0x68,
+                (byte) 0x50, (byte) 0x10, (byte) 0x20, (byte) 0x00,
+                (byte) 0x48, (byte) 0x44, (byte) 0x00, (byte) 0x00,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                 type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433))
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62
+            };
+
+    private static final byte[] TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.Ether(src="11:22:33:44:55:66", dst="aa:bb:cc:dd:ee:ff",
+                //                 type='IPv4') /
+                //           scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433) /
+                //           b'\xde\xad\xbe\xef')
+                // Ether header
+                (byte) 0xaa, (byte) 0xbb, (byte) 0xcc, (byte) 0xdd,
+                (byte) 0xee, (byte) 0xff, (byte) 0x11, (byte) 0x22,
+                (byte) 0x33, (byte) 0x44, (byte) 0x55, (byte) 0x66,
+                (byte) 0x08, (byte) 0x00,
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433))
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x1c,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x8d,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x08, (byte) 0xeb, (byte) 0x62
+            };
+
+    private static final byte[] TEST_PACKET_IPV4HDR_UDPHDR_DATA =
+            new byte[] {
+                // packet = (scapy.IP(src="192.0.2.1", dst="198.51.100.1",
+                //                 tos=0, id=27149, flags='DF') /
+                //           scapy.UDP(sport=9876, dport=433) /
+                //           b'\xde\xad\xbe\xef')
+                // IP header
+                (byte) 0x45, (byte) 0x00, (byte) 0x00, (byte) 0x20,
+                (byte) 0x6a, (byte) 0x0d, (byte) 0x40, (byte) 0x00,
+                (byte) 0x40, (byte) 0x11, (byte) 0xe4, (byte) 0x89,
+                (byte) 0xc0, (byte) 0x00, (byte) 0x02, (byte) 0x01,
+                (byte) 0xc6, (byte) 0x33, (byte) 0x64, (byte) 0x01,
+                // UDP header
+                (byte) 0x26, (byte) 0x94, (byte) 0x01, (byte) 0xb1,
+                (byte) 0x00, (byte) 0x0c, (byte) 0x4d, (byte) 0xbd,
+                // Data
+                (byte) 0xde, (byte) 0xad, (byte) 0xbe, (byte) 0xef
+            };
+
+    /**
+     * Build an IPv4 packet which has ether header, IPv4 header, TCP/UDP header and data.
+     * The ethernet header and data are optional. Note that both source mac address and
+     * destination mac address are required for ethernet header.
+     *
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |                Layer 2 header (EthernetHeader)                | (optional)
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |                  Layer 3 header (Ipv4Header)                  |
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |           Layer 4 header (TcpHeader, UdpHeader)               |
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     * |                          Payload                              | (optional)
+     * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+     *
+     * @param srcMac source MAC address. used by L2 ether header.
+     * @param dstMac destination MAC address. used by L2 ether header.
+     * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+     * @param payload the payload.
+     */
+    @NonNull
+    private ByteBuffer buildIpv4Packet(@Nullable final MacAddress srcMac,
+            @Nullable final MacAddress dstMac, final int l4proto,
+            @Nullable final ByteBuffer payload)
+            throws Exception {
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            fail("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        final boolean hasEther = (srcMac != null && dstMac != null);
+        final int payloadLen = (payload == null) ? 0 : payload.limit();
+        final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, l4proto,
+                payloadLen);
+        final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+
+        if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4);
+        packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+                TIME_TO_LIVE, (byte) l4proto, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+        if (l4proto == IPPROTO_TCP) {
+            packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+                    TCPHDR_ACK, WINDOW, URGENT_POINTER);
+        } else if (l4proto == IPPROTO_UDP) {
+            packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT);
+        }
+        if (payload != null) {
+            buffer.put(payload);
+            // in case data might be reused by caller, restore the position and
+            // limit of bytebuffer.
+            payload.clear();
+        }
+
+        return packetBuilder.finalizePacket();
+    }
+
+    /**
+     * Check ethernet header.
+     *
+     * @param actual the packet to check.
+     */
+    private void checkEtherHeader(final ByteBuffer actual) {
+        final EthernetHeader eth = Struct.parse(EthernetHeader.class, actual);
+        assertEquals(SRC_MAC, eth.srcMac);
+        assertEquals(DST_MAC, eth.dstMac);
+        assertEquals(ETHER_TYPE_IPV4, eth.etherType);
+    }
+
+    /**
+     * Check IPv4 header.
+     *
+     * @param l4proto the layer 4 protocol. support either IPPROTO_TCP or IPPROTO_UDP.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkIpv4Header(final int l4proto, final boolean hasData,
+            final ByteBuffer actual) {
+        if (l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP) {
+            fail("Unsupported layer 4 protocol " + l4proto);
+        }
+
+        final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, actual);
+        assertEquals(Ipv4Header.IPHDR_VERSION_IHL, ipv4Header.vi);
+        assertEquals(TYPE_OF_SERVICE, ipv4Header.tos);
+        assertEquals(ID, ipv4Header.id);
+        assertEquals(FLAGS_AND_FRAGMENT_OFFSET, ipv4Header.flagsAndFragmentOffset);
+        assertEquals(TIME_TO_LIVE, ipv4Header.ttl);
+        assertEquals(IPV4_SRC_ADDR, ipv4Header.srcIp);
+        assertEquals(IPV4_DST_ADDR, ipv4Header.dstIp);
+
+        final int dataLength = hasData ? DATA.limit() : 0;
+        if (l4proto == IPPROTO_TCP) {
+            assertEquals(IPV4_HEADER_MIN_LEN + TCP_HEADER_MIN_LEN + dataLength,
+                    ipv4Header.totalLength);
+            assertEquals((byte) IPPROTO_TCP, ipv4Header.protocol);
+            assertEquals(hasData ? (short) 0xe488 : (short) 0xe48c, ipv4Header.checksum);
+        } else if (l4proto == IPPROTO_UDP) {
+            assertEquals(IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + dataLength,
+                    ipv4Header.totalLength);
+            assertEquals((byte) IPPROTO_UDP, ipv4Header.protocol);
+            assertEquals(hasData ? (short) 0xe489 : (short) 0xe48d, ipv4Header.checksum);
+        }
+    }
+
+    /**
+     * Check TCPv4 packet.
+     *
+     * @param hasEther true if the packet has ether header; false otherwise.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkTcpv4Packet(final boolean hasEther, final boolean hasData,
+            final ByteBuffer actual) {
+        if (hasEther) {
+            checkEtherHeader(actual);
+        }
+        checkIpv4Header(IPPROTO_TCP, hasData, actual);
+
+        final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, actual);
+        assertEquals(SRC_PORT, tcpHeader.srcPort);
+        assertEquals(DST_PORT, tcpHeader.dstPort);
+        assertEquals(SEQ_NO, tcpHeader.seq);
+        assertEquals(ACK_NO, tcpHeader.ack);
+        assertEquals((short) 0x5010 /* offset=5(*4bytes), control bits=ACK */,
+                tcpHeader.dataOffsetAndControlBits);
+        assertEquals(WINDOW, tcpHeader.window);
+        assertEquals(hasData ? (short) 0x4844 : (short) 0xe5e5, tcpHeader.checksum);
+        assertEquals(URGENT_POINTER, tcpHeader.urgentPointer);
+
+        if (hasData) {
+            assertEquals(0xdeadbeef, actual.getInt());
+        }
+    }
+
+    /**
+     * Check UDPv4 packet.
+     *
+     * @param hasEther true if the packet has ether header; false otherwise.
+     * @param hasData true if the packet has data payload; false otherwise.
+     * @param actual the packet to check.
+     */
+    private void checkUdpv4Packet(final boolean hasEther, final boolean hasData,
+            final ByteBuffer actual) {
+        if (hasEther) {
+            checkEtherHeader(actual);
+        }
+        checkIpv4Header(IPPROTO_UDP, hasData, actual);
+
+        final UdpHeader udpHeader = Struct.parse(UdpHeader.class, actual);
+        assertEquals(SRC_PORT, udpHeader.srcPort);
+        assertEquals(DST_PORT, udpHeader.dstPort);
+        final int dataLength = hasData ? DATA.limit() : 0;
+        assertEquals(UDP_HEADER_LEN + dataLength, udpHeader.length);
+        assertEquals(hasData ? (short) 0x4dbd : (short) 0xeb62, udpHeader.checksum);
+
+        if (hasData) {
+            assertEquals(0xdeadbeef, actual.getInt());
+        }
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4Tcp() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, null /* data */);
+        checkTcpv4Packet(true /* hasEther */, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4TcpData() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_TCP, DATA);
+        checkTcpv4Packet(true /* hasEther */, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_TCPHDR_DATA,
+                packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4Tcp() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+                IPPROTO_TCP, null /* data */);
+        checkTcpv4Packet(false /* hasEther */, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4TcpData() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+                IPPROTO_TCP, DATA);
+        checkTcpv4Packet(false /* hasEther */, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_TCPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4Udp() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_UDP, null /* data */);
+        checkUdpv4Packet(true /* hasEther */, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketEtherIPv4UdpData() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(SRC_MAC, DST_MAC, IPPROTO_UDP, DATA);
+        checkUdpv4Packet(true /* hasEther */, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_ETHERHDR_IPV4HDR_UDPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4Udp() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+                IPPROTO_UDP, null /*data*/);
+        checkUdpv4Packet(false /* hasEther */, false /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR, packet.array());
+    }
+
+    @Test
+    public void testBuildPacketIPv4UdpData() throws Exception {
+        final ByteBuffer packet = buildIpv4Packet(null /* srcMac */, null /* dstMac */,
+                IPPROTO_UDP, DATA);
+        checkUdpv4Packet(false /* hasEther */, true /* hasData */, packet);
+        assertArrayEquals(TEST_PACKET_IPV4HDR_UDPHDR_DATA, packet.array());
+    }
+
+    @Test
+    public void testFinalizePacketWithoutIpv4Header() throws Exception {
+        final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
+                IPPROTO_TCP, 0 /* payloadLen */);
+        final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+        packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+                TCPHDR_ACK, WINDOW, URGENT_POINTER);
+        assertThrows("java.io.IOException: Packet is missing IPv4 header", IOException.class,
+                () -> packetBuilder.finalizePacket());
+    }
+
+    @Test
+    public void testFinalizePacketWithoutL4Header() throws Exception {
+        final ByteBuffer buffer = PacketBuilder.allocate(false /* hasEther */, IPPROTO_IP,
+                IPPROTO_TCP, 0 /* payloadLen */);
+        final PacketBuilder packetBuilder = new PacketBuilder(buffer);
+        packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+                TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR);
+        assertThrows("java.io.IOException: Packet is missing neither TCP nor UDP header",
+                IOException.class, () -> packetBuilder.finalizePacket());
+    }
+
+    @Test
+    public void testWriteL2HeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class,
+                () -> packetBuilder.writeL2Header(SRC_MAC, DST_MAC, (short) ETHER_TYPE_IPV4));
+    }
+
+    @Test
+    public void testWriteIpv4HeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class,
+                () -> packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET,
+                        TIME_TO_LIVE, (byte) IPPROTO_TCP, IPV4_SRC_ADDR, IPV4_DST_ADDR));
+    }
+
+    @Test
+    public void testWriteTcpHeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class,
+                () -> packetBuilder.writeTcpHeader(SRC_PORT, DST_PORT, SEQ_NO, ACK_NO,
+                        TCPHDR_ACK, WINDOW, URGENT_POINTER));
+    }
+
+    @Test
+    public void testWriteUdpHeaderToInsufficientBuffer() throws Exception {
+        final PacketBuilder packetBuilder = new PacketBuilder(ByteBuffer.allocate(1));
+        assertThrows(IOException.class, () -> packetBuilder.writeUdpHeader(SRC_PORT, DST_PORT));
+    }
+
+    private static Inet4Address addr(String addr) {
+        return (Inet4Address) InetAddresses.parseNumericAddress(addr);
+    }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
index 6da5e7d..1b6cbcb 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/PermissionUtilsTest.kt
@@ -18,22 +18,28 @@
 
 import android.Manifest.permission.NETWORK_STACK
 import android.content.Context
+import android.content.pm.PackageManager
 import android.content.pm.PackageManager.PERMISSION_DENIED
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK
 import androidx.test.filters.SmallTest
 import androidx.test.runner.AndroidJUnit4
 import com.android.net.module.util.PermissionUtils.checkAnyPermissionOf
+import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
 import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission
 import com.android.net.module.util.PermissionUtils.enforceNetworkStackPermissionOr
-import com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf
+import com.android.net.module.util.PermissionUtils.enforceSystemFeature
+import org.junit.Assert
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
+import kotlin.test.assertEquals
 import kotlin.test.assertFailsWith
 
 /** Tests for PermissionUtils */
@@ -43,6 +49,12 @@
     private val TEST_PERMISSION1 = "android.permission.TEST_PERMISSION1"
     private val TEST_PERMISSION2 = "android.permission.TEST_PERMISSION2"
     private val context = mock(Context::class.java)
+    private val packageManager = mock(PackageManager::class.java)
+
+    @Before
+    fun setup() {
+        doReturn(packageManager).`when`(context).packageManager
+    }
 
     @Test
     fun testEnforceAnyPermissionOf() {
@@ -90,4 +102,26 @@
         assertFailsWith<SecurityException>("Expect fail but permission granted.") {
             enforceNetworkStackPermissionOr(context, TEST_PERMISSION2) }
     }
+
+    private fun mockHasSystemFeature(featureName: String, hasFeature: Boolean) {
+        doReturn(hasFeature).`when`(packageManager)
+                .hasSystemFeature(ArgumentMatchers.eq(featureName))
+    }
+
+    @Test
+    fun testEnforceSystemFeature() {
+        val systemFeature = "test.system.feature"
+        val exceptionMessage = "test exception message"
+        mockHasSystemFeature(featureName = systemFeature, hasFeature = false)
+        val e = assertFailsWith<UnsupportedOperationException>("Should fail without feature") {
+            enforceSystemFeature(context, systemFeature, exceptionMessage) }
+        assertEquals(exceptionMessage, e.message)
+
+        mockHasSystemFeature(featureName = systemFeature, hasFeature = true)
+        try {
+            enforceSystemFeature(context, systemFeature, "")
+        } catch (e: UnsupportedOperationException) {
+            Assert.fail("Exception should have not been thrown with system feature enabled")
+        }
+    }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index eabc14b..4e46210 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.annotation.SuppressLint;
 import android.net.InetAddresses;
 import android.net.IpPrefix;
 import android.net.MacAddress;
@@ -453,6 +454,7 @@
         }
     }
 
+    @SuppressLint("NewApi")
     private void verifyPrefixByteArrayParsing(final PrefixMessage msg) throws Exception {
         // The original PREF64 option message has just 12 bytes for prefix byte array
         // (Highest 96 bits of the Prefix), copyOf pads the 128-bits IPv6 address with
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
index 5d446b8..9db63db 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java
@@ -47,7 +47,7 @@
     private static final String RTM_NEWLINK_HEX =
             "64000000100000000000000000000000"   // struct nlmsghr
             + "000001001E0000000210000000000000" // struct ifinfo
-            + "0A000300776C616E30000000"         // IFLA_IFNAME
+            + "0A000300776C616E30000000"         // IFLA_IFNAME(wlan0)
             + "08000D00B80B0000"                 // IFLA_PROTINFO
             + "0500100002000000"                 // IFLA_OPERSTATE
             + "0500110001000000"                 // IFLA_LINKMODE
@@ -88,12 +88,47 @@
         assertTrue(linkMsg.getInterfaceName().equals("wlan0"));
     }
 
+    /**
+     * Example:
+     * # adb shell ip tunnel add トン0 mode sit local any remote 8.8.8.8
+     * # adb shell ip link show | grep トン
+     * 33: トン0@NONE: <POINTOPOINT,NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group
+     *     default qlen 1000
+     *
+     * IFLA_IFNAME attribute: \x0c\x00\x03\x00\xe3\x83\x88\xe3\x83\xb3\x30\x00
+     *     length: 0x000c
+     *     type: 0x0003
+     *     value: \xe3\x83\x88\xe3\x83\xb3\x30\x00
+     *            ト (\xe3\x83\x88)
+     *            ン (\xe3\x83\xb3)
+     *            0  (\x30)
+     *            null terminated (\x00)
+     */
+    private static final String RTM_NEWLINK_UTF8_HEX =
+            "34000000100000000000000000000000"   // struct nlmsghr
+            + "000001001E0000000210000000000000" // struct ifinfo
+            + "08000400DC050000"                 // IFLA_MTU
+            + "0A00010092C3E3C9374E0000"         // IFLA_ADDRESS
+            + "0C000300E38388E383B33000";        // IFLA_IFNAME(トン0)
+
+    @Test
+    public void testParseRtmNewLink_utf8Ifname() {
+        final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_UTF8_HEX);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);  // For testing.
+        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE);
+        assertNotNull(msg);
+        assertTrue(msg instanceof RtNetlinkLinkMessage);
+        final RtNetlinkLinkMessage linkMsg = (RtNetlinkLinkMessage) msg;
+
+        assertTrue(linkMsg.getInterfaceName().equals("トン0"));
+    }
+
     private static final String RTM_NEWLINK_PACK_HEX =
             "34000000100000000000000000000000"   // struct nlmsghr
             + "000001001E0000000210000000000000" // struct ifinfo
             + "08000400DC050000"                 // IFLA_MTU
             + "0A00010092C3E3C9374E0000"         // IFLA_ADDRESS
-            + "0A000300776C616E30000000";        // IFLA_IFNAME
+            + "0A000300776C616E30000000";        // IFLA_IFNAME(wlan0)
 
     @Test
     public void testPackRtmNewLink() {
@@ -117,7 +152,7 @@
             + "0500100002000000"                 // IFLA_OPERSTATE
             + "0800010092C3E3C9"                 // IFLA_ADDRESS(truncated)
             + "0500110001000000"                 // IFLA_LINKMODE
-            + "0A000300776C616E30000000"         // IFLA_IFNAME
+            + "0A000300776C616E30000000"         // IFLA_IFNAME(wlan0)
             + "08000400DC050000";                // IFLA_MTU
 
     @Test
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java
index 57248ea..beed838 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
+import android.annotation.SuppressLint;
 import android.net.IpPrefix;
 
 import androidx.test.filters.SmallTest;
@@ -51,6 +52,7 @@
         return prefixBytes;
     }
 
+    @SuppressLint("NewApi")
     private static IpPrefix prefix(String addrString, int prefixLength) throws Exception {
         return new IpPrefix(InetAddress.getByName(addrString), prefixLength);
     }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
index 72e179b..af3fac2 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlAttrTest.java
@@ -16,6 +16,7 @@
 
 package com.android.net.module.util.netlink;
 
+import static com.android.net.module.util.netlink.RtNetlinkAddressMessage.IFA_FLAGS;
 import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_ADDRESS;
 import static com.android.net.module.util.netlink.RtNetlinkLinkMessage.IFLA_IFNAME;
 
@@ -35,6 +36,7 @@
 public class StructNlAttrTest {
     private static final MacAddress TEST_MAC_ADDRESS = MacAddress.fromString("00:11:22:33:44:55");
     private static final String TEST_INTERFACE_NAME = "wlan0";
+    private static final int TEST_ADDR_FLAGS = 0x80;
 
     @Test
     public void testGetValueAsMacAddress() {
@@ -65,4 +67,29 @@
         final String str2 = attr2.getValueAsString();
         assertEquals(str2, TEST_INTERFACE_NAME);
     }
+
+    @Test
+    public void testGetValueAsIntger() {
+        final StructNlAttr attr1 = new StructNlAttr(IFA_FLAGS, TEST_ADDR_FLAGS);
+        final Integer integer1 = attr1.getValueAsInteger();
+        final int int1 = attr1.getValueAsInt(0x08 /* default value */);
+        assertEquals(integer1, new Integer(TEST_ADDR_FLAGS));
+        assertEquals(int1, TEST_ADDR_FLAGS);
+
+        // Malformed attribute.
+        final byte[] malformed_int = new byte[] { (byte) 0x0, (byte) 0x0, (byte) 0x80, };
+        final StructNlAttr attr2 = new StructNlAttr(IFA_FLAGS, malformed_int);
+        final Integer integer2 = attr2.getValueAsInteger();
+        final int int2 = attr2.getValueAsInt(0x08 /* default value */);
+        assertNull(integer2);
+        assertEquals(int2, 0x08 /* default value */);
+
+        // Null attribute value.
+        final byte[] null_int = null;
+        final StructNlAttr attr3 = new StructNlAttr(IFA_FLAGS, null_int);
+        final Integer integer3 = attr3.getValueAsInteger();
+        final int int3 = attr3.getValueAsInt(0x08 /* default value */);
+        assertNull(integer3);
+        assertEquals(int3, 0x08 /* default value */);
+    }
 }
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 73ddd4b..133c983 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -38,28 +38,38 @@
         "net-utils-device-common-netlink",
         "modules-utils-build_system",
     ],
+    lint: { strict_updatability_linting: true },
 }
 
 java_library {
-  // Consider using net-tests-utils instead if writing device code.
-  // That library has a lot more useful tools into it for users that
-  // work on Android and includes this lib.
-  name: "net-tests-utils-host-device-common",
-  srcs: [
-      "hostdevice/**/*.java",
-      "hostdevice/**/*.kt",
-  ],
-  host_supported: true,
-  visibility: [
-      "//frameworks/libs/net/common/tests:__subpackages__",
-      "//frameworks/libs/net/client-libs/tests:__subpackages__",
-  ],
-  libs: [
-      "jsr305",
-  ],
-  static_libs: [
-      "kotlin-test"
-  ]
+    // Consider using net-tests-utils instead if writing device code.
+    // That library has a lot more useful tools into it for users that
+    // work on Android and includes this lib.
+    name: "net-tests-utils-host-device-common",
+    srcs: [
+        "hostdevice/**/*.java",
+        "hostdevice/**/*.kt",
+    ],
+    host_supported: true,
+    visibility: [
+        "//frameworks/libs/net/common/tests:__subpackages__",
+        "//frameworks/libs/net/client-libs/tests:__subpackages__",
+    ],
+    // There are downstream branches using an old version of Kotlin
+    // that used to reserve the right to make breaking changes to the
+    // Result type and disallowed returning an instance of it.
+    // Later versions allowed this and there was never a change,
+    // so no matter the version returning Result is always fine,
+    // but on sc-mainline-prod the compiler rejects it without
+    // the following flag.
+    kotlincflags: ["-Xallow-result-return-type"],
+    libs: [
+        "jsr305",
+    ],
+    static_libs: [
+        "kotlin-test"
+    ],
+    lint: { strict_updatability_linting: true },
 }
 
 java_test_host {
diff --git a/staticlibs/testutils/app/connectivitychecker/Android.bp b/staticlibs/testutils/app/connectivitychecker/Android.bp
index 79a4343..58f22db 100644
--- a/staticlibs/testutils/app/connectivitychecker/Android.bp
+++ b/staticlibs/testutils/app/connectivitychecker/Android.bp
@@ -30,4 +30,5 @@
         "net-tests-utils",
     ],
     host_required: ["net-tests-utils-host-common"],
+    lint: { strict_updatability_linting: true },
 }
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
new file mode 100644
index 0000000..814a75b
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/ContextUtils.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("ContextUtils")
+
+package com.android.testutils
+
+import android.content.Context
+import android.os.UserHandle
+import org.mockito.AdditionalAnswers.delegatesTo
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.util.function.BiConsumer
+
+// Helper function so that Java doesn't have to pass a method that returns Unit
+fun mockContextAsUser(context: Context, functor: BiConsumer<Context, UserHandle>? = null) =
+    mockContextAsUser(context) { c, h -> functor?.accept(c, h) }
+
+/**
+ * Return a context with assigned user and delegate to original context.
+ *
+ * @param context the mock context to set up createContextAsUser on. After this function
+ *                is called, client code can call createContextAsUser and expect a context that
+ *                will return the correct user and userId.
+ *
+ * @param functor additional code to run on the created context-as-user instances, for example to
+ *                set up further mocks on these contexts.
+ */
+fun mockContextAsUser(context: Context, functor: ((Context, UserHandle) -> Unit)? = null) {
+    doAnswer { invocation ->
+        val asUserContext = mock(Context::class.java, delegatesTo<Context>(context))
+        val user = invocation.arguments[0] as UserHandle
+        val userId = user.identifier
+        doReturn(user).`when`(asUserContext).user
+        doReturn(userId).`when`(asUserContext).userId
+        functor?.let { it(asUserContext, user) }
+        asUserContext
+    }.`when`(context).createContextAsUser(any(UserHandle::class.java), anyInt() /* flags */)
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
new file mode 100644
index 0000000..40fb773
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkAgent.kt
@@ -0,0 +1,200 @@
+/*
+ * 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 android.content.Context
+import android.net.KeepalivePacketData
+import android.net.LinkProperties
+import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.QosFilter
+import android.net.Uri
+import android.os.Looper
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
+import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Assert.assertArrayEquals
+
+// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
+// requests filed by the test and should never match normal internet requests. 70 is the default
+// score of Ethernet networks, it's as good a value as any other.
+private const val TEST_NETWORK_SCORE = 70
+
+private class Provider(context: Context, looper: Looper) :
+            NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
+
+public open class TestableNetworkAgent(
+    context: Context,
+    looper: Looper,
+    val nc: NetworkCapabilities,
+    val lp: LinkProperties,
+    conf: NetworkAgentConfig
+) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
+        nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+
+    val DEFAULT_TIMEOUT_MS = 5000L
+
+    val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+    sealed class CallbackEntry {
+        object OnBandwidthUpdateRequested : CallbackEntry()
+        object OnNetworkUnwanted : CallbackEntry()
+        data class OnAddKeepalivePacketFilter(
+            val slot: Int,
+            val packet: KeepalivePacketData
+        ) : CallbackEntry()
+        data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry()
+        data class OnStartSocketKeepalive(
+            val slot: Int,
+            val interval: Int,
+            val packet: KeepalivePacketData
+        ) : CallbackEntry()
+        data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry()
+        data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry()
+        object OnAutomaticReconnectDisabled : CallbackEntry()
+        data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry()
+        data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
+        object OnNetworkCreated : CallbackEntry()
+        object OnNetworkDestroyed : CallbackEntry()
+        data class OnRegisterQosCallback(
+            val callbackId: Int,
+            val filter: QosFilter
+        ) : CallbackEntry()
+        data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry()
+    }
+
+    override fun onBandwidthUpdateRequested() {
+        history.add(OnBandwidthUpdateRequested)
+    }
+
+    override fun onNetworkUnwanted() {
+        history.add(OnNetworkUnwanted)
+    }
+
+    override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
+        history.add(OnAddKeepalivePacketFilter(slot, packet))
+    }
+
+    override fun onRemoveKeepalivePacketFilter(slot: Int) {
+        history.add(OnRemoveKeepalivePacketFilter(slot))
+    }
+
+    override fun onStartSocketKeepalive(
+        slot: Int,
+        interval: Duration,
+        packet: KeepalivePacketData
+    ) {
+        history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
+    }
+
+    override fun onStopSocketKeepalive(slot: Int) {
+        history.add(OnStopSocketKeepalive(slot))
+    }
+
+    override fun onSaveAcceptUnvalidated(accept: Boolean) {
+        history.add(OnSaveAcceptUnvalidated(accept))
+    }
+
+    override fun onAutomaticReconnectDisabled() {
+        history.add(OnAutomaticReconnectDisabled)
+    }
+
+    override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
+        history.add(OnSignalStrengthThresholdsUpdated(thresholds))
+    }
+
+    fun expectSignalStrengths(thresholds: IntArray? = intArrayOf()) {
+        expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+            assertArrayEquals(thresholds, it.thresholds)
+        }
+    }
+
+    override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
+        history.add(OnRegisterQosCallback(qosCallbackId, filter))
+    }
+
+    override fun onQosCallbackUnregistered(qosCallbackId: Int) {
+        history.add(OnUnregisterQosCallback(qosCallbackId))
+    }
+
+    override fun onValidationStatus(status: Int, uri: Uri?) {
+        history.add(OnValidationStatus(status, uri))
+    }
+
+    override fun onNetworkCreated() {
+        history.add(OnNetworkCreated)
+    }
+
+    override fun onNetworkDestroyed() {
+        history.add(OnNetworkDestroyed)
+    }
+
+    // Expects the initial validation event that always occurs immediately after registering
+    // a NetworkAgent whose network does not require validation (which test networks do
+    // not, since they lack the INTERNET capability). It always contains the default argument
+    // for the URI.
+    fun expectValidationBypassedStatus() = expectCallback<OnValidationStatus>().let {
+        assertEquals(it.status, VALID_NETWORK)
+        // The returned Uri is parsed from the empty string, which means it's an
+        // instance of the (private) Uri.StringUri. There are no real good ways
+        // to check this, the least bad is to just convert it to a string and
+        // make sure it's empty.
+        assertEquals("", it.uri.toString())
+    }
+
+    inline fun <reified T : CallbackEntry> expectCallback(): T {
+        val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+        assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+        return foundCallback
+    }
+
+    inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+        val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+        assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+        assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+    }
+
+    inline fun <reified T : CallbackEntry> eventuallyExpect() =
+            history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
+                assertNotNull(it, "Callback ${T::class} not received")
+    } as T
+
+    fun assertNoCallback() {
+        assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS),
+                "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms")
+        assertNull(history.peek())
+    }
+}
\ No newline at end of file
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 7d851f1..c2b5a5c 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -330,13 +330,13 @@
 
     fun expectBlockedStatusCallback(blocked: Boolean, net: Network, tmt: Long = defaultTimeoutMs) {
         expectCallback<BlockedStatus>(net, tmt).also {
-            assertEquals(it.blocked, blocked, "Unexpected blocked status ${it.blocked}")
+            assertEquals(blocked, it.blocked, "Unexpected blocked status ${it.blocked}")
         }
     }
 
     fun expectBlockedStatusCallback(blocked: Int, net: Network, tmt: Long = defaultTimeoutMs) {
         expectCallback<BlockedStatusInt>(net, tmt).also {
-            assertEquals(it.blocked, blocked, "Unexpected blocked status ${it.blocked}")
+            assertEquals(blocked, it.blocked, "Unexpected blocked status ${it.blocked}")
         }
     }
 
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
index be5c9b2..4a7b351 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
@@ -17,6 +17,7 @@
 package com.android.testutils
 
 import android.net.netstats.provider.NetworkStatsProvider
+import android.util.Log
 import com.android.net.module.util.ArrayTrackRecord
 import kotlin.test.assertEquals
 import kotlin.test.assertTrue
@@ -43,23 +44,28 @@
         data class OnSetAlert(val quotaBytes: Long) : CallbackType()
     }
 
+    private val TAG = this::class.simpleName
     val history = ArrayTrackRecord<CallbackType>().newReadHead()
     // See ReadHead#mark
     val mark get() = history.mark
 
     override fun onRequestStatsUpdate(token: Int) {
+        Log.d(TAG, "onRequestStatsUpdate $token")
         history.add(CallbackType.OnRequestStatsUpdate(token))
     }
 
     override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+        Log.d(TAG, "onSetWarningAndLimit $iface $warningBytes $limitBytes")
         history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes))
     }
 
     override fun onSetLimit(iface: String, quotaBytes: Long) {
+        Log.d(TAG, "onSetLimit $iface $quotaBytes")
         history.add(CallbackType.OnSetLimit(iface, quotaBytes))
     }
 
     override fun onSetAlert(quotaBytes: Long) {
+        Log.d(TAG, "onSetAlert $quotaBytes")
         history.add(CallbackType.OnSetAlert(quotaBytes))
     }
 
diff --git a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
index 769d980..45783d8 100644
--- a/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
+++ b/staticlibs/testutils/hostdevice/com/android/testutils/Cleanup.kt
@@ -19,6 +19,7 @@
 package com.android.testutils
 
 import com.android.testutils.ExceptionUtils.ThrowingRunnable
+import com.android.testutils.ExceptionUtils.ThrowingSupplier
 import javax.annotation.CheckReturnValue
 
 /**
@@ -45,51 +46,80 @@
  * to the standard try{}finally{}, if both throws, the construct throws the exception that happened
  * in tryTest{} rather than the one that happened in cleanup{}.
  *
- * Kotlin usage is as try{}finally{} :
+ * Kotlin usage is as try{}finally{}, but with multiple finally{} blocks :
  * tryTest {
  *   testing code
+ * } cleanupStep {
+ *   cleanup code 1
+ * } cleanupStep {
+ *   cleanup code 2
  * } cleanup {
- *   cleanup code
+ *   cleanup code 3
+ * }
+ * Catch blocks can be added with the following syntax :
+ * tryTest {
+ *   testing code
+ * }.catch<ExceptionType> { it ->
+ *   do something to it
  * }
  *
- * Java doesn't allow this kind of syntax, so instead a function taking 2 lambdas is provided.
+ * Java doesn't allow this kind of syntax, so instead a function taking lambdas is provided.
  * testAndCleanup(() -> {
  *   testing code
  * }, () -> {
- *   cleanup code
+ *   cleanup code 1
+ * }, () -> {
+ *   cleanup code 2
  * });
  */
-class ExceptionCleanupBlock(val originalException: Exception?) {
-    inline infix fun cleanup(block: () -> Unit) {
-        try {
-            block()
-            if (null != originalException) throw originalException
-        } catch (e: Exception) {
-            if (null == originalException) {
-                throw e
-            } else {
-                originalException.addSuppressed(e)
-                throw originalException
-            }
-        }
-    }
-}
 
 @CheckReturnValue
-inline fun tryTest(block: () -> Unit): ExceptionCleanupBlock {
-    try {
-        block()
-    } catch (e: Exception) {
-        return ExceptionCleanupBlock(e)
+fun <T> tryTest(block: () -> T) = TryExpr(
+        try {
+            Result.success(block())
+        } catch (e: Throwable) {
+            Result.failure(e)
+        })
+
+// Some downstream branches have an older kotlin that doesn't know about value classes.
+// TODO : Change this to "value class" when aosp no longer merges into such branches.
+@Suppress("INLINE_CLASS_DEPRECATED")
+inline class TryExpr<T>(val result: Result<T>) {
+    inline infix fun <reified E : Throwable> catch(block: (E) -> T): TryExpr<T> {
+        val originalException = result.exceptionOrNull()
+        if (originalException !is E) return this
+        return TryExpr(try {
+            Result.success(block(originalException))
+        } catch (e: Exception) {
+            Result.failure(e)
+        })
     }
-    return ExceptionCleanupBlock(null)
+
+    @CheckReturnValue
+    inline infix fun cleanupStep(block: () -> Unit): TryExpr<T> {
+        try {
+            block()
+        } catch (e: Throwable) {
+            val originalException = result.exceptionOrNull()
+            return TryExpr(if (null == originalException) {
+                Result.failure(e)
+            } else {
+                originalException.addSuppressed(e)
+                Result.failure(originalException)
+            })
+        }
+        return this
+    }
+
+    inline infix fun cleanup(block: () -> Unit): T = cleanupStep(block).result.getOrThrow()
 }
 
 // Java support
-fun testAndCleanup(tryBlock: ThrowingRunnable, cleanupBlock: ThrowingRunnable) {
-    tryTest {
-        tryBlock.run()
-    } cleanup {
-        cleanupBlock.run()
-    }
+fun <T> testAndCleanup(tryBlock: ThrowingSupplier<T>, vararg cleanupBlock: ThrowingRunnable): T {
+    return cleanupBlock.fold(tryTest { tryBlock.get() }) { previousExpr, nextCleanup ->
+        previousExpr.cleanupStep { nextCleanup.run() }
+    }.cleanup {}
+}
+fun testAndCleanup(tryBlock: ThrowingRunnable, vararg cleanupBlock: ThrowingRunnable) {
+    return testAndCleanup(ThrowingSupplier { tryBlock.run() }, *cleanupBlock)
 }