Merge "Add javadoc descriptions for family of methods"
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index 1fe6c2c..904e8c6 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -328,6 +328,38 @@
     lint: { strict_updatability_linting: true },
 }
 
+// Limited set of utilities for use by service-connectivity-mdns-standalone-build-test, to make sure
+// the mDNS code can build with only system APIs.
+// The mDNS code is platform code so it should use framework-annotations-lib, contrary to apps that
+// should use sdk_version: "system_current" and only androidx.annotation_annotation. But this build
+// rule verifies that the mDNS code can be built into apps, if code transformations are applied to
+// the annotations.
+// When using "system_current", framework annotations are not available; they would appear as
+// package-private as they are marked as such in the system_current stubs. So build against
+// core_platform and add the stubs manually in "libs". See http://b/147773144#comment7.
+java_library {
+    name: "net-utils-device-common-mdns-standalone-build-test",
+    // Build against core_platform and add the stub libraries manually in "libs", as annotations
+    // are already included in android_system_stubs_current but package-private, so
+    // "framework-annotations-lib" needs to be manually included before
+    // "android_system_stubs_current" (b/272392042)
+    sdk_version: "core_platform",
+    srcs: [
+        "device/com/android/net/module/util/FdEventsReader.java",
+        "device/com/android/net/module/util/HexDump.java",
+        "device/com/android/net/module/util/SharedLog.java",
+        "framework/com/android/net/module/util/ByteUtils.java",
+        "framework/com/android/net/module/util/CollectionUtils.java",
+        "framework/com/android/net/module/util/LinkPropertiesUtils.java",
+    ],
+    libs: [
+        "framework-annotations-lib",
+        "android_system_stubs_current",
+        "androidx.annotation_annotation",
+    ],
+    visibility: ["//packages/modules/Connectivity/service-t"],
+}
+
 // Use a filegroup and not a library for telephony sources, as framework-annotations cannot be
 // included either (some annotations would be duplicated on the bootclasspath).
 filegroup {
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 c07cec0..2829b92 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkAddressMessage.java
@@ -17,6 +17,7 @@
 package com.android.net.module.util.netlink;
 
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK;
+import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE;
 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
 
 import android.system.OsConstants;
@@ -58,12 +59,20 @@
     @Nullable
     private StructIfacacheInfo mIfacacheInfo;
 
-    private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) {
+    @VisibleForTesting
+    public RtNetlinkAddressMessage(@NonNull final StructNlMsgHdr header,
+            @NonNull final StructIfaddrMsg ifaddrMsg,
+            @NonNull final InetAddress ipAddress,
+            @Nullable final StructIfacacheInfo structIfacacheInfo,
+            int flags) {
         super(header);
-        mIfaddrmsg = null;
-        mIpAddress = null;
-        mIfacacheInfo = null;
-        mFlags = 0;
+        mIfaddrmsg = ifaddrMsg;
+        mIpAddress = ipAddress;
+        mIfacacheInfo = structIfacacheInfo;
+        mFlags = flags;
+    }
+    private RtNetlinkAddressMessage(@NonNull StructNlMsgHdr header) {
+        this(header, null, null, null, 0);
     }
 
     public int getFlags() {
@@ -160,7 +169,7 @@
 
         final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
         nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWADDR;
-        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+        nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
         nlmsghdr.nlmsg_seq = seqNo;
 
         final RtNetlinkAddressMessage msg = new RtNetlinkAddressMessage(nlmsghdr);
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
index 9196feb..2802726 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructIfaddrMsg.java
@@ -18,6 +18,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.net.module.util.Struct;
 import com.android.net.module.util.Struct.Field;
@@ -49,7 +50,8 @@
     @Field(order = 4, type = Type.S32)
     public final int index;
 
-    StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) {
+    @VisibleForTesting
+    public StructIfaddrMsg(short family, short prefixLen, short flags, short scope, int index) {
         this.family = family;
         this.prefixLen = prefixLen;
         this.flags = flags;
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
index ea018cf..cbd895d 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java
@@ -43,12 +43,22 @@
  */
 public class StructInetDiagMsg {
     public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20;
-    private static final int IDIAG_SOCK_ID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4;
-    private static final int IDIAG_UID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4
-            + StructInetDiagSockId.STRUCT_SIZE + 12;
-    public int idiag_uid;
+    public short idiag_family;
+    public short idiag_state;
+    public short idiag_timer;
+    public short idiag_retrans;
     @NonNull
     public StructInetDiagSockId id;
+    public long idiag_expires;
+    public long idiag_rqueue;
+    public long idiag_wqueue;
+    // Use int for uid since other code use int for uid and uid fits to int
+    public int idiag_uid;
+    public long idiag_inode;
+
+    private static short unsignedByte(byte b) {
+        return (short) (b & 0xFF);
+    }
 
     /**
      * Parse inet diag netlink message from buffer.
@@ -59,21 +69,35 @@
             return null;
         }
         StructInetDiagMsg struct = new StructInetDiagMsg();
-        final byte family = byteBuffer.get();
-        byteBuffer.position(IDIAG_SOCK_ID_OFFSET);
-        struct.id = StructInetDiagSockId.parse(byteBuffer, family);
+        struct.idiag_family = unsignedByte(byteBuffer.get());
+        struct.idiag_state = unsignedByte(byteBuffer.get());
+        struct.idiag_timer = unsignedByte(byteBuffer.get());
+        struct.idiag_retrans = unsignedByte(byteBuffer.get());
+        struct.id = StructInetDiagSockId.parse(byteBuffer, struct.idiag_family);
         if (struct.id == null) {
             return null;
         }
-        struct.idiag_uid = byteBuffer.getInt(IDIAG_UID_OFFSET);
+        struct.idiag_expires = Integer.toUnsignedLong(byteBuffer.getInt());
+        struct.idiag_rqueue = Integer.toUnsignedLong(byteBuffer.getInt());
+        struct.idiag_wqueue = Integer.toUnsignedLong(byteBuffer.getInt());
+        struct.idiag_uid = byteBuffer.getInt();
+        struct.idiag_inode = Integer.toUnsignedLong(byteBuffer.getInt());
         return struct;
     }
 
     @Override
     public String toString() {
         return "StructInetDiagMsg{ "
-                + "idiag_uid{" + idiag_uid + "}, "
+                + "idiag_family{" + idiag_family + "}, "
+                + "idiag_state{" + idiag_state + "}, "
+                + "idiag_timer{" + idiag_timer + "}, "
+                + "idiag_retrans{" + idiag_retrans + "}, "
                 + "id{" + id + "}, "
+                + "idiag_expires{" + idiag_expires + "}, "
+                + "idiag_rqueue{" + idiag_rqueue + "}, "
+                + "idiag_wqueue{" + idiag_wqueue + "}, "
+                + "idiag_uid{" + idiag_uid + "}, "
+                + "idiag_inode{" + idiag_inode + "}, "
                 + "}";
     }
 }
diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
index 648a020..1e728ea 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java
@@ -80,7 +80,7 @@
      * Parse inet diag socket id from buffer.
      */
     @Nullable
-    public static StructInetDiagSockId parse(final ByteBuffer byteBuffer, final byte family) {
+    public static StructInetDiagSockId parse(final ByteBuffer byteBuffer, final short family) {
         if (byteBuffer.remaining() < STRUCT_SIZE) {
             return null;
         }
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
index 5a09c40..dbf79dc 100644
--- a/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPdOption.java
@@ -50,15 +50,15 @@
     public static final int LENGTH = 12; // option length excluding IA_PD options
 
     @Field(order = 0, type = Type.S16)
-    public short code;
+    public final short code;
     @Field(order = 1, type = Type.S16)
-    public short length;
+    public final short length;
     @Field(order = 2, type = Type.U32)
-    public long id;
+    public final long id;
     @Field(order = 3, type = Type.U32)
-    public long t1;
+    public final long t1;
     @Field(order = 4, type = Type.U32)
-    public long t2;
+    public final long t2;
 
     IaPdOption(final short code, final short length, final long id, final long t1,
             final long t2) {
diff --git a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
index 1ac21ff..cd974e6 100644
--- a/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
+++ b/staticlibs/device/com/android/net/module/util/structs/IaPrefixOption.java
@@ -55,17 +55,17 @@
     public static final int LENGTH = 25; // option length excluding IAprefix-options
 
     @Field(order = 0, type = Type.S16)
-    public short code;
+    public final short code;
     @Field(order = 1, type = Type.S16)
-    public short length;
+    public final short length;
     @Field(order = 2, type = Type.U32)
-    public long preferred;
+    public final long preferred;
     @Field(order = 3, type = Type.U32)
-    public long valid;
+    public final long valid;
     @Field(order = 4, type = Type.U8)
-    public short prefixLen;
+    public final short prefixLen;
     @Field(order = 5, type = Type.ByteArray, arraysize = 16)
-    public byte[] prefix;
+    public final byte[] prefix;
 
     IaPrefixOption(final short code, final short length, final long preferred,
             final long valid, final short prefixLen, final byte[] prefix) {
diff --git a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
index 1d88d6e..aa2dd4c 100644
--- a/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/staticlibs/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -208,6 +208,9 @@
      */
     public static final int INFINITE_LEASE = 0xffffffff;
     public static final int DHCP4_CLIENT_PORT = 68;
+    // The maximum length of a DHCP packet that can be constructed.
+    public static final int DHCP_MAX_LENGTH = 1500;
+    public static final int DHCP_MAX_OPTION_LEN = 255;
 
     /**
      * DHCPv6 constants.
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
new file mode 100644
index 0000000..9b38dee
--- /dev/null
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfClassic.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+// Accept the full packet
+#define BPF_ACCEPT BPF_STMT(BPF_RET | BPF_K, 0xFFFFFFFF)
+
+// Reject the packet
+#define BPF_REJECT BPF_STMT(BPF_RET | BPF_K, 0)
+
+// *TWO* instructions: compare and if equal jump over the reject statement
+#define BPF2_REJECT_IF_NOT_EQUAL(v) \
+	BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 1, 0), \
+	BPF_REJECT
+
+// 8-bit load relative to start of link layer (mac/ethernet) header.
+#define BPF_LOAD_MAC_RELATIVE_U8(ofs) \
+	BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
+
+// Big/Network Endian 16-bit load relative to start of link layer (mac/ethernet) header.
+#define BPF_LOAD_MAC_RELATIVE_BE16(ofs) \
+	BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
+
+// Big/Network Endian 32-bit load relative to start of link layer (mac/ethernet) header.
+#define BPF_LOAD_MAC_RELATIVE_BE32(ofs) \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (__u32)SKF_LL_OFF + (ofs))
+
+// 8-bit load relative to start of network (IPv4/IPv6) header.
+#define BPF_LOAD_NET_RELATIVE_U8(ofs) \
+	BPF_STMT(BPF_LD | BPF_B | BPF_ABS, (__u32)SKF_NET_OFF + (ofs))
+
+// Big/Network Endian 16-bit load relative to start of network (IPv4/IPv6) header.
+#define BPF_LOAD_NET_RELATIVE_BE16(ofs) \
+	BPF_STMT(BPF_LD | BPF_H | BPF_ABS, (__u32)SKF_NET_OFF + (ofs))
+
+// Big/Network Endian 32-bit load relative to start of network (IPv4/IPv6) header.
+#define BPF_LOAD_NET_RELATIVE_BE32(ofs) \
+	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (__u32)SKF_NET_OFF + (ofs))
+
+#define field_sizeof(struct_type,field) sizeof(((struct_type *)0)->field)
+
+// 8-bit load from IPv4 header field.
+#define BPF_LOAD_IPV4_U8(field) \
+	BPF_LOAD_NET_RELATIVE_U8(({ \
+	  _Static_assert(field_sizeof(struct iphdr, field) == 1, "field of wrong size"); \
+	  offsetof(iphdr, field); \
+	}))
+
+// Big/Network Endian 16-bit load from IPv4 header field.
+#define BPF_LOAD_IPV4_BE16(field) \
+	BPF_LOAD_NET_RELATIVE_BE16(({ \
+	  _Static_assert(field_sizeof(struct iphdr, field) == 2, "field of wrong size"); \
+	  offsetof(iphdr, field); \
+	}))
+
+// Big/Network Endian 32-bit load from IPv4 header field.
+#define BPF_LOAD_IPV4_BE32(field) \
+	BPF_LOAD_NET_RELATIVE_BE32(({ \
+	  _Static_assert(field_sizeof(struct iphdr, field) == 4, "field of wrong size"); \
+	  offsetof(iphdr, field); \
+	}))
+
+// 8-bit load from IPv6 header field.
+#define BPF_LOAD_IPV6_U8(field) \
+	BPF_LOAD_NET_RELATIVE_U8(({ \
+	  _Static_assert(field_sizeof(struct ipv6hdr, field) == 1, "field of wrong size"); \
+	  offsetof(ipv6hdr, field); \
+	}))
+
+// Big/Network Endian 16-bit load from IPv6 header field.
+#define BPF_LOAD_IPV6_BE16(field) \
+	BPF_LOAD_NET_RELATIVE_BE16(({ \
+	  _Static_assert(field_sizeof(struct ipv6hdr, field) == 2, "field of wrong size"); \
+	  offsetof(ipv6hdr, field); \
+	}))
+
+// Big/Network Endian 32-bit load from IPv6 header field.
+#define BPF_LOAD_IPV6_BE32(field) \
+	BPF_LOAD_NET_RELATIVE_BE32(({ \
+	  _Static_assert(field_sizeof(struct ipv6hdr, field) == 4, "field of wrong size"); \
+	  offsetof(ipv6hdr, field); \
+	}))
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
index e2cb676..99c7a91 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfUtils.h
@@ -33,17 +33,26 @@
 namespace android {
 namespace bpf {
 
+// See kernel's net/core/sock_diag.c __sock_gen_cookie()
+// the implementation of which guarantees 0 will never be returned,
+// primarily because 0 is used to mean not yet initialized,
+// and socket cookies are only assigned on first fetch.
 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.
+    if (getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len)) {
+        // Failure is almost certainly either EBADF or ENOTSOCK
+        const int err = errno;
+        ALOGE("Failed to get socket cookie: %s\n", strerror(err));
+        errno = err;
+        return NONEXISTENT_COOKIE;
+    }
+    if (cookie_len != sizeof(sock_cookie)) {
+        // This probably cannot actually happen, but...
+        ALOGE("Failed to get socket cookie: len %d != 8\n", cookie_len);
+        errno = 523; // EBADCOOKIE: kernel internal, seems reasonable enough...
         return NONEXISTENT_COOKIE;
     }
     return sock_cookie;
@@ -54,21 +63,22 @@
     // 4.9 kernels. The kernel code of socket release on pf_key socket will
     // explicitly call synchronize_rcu() which is exactly what we need.
     //
-    // Linux 4.14/4.19/5.4/5.10/5.15 (and 5.18) still have this same behaviour.
+    // Linux 4.14/4.19/5.4/5.10/5.15/6.1 (and 6.3-rc5) still have this same behaviour.
     // see net/key/af_key.c: pfkey_release() -> synchronize_rcu()
-    int pfSocket = socket(AF_KEY, SOCK_RAW | SOCK_CLOEXEC, PF_KEY_V2);
+    // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/key/af_key.c?h=v6.3-rc5#n185
+    const 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;
+        const int err = errno;
+        ALOGE("create PF_KEY socket failed: %s", strerror(err));
+        return -err;
     }
 
     // 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;
+        const int err = errno;
+        ALOGE("failed to close the PF_KEY socket: %s", strerror(err));
+        return -err;
     }
     return 0;
 }
@@ -79,10 +89,8 @@
             .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));
-    }
+    const int res = setrlimit(RLIMIT_MEMLOCK, &limit);
+    if (res) ALOGE("Failed to set the default MEMLOCK rlimit: %s", strerror(errno));
     return res;
 }
 
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index 36865f3..0300b5e 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -305,6 +305,7 @@
 
 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 int (*bpf_probe_read_user_str)(void* dst, int size, const void* unsafe_ptr) = (void*) BPF_FUNC_probe_read_user_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;
diff --git a/staticlibs/native/ip_checksum/checksum.c b/staticlibs/native/ip_checksum/checksum.c
index 04217a7..5641fad 100644
--- a/staticlibs/native/ip_checksum/checksum.c
+++ b/staticlibs/native/ip_checksum/checksum.c
@@ -32,20 +32,16 @@
  *   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;
+    while (len >= 2) {
+        current += *data_16;
         data_16++;
-        left -= 2;
+        len -= 2;
     }
-    if (left) {
-        checksum += *(uint8_t*)data_16;
-    }
+    if (len) current += *(uint8_t*)data_16;  // assumes little endian!
 
-    return checksum;
+    return current;
 }
 
 /* function: ip_checksum_fold
@@ -54,9 +50,8 @@
  *   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);
-    }
+    temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
+    temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF);
     return temp_sum;
 }
 
@@ -75,12 +70,7 @@
  *   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);
+    return ip_checksum_finish(ip_checksum_add(0xFFFF, data, len));
 }
 
 /* function: ipv6_pseudo_header_checksum
@@ -92,7 +82,6 @@
 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));
@@ -109,11 +98,8 @@
  *   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);
-
+    uint16_t temp_protocol = htons(ip->protocol);
+    uint16_t temp_length = htons(len);
     uint32_t current = 0;
 
     current = ip_checksum_add(current, &(ip->saddr), sizeof(uint32_t));
@@ -135,7 +121,7 @@
     // 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_sum = ip_checksum_fold(new_hdr_sum + checksum);
     uint16_t folded_old = ip_checksum_fold(old_hdr_sum);
     if (folded_sum > folded_old) {
         return ~(folded_sum - folded_old);
diff --git a/staticlibs/native/ip_checksum/checksum.h b/staticlibs/native/ip_checksum/checksum.h
index 868217c..87393c9 100644
--- a/staticlibs/native/ip_checksum/checksum.h
+++ b/staticlibs/native/ip_checksum/checksum.h
@@ -12,11 +12,8 @@
  * 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__
+#pragma once
 
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
@@ -30,5 +27,3 @@
 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/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
index c7e2a4d..6837c83 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
@@ -221,19 +221,36 @@
         assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msgExt);
     }
 
-    // Hexadecimal representation of InetDiagReqV2 request.
-    private static final String INET_DIAG_MSG_HEX =
+    private void assertNlMsgHdr(StructNlMsgHdr hdr, short type, short flags, int seq, int pid) {
+        assertNotNull(hdr);
+        assertEquals(type, hdr.nlmsg_type);
+        assertEquals(flags, hdr.nlmsg_flags);
+        assertEquals(seq, hdr.nlmsg_seq);
+        assertEquals(pid, hdr.nlmsg_pid);
+    }
+
+    private void assertInetDiagSockId(StructInetDiagSockId sockId,
+            InetSocketAddress locSocketAddress, InetSocketAddress remSocketAddress,
+            int ifIndex, long cookie) {
+        assertEquals(locSocketAddress, sockId.locSocketAddress);
+        assertEquals(remSocketAddress, sockId.remSocketAddress);
+        assertEquals(ifIndex, sockId.ifIndex);
+        assertEquals(cookie, sockId.cookie);
+    }
+
+    // Hexadecimal representation of InetDiagMessage
+    private static final String INET_DIAG_MSG_HEX1 =
             // struct nlmsghdr
             "58000000" +     // length = 88
             "1400" +         // type = SOCK_DIAG_BY_FAMILY
             "0200" +         // flags = NLM_F_MULTI
             "00000000" +     // seqno
-            "f5220000" +     // pid (0 == kernel)
+            "f5220000" +     // pid
             // struct inet_diag_msg
             "0a" +           // family = AF_INET6
-            "01" +           // idiag_state
-            "00" +           // idiag_timer
-            "00" +           // idiag_retrans
+            "01" +           // idiag_state = 1
+            "02" +           // idiag_timer = 2
+            "ff" +           // idiag_retrans = 255
                 // inet_diag_sockid
                 "a817" +     // idiag_sport = 43031
                 "960f" +     // idiag_dport = 38415
@@ -241,39 +258,113 @@
                 "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
                 "07000000" + // idiag_if = 7
                 "5800000000000000" + // idiag_cookie = 88
-            "00000000" +     // idiag_expires
-            "00000000" +     // idiag_rqueue
-            "00000000" +     // idiag_wqueue
-            "a3270000" +     // idiag_uid
-            "A57E1900";      // idiag_inode
+            "04000000" +     // idiag_expires = 4
+            "05000000" +     // idiag_rqueue = 5
+            "06000000" +     // idiag_wqueue = 6
+            "a3270000" +     // idiag_uid = 10147
+            "a57e19f0";      // idiag_inode = 4028202661
+
+    private void assertInetDiagMsg1(final NetlinkMessage msg) {
+        assertNotNull(msg);
+
+        assertTrue(msg instanceof InetDiagMessage);
+        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+
+        assertNlMsgHdr(inetDiagMsg.getHeader(),
+                NetlinkConstants.SOCK_DIAG_BY_FAMILY,
+                StructNlMsgHdr.NLM_F_MULTI,
+                0    /* seq */,
+                8949 /* pid */);
+
+        assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family);
+        assertEquals(1, inetDiagMsg.inetDiagMsg.idiag_state);
+        assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_timer);
+        assertEquals(255, inetDiagMsg.inetDiagMsg.idiag_retrans);
+        assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::1"), 43031),
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::2"), 38415),
+                7  /* ifIndex */,
+                88 /* cookie */);
+        assertEquals(4, inetDiagMsg.inetDiagMsg.idiag_expires);
+        assertEquals(5, inetDiagMsg.inetDiagMsg.idiag_rqueue);
+        assertEquals(6, inetDiagMsg.inetDiagMsg.idiag_wqueue);
+        assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
+        assertEquals(4028202661L, inetDiagMsg.inetDiagMsg.idiag_inode);
+    }
+
+    // Hexadecimal representation of InetDiagMessage
+    private static final String INET_DIAG_MSG_HEX2 =
+            // struct nlmsghdr
+            "58000000" +     // length = 88
+            "1400" +         // type = SOCK_DIAG_BY_FAMILY
+            "0200" +         // flags = NLM_F_MULTI
+            "00000000" +     // seqno
+            "f5220000" +     // pid
+            // struct inet_diag_msg
+            "0a" +           // family = AF_INET6
+            "02" +           // idiag_state = 2
+            "10" +           // idiag_timer = 16
+            "20" +           // idiag_retrans = 32
+                // inet_diag_sockid
+                "a845" +     // idiag_sport = 43077
+                "01bb" +     // idiag_dport = 443
+                "20010db8000000000000000000000003" + // idiag_src = 2001:db8::3
+                "20010db8000000000000000000000004" + // idiag_dst = 2001:db8::4
+                "08000000" + // idiag_if = 8
+                "6300000000000000" + // idiag_cookie = 99
+            "30000000" +     // idiag_expires = 48
+            "40000000" +     // idiag_rqueue = 64
+            "50000000" +     // idiag_wqueue = 80
+            "39300000" +     // idiag_uid = 12345
+            "851a0000";      // idiag_inode = 6789
+
+    private void assertInetDiagMsg2(final NetlinkMessage msg) {
+        assertNotNull(msg);
+
+        assertTrue(msg instanceof InetDiagMessage);
+        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
+
+        assertNlMsgHdr(inetDiagMsg.getHeader(),
+                NetlinkConstants.SOCK_DIAG_BY_FAMILY,
+                StructNlMsgHdr.NLM_F_MULTI,
+                0    /* seq */,
+                8949 /* pid */);
+
+        assertEquals(AF_INET6, inetDiagMsg.inetDiagMsg.idiag_family);
+        assertEquals(2, inetDiagMsg.inetDiagMsg.idiag_state);
+        assertEquals(16, inetDiagMsg.inetDiagMsg.idiag_timer);
+        assertEquals(32, inetDiagMsg.inetDiagMsg.idiag_retrans);
+        assertInetDiagSockId(inetDiagMsg.inetDiagMsg.id,
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::3"), 43077),
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2001:db8::4"), 443),
+                8  /* ifIndex */,
+                99 /* cookie */);
+        assertEquals(48, inetDiagMsg.inetDiagMsg.idiag_expires);
+        assertEquals(64, inetDiagMsg.inetDiagMsg.idiag_rqueue);
+        assertEquals(80, inetDiagMsg.inetDiagMsg.idiag_wqueue);
+        assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid);
+        assertEquals(6789, inetDiagMsg.inetDiagMsg.idiag_inode);
+    }
+
     private static final byte[] INET_DIAG_MSG_BYTES =
-            HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false);
+            HexEncoding.decode(INET_DIAG_MSG_HEX1.toCharArray(), false);
 
     @Test
     public void testParseInetDiagResponse() throws Exception {
         final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES);
         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
-        final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG);
-        assertNotNull(msg);
+        assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+    }
 
-        assertTrue(msg instanceof InetDiagMessage);
-        final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg;
-        assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
-        final StructInetDiagSockId sockId = inetDiagMsg.inetDiagMsg.id;
-        assertEquals(43031, sockId.locSocketAddress.getPort());
-        assertEquals(InetAddresses.parseNumericAddress("2001:db8::1"),
-                sockId.locSocketAddress.getAddress());
-        assertEquals(38415, sockId.remSocketAddress.getPort());
-        assertEquals(InetAddresses.parseNumericAddress("2001:db8::2"),
-                sockId.remSocketAddress.getAddress());
-        assertEquals(7, sockId.ifIndex);
-        assertEquals(88, sockId.cookie);
 
-        final StructNlMsgHdr hdr = inetDiagMsg.getHeader();
-        assertNotNull(hdr);
-        assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type);
-        assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags);
-        assertEquals(0, hdr.nlmsg_seq);
-        assertEquals(8949, hdr.nlmsg_pid);
+    private static final byte[] INET_DIAG_MSG_BYTES_MULTIPLE =
+            HexEncoding.decode((INET_DIAG_MSG_HEX1 + INET_DIAG_MSG_HEX2).toCharArray(), false);
+
+    @Test
+    public void testParseInetDiagResponseMultiple() {
+        final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES_MULTIPLE);
+        byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        assertInetDiagMsg1(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+        assertInetDiagMsg2(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
     }
 }
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
index f845eb4..99d96b5 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkAddressMessageTest.java
@@ -144,7 +144,7 @@
                 // struct nlmsghdr
                 "48000000" +    // length = 72
                 "1400" +        // type = 20 (RTM_NEWADDR)
-                "0500" +        // flags = NLM_F_ACK | NLM_F_REQUEST
+                "0501" +        // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE
                 "01000000" +    // seqno = 1
                 "00000000" +    // pid = 0 (send to kernel)
                 // struct IfaddrMsg
@@ -195,7 +195,7 @@
                 // struct nlmsghdr
                 "48000000" +    // length = 72
                 "1400" +        // type = 20 (RTM_NEWADDR)
-                "0500" +        // flags = NLM_F_ACK | NLM_F_REQUEST
+                "0501" +        // flags = NLM_F_ACK | NLM_F_REQUEST | NLM_F_REPLACE
                 "01000000" +    // seqno = 1
                 "00000000" +    // pid = 0 (send to kernel)
                 // struct IfaddrMsg
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt
new file mode 100644
index 0000000..d7961a0
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatExternalPacketForwarder.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import java.io.FileDescriptor
+import java.net.InetAddress
+
+/**
+ * A class that forwards packets from the external {@link TestNetworkInterface} to the internal
+ * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail.
+ */
+class NatExternalPacketForwarder(
+    srcFd: FileDescriptor,
+    mtu: Int,
+    dstFd: FileDescriptor,
+    extAddr: InetAddress,
+    natMap: PacketBridge.NatMap
+) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) {
+
+    /**
+     * Rewrite addresses, ports and fix up checksums for packets received on the external
+     * interface.
+     *
+     * Incoming response from external interface which is being forwarded to the internal
+     * interface with translated address, e.g. 1.2.3.4:80 -> 8.8.8.8:1234
+     * will be translated into 8.8.8.8:80 -> 192.168.1.1:5678.
+     *
+     * For packets that are not an incoming response, do not forward them to the
+     * internal interface.
+     */
+    override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) {
+        val (addrPos, addrLen) = getAddressPositionAndLength(version)
+
+        // TODO: support one external address per ip version.
+        val extAddrBuf = mExtAddr.address
+        if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch")
+
+        // Get internal address by port.
+        val transportOffset =
+            if (version == 4) PacketReflector.IPV4_HEADER_LENGTH
+            else PacketReflector.IPV6_HEADER_LENGTH
+        val dstPort = getPortAt(buf, transportOffset + DESTINATION_PORT_OFFSET)
+        val intAddrInfo = synchronized(mNatMap) { mNatMap.fromExternalPort(dstPort) }
+        // No mapping, skip. This usually happens if the connection is initiated directly on
+        // the external interface, e.g. DNS64 resolution, network validation, etc.
+        if (intAddrInfo == null) return
+
+        val intAddrBuf = intAddrInfo.address.address
+        val intPort = intAddrInfo.port
+
+        // Copy the original destination to into the source address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + i] = buf[addrPos + addrLen + i]
+        }
+
+        // Copy the internal address into the destination address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + addrLen + i] = intAddrBuf[i]
+        }
+
+        // Copy the internal port into the destination port.
+        setPortAt(intPort, buf, transportOffset + DESTINATION_PORT_OFFSET)
+
+        // Fix IP and Transport layer checksum.
+        fixPacketChecksum(buf, len, version, proto.toByte())
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt
new file mode 100644
index 0000000..fa39d19
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatInternalPacketForwarder.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import java.io.FileDescriptor
+import java.net.InetAddress
+
+/**
+ * A class that forwards packets from the internal {@link TestNetworkInterface} to the external
+ * {@link TestNetworkInterface} with NAT. See {@link NatPacketForwarderBase} for detail.
+ */
+class NatInternalPacketForwarder(
+    srcFd: FileDescriptor,
+    mtu: Int,
+    dstFd: FileDescriptor,
+    extAddr: InetAddress,
+    natMap: PacketBridge.NatMap
+) : NatPacketForwarderBase(srcFd, mtu, dstFd, extAddr, natMap) {
+
+    /**
+     * Rewrite addresses, ports and fix up checksums for packets received on the internal
+     * interface.
+     *
+     * Outgoing packet from the internal interface which is being forwarded to the
+     * external interface with translated address, e.g. 192.168.1.1:5678 -> 8.8.8.8:80
+     * will be translated into 8.8.8.8:1234 -> 1.2.3.4:80.
+     *
+     * The external port, e.g. 1234 in the above example, is the port number assigned by
+     * the forwarder when creating the mapping to identify the source address and port when
+     * the response is coming from the external interface. See {@link PacketBridge.NatMap}
+     * for detail.
+     */
+    override fun preparePacketForForwarding(buf: ByteArray, len: Int, version: Int, proto: Int) {
+        val (addrPos, addrLen) = getAddressPositionAndLength(version)
+
+        // TODO: support one external address per ip version.
+        val extAddrBuf = mExtAddr.address
+        if (addrLen != extAddrBuf.size) throw IllegalStateException("Packet IP version mismatch")
+
+        val srcAddr = getInetAddressAt(buf, addrPos, addrLen)
+
+        // Copy the original destination to into the source address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + i] = buf[addrPos + addrLen + i]
+        }
+
+        // Copy the external address into the destination address.
+        for (i in 0 until addrLen) {
+            buf[addrPos + addrLen + i] = extAddrBuf[i]
+        }
+
+        // Add an entry to NAT mapping table.
+        val transportOffset =
+            if (version == 4) PacketReflector.IPV4_HEADER_LENGTH
+            else PacketReflector.IPV6_HEADER_LENGTH
+        val srcPort = getPortAt(buf, transportOffset)
+        val extPort = synchronized(mNatMap) { mNatMap.toExternalPort(srcAddr, srcPort, proto) }
+        // Copy the external port to into the source port.
+        setPortAt(extPort, buf, transportOffset)
+
+        // Fix IP and Transport layer checksum.
+        fixPacketChecksum(buf, len, version, proto.toByte())
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
new file mode 100644
index 0000000..85c6493
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/NatPacketForwarderBase.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils;
+
+import static com.android.testutils.PacketReflector.IPPROTO_TCP;
+import static com.android.testutils.PacketReflector.IPPROTO_UDP;
+import static com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH;
+import static com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH;
+import static com.android.testutils.PacketReflector.IPV6_PROTO_OFFSET;
+import static com.android.testutils.PacketReflector.TCP_HEADER_LENGTH;
+import static com.android.testutils.PacketReflector.UDP_HEADER_LENGTH;
+
+import android.annotation.NonNull;
+import android.net.TestNetworkInterface;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.Objects;
+
+/**
+ * A class that forwards packets from a {@link TestNetworkInterface} to another
+ * {@link TestNetworkInterface} with NAT.
+ *
+ * For testing purposes, a {@link TestNetworkInterface} provides a {@link FileDescriptor}
+ * which allows content injection on the test network. However, this could be hard to use
+ * because the callers need to compose IP packets in order to inject content to the
+ * test network.
+ *
+ * In order to remove the need of composing the IP packets, this class forwards IP packets to
+ * the {@link FileDescriptor} of another {@link TestNetworkInterface} instance. Thus,
+ * the TCP/IP headers could be parsed/composed automatically by the protocol stack of this
+ * additional {@link TestNetworkInterface}, while the payload is supplied by the
+ * servers run on the interface.
+ *
+ * To make it work, an internal interface and an external interface are defined, where
+ * the client might send packets from the internal interface which are originated from
+ * multiple addresses to a server that listens on the external address.
+ *
+ * When forwarding the outgoing packet on the internal interface, a simple NAT mechanism
+ * is implemented during forwarding, which will swap the source and destination,
+ * but replacing the source address with the external address,
+ * e.g. 192.168.1.1:1234 -> 8.8.8.8:80 will be translated into 8.8.8.8:1234 -> 1.2.3.4:80.
+ *
+ * For the above example, a client who sends http request will have a hallucination that
+ * it is talking to a remote server at 8.8.8.8. Also, the server listens on 1.2.3.4 will
+ * have a different hallucination that the request is sent from a remote client at 8.8.8.8,
+ * to a local address 1.2.3.4.
+ *
+ * And a NAT mapping is created at the time when the outgoing packet is forwarded.
+ * With a different internal source port, the instance learned that when a response with the
+ * destination port 1234, it should forward the packet to the internal address 192.168.1.1.
+ *
+ * For the incoming packet received from external interface, for example a http response sent
+ * from the http server, the same mechanism is applied but in a different direction,
+ * where the source and destination will be swapped, and the source address will be replaced
+ * with the internal address, which is obtained from the NAT mapping described above.
+ */
+public abstract class NatPacketForwarderBase extends Thread {
+    private static final String TAG = "NatPacketForwarder";
+    static final int DESTINATION_PORT_OFFSET = 2;
+
+    // The source fd to read packets from.
+    @NonNull
+    final FileDescriptor mSrcFd;
+    // The buffer to temporarily hold the entire packet after receiving.
+    @NonNull
+    final byte[] mBuf;
+    // The destination fd to write packets to.
+    @NonNull
+    final FileDescriptor mDstFd;
+    // The NAT mapping table shared between two NatPacketForwarder instances to map from
+    // the source port to the associated internal address. The map can be read/write from two
+    // different threads on any given time whenever receiving packets on the
+    // {@link TestNetworkInterface}. Thus, synchronize on the object when reading/writing is needed.
+    @GuardedBy("mNatMap")
+    @NonNull
+    final PacketBridge.NatMap mNatMap;
+    // The address of the external interface. See {@link NatPacketForwarder}.
+    @NonNull
+    final InetAddress mExtAddr;
+
+    /**
+     * Construct a {@link NatPacketForwarderBase}.
+     *
+     * This class reads packets from {@code srcFd} of a {@link TestNetworkInterface}, and
+     * forwards them to the {@code dstFd} of another {@link TestNetworkInterface} with
+     * NAT applied. See {@link NatPacketForwarderBase}.
+     *
+     * To apply NAT, the address of the external interface needs to be supplied through
+     * {@code extAddr} to identify the external interface. And a shared NAT mapping table,
+     * {@code natMap} is needed to be shared between these two instances.
+     *
+     * Note that this class is not useful if the instance is not managed by a
+     * {@link PacketBridge} to set up a two-way communication.
+     *
+     * @param srcFd   {@link FileDescriptor} to read packets from.
+     * @param mtu     MTU of the test network.
+     * @param dstFd   {@link FileDescriptor} to write packets to.
+     * @param extAddr the external address, which is the address of the external interface.
+     *                See {@link NatPacketForwarderBase}.
+     * @param natMap  the NAT mapping table shared between two {@link NatPacketForwarderBase}
+     *                instance.
+     */
+    public NatPacketForwarderBase(@NonNull FileDescriptor srcFd, int mtu,
+            @NonNull FileDescriptor dstFd, @NonNull InetAddress extAddr,
+            @NonNull PacketBridge.NatMap natMap) {
+        super(TAG);
+        mSrcFd = Objects.requireNonNull(srcFd);
+        mBuf = new byte[mtu];
+        mDstFd = Objects.requireNonNull(dstFd);
+        mExtAddr = Objects.requireNonNull(extAddr);
+        mNatMap = Objects.requireNonNull(natMap);
+    }
+
+    /**
+     * A method to prepare forwarding packets between two instances of {@link TestNetworkInterface},
+     * which includes re-write addresses, ports and fix up checksums.
+     * Subclasses should override this method to implement a simple NAT.
+     */
+    abstract void preparePacketForForwarding(@NonNull byte[] buf, int len, int version, int proto);
+
+    private void forwardPacket(@NonNull byte[] buf, int len) {
+        try {
+            Os.write(mDstFd, buf, 0, len);
+        } catch (ErrnoException | IOException e) {
+            Log.e(TAG, "Error writing packet: " + e.getMessage());
+        }
+    }
+
+    // Reads one packet from mSrcFd, and writes the packet to the mDstFd for supported protocols.
+    private void processPacket() {
+        final int len = PacketReflectorUtil.readPacket(mSrcFd, mBuf);
+        if (len < 1) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+
+        final int version = mBuf[0] >>> 4;
+        final int protoPos, ipHdrLen;
+        switch (version) {
+            case 4:
+                ipHdrLen = IPV4_HEADER_LENGTH;
+                protoPos = PacketReflector.IPV4_PROTO_OFFSET;
+                break;
+            case 6:
+                ipHdrLen = IPV6_HEADER_LENGTH;
+                protoPos = IPV6_PROTO_OFFSET;
+                break;
+            default:
+                throw new IllegalStateException("Unexpected version: " + version);
+        }
+        if (len < ipHdrLen) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+
+        final byte proto = mBuf[protoPos];
+        final int transportHdrLen;
+        switch (proto) {
+            case IPPROTO_TCP:
+                transportHdrLen = TCP_HEADER_LENGTH;
+                break;
+            case IPPROTO_UDP:
+                transportHdrLen = UDP_HEADER_LENGTH;
+                break;
+            // TODO: Support ICMP.
+            default:
+                return; // Unknown protocol, ignored.
+        }
+
+        if (len < ipHdrLen + transportHdrLen) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+        // Re-write addresses, ports and fix up checksums.
+        preparePacketForForwarding(mBuf, len, version, proto);
+        // Send the packet to the destination fd.
+        forwardPacket(mBuf, len);
+    }
+
+    @Override
+    public void run() {
+        Log.i(TAG, "starting fd=" + mSrcFd + " valid=" + mSrcFd.valid());
+        while (!interrupted() && mSrcFd.valid()) {
+            processPacket();
+        }
+        Log.i(TAG, "exiting fd=" + mSrcFd + " valid=" + mSrcFd.valid());
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
new file mode 100644
index 0000000..da3508d
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketBridge.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils
+
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import android.net.TestNetworkSpecifier
+import android.os.Binder
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import java.net.InetAddress
+import libcore.io.IoUtils
+
+private const val MIN_PORT_NUMBER = 1025
+private const val MAX_PORT_NUMBER = 65535
+
+/**
+ * A class that set up two {@link TestNetworkInterface} with NAT, and forward packets between them.
+ *
+ * See {@link NatPacketForwarder} for more detailed information.
+ */
+class PacketBridge(
+    context: Context,
+    internalAddr: LinkAddress,
+    externalAddr: LinkAddress,
+    dnsAddr: InetAddress
+) {
+    private val natMap = NatMap()
+    private val binder = Binder()
+
+    private val cm = context.getSystemService(ConnectivityManager::class.java)
+    private val tnm = context.getSystemService(TestNetworkManager::class.java)
+
+    // Create test networks.
+    private val internalIface = tnm.createTunInterface(listOf(internalAddr))
+    private val externalIface = tnm.createTunInterface(listOf(externalAddr))
+
+    // Register test networks to ConnectivityService.
+    private val internalNetworkCallback: TestableNetworkCallback
+    private val externalNetworkCallback: TestableNetworkCallback
+    val internalNetwork: Network
+    val externalNetwork: Network
+    init {
+        val (inCb, inNet) = createTestNetwork(internalIface, internalAddr, dnsAddr)
+        val (exCb, exNet) = createTestNetwork(externalIface, externalAddr, dnsAddr)
+        internalNetworkCallback = inCb
+        externalNetworkCallback = exCb
+        internalNetwork = inNet
+        externalNetwork = exNet
+    }
+
+    // Setup the packet bridge.
+    private val internalFd = internalIface.fileDescriptor.fileDescriptor
+    private val externalFd = externalIface.fileDescriptor.fileDescriptor
+
+    private val pr1 = NatInternalPacketForwarder(
+        internalFd,
+        1500,
+        externalFd,
+        externalAddr.address,
+        natMap
+    )
+    private val pr2 = NatExternalPacketForwarder(
+        externalFd,
+        1500,
+        internalFd,
+        externalAddr.address,
+        natMap
+    )
+
+    fun start() {
+        IoUtils.setBlocking(internalFd, true /* blocking */)
+        IoUtils.setBlocking(externalFd, true /* blocking */)
+        pr1.start()
+        pr2.start()
+    }
+
+    fun stop() {
+        pr1.interrupt()
+        pr2.interrupt()
+        cm.unregisterNetworkCallback(internalNetworkCallback)
+        cm.unregisterNetworkCallback(externalNetworkCallback)
+    }
+
+    /**
+     * Creates a test network with given test TUN interface and addresses.
+     */
+    private fun createTestNetwork(
+        testIface: TestNetworkInterface,
+        addr: LinkAddress,
+        dnsAddr: InetAddress
+    ): Pair<TestableNetworkCallback, Network> {
+        // Make a network request to hold the test network
+        val nr = NetworkRequest.Builder()
+            .clearCapabilities()
+            .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+            .setNetworkSpecifier(TestNetworkSpecifier(testIface.interfaceName))
+            .build()
+        val testCb = TestableNetworkCallback()
+        cm.requestNetwork(nr, testCb)
+
+        val lp = LinkProperties().apply {
+            addLinkAddress(addr)
+            interfaceName = testIface.interfaceName
+            addDnsServer(dnsAddr)
+        }
+        tnm.setupTestNetwork(lp, true /* isMetered */, binder)
+
+        // Wait for available before return.
+        val network = testCb.expect<Available>().network
+        return testCb to network
+    }
+
+    /**
+     * A helper class to maintain the mappings between internal addresses/ports and external
+     * ports.
+     *
+     * This class assigns an unused external port number if the mapping between
+     * srcaddress:srcport:protocol and the external port does not exist yet.
+     *
+     * Note that this class is not thread-safe. The instance of the class needs to be
+     * synchronized in the callers when being used in multiple threads.
+     */
+    class NatMap {
+        data class AddressInfo(val address: InetAddress, val port: Int, val protocol: Int)
+
+        private val mToExternalPort = HashMap<AddressInfo, Int>()
+        private val mFromExternalPort = HashMap<Int, AddressInfo>()
+
+        // Skip well-known port 0~1024.
+        private var nextExternalPort = MIN_PORT_NUMBER
+
+        fun toExternalPort(addr: InetAddress, port: Int, protocol: Int): Int {
+            val info = AddressInfo(addr, port, protocol)
+            val extPort: Int
+            if (!mToExternalPort.containsKey(info)) {
+                extPort = nextExternalPort++
+                if (nextExternalPort > MAX_PORT_NUMBER) {
+                    throw IllegalStateException("Available ports are exhausted")
+                }
+                mToExternalPort[info] = extPort
+                mFromExternalPort[extPort] = info
+            } else {
+                extPort = mToExternalPort[info]!!
+            }
+            return extPort
+        }
+
+        fun fromExternalPort(port: Int): AddressInfo? {
+            return mFromExternalPort[port]
+        }
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
new file mode 100644
index 0000000..69392d4
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflector.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.testutils;
+
+import static android.system.OsConstants.ICMP6_ECHO_REPLY;
+import static android.system.OsConstants.ICMP6_ECHO_REQUEST;
+
+import android.annotation.NonNull;
+import android.net.TestNetworkInterface;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * A class that echoes packets received on a {@link TestNetworkInterface} back to itself.
+ *
+ * For testing purposes, sometimes a mocked environment to simulate a simple echo from the
+ * server side is needed. This is particularly useful if the test, e.g. VpnTest, is
+ * heavily relying on the outside world.
+ *
+ * This class reads packets from the {@link FileDescriptor} of a {@link TestNetworkInterface}, and:
+ *   1. For TCP and UDP packets, simply swaps the source address and the destination
+ *      address, then send it back to the {@link FileDescriptor}.
+ *   2. For ICMP ping packets, composes a ping reply and sends it back to the sender.
+ *   3. Ignore all other packets.
+ */
+public class PacketReflector extends Thread {
+
+    static final int IPV4_HEADER_LENGTH = 20;
+    static final int IPV6_HEADER_LENGTH = 40;
+
+    static final int IPV4_ADDR_OFFSET = 12;
+    static final int IPV6_ADDR_OFFSET = 8;
+    static final int IPV4_ADDR_LENGTH = 4;
+    static final int IPV6_ADDR_LENGTH = 16;
+
+    static final int IPV4_PROTO_OFFSET = 9;
+    static final int IPV6_PROTO_OFFSET = 6;
+
+    static final byte IPPROTO_ICMP = 1;
+    static final byte IPPROTO_TCP = 6;
+    static final byte IPPROTO_UDP = 17;
+    private static final byte IPPROTO_ICMPV6 = 58;
+
+    private static final int ICMP_HEADER_LENGTH = 8;
+    static final int TCP_HEADER_LENGTH = 20;
+    static final int UDP_HEADER_LENGTH = 8;
+
+    private static final byte ICMP_ECHO = 8;
+    private static final byte ICMP_ECHOREPLY = 0;
+
+    private static String TAG = "PacketReflector";
+
+    @NonNull
+    private final FileDescriptor mFd;
+    @NonNull
+    private final byte[] mBuf;
+
+    /**
+     * Construct a {@link PacketReflector} from the given {@code fd} of
+     * a {@link TestNetworkInterface}.
+     *
+     * @param fd {@link FileDescriptor} to read/write packets.
+     * @param mtu MTU of the test network.
+     */
+    public PacketReflector(@NonNull FileDescriptor fd, int mtu) {
+        super("PacketReflector");
+        mFd = Objects.requireNonNull(fd);
+        mBuf = new byte[mtu];
+    }
+
+    private static void swapBytes(@NonNull byte[] buf, int pos1, int pos2, int len) {
+        for (int i = 0; i < len; i++) {
+            byte b = buf[pos1 + i];
+            buf[pos1 + i] = buf[pos2 + i];
+            buf[pos2 + i] = b;
+        }
+    }
+
+    private static void swapAddresses(@NonNull byte[] buf, int version) {
+        int addrPos, addrLen;
+        switch (version) {
+            case 4:
+                addrPos = IPV4_ADDR_OFFSET;
+                addrLen = IPV4_ADDR_LENGTH;
+                break;
+            case 6:
+                addrPos = IPV6_ADDR_OFFSET;
+                addrLen = IPV6_ADDR_LENGTH;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
+    }
+
+    // Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
+    // This is used by the test to "connect to itself" through the VPN.
+    private void processTcpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + TCP_HEADER_LENGTH) {
+            return;
+        }
+
+        // Swap src and dst IP addresses.
+        swapAddresses(buf, version);
+
+        // Send the packet back.
+        writePacket(buf, len);
+    }
+
+    // Echo UDP packets: swap source and destination addresses, and source and destination ports.
+    // This is used by the test to check that the bytes it sends are echoed back.
+    private void processUdpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + UDP_HEADER_LENGTH) {
+            return;
+        }
+
+        // Swap src and dst IP addresses.
+        swapAddresses(buf, version);
+
+        // Swap dst and src ports.
+        int portOffset = hdrLen;
+        swapBytes(buf, portOffset, portOffset + 2, 2);
+
+        // Send the packet back.
+        writePacket(buf, len);
+    }
+
+    private void processIcmpPacket(@NonNull byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + ICMP_HEADER_LENGTH) {
+            return;
+        }
+
+        byte type = buf[hdrLen];
+        if (!(version == 4 && type == ICMP_ECHO) &&
+                !(version == 6 && type == (byte) ICMP6_ECHO_REQUEST)) {
+            return;
+        }
+
+        // Save the ping packet we received.
+        byte[] request = buf.clone();
+
+        // Swap src and dst IP addresses, and send the packet back.
+        // This effectively pings the device to see if it replies.
+        swapAddresses(buf, version);
+        writePacket(buf, len);
+
+        // The device should have replied, and buf should now contain a ping response.
+        int received = PacketReflectorUtil.readPacket(mFd, buf);
+        if (received != len) {
+            Log.i(TAG, "Reflecting ping did not result in ping response: " +
+                    "read=" + received + " expected=" + len);
+            return;
+        }
+
+        byte replyType = buf[hdrLen];
+        if ((type == ICMP_ECHO && replyType != ICMP_ECHOREPLY)
+                || (type == (byte) ICMP6_ECHO_REQUEST && replyType != (byte) ICMP6_ECHO_REPLY)) {
+            Log.i(TAG, "Received unexpected ICMP reply: original " + type
+                    + ", reply " + replyType);
+            return;
+        }
+
+        // Compare the response we got with the original packet.
+        // The only thing that should have changed are addresses, type and checksum.
+        // Overwrite them with the received bytes and see if the packet is otherwise identical.
+        request[hdrLen] = buf[hdrLen];          // Type
+        request[hdrLen + 2] = buf[hdrLen + 2];  // Checksum byte 1.
+        request[hdrLen + 3] = buf[hdrLen + 3];  // Checksum byte 2.
+
+        // Since Linux kernel 4.2, net.ipv6.auto_flowlabels is set by default, and therefore
+        // the request and reply may have different IPv6 flow label: ignore that as well.
+        if (version == 6) {
+            request[1] = (byte) (request[1] & 0xf0 | buf[1] & 0x0f);
+            request[2] = buf[2];
+            request[3] = buf[3];
+        }
+
+        for (int i = 0; i < len; i++) {
+            if (buf[i] != request[i]) {
+                Log.i(TAG, "Received non-matching packet when expecting ping response.");
+                return;
+            }
+        }
+
+        // Now swap the addresses again and reflect the packet. This sends a ping reply.
+        swapAddresses(buf, version);
+        writePacket(buf, len);
+    }
+
+    private void writePacket(@NonNull byte[] buf, int len) {
+        try {
+            Os.write(mFd, buf, 0, len);
+        } catch (ErrnoException | IOException e) {
+            Log.e(TAG, "Error writing packet: " + e.getMessage());
+        }
+    }
+
+    // Reads one packet from our mFd, and possibly writes the packet back.
+    private void processPacket() {
+        int len = PacketReflectorUtil.readPacket(mFd, mBuf);
+        if (len < 1) {
+            // Usually happens when socket read is being interrupted, e.g. stopping PacketReflector.
+            return;
+        }
+
+        int version = mBuf[0] >> 4;
+        int protoPos, hdrLen;
+        if (version == 4) {
+            hdrLen = IPV4_HEADER_LENGTH;
+            protoPos = IPV4_PROTO_OFFSET;
+        } else if (version == 6) {
+            hdrLen = IPV6_HEADER_LENGTH;
+            protoPos = IPV6_PROTO_OFFSET;
+        } else {
+            throw new IllegalStateException("Unexpected version: " + version);
+        }
+
+        if (len < hdrLen) {
+            throw new IllegalStateException("Unexpected buffer length: " + len);
+        }
+
+        byte proto = mBuf[protoPos];
+        switch (proto) {
+            case IPPROTO_ICMP:
+                // fall through
+            case IPPROTO_ICMPV6:
+                processIcmpPacket(mBuf, version, len, hdrLen);
+                break;
+            case IPPROTO_TCP:
+                processTcpPacket(mBuf, version, len, hdrLen);
+                break;
+            case IPPROTO_UDP:
+                processUdpPacket(mBuf, version, len, hdrLen);
+                break;
+        }
+    }
+
+    public void run() {
+        Log.i(TAG, "starting fd=" + mFd + " valid=" + mFd.valid());
+        while (!interrupted() && mFd.valid()) {
+            processPacket();
+        }
+        Log.i(TAG, "exiting fd=" + mFd + " valid=" + mFd.valid());
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
new file mode 100644
index 0000000..b028045
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/PacketReflectorUtil.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:JvmName("PacketReflectorUtil")
+
+package com.android.testutils
+
+import android.system.ErrnoException
+import android.system.Os
+import com.android.net.module.util.IpUtils
+import com.android.testutils.PacketReflector.IPV4_HEADER_LENGTH
+import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH
+import java.io.FileDescriptor
+import java.io.IOException
+import java.net.InetAddress
+import java.nio.ByteBuffer
+
+fun readPacket(fd: FileDescriptor, buf: ByteArray): Int {
+    return try {
+        Os.read(fd, buf, 0, buf.size)
+    } catch (e: ErrnoException) {
+        -1
+    } catch (e: IOException) {
+        -1
+    }
+}
+
+fun getInetAddressAt(buf: ByteArray, pos: Int, len: Int): InetAddress =
+    InetAddress.getByAddress(buf.copyOfRange(pos, pos + len))
+
+/**
+ * Reads a 16-bit unsigned int at pos in big endian, with no alignment requirements.
+ */
+fun getPortAt(buf: ByteArray, pos: Int): Int {
+    return (buf[pos].toInt() and 0xff shl 8) + (buf[pos + 1].toInt() and 0xff)
+}
+
+fun setPortAt(port: Int, buf: ByteArray, pos: Int) {
+    buf[pos] = (port ushr 8).toByte()
+    buf[pos + 1] = (port and 0xff).toByte()
+}
+
+fun getAddressPositionAndLength(version: Int) = when (version) {
+    4 -> PacketReflector.IPV4_ADDR_OFFSET to PacketReflector.IPV4_ADDR_LENGTH
+    6 -> PacketReflector.IPV6_ADDR_OFFSET to PacketReflector.IPV6_ADDR_LENGTH
+    else -> throw IllegalArgumentException("Unknown IP version $version")
+}
+
+private const val IPV4_CHKSUM_OFFSET = 10
+private const val UDP_CHECKSUM_OFFSET = 6
+private const val TCP_CHECKSUM_OFFSET = 16
+
+fun fixPacketChecksum(buf: ByteArray, len: Int, version: Int, protocol: Byte) {
+    // Fill Ip checksum for IPv4. IPv6 header doesn't have a checksum field.
+    if (version == 4) {
+        val checksum = IpUtils.ipChecksum(ByteBuffer.wrap(buf), 0)
+        // Place checksum in Big-endian order.
+        buf[IPV4_CHKSUM_OFFSET] = (checksum.toInt() ushr 8).toByte()
+        buf[IPV4_CHKSUM_OFFSET + 1] = (checksum.toInt() and 0xff).toByte()
+    }
+
+    // Fill transport layer checksum.
+    val transportOffset = if (version == 4) IPV4_HEADER_LENGTH else IPV6_HEADER_LENGTH
+    when (protocol) {
+        PacketReflector.IPPROTO_UDP -> {
+            val checksumPos = transportOffset + UDP_CHECKSUM_OFFSET
+            // Clear before calculate.
+            buf[checksumPos + 1] = 0x00
+            buf[checksumPos] = buf[checksumPos + 1]
+            val checksum = IpUtils.udpChecksum(
+                ByteBuffer.wrap(buf), 0,
+                transportOffset
+            )
+            buf[checksumPos] = (checksum.toInt() ushr 8).toByte()
+            buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte()
+        }
+        PacketReflector.IPPROTO_TCP -> {
+            val checksumPos = transportOffset + TCP_CHECKSUM_OFFSET
+            // Clear before calculate.
+            buf[checksumPos + 1] = 0x00
+            buf[checksumPos] = buf[checksumPos + 1]
+            val transportLen: Int = len - transportOffset
+            val checksum = IpUtils.tcpChecksum(
+                ByteBuffer.wrap(buf), 0, transportOffset,
+                transportLen
+            )
+            buf[checksumPos] = (checksum.toInt() ushr 8).toByte()
+            buf[checksumPos + 1] = (checksum.toInt() and 0xff).toByte()
+        }
+        // TODO: Support ICMP.
+        else -> throw IllegalArgumentException("Unsupported protocol: $protocol")
+    }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
index 39ce487..740bf63 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/TestHttpServer.kt
@@ -19,6 +19,7 @@
 import android.net.Uri
 import com.android.net.module.util.ArrayTrackRecord
 import fi.iki.elonen.NanoHTTPD
+import java.io.IOException
 
 /**
  * A minimal HTTP server running on a random available port.
@@ -82,7 +83,23 @@
         val request = Request(session.uri
                 ?: "", session.method, session.queryParameterString ?: "")
         requestsRecord.add(request)
+
+        // For PUT and POST, call parseBody to read InputStream before responding.
+        if (Method.PUT == session.method || Method.POST == session.method) {
+            try {
+                session.parseBody(HashMap())
+            } catch (e: Exception) {
+                when (e) {
+                    is IOException, is ResponseException -> e.toResponse()
+                    else -> throw e
+                }
+            }
+        }
+
         // Default response is a 404
         return responses[request] ?: super.serve(session)
     }
-}
\ No newline at end of file
+
+    fun Exception.toResponse() =
+        newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "text/plain", this.toString())
+}